




已阅读5页,还剩56页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析在前面一篇文章中,我们分析了Android应用程序请求SurfaceFlinger服务创建Surface的过程。有了Surface之后,Android应用程序就可以在上面绘制自己的UI了,接着再请求SurfaceFlinger服务将这个已经绘制好了UI的Surface渲染到设备显示屏上去。在本文中,我们就将详细分析Android应用程序请求SurfaceFlinger服务渲染Surface的过程。 Android应用程序在请求SurfaceFlinger服务渲染一个Surface之前,首先要将该Surface作为当前活动的绘图上下文,以便可以使用OpengGL库或者其它库的API来在上面绘制UI,我们以Android系统的开机动画应用程序bootanim为例,来说明这个问题。 从前面 一文可以知道,Android系统的开机动画应用程序bootanim是在BootAnimation类的成员函数readyToRun中请求SurfaceFlinger服务创建Surface的。这个Surface创建完成之后,就会被设置为当前活动的绘图上下文,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片status_t BootAnimation:readyToRun() . / create the native surface sp control = session()-createSurface( getpid(), 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); . sp s = control-getSurface(); . / initialize opengl and egl const EGLint attribs = EGL_DEPTH_SIZE, 0, EGL_NONE ; . EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); EGLUtils:selectConfigForNativeWindow(display, attribs, s.get(), &config); surface = eglCreateWindowSurface(display, config, s.get(), NULL); context = eglCreateContext(display, config, NULL, NULL); . if (eglMakeCurrent(display, surface, surface, context) = EGL_FALSE) return NO_INIT; . BootAnimation类的成员函数readyToRun首先调用eglGetDisplay和eglInitialize函数来获得和初始化OpengGL库的默认显示屏,接着再调用EGLUtils:selectConfigForNativeWindow函数来获得前面所创建的一个Surface(由sp指针s来描述)的配置信息。有了这些信息之后,接下来就分别调用eglCreateWindowSurface和eglCreateContext函数来创建一个适用于OpenGL库使用的绘图表面surface以及绘图上下文context,最后就可以调用eglMakeCurrent函数来将绘图表面surface和绘图上下文context设置为当前活动的绘图表面和绘图上下文,这就相当于是将前面请求SurfaceFlinger服务创建的一个Surface设置为当前活动的绘图上下文了。 完成了上述操作之后,Android系统的开机动画应用程序bootanim就可以继续使用OpengGL库的其它API来在当前活动的Surface上绘制UI了,不过,通过前面 一文的学习,我们知道,此时SurfaceFlinger服务为Android应用程序创建的Surface只有UI元数据缓冲区,而没有UI数据缓冲区,即还没有图形缓冲区,换句来说,就是还没有可以用来绘制UI的载体。那么,这些用来绘制UI的图形缓冲区是什么时候创建的呢? 从前面 一文可以知道,每一个Surface都有一个对应的UI元数据缓冲区堆栈,这个UI元数据缓冲区堆栈是使用一个SharedBufferStack来描述的,如图1所示。从图1就可以看出,每一个UI元数据缓冲区都可能对应有一个UI数据缓冲区,这个UI数据缓冲区又可以称为图形缓冲区,它使用一个GraphicBuffer对象来描述。注意,一个UI元数据缓冲区只有第一次被使用时,Android应用程序才会为它创建一个图形缓冲区,因此,我们才说每一个UI元数据缓冲区都可能对应有一个UI数据缓冲区。例如,在图1中,目前只使到了编号为1和2的UI元数据缓冲区,因此,只有它们才有对应的图形缓冲区,而编号为3、4和5的UI元数据缓冲区没有。 Android应用程序渲染一个Surface的过程大致如下所示: 1. 从UI元数据缓冲区堆栈中得到一个空闲的UI元数据缓冲区; 2. 请求SurfaceFlinger服务为这个空闲的UI元数据缓冲区分配一个图形缓冲区; 3. 在图形缓冲区上面绘制好UI之后,即填充好UI数据之后,就将前面得到的空闲UI元数据缓冲区添加到UI元数据缓冲区堆栈中的待渲染队列中去; 4. 请求SurfaceFlinger服务渲染前面已经准备好了图形缓冲区的Surface; 5. SurfaceFlinger服务从即将要渲染的Surface的UI元数据缓冲区堆栈的待渲染队列中找到待渲染的UI元数据缓冲区; 6. SurfaceFlinger服务得到了待渲染的UI元数据缓冲区之后,接着再找到在前面第2步为它所分配的图形缓冲区,最后就可以将这个图形缓冲区渲染到设备显示屏上去。 这个过程的第1步、第3步和第5步涉到UI元数据缓冲区堆栈的一些出入栈操作,为了方便后面描述Android应用程序请求SurfaceFlinger服务渲染Surface的过程,我们首先介绍一下UI元数据缓冲区堆栈的一些出入栈操作。 在前面 一文中,我们分析了用来描述UI元数据缓冲区堆栈的SharedBufferServer和SharedBufferClient类的父类SharedBufferBase,它有一个成员函数waitForCondition,用来等待一个条件得到满足,它定义在文件frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class SharedBufferBase . protected: . struct ConditionBase SharedBufferStack& stack; inline ConditionBase(SharedBufferBase* sbc) : stack(*sbc-mSharedStack) virtual ConditionBase() ; virtual bool operator()() const = 0; virtual const char* name() const = 0; ; status_t waitForCondition(const ConditionBase& condition); . ; SharedBufferBase类的成员函数waitForCondition只有一个参数condition,它的类型为ConditionBase,用来描述一个需要等待满足的条件。ConditionBase类是一个抽象类,我们需要以它来为父类,来实现一个自定义的条件,并且重写操作符号()和成员函数name。接下来,我们分析SharedBufferBase类的成员函数waitForCondition的实现,接着再分析ConditionBase类的一个子类的实现。 SharedBufferBase类的成员函数waitForCondition实现在文件frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp文件中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片status_t SharedBufferBase:waitForCondition(const ConditionBase& condition) const SharedBufferStack& stack( *mSharedStack ); SharedClient& client( *mSharedClient ); const nsecs_t TIMEOUT = s2ns(1); const int identity = mIdentity; Mutex:Autolock _l(client.lock); while (condition()=false) & (stack.identity = identity) & (stack.status = NO_ERROR) status_t err = client.cv.waitRelative(client.lock, TIMEOUT); / handle errors and timeouts if (CC_UNLIKELY(err != NO_ERROR) if (err = TIMED_OUT) if (condition() LOGE(waitForCondition(%s) timed out (identity=%d), but condition is true! We recovered but it shouldnt happen. , (), stack.identity); break; else LOGW(waitForCondition(%s) timed out (identity=%d, status=%d). CPU may be pegged. trying again., (), stack.identity, stack.status); else LOGE(waitForCondition(%s) error (%s) , (), strerror(-err); return err; return (stack.identity != mIdentity) ? status_t(BAD_INDEX) : stack.status; SharedBufferBase类的成员变量mSharedStack指向了一个SharedBufferStack对象,即一个UI元数据缓冲区堆栈,另外一个成员变量mSharedClient指向了当前应用程序进程的一个SharedClient单例。 SharedClient类有一个类型为Condition的成员变量cv,用来描述一个条件变量,同时,SharedClient类还有一个类型为Mutex的成员变量lock,用来描述一个互斥锁。通过调用一个Condition对象的成员函数waitRelative,就可以在指定的时间内等待一个互斥锁变为可用。 SharedBufferBase类的成员函数waitForCondition中的while循环的作用是循环等待一个UI元数据缓冲区堆栈满足某一个条件,这个条件是通过参数condition来描述的。当调用参数condition所描述的一个CondtitionBase对象的重载操作符号()的返回值等于true的时候,就表示所要等待的条件得到满足了,这时候函数就会停止执行中间的while循环语句。另一方面,当调用参数condition所描述的一个CondtitionBase对象的重载操作符号()的返回值等于flase的时候,就表示所要等待的条件还没有得到满足,这时候函数就会继续执行中间的while循环,直到所要等待的条件得到满足为止。等待的操作是通过调用下面这个语句来完成的:cpp view plain copy 在CODE上查看代码片派生到我的代码片status_t err = client.cv.waitRelative(client.lock, TIMEOUT); 即调用当前应用程序进程的SharedClient单例client的成员变量cv所描述的一个条件变量的成员函数waitRelative来完成,并且指定要等待的互斥锁为当前应用程序进程的SharedClient单例client的成员变量lock所描述的一个互斥锁,以及指定等待的时间为TIMEOUT,即1秒。如果在1秒内,当前应用程序进程的SharedClient单例client的成员变量lock所描述的一个互斥锁还是不可用,那么上述等待操作就会超时,然后导致重新执行外层的while循环,否则的话,等待操作就完成了。 在SharedBufferClient类中,定义了一个ConditionBase子类DequeueCondition,用来描述一个UI元数据缓冲区堆栈是否有空闲的缓冲区可以出栈,它定义在文件frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class SharedBufferClient : public SharedBufferBase . private: . struct DequeueCondition : public ConditionBase inline DequeueCondition(SharedBufferClient* sbc); inline bool operator()() const; inline const char* name() const return DequeueCondition; ; . ; 一个UI元数据缓冲区堆栈是否有空闲的缓冲区可以出栈是由DequeueCondition类的重载操作符号()来决定的,它实现在文件frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp文件中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool SharedBufferClient:DequeueCondition:operator()() const return stack.available 0; DequeueCondition类的成员变量stack是从父类ConditionBase继承下来的,它指向了一个SharedBufferStack对象,即用来描述一个UI元数据缓冲区堆栈。从前面 一文可以知道,当一个SharedBufferStack对象的成员变量available的值大于0的时候,就说明它所描述的UI元数据缓冲区堆栈有空闲的缓冲区可以使用,因此,这时候DequeueCondition类的重载操作符号()的返回值就等于true,表示一个UI元数据缓冲区堆栈有空闲的缓冲区可以出栈。 SharedBufferBase类还有一个成员函数updateCondition,用来操作一个UI元数据缓冲区堆栈,例如,执行一个UI元数据缓冲区的出入栈操作。这个成员函数定义和实现在文件rameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class SharedBufferBase . protected: . struct UpdateBase SharedBufferStack& stack; inline UpdateBase(SharedBufferBase* sbb) : stack(*sbb-mSharedStack) ; template status_t updateCondition(T update); ; template status_t SharedBufferBase:updateCondition(T update) SharedClient& client( *mSharedClient ); Mutex:Autolock _l(client.lock); ssize_t result = update(); client.cv.broadcast(); return result; SharedBufferBase类的成员函数updateCondition是一个模板函数,它过调用参数T的重载操作符号()来实现一个具体的UI元数据缓冲区堆栈操作。这个参数T必须要从基类UpdateBase继承下来,并且重载操作符号()。 SharedBufferBase类的成员函数updateCondition执行完成一个UI元数据缓冲区堆栈操作之后,还会调用当前应用进程的SharedClient单例client的成员变量cv所描述的一个条件变量的成员函数broadcast,用来唤醒那些在当前应用进程的SharedClient单例client的成员变量lock所描述的一个互斥锁上等待的其它线程,以便它们可以继续执行自己的操作,这样,SharedBufferBase类的成员函数updateCondition就可以和前面介绍的成员函数waitCondition对应起来。 接下来,我们就分别分析UpdateBase的三个子类QueueUpdate、DequeueUpdate和RetireUpdate。QueueUpdate和DequeueUpdate两个子类是Android应用程序这一侧使用的,前者用来向一个UI元数据缓冲区堆栈的待渲染队列增加一个缓冲区,而后者用来从一个UI元数据缓冲区堆栈出栈一个空闲的缓冲区。RetireUpdate类是在SurfaceFlinger服务这一侧使用的,用来从一个UI元数据缓冲区堆栈的待渲染队列出栈一个缓冲区,以便可以将与它所对应的图形缓冲区渲染到设备显示屏去。 QueueUpdate类定义在文件frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class SharedBufferClient : public SharedBufferBase . private: . struct QueueUpdate : public UpdateBase inline QueueUpdate(SharedBufferBase* sbb); inline ssize_t operator()(); ; . ; 它的重载操作符号()实现在文件frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp文件中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片ssize_t SharedBufferClient:QueueUpdate:operator()() android_atomic_inc(&stack.queued); return NO_ERROR; QueueUpdate类的成员变量stack是从父类UpdateBase继承下来的,它指向了一个SharedBufferStack对象,用来描述当前要操作的UI元数据缓冲区堆栈。从前面的图1可以知道,当我们将一个SharedBufferStack对象的成员变量queued的值增加1的时候,就表示这个SharedBufferStack对象所描述的UI元数据缓冲区堆栈的待渲染队列的大小增加了1。不过,在执行这个操作之前,我们还需要将用来这个待渲染队列头queue_head往前移动一个位置。后面在分析Surface的渲染过程时,我们再详细分析。 DequeueUpdate类定义在文件frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class SharedBufferClient : public SharedBufferBase . private: . struct DequeueUpdate : public UpdateBase inline DequeueUpdate(SharedBufferBase* sbb); inline ssize_t operator()(); ; . ; 它的重载操作符号()实现在文件frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp文件中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片ssize_t SharedBufferClient:DequeueUpdate:operator()() if (android_atomic_dec(&stack.available) = 0) LOGW(dequeue probably called from multiple threads!); return NO_ERROR; DequeueUpdate类的成员变量stack是从父类UpdateBase继承下来的,它指向了一个SharedBufferStack对象,用来描述当前要操作的UI元数据缓冲区堆栈。从前面的图1可以知道,当我们将一个SharedBufferStack对象的成员变量available的值减少1的时候,就表示这个SharedBufferStack对象所描述的UI元数据缓冲区堆栈的空闲缓冲区的大小就减少了1。不过,在执行这个操作之前,我们还需要将用来这个UI元数据缓冲区堆栈尾tail往前移动一个位置。后面在分析Surface的渲染过程时,我们再详细分析。 RetireUpdate类定义在文件frameworks/base/include/private/surfaceflinger/SharedBufferStack.h中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class SharedBufferServer : public SharedBufferBase, public LightRefBase . private: . struct RetireUpdate : public UpdateBase const int numBuffers; inline RetireUpdate(SharedBufferBase* sbb, int numBuffers); inline ssize_t operator()(); ; . ; RetireUpdate类的成员变量numBuffers用来描述一个UI元数据缓冲区堆栈的大小,它的重载操作符号()实现在文件frameworks/base/libs/surfaceflinger_client/SharedBufferStack.cpp文件中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片ssize_t SharedBufferServer:RetireUpdate:operator()() int32_t head = stack.head; if (uint32_t(head) = SharedBufferStack:NUM_BUFFER_MAX) return BAD_VALUE; / Decrement the number of queued buffers int32_t queued; do queued = stack.queued; if (queued = 0) return NOT_ENOUGH_DATA; while (android_atomic_cmpxchg(queued, queued-1, &stack.queued); / lock the buffer before advancing head, which automatically unlocks / the buffer we preventively locked upon entering this function head = (head + 1) % numBuffers; const int8_t headBuf = stack.indexhead; stack.headBuf = headBuf; / head is only modified here, so we dont need to use cmpxchg android_atomic_write(head, &stack.head); / now that head has moved, we can increment the number of available buffers android_atomic_inc(&stack.available); return head; 在前面 一文中提到,在图1所描述的UI元数据缓冲区堆栈中,位于(head, queue_head里面的缓冲区组成了一个待渲染队列,而SurfaceFlinger服务就是按照head到queue_head的顺序来渲染这个队列中的缓冲区的。理解了这一点之后,RetireUpdate类的重载操作符号()的实现就好理解了。 首先,函数使用一个do.while循环来将queued的值减少1,即将待渲染队列的大小减少1。当然,如果这个待渲染队列的大小本来就等于0,那么函数就什么也不做就返回了。接着,函数将待渲染队列的头部head向前移一个位置。移动后的得到的位置所对应的缓冲区就是接下来要渲染的,因此,函数最后要将它返回给调用者。函数在将要渲染的缓冲区的位置返回给调用者之前,还会将当前正在操作的UI元数据缓冲区的空闲缓冲区的个数available增加1。 至此,DequeueCondition、QueueUpdate、DequeueUpdate和RetireUpdate这四个辅助类就介绍完成了,接下来,我们就可以继续分析Android应用程序请求SurfaceFlinger服务渲染Surface的过程了。在分析的过程中,我们还会继续看到这四个辅助类的使用方法。 在前面 一文的Step 16中,我们将在Android应用程序这一侧所创建的一个Surface的父类ANativeWindow的OpenGL回调函数dequeueBuffer和queueBuffer分别设置为Surface类的静态成员函数dequeueBuffer和queueBuffer。OpenGL在绘图之前,就首先会调用Surface类的静态成员函数dequeueBuffer来获得一个空闲的UI元数据缓冲区,接着请求SurfaceFlinger服务为这个UI元数据缓冲区分配一个图形缓冲区。有了图形缓冲区之后,OpengGL库就可以往里面填入UI数据。在往图形缓冲区填入UI数据的同时,OpenGL库也会往前面获得的UI元数据缓冲区填入当前正在操作的Surface的裁剪区域、纹理坐标和旋转方向等信息。再接下来,OpenGL库就会调用Surface类的静态成员函数queueBuffer来将前面已经填好了数据的UI元数据缓冲区添加到当前正在操作的Surface的UI元数缓冲区堆栈的待渲染队列中。最后,Android应用程序就会请求SurfaceFlinger服务将当前正在操作的Surface的UI数据渲染到设备显示屏去。 接下来,我们就首先分析Surface类的静态成员函数dequeueBuffer的实现,接着再分析Surface类的静态成员函数queueBuffer的实现,最后分析SurfaceFlinger服务渲染Surface的图形缓冲区的过程。 Surface类的静态成员函数dequeueBuffer获得空闲UI元数据缓冲区,以及请求SurfaceFlinger服务为这个空闲UI元数据缓冲区分配图形缓冲区的过程如图2所示:这个过程一共分为12个步骤,接下来我们就详细分析每一个步骤。 Step 1. Surface.dequeueBuffercpp view plain copy 在CODE上查看代码片派生到我的代码片int Surface:dequeueBuffer(ANativeWindow* window, android_native_buffer_t* buffer) Surface* self = getSelf(window); return self-dequeueBuffer(buffer); 这个函数定义在文件frameworks/base/libs/surfaceflinger_client/Surface.cpp中。 参数window虽然是一个ANativeWindow指针,但是它实际上指向的是一个Surface对象,因此,函数首先调用另外一个静态成员函数getSelf来将它转换为一个Surface对象self,接着再调用这个Surface对象self的成员函数dequeueBuffer来分配一个空闲UI元数据缓冲区和一个图形缓冲区,其中,分配的图形缓冲区就保存在输出参数buffer中。 Surface类的非静态成员函数dequeueBuffer的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片int Surface:dequeueBuffer(android_native_buffer_t* buffer) status_t err = validate(); if (err != NO_ERROR) return err; . ssize_t bufIdx = mSharedBufferClient-dequeue(); . if (bufIdx 0) . return bufIdx; / grow the buffer array if needed const size_t size = mBuffers.size(); const size_t needed = bufIdx+1; if (size needed) mBuffers.insertAt(size, needed-size); uint32_t w, h, format, usage; if (needNewBuffer(bufIdx, &w, &h, &format, &usage) err = getBufferLocked(bufIdx, w, h, format, usage); . if (err = NO_ERROR) / reset the width/height with the what we get from the buffer const sp& backBuffer(mBuffersbufIdx); mWidth = uint32_t(backBuffer-width); mHeight = uint32_t(backBuffer-height); / if we still dont have a buffer here, we probably ran out of memory const sp& backBuffer(mBuffersbufIdx); if (!err & backBuffer=0) err = NO_MEMORY; if (err = NO_ERROR) mDirtyRegion.set(backBuffer-width, backBuffer-height); *buffer = backBuffer.get(); else mSharedBufferClient-undoDequeue(bufIdx); return err; 这个函数定义在文件frameworks/base/libs/surfaceflinger_client/Surface.cpp中。 函数首先调用Surface类的成员变量mSharedBufferClient所指向的一个SharedBufferClient对象的成员函数dequeue来从UI元数据缓冲区堆栈中获得一个空闲的缓冲区。获得的空闲缓冲区使用一个编号来描述,这个编号保存在变量bufIdx中。后面我们再分析SharedBufferClient类的成员函数dequeue的实现。 获最一个空闲UI元数据缓冲区之后,函数接下来判断该缓冲区的编号是否大于Surface类的成员变量mBuffers所描述的一个GraphicBuffer向量的大小。如果大于,那么就需要扩充这个向量的大小,以便后面可以用来保存与该缓冲区对应的一个GraphicBuffer,即一个图形缓冲区。 函数再接下来调用Surface类的另外一个成员函数needNewBuffer来判断之前是否已经为编号为bufIdx的UI元数据缓冲区分配过图形缓冲区了,它的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool Surface:needNewBuffer(int bufIdx, uint32_t *pWidth, uint32_t *pHeight, uint32_t *pFormat, uint32_t *pUsage) const Mutex:Autolock _l(mSurfaceLock); / Always call needNewBuffer(), since it clears the needed buffers flags bool needNewBuffer = mSharedBufferClient-needNewBuffer(bufIdx); bool validBuffer = mBufferInfo.validateBuffer(mBuffersbufIdx); bool newNeewBuffer = needNewBuffer | !validBuffer; if (newNeewBuffer) mBufferInfo.get(pWidth, pHeight, pFormat, pUsage); return newNeewBuffer; 这个函数定义在文件frameworks/base/libs/surfaceflinger_client/Surface.cpp中。 由于UI元数据缓冲区堆栈中的缓冲区是循环使用的。当一个UI元数据缓冲区第一次被使用的时候,应用程序就会请求SurfaceFlinger服务为它分配一个图形缓冲区。这个图形缓
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 大学生心理健康教育 课件 第四章大学生学习心理
- 应急安全和防汛培训课件
- 2025石油石化职业技能鉴定考试练习题附参考答案详解【模拟题】
- 秋季腹泻病程发展规律与预后评估
- 新生儿苯丙酮尿症筛查与饮食管理
- 共建房屋合同(标准版)
- 儿童常见传染病预防与护理
- 2025辽宁省灯塔市中考数学复习提分资料及参考答案详解【完整版】
- 执业药师之《药事管理与法规》题库检测试题打印及答案详解【基础+提升】
- 2025自考公共课能力检测试卷【重点】附答案详解
- 青少年无人机课程大纲
- 2025-2030中国耳鼻喉外科手术导航系统行业市场发展趋势与前景展望战略研究报告
- 剪彩仪式方案超详细流程
- 2024年二级建造师考试《矿业工程管理与实物》真题及答案
- 人教版初中九年级化学上册第七单元课题1燃料的燃烧第2课时易燃物和易爆物的安全知识合理调控化学反应课件
- 发电厂继电保护培训课件
- 校企“双元”合作探索开发轨道交通新型活页式、工作手册式教材
- 肺癌全程管理
- 2024年考研英语核心词汇
- 信息系统定期安全检查检查表和安全检查报告
- 颅脑外伤患者的麻醉管理专家共识(2021版)
评论
0/150
提交评论