版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、android6.0源碼分析之Camera API2.0下的Preview(預覽)流程分析本文將基于 android6.0 的源碼,對 Camera API2.0 下 Camera 的 preview 的流程進行分析。在文章 android6.0 源碼 分析之 Camera API2.0 下的初始化流程分析中,已經對 Camera2 內置應用的 Open 即初始化流程進行了詳細的分析, 而在 open 過程中,定義了一個 PreviewCallback ,當時并未 詳細分析,即 Open 過程中,會自動開啟預覽過程,即會調 用 OneCameraImpl 的 startPreview 方法,它
2、是捕獲和繪制屏 幕預覽幀的開始,預覽才會真正開始提供一個表面。Camera2 文章分析目錄:android6.0 源碼分析之 Camera API2.0 簡介 android6.0 源碼分析之 Camera2 HAL 分析 android6.0 源碼分析之 Camera API2.0 下的初始化流程分析 android6.0源碼分析之Camera API2.0下的Preview(預覽)流程 分析android6.0源碼分析之 Camera API2.0下的Capture流程分析 android6.0 源碼分析之 Camera API2.0 下的 video 流程分析 Camera API2.0
3、 的應用 1、Camera2 preview 的應用層流程分 析 preview 流程都是從 startPreview 開始的,所以來看 startPreview 方法的代碼:/OneCameraImpl.javaOverridepublic void startPreview(Surface previewSurface, CaptureReadyCallback listener) mPreviewSurface = previewSurface; / 根據 Surface 以及 CaptureReadyCallback 回調來建立 preview 環(huán)境 setupAsync(mPrevi
4、ewSurface, listener);1234567 這其中有一 個比較重要的回調 CaptureReadyCallback ,先分析 setupAsync 方法: /OneCameraImpl.javaprivate void setupAsync(final Surface previewSurface, final CaptureReadyCallback listener) mCameraHandler.post(new Runnable() Override public void run() / 建立 preview 環(huán)境 setup(previewSurface, liste
5、ner); 這里通過 CameraHandler 來 post 一個 Runnable 對象,它只會調用 Runnable 的 run 方法,它 仍然屬于 UI 線程,并沒有創(chuàng)建新的線程。所以,繼續(xù)分析 setup 方法:/ OneCameralmpl.javaprivate void setup(Surface previewSurface, final CaptureReadyCallback listener) try if (mCaptureSession != null) mCaptureSession.abortCaptures(); mCaptureSession = null;
6、 List outputSurfaces = new ArrayList(2); outputSurfaces.add(previewSurface);outputSurfaces.add(mCapturelmageReader.getSurface(); /創(chuàng)建 CaptureSession 會話來與 Camera Device 發(fā)送 Preview 請求 mDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() Override public void onConfigureF
7、ailed(CameraCaptureSession session) / 如 果配置失敗,則回調 CaptureReadyCallback 的 onSetupFailed 方法 listener.onSetupFailed(); Override public void onConfigured(CameraCaptureSession session) mCaptureSession = session; mAFRegions = ZERO_WEIGHT_3A_REGION; mAERegions = ZERO_WEIGHT_3A_REGION; mZoomValue = 1f; mCro
8、pRegion = cropRegionForZoom(mZoomValue); / 調用 repeatingPreview 來啟動 preview boolean success = repeatingPreview(null); if (success) / 若啟動成功,則回調 CaptureReadyCallback 的 onReadyForCapture,表示準備拍照 成功 listener.onReadyForCapture(); else / 若啟動失敗,則 回調 CaptureReadyCallback 的 onSetupFailed,表示 preview 建立失敗 listen
9、er.onSetupFailed(); Override public void onClosed(CameraCaptureSession session) super.onClosed(session); , mCameraHandler); catch (CameraAccessException ex) Log.e(TAG , Could not set up capture session, ex);54647 首先,調用 Device 的 createCaptureSession方法來創(chuàng)建 一個會話,并定義了會話的狀態(tài)回調CameraCaptureSession.StateCall
10、back(),其中,當會話創(chuàng)建成 功,則會回調 onConfigured() 方法 ,在其中,首先調用 repeatingPreview 來啟動 preview ,然后處理 preview 的結果并 調用先前定義的 CaptureReadyCallback 來通知用戶進行 Capture 操作。先分析 repeatingPreview 方法: / OneCameraImpl.javaprivate boolean repeatingPreview(Object tag) try / 通過 CameraDevice 對象創(chuàng)建一個 CaptureRequest 的 preview 請求 Captu
11、reRequest.Builder builder = mDevice.createCaptureRequest( CameraDevice.TEMPLATE_P REVIEW); / 添加預覽的目標 Surface builder.addTarget(mPreviewSurface); / 設置預覽模式 builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); addBaselineCaptureKeysToRequest(builder); / 利用會話發(fā)送請 求, mCaptureCallbac
12、k 為 mCaptureSession.setRepeatingRequest(builder.build(), mCaptureCallback,mCameraHandler); Log.v(TAG, String.format(Sent repeating Preview request, zoom = %.2f, mZoomValue); return true; catch (CameraAccessException ex) Log.e(TAG, Could not access camera setting up preview., ex); return false; 首先 調用
13、 CameraDevicelmpI 的 createCaptureRequest方法創(chuàng)建類 型為 TEMPLATE_PREVIEW 的 CaptureRequest,然后調用 CameraCaptureSessionlmpI 的 setRepeatingRequest方法將止匕 請求發(fā)送出去:/CameraCaptureSessionImpI.javaOverridepubIic synchronized int setRepeatingRequest(CaptureRequest request, CaptureCaIIback caIIback, HandIer handIer) thro
14、ws CameraAccessException if (request = nuII) throw new IIIegaIArgumentException(request must not be nuII); eIse if (request.isReprocess() throw new IIIegaIArgumentException(repeating reprocess requests are not supported); checkNotCIosed(); handIer = checkHandIer(handIer, caIIback); . / 將此請求添加到待處理 的序
15、列里 至此應用層 的 preview 的請求流程分析結束,繼續(xù)分析其結果處理,如 果 preview 開啟成功,則會回調 CaptureReadyCaIIback 的 onReadyForCapture 方法, 現在分析 CaptureReadyCaIIback 回 調:/CaptureModule.javanew CaptureReadyCallback() Override pubIic void onSetupFaiIed() mCameraOpenCloseLock.release(); Log.e(TAG, Could not set up preview.); mMainThrea
16、d.execute(new Runnable() Override public void run() if (mCamera = null) Log.d(TAG , Camera closed, aborting.); return; mCamera.close(); mCamera = null; ); Override public void onReadyForCapture() mCameraOpenCloseLock.release(); mMainThread.execute(new Runnable() Override public void run() Log.d(TAG,
17、 Ready for capture.); if (mCamera = null) Log.d(TAG , Camera closed, aborting.); return; / onPreviewStarted(); onReadyStateChanged(true); mCamera.setReadyStateChangedListener(CaptureModule.this); mUI.initializeZoom(mCamera.getMaxZoom(); mCamera.setFocusStateListener(CaptureModule.this); 根據前面的分析,預覽成功
18、后會回調 onReadyForCapture 方法,它主要是通知主線程的狀態(tài)改變, 并設置 Camera 的 ReadyStateChangedListener 的監(jiān)聽,其回 調方法如下: /CaptureModule.javaOverridepublic void onReadyStateChanged(boolean readyForCapture) if (readyForCapture) mAppController.getCameraAppUI().enableModeOptions(); mAppController.setShutterEnabled(readyForCaptur
19、e);1234567 8 如代碼所示,當其狀態(tài)變成準備好拍照,則將會調用CameraActivity 的 setShutterEnabled 方法,即使能快門按鍵, 此時也就是說預覽成功結束, 可以按快門進行拍照了, 所以, 到這里,應用層的 preview 的流程基本分析完畢,下圖是應 用層的關鍵調用的流程時序圖:2、 Camera2 preview 的 Native 層流程分析分析 Preview 的 Native 的代碼真是費了九牛二虎之力, 若有分析不正確之處, 請各位大神指正,在第一小節(jié)的后段最后會調用 CameraDeviceImpl 的 setRepeatingRequest 方
20、法來提交請求, 而在 android6.0 源碼分析之 Camera API2.0 簡介中,分析了Camera2框架 Java IPC通信使用了 CameraDeviceUser 來進行 通信,所以看 Native 層的 ICameraDeviceUser 的 onTransact 方法來處理請求的提交: /ICameraDeviceUser.cppstatus_t BnCameraDeviceUser:onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) switch(code) /請求提交
21、case SUBMIT_REQUEST: CHECK_INTERFACE(ICameraDeviceUser, data, reply); / arg0 = request spCaptureRequest request; if (data.readInt32() != 0) request = new CaptureRequest(); request-readFromParcel(const_castParcel*(&data); / arg1 = streaming (bool) bool repeating = data.readInt32(); / return code: req
22、uestId (int32) reply-writeNoException(); int64_t lastFrameNumber = -1; /將實現 BnCameraDeviceUser 的對下 崗的 submitRequest 方法代碼寫入 Binder reply-writeInt32(submitRequest(request, repeating, &lastFrameNumber); reply-writeInt32(1); reply-writeInt64(lastFrameNumber); return NO_ERROR; break; 728293031CameraDevic
23、eClientBase 繼承了 BnCameraDeviceUser 類, 所以 CameraDeviceClientBase 相當 于 IPC Binder 中的 client ,所以會調用其 submitRequest 方法, 此處,至于 IPC Binder 通信原理不做分析, 其參照其它資料: /CameraDeviceClient.cppstatus_t CameraDeviceClient:submitRequest(spCaptureRequest request,bool streaming, /*out*/int64_t* lastFrameNumber) ListspCap
24、tureRequest requestList; requestList.push_back(request); return submitRequestList(requestList, streaming, lastFrameNumber);1234567 簡單的調用,繼續(xù)分析 submitRequestList : / CameraDeviceClientstatus_t CameraDeviceClient:submitRequestList(List requests,bool streaming, int64_t* lastFrameNumber) . /Metadata 鏈表 L
25、ist metadataRequestList; . for (List :iterator it = requests.begin(); it != requests.end(); +it) sp request = *it; . /初始化 Metadata 數據 CameraMetadatametadata(request-mMetadata); . /設置 Stream 的容量 Vector outputStreamIds;outputStreamIds.setCapacity(request-mSurfaceList.size(); / 循 環(huán)初始化 Surface for (size
26、_t i = 0; i mSurfaceList.size(); +i) sp surface = request-mSurfaceListi; if (surface = 0) continue; sp gbp = surface-getIGraphicBufferProducer(); int idx = mStreamMap.indexOfKey(IInterface:asBinder(gbp); . int streamId = mStreamMap.valueAt(idx); outputStreamIds.push_back(streamId); / 更新數據 metadata.u
27、pdate(ANDROID_REQUEST_OUTPUT_STREAM S, &outputStreamIds0, outputStreamIds.size(); if (request-mIsReprocess) metadata.update(ANDROID_REQUEST_INPUT_STREAMS, &mInputStream.id, 1); metadata.update(ANDROID_REQUEST_ID, &requestId, /*size*/1); loopCounter+; / loopCounter starts from 1 / 壓棧 metadataRequestL
28、ist.push_back(metadata); mRequestIdCounter+; if (streaming) / 預覽會走此條通道 res = mDevice-setStreamingRequestList(metadataRequestList, lastFrameNumber); if (res != OK) . else mStreamingRequestList.push_back(requestId); else /Capture 等走此條通道 res =mDevice-captureList(metadataRequestList, lastFrameNumber); i
29、f (res != OK) . if (res = OK) return requestId; 5556575859setStreamingRequestList 和 captureList 方法都調用 了 submitRequestsHelper 方法,只是他們的 repeating 參數一 個ture,一個為false,而本節(jié)分析的 preview調用的是 setStreamingRequestList 方法,并且 API2.0 下 Device 的實現 為 Camera3Device ,所以看它的 submitRequestsHelper 實現: / Camera3Device.cpps
30、tatus_tCamera3Device:submitRequestsHelper(const List &requests, bool repeating,/*out*/int64_t *lastFrameNumber) . RequestList requestList; / 在這里面會進行 CaptureRequest 的 創(chuàng)建, 并調用 configureStreamLocked 進行 stream 的配置, 主 要是設置了一個回調 captureResultCb,即后面要分析的重要 的回調 res = convertMetadataListToRequestListLocked(re
31、quests, /*out*/&requestList); . if (repeating) / 眼熟不,這個方法名 和應用層中 CameraDevice 的 setRepeatingRequests樣 res = mRequestThread-setRepeatingRequests(requestList, lastFrameNumber); else / 不需重復,即 repeating 為 false 時,調用此方法來講請求提交 res =mRequestThread-queueRequestList(requestList, lastFrameNumber); . return從代碼
32、可知,在 Camera3Device 里創(chuàng)建了要給 RequestThread 線程,調用它的 setRepeatingRequests或者 queueRequestList 方法來將應用層 發(fā)送過來的 Request提交,繼續(xù)看setRepeatingRequests方法: / Camera3Device.cppstatus_tCamera3Device:RequestThread:setRepeatingRequests(const RequestList &requests, /*out*/int64_t *lastFrameNumber) Mutex:Autolock l(mReque
33、stLock); if (lastFrameNumber !=NULL) *lastFrameNumber = mRepeatingLastFrameNumber; mRepeatingRequests.clear(); /將其插入 mRepeatingRequest鏈 表 mRepeatingRequests.insert(mRepeatingRequests.begin(), requests.begin(), requests.end(); unpauseForNewRequests(); mRepeatingLastFrameNumber =NO_IN_FLIGHT_REPEATING
34、_FRAMES; return至此,Native 層的 preview 過程基本分析結束,下面的工作將會交給 Camera HAL 層來 處理,先給出 Native 層的調用時序圖:3、 Camera2 preview 的 CameraHAL 層流程分析本節(jié)將不再 對 Camera 的 HAL 層的初始化以及相關配置進行分析, 只對 preview等相關流程中的 frame metadata的處理流程進行分 析,具體的 CameraHAL 分析請參考 android6.0 源碼分析之 Camera2 HAL 分析 .在第二小節(jié)的 submitRequestsHelper 方法 中調用 conv
35、ertMetadataListToRequestListLocked 的時候會進 行 CaptureRequest 的創(chuàng)建,并調用 configureStreamLocked 進 行 stream 的配置,主要是設置了一個回調 captureResultCb , 所以 Native 層在 request 提交后,會回調此 captureResultCb 方法,首先分析 captureResultCb: / QCamera3HWI.cppvoid QCamera3HardwareInterface:captureResultCb(mm_camera_su per_buf_t *metadata_
36、buf, camera3_stream_buffer_t *buffer, uint32_t frame_number) if (metadata_buf) if (mBatchSize) / 批處理模式,但代碼也是循環(huán)調用 handleMetadataWithLock 方法 handleBatchMetadata(metadata_buf, true /* free_and_bufdone_meta_buf */); else /* mBatchSize = 0 */ pthread_mutex_lock(&mMutex); / 處理元數據 handleMetadataWithLock(me
37、tadata_buf, true /* free_and_bufdone_meta_buf */); pthread_mutex_unlock(&mMutex); else pthread_mutex_lock(&mMutex); handleBufferWithLock(buffer, frame_number); pthread_mutex_unlock(&mMutex); 一種是通過循環(huán)來進行元數據的批處理,另一種是直接進行元數據的處 理,但是批處理最終也是循環(huán)調用 handleMetadataWithLock 來處理: / QCamera3HWI.cppvoid QCamera3Har
38、dwareInterface:handleMetadataWithLock(mm_c amera_super_buf_t *metadata_buf, bool free_and_bufdone_meta_buf) . /Partial result on process_capture_result for timestamp if (urgent_frame_number_valid) . for (List:iterator i =mPendingRequestsList.begin(); i != mPendingRequestsList.end(); i+) . if (i-fram
39、e_number = urgent_frame_number &i-bUrgentReceived = 0) camera3_capture_result_t result; memset(&result, 0, sizeof(camera3_capture_result_t); i-partial_result_cnt+; i-bUrgentReceived = 1; / 提取 3A 數據 result.result =translateCbUrgentMetadataToResultMetadata(metadata); . / 對 Capture Result 進行處理 mCallbac
40、kOps-process_capture_result(mCallbackOps, &result); / 釋放 camera_metadata_t free_camera_metadata(camera_metadata_t *)result.result); break; . for (List:iterator i = mPendingRequestsList.begin(); i != mPendingRequestsList.end() & i-frame_number 0, sizeof(camera3_capture_result_t); .if (i-frame_number
41、0, sizeof(camera3_notify_msg_t); / 定義 消息類型 notify_msg.type = = = (uint64_t)capture_time (urgent_frame_number - i-frame_number) * NSEC_PER_33MSEC; / 調用回調通知應用層發(fā)生 CAMERA3_MSG_SHUTTER 消息 mCallbackOps-notify(mCallbackOps, ¬ify_msg); . CameraMetadata dummyMetadata; / 更新元數據 dummyMetadata.update(ANDROID_
42、SENSOR_TIMESTAMP, &i-timestamp, 1);dummyMetadata.update(ANDROID_REQUEST_ID, &(i-request_id), 1); / 得到元數據釋放結果 result.result = dummyMetadata.release(); else camera3_notify_msg_t notify_msg; memset(¬ify_msg, 0, sizeof(camera3_notify_msg_t); / Send shutter notify to frameworks notify_msg.type =CAMERA
43、3_MSG_SHUTTER; . / 從 HAL 中獲得 Metadata result.result = translateFromHalMetadata(metadata, i-timestamp, i-request_id, i-jpegMetadata, i-pipeline_depth, i-capture_intent); saveExifParams(metadata); if (i-blob_request) . if (enabled &metadata-is_tuning_params_valid) / 將 Metadata 復制到文件 dumpMetadataToFile
44、(metadata-tuning_params, mMetaFrameCount, enabled, Snapshot,frame_number); mPictureChannel-queueReprocMetadata(metadata_buf); else / Return metadata buffer if (free_and_bufdone_meta_buf) mMetadataChannel-bufDone(metadata_buf); free(metadata_buf); 其中,首先會調用回調的 process_capture_result 方法來對 Capture Resul
45、t 進行處理,然 后會調用回調的 notify 方法來發(fā)送一個 CAMERA3_MSG_SHUTTER 消息, 而 process_capture_result 所對應的實現其實就是 Camera3Device 的 processCaptureResult 方法,先分析 processCaptureResult: /Camera3Device.cppvoid Camera3Device:processCaptureResult(const camera3_capture_result *result) . / 對于 HAL3.2+, 如果 HAL 不支持partial,當metadata被包含
46、在result中時,它必須將 partial_result 設置為 1 . Mutex:Autolock l(mInFlightLock); ssize_t idx = mInFlightMap.indexOfKey(frameNumber); . InFlightRequest &request = mInFlightMap.editValueAt(idx); if(result-partial_result != 0) = result-partial_result; / 檢查結果是否只有 partial metadata if (mUsePartialResult & result-re
47、sult != NULL) if (mDeviceVersion = CAMERA_DEVICE_API_VERSION_3_2) /HAL 版本高于 3.2 if (result-partial_result mNumPartialResults | result-partial_result 1) /Log 顯示錯誤 return; isPartialResult = (result-partial_result if (isPartialResult) / 將 結果加入到請求的結果集中 else /低于 3.2 . if (isPartialResult) / . if (result-
48、result != NULL & !isPartialResult) if (shutterTimestamp = 0) request.pendingMetadata = = collectedPartialResult; else CameraMetadata metadata; metadata = result-result; / 發(fā)送 Capture Result sendCaptureResult(metadata, request.resultExtras, collectedPartialResult, frameNumber,hasInputBufferInRequest,r
49、equest.aeTriggerCancelOverride); /結果處理好了,將請求移除 removeInFlightRequestIfReadyLocked(idx); / scope for mInFlightLock 由代碼可知,它會處理局部的或者全部的 metadata 數據,最后如果 result 不為空,且得到的是請求處 理的全部數據,則會調用 sendCaptureResult 方法來將請求結 果發(fā)送出去: /Camera3Device.cppvoid Camera3Device:sendCaptureResult(CameraMetadata &pendingMetadat
50、a,CaptureResultExtras &resultExtras,CameraMetadata &collectedPartialResult,uint32_t frameNumber,bool reprocess, const AeTriggerCancelOverride_t &aeTriggerCancelOverride) if (pendingMetadata.isEmpty()/ 如果數據為空,直接返回 return; . CaptureResult captureResult; captureResult.mResultExtras = resultExtras; capt
51、ureResult.mMetadata = pendingMetadata; / 更新 1) != OK) SET_ERR(Failed to set frame# in metadata (%d),frameNumber); return; else . / Append anyprevious partials to form a complete result if (mUsePartialResult & !collectedPartialResult.isEmpty() / 排序 / Check that theres a timestamp in the result metada
52、ta camera_metadata_entry entry = .overrideResultForPrecaptureCancel(&captureResult.mMetadata, aeTriggerCancelOverride); / 有效的結果,將其插入 Buffer List:iterator queuedResult=mResultQueue.insert(mResultQueue.end(), CaptureResult(captureResult); . 最后,它將 Capture Result 插入了結果隊列,并釋放了結果的信號量,所以到這 里, Capture Resul
53、t 處理成功,下面分析前面的 notify 發(fā)送 CAMERA3_MSG_SHUTTER 消息: /Camera3Device.cppvoid Camera3Device:notify(const camera3_notify_msg *msg) NotificationListener *listener; Mutex:Autolock l(mOutputLock); listener = mListener; . switch (msg-type) case CAMERA3_MSG_ERROR: notifyError(msg-message.error, listener); break
54、; caseCAMERA3_MSG_SHUTTER: notifyShutter(msg-message.shutter, listener); break; default: SET_ERR(Unknown notify message from HAL: %d, msg-type); 它 調用了 notifyShutter 方法: / Camera3Device.cppvoid Camera3Device:notifyShutter(const camera3_shutter_msg_t &msg, NotificationListener *listener) . / Set times
55、tamp for the request in the in-flight tracking / and get the request ID to send upstream Mutex:Autolock l(mInFlightLock); idx = mInFlightMap.indexOfKey(msg.frame_number); if (idx = 0) InFlightRequest &r = mInFlightMap.editValueAt(idx); / Call listener, if any if (listener != NULL) / 調用監(jiān)聽的 notifyShutter 法國法 listener-notifyShutter(r.resultExtras, msg.timestamp); . / 將待處理的 result 發(fā)送到 首先它會通 知 listener preview 成功,最后會調用 sendCaptureResult 將結 果加入到結果隊列。它會調用 listener 的 notifyShutter 方法, 此處的 listener 其實是 CameraDeviceClient 類,所以會調用 CameraDeviceClient 類的 notifyShutter 方法: /Camer
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年磚瓦及建筑砌塊合作協(xié)議書
- 2024版軟件委托開發(fā)合同
- 滬教版三年級下冊數學第二單元 用兩位數乘除 測試卷帶答案(培優(yōu))
- 2024個人借款合同范文專業(yè)版
- 2024體育賽事用品定作合同
- 2024商業(yè)合同樣本標準版
- 直播基地環(huán)境影響評估
- 在線征婚咨詢服務合同
- 網點客服業(yè)務技能考試
- 2024銷售入職合同范本
- 音樂劇院演出商業(yè)計劃書
- 糖尿病中醫(yī)特色治療課件
- 提升員工服務意識培訓課件
- 大學生職業(yè)生涯規(guī)劃書環(huán)境設計
- 園林專業(yè)大學生職業(yè)生涯規(guī)劃
- 【川教版】《生命 生態(tài) 安全》五上第17課《發(fā)明讓生活更美好》課件
- 第四章 學前兒童記憶的發(fā)展
- 國家開放大學兒童發(fā)展問題的咨詢與輔導形考周測驗三周-周參考答案
- 五年級上冊口算練習400題及答案
- 就業(yè)引航筑夢未來
- 班會議題探索未來職業(yè)的發(fā)展趨勢
評論
0/150
提交評論