浅析Android Camera架构
来源:互联网 发布:淘宝代销退货如处理 编辑:程序博客网 时间:2024/05/02 00:20
本博文是基于Android 4.4讲解
1、application 层:
当我们Android工程师想打开camera时通常直接调用Camera.java中的 Camer.open(cameraId)静态函数
camera.java 位于 frameworks/base/core/java/android/hardware目录下
//以下是部分代码
public static Camera open(int cameraId) { return new Camera(cameraId); }
</pre><pre code_snippet_id="1846209" snippet_file_name="blog_20160823_3_8904287" name="code" class="html"><pre name="code" class="html">Camera(int cameraId) { mShutterCallback = null; mRawImageCallback = null; mJpegCallback = null; mPreviewCallback = null; mPostviewCallback = null; mUsingPreviewAllocation = false; mZoomListener = null; Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; } String packageName = ActivityThread.currentPackageName(); <span style="color:#ff0000;">native_setup(new WeakReference<Camera>(this), cameraId, packageName);</span> }
其中native_setup接口是native接口其函数声明如下
private native final void native_setup(Object camera_this, int cameraId, String packageName);
2、jni层
在camera.java中会调用到native_setup函数,此jni接口定义在 frameworks/base/core/jni下面的android_hardware_Camera.cpp中
// connect to camera servicestatic void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jint cameraId, jstring clientPackageName){ // Convert jstring to String16 const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL); jsize rawClientNameLen = env->GetStringLength(clientPackageName); String16 clientName(rawClientName, rawClientNameLen); env->ReleaseStringChars(clientPackageName, rawClientName); <span style="color:#ff0000;"> sp<Camera> camera = Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID);</span> if (camera == NULL) { jniThrowRuntimeException(env, "Fail to connect to camera service"); return; } // make sure camera hardware is alive if (camera->getStatus() != NO_ERROR) { jniThrowRuntimeException(env, "Camera initialization failed"); return; } jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { jniThrowRuntimeException(env, "Can't find android/hardware/Camera"); return; } // We use a weak reference so the Camera object can be garbage collected. // The reference is only used as a proxy for callbacks. sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera); context->incStrong((void*)android_hardware_Camera_native_setup); camera->setListener(context); // save context in opaque field env->SetIntField(thiz, fields.context, (int)context.get());}其中上面会调Camera::connect接口此类是frameworks/av/camera/Camera.cpp
sp<Camera> Camera::connect(int cameraId, const String16& clientPackageName, int clientUid){ return <span style="color:#ff0000;">CameraBaseT::connect</span>(cameraId, clientPackageName, clientUid);//调用了父类的connect接口}frameworks/av/camera/CameraBase.cpp
template <typename TCam, typename TCamTraits>sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId, const String16& clientPackageName, int clientUid){ ALOGV("%s: connect", __FUNCTION__); sp<TCam> c = new TCam(cameraId); sp<TCamCallbacks> cl = c; status_t status = NO_ERROR; <span style="color:#ff0000;">const sp<ICameraService>& cs = getCameraService(); //得到CameraService.cpp实例</span> if (cs != 0) { <span style="color:#ff0000;"> TCamConnectService fnConnectService = TCamTraits::fnConnectService; status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid, /*out*/ c->mCamera); //此行最终会调用到CameraService.cpp中的connect接口</span> } if (status == OK && c->mCamera != 0) { c->mCamera->asBinder()->linkToDeath(c); c->mStatus = NO_ERROR; } else { ALOGW("An error occurred while connecting to camera: %d", cameraId); c.clear(); } return c;}frameworks/av/services/camera/libcameraservicw/CameraService.cpp
status_t CameraService::connect( const sp<ICameraClient>& cameraClient, int cameraId, const String16& clientPackageName, int clientUid, /*out*/ sp<ICamera>& device) { String8 clientName8(clientPackageName); int callingPid = getCallingPid(); LOG1("CameraService::connect E (pid %d \"%s\", id %d)", callingPid, clientName8.string(), cameraId); status_t status = validateConnect(cameraId, /*inout*/clientUid); if (status != OK) { return status; } sp<Client> client; { Mutex::Autolock lock(mServiceLock); sp<BasicClient> clientTmp; if (!canConnectUnsafe(cameraId, clientPackageName, cameraClient->asBinder(), /*out*/clientTmp)) { return -EBUSY; } else if (client.get() != NULL) { device = static_cast<Client*>(clientTmp.get()); return OK; } int facing = -1; int deviceVersion = getDeviceVersion(cameraId, &facing); // If there are other non-exclusive users of the camera, // this will tear them down before we can reuse the camera if (isValidCameraId(cameraId)) { // transition from PRESENT -> NOT_AVAILABLE updateStatus(ICameraServiceListener::STATUS_NOT_AVAILABLE, cameraId); } switch(deviceVersion) { case CAMERA_DEVICE_API_VERSION_1_0: <span style="color:#ff0000;">client = new CameraClient(this, cameraClient, clientPackageName, cameraId, facing, callingPid, clientUid, getpid()); //我用的是camer1</span> break; case CAMERA_DEVICE_API_VERSION_2_0: case CAMERA_DEVICE_API_VERSION_2_1: case CAMERA_DEVICE_API_VERSION_3_0: client = new Camera2Client(this, cameraClient, clientPackageName, cameraId, facing, callingPid, clientUid, getpid(), deviceVersion); break; case -1: ALOGE("Invalid camera id %d", cameraId); return BAD_VALUE; default: ALOGE("Unknown camera device HAL version: %d", deviceVersion); return INVALID_OPERATION; } <span style="color:#ff0000;"> status_t status = connectFinishUnsafe(client, client->getRemote()); //见下面定义</span> if (status != OK) { // this is probably not recoverable.. maybe the client can try again // OK: we can only get here if we were originally in PRESENT state updateStatus(ICameraServiceListener::STATUS_PRESENT, cameraId); return status; } mClient[cameraId] = client; LOG1("CameraService::connect X (id %d, this pid is %d)", cameraId, getpid()); } // important: release the mutex here so the client can call back // into the service from its destructor (can be at the end of the call) device = client; return OK;}
status_t CameraService::connectFinishUnsafe(const sp<BasicClient>& client, const sp<IBinder>& remoteCallback) { <span style="color:#ff0000;"> status_t status = client->initialize(mModule); //其中 camera_module_t *mModule为一个结构体指针,此指针会在onFirstRef()接口中初始化见下面代码</span> if (status != OK) { return status; } remoteCallback->linkToDeath(this); return OK;}
//此接口会在CameraService实例化后调用,其中用到了sp机制,目前尚未研究通。
void CameraService::onFirstRef() { LOG1("CameraService::onFirstRef"); BnCameraService::onFirstRef(); if (<span style="color:#ff0000;">hw_get_module</span>(CAMERA_HARDWARE_MODULE_ID,//此句代码是关键之处 (const hw_module_t **)&mModule) < 0) { ALOGE("Could not load camera HAL module"); mNumberOfCameras = 0; } else { ALOGI("Loaded \"%s\" camera module", mModule->common.name); mNumberOfCameras = mModule->get_number_of_cameras(); if (mNumberOfCameras > MAX_CAMERAS) { ALOGE("Number of cameras(%d) > MAX_CAMERAS(%d).", mNumberOfCameras, MAX_CAMERAS); mNumberOfCameras = MAX_CAMERAS; } for (int i = 0; i < mNumberOfCameras; i++) { setCameraFree(i); } if (mModule->common.module_api_version >= CAMERA_MODULE_API_VERSION_2_1) { mModule->set_callbacks(this); } CameraDeviceFactory::registerService(this); }}
hw_get_module位于Android源码目录下 hardware/libhardware/Hardware.c
int hw_get_module(const char *id, const struct hw_module_t **module){ return hw_get_module_by_class(id, NULL, module);}//此函数会加载动态库
"/system/lib/hw"下面有名字为class_id的动态库
int hw_get_module_by_class(const char *class_id, const char *inst, const struct hw_module_t **module){ int status; int i; const struct hw_module_t *hmi = NULL; char prop[PATH_MAX]; char path[PATH_MAX]; char name[PATH_MAX]; if (inst) snprintf(name, PATH_MAX, "%s.%s", class_id, inst); else strlcpy(name, class_id, PATH_MAX); /* * Here we rely on the fact that calling dlopen multiple times on * the same .so will simply increment a refcount (and not load * a new copy of the library). * We also assume that dlopen() is thread-safe. */ /* Loop through the configuration variants looking for a module */ for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) { if (i < HAL_VARIANT_KEYS_COUNT) { if (property_get(variant_keys[i], prop, NULL) == 0) { continue; } snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH2, name, prop); // PATH2: "/vendor/lib/hw" if (access(path, R_OK) == 0) break; snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH1, name, prop); //PATH1: "/system/lib/hw" if (access(path, R_OK) == 0) break; } else { snprintf(path, sizeof(path), "%s/%s.default.so", HAL_LIBRARY_PATH2, name); if (access(path, R_OK) == 0) break; snprintf(path, sizeof(path), "%s/%s.default.so", HAL_LIBRARY_PATH1, name); if (access(path, R_OK) == 0) break; } } status = -ENOENT; if (i < HAL_VARIANT_KEYS_COUNT+1) { /* load the module, if this fails, we're doomed, and we should not try * to load a different variant. */ status = load(class_id, path, module); } return status;}
<span style="color:#ff0000;">//</span>
static int load(const char *id, const char *path, const struct hw_module_t **pHmi){ int status; void *handle; struct hw_module_t *hmi; /* * load the symbols resolving undefined symbols before * dlopen returns. Since RTLD_GLOBAL is not or'd in with * RTLD_NOW the external symbols will not be global */ handle = dlopen(path, RTLD_NOW); if (handle == NULL) { char const *err_str = dlerror(); ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown"); status = -EINVAL; goto done; } /* Get the address of the struct hal_module_info. */ const char *sym = HAL_MODULE_INFO_SYM_AS_STR; //HAL_MODULE_INFO_SYM_AS_STR 为 "HMI"
<span style="color:#ff0000;">//dlsym根据sym去实例化hmi,其中Camera_Moduel.cpp下有"HMI"名字的camera_module_t变量,见下面代码</span> hmi = (struct hw_module_t *)dlsym(handle, sym); if (hmi == NULL) { ALOGE("load: couldn't find symbol %s", sym); status = -EINVAL; goto done; } /* Check that the id matches */ if (strcmp(id, hmi->id) != 0) { ALOGE("load: id=%s != hmi->id=%s", id, hmi->id); status = -EINVAL; goto done; } hmi->dso = handle; /* success */ status = 0; done: if (status != 0) { hmi = NULL; if (handle != NULL) { dlclose(handle); handle = NULL; } } else { ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p", id, path, *pHmi, handle); } *pHmi = hmi; return status;}
CameraHal_Module.cpp //此类位于hardware/rk29/camera/camerahal目录下,这是我的源代码camera模块
camera_module_t HAL_MODULE_INFO_SYM = { common: { tag: HARDWARE_MODULE_TAG, version_major: ((CONFIG_CAMERAHAL_VERSION&0xff00)>>8), version_minor: CONFIG_CAMERAHAL_VERSION&0xff, id: CAMERA_HARDWARE_MODULE_ID, name: CAMERA_MODULE_NAME, author: "RockChip", <span style="color:#ff0000;">methods: &camera_module_methods,</span> dso: NULL, /* remove compilation warnings */ reserved: {0}, /* remove compilation warnings */ }, get_number_of_cameras: camera_get_number_of_cameras, get_camera_info: camera_get_camera_info, set_callbacks:NULL, get_vendor_tag_ops:NULL, reserved: {0}};
到此frameworks/av/services/camera/libcameraservicw/CameraService.cpp下的mModule实例化完成
我们在进入到CameraClient.cpp(frameworks/av/services/camera/libcameraservice/api1)中(上面的CameraService.cpp实例化了CameraClient并且调用了它内部的initialize(camera_module_t *module)接口)
status_t CameraClient::initialize(camera_module_t *module) { int callingPid = getCallingPid(); status_t res; LOG1("CameraClient::initialize E (pid %d, id %d)", callingPid, mCameraId); // Verify ops permissions res = startCameraOps(); if (res != OK) { return res; } char camera_device_name[10]; snprintf(camera_device_name, sizeof(camera_device_name), "%d", mCameraId); <span style="color:#ff0000;">mHardware = new CameraHardwareInterface(camera_device_name);</span> res = mHardware->initialize(&module->common); if (res != OK) { ALOGE("%s: Camera %d: unable to initialize device: %s (%d)", __FUNCTION__, mCameraId, strerror(-res), res); mHardware.clear(); return NO_INIT; } mHardware->setCallbacks(notifyCallback, dataCallback, dataCallbackTimestamp, (void *)mCameraId); // Enable zoom, error, focus, and metadata messages by default enableMsgType(CAMERA_MSG_ERROR | CAMERA_MSG_ZOOM | CAMERA_MSG_FOCUS | CAMERA_MSG_PREVIEW_METADATA | CAMERA_MSG_FOCUS_MOVE); LOG1("CameraClient::initialize X (pid %d, id %d)", callingPid, mCameraId); return OK;}
CameraHardwareInterface.h位于 frameworks/av/services/camera/libcameraservice/device1
status_t initialize(hw_module_t *module) { ALOGI("Opening camera %s", mName.string()); <span style="color:#ff0000;">int rc = module->methods->open(module, mName.string(), (hw_device_t **)&mDevice); //调用了上面CameraHal_Module.cpp下的</span><span style="color: rgb(255, 0, 0); font-family: Arial, Helvetica, sans-serif;">camera_module_t HAL_MODULE_INFO_SYM的open函数</span><span style="color:#ff0000;">,此函数定义见下面代码</span> if (rc != OK) { ALOGE("Could not open camera %s: %d", mName.string(), rc); return rc; } initHalPreviewWindow(); return rc; }
static struct hw_module_methods_t camera_module_methods = { open: camera_device_open};
int camera_device_open(const hw_module_t* module, const char* name, hw_device_t** device){ int rv = 0; int cameraid; rk_camera_device_t* camera_device = NULL; camera_device_ops_t* camera_ops = NULL; android::CameraHal* camera = NULL; android::Mutex::Autolock lock(gCameraHalDeviceLock); LOGI("camera_device open"); if (name != NULL) { cameraid = atoi(name); if(cameraid > gCamerasNumber) { LOGE("camera service provided cameraid out of bounds, " "cameraid = %d, num supported = %d", cameraid, gCamerasNumber); rv = -EINVAL; goto fail; } if(gCamerasOpen >= CAMERAS_SUPPORTED_SIMUL_MAX) { LOGE("maximum number(%d) of cameras already open",gCamerasOpen); rv = -ENOMEM; goto fail; } camera_device = (rk_camera_device_t*)malloc(sizeof(*camera_device)); if(!camera_device) { LOGE("camera_device allocation fail"); rv = -ENOMEM; goto fail; } camera_ops = (camera_device_ops_t*)malloc(sizeof(*camera_ops)); if(!camera_ops) { LOGE("camera_ops allocation fail"); rv = -ENOMEM; goto fail; } memset(camera_device, 0, sizeof(*camera_device)); memset(camera_ops, 0, sizeof(*camera_ops)); camera_device->base.common.tag = HARDWARE_DEVICE_TAG; camera_device->base.common.version = 0; camera_device->base.common.module = (hw_module_t *)(module); camera_device->base.common.close = camera_device_close; camera_device->base.ops = camera_ops; camera_ops->set_preview_window = camera_set_preview_window; camera_ops->set_callbacks = camera_set_callbacks; camera_ops->enable_msg_type = camera_enable_msg_type; camera_ops->disable_msg_type = camera_disable_msg_type; camera_ops->msg_type_enabled = camera_msg_type_enabled; camera_ops->start_preview = camera_start_preview; camera_ops->stop_preview = camera_stop_preview; camera_ops->preview_enabled = camera_preview_enabled; camera_ops->store_meta_data_in_buffers = camera_store_meta_data_in_buffers; camera_ops->start_recording = camera_start_recording; camera_ops->stop_recording = camera_stop_recording; camera_ops->recording_enabled = camera_recording_enabled; camera_ops->release_recording_frame = camera_release_recording_frame; camera_ops->auto_focus = camera_auto_focus; camera_ops->cancel_auto_focus = camera_cancel_auto_focus; camera_ops->take_picture = camera_take_picture; camera_ops->cancel_picture = camera_cancel_picture; camera_ops->set_parameters = camera_set_parameters; camera_ops->get_parameters = camera_get_parameters; camera_ops->put_parameters = camera_put_parameters; camera_ops->send_command = camera_send_command; camera_ops->release = camera_release; camera_ops->dump = camera_dump; *device = &camera_device->base.common; // -------- RockChip specific stuff -------- camera_device->cameraid = cameraid; <span style="color:#ff0000;">camera = new android::CameraHal(cameraid); //进入hal层</span> if(!camera) { LOGE("Couldn't create instance of CameraHal class"); rv = -ENOMEM; goto fail; } gCameraHals[cameraid] = camera; gCamerasOpen++; } return rv;fail: if(camera_device) { free(camera_device); camera_device = NULL; } if(camera_ops) { free(camera_ops); camera_ops = NULL; } if(camera) { delete camera; camera = NULL; } *device = NULL; return rv;}
3、hal层
hardware/rk29/camera/camerahal/CameraHal.cpp
//以下会根据camera硬件分类uvc soc isp等,实例化成对应的adapter.cpp(数据适配器,此类会调用iotrl系统接口和驱动层进行通信)
CameraHal::CameraHal(int cameraId) :commandThreadCommandQ("commandCmdQ"){LOG_FUNCTION_NAME { char trace_level[PROPERTY_VALUE_MAX]; int level; property_get(CAMERAHAL_TRACE_LEVEL_PROPERTY_KEY, trace_level, "0"); sscanf(trace_level,"%d",&level); setTracerLevel(level);} mCamId = cameraId; mCamFd = -1; mCommandRunning = -1;mCameraStatus = 0; #if (CONFIG_CAMERA_MEM == CAMERA_MEM_ION) mCamMemManager = new IonMemManager(); LOG1("%s(%d): Camera Hal memory is alloced from ION device",__FUNCTION__,__LINE__);#elif(CONFIG_CAMERA_MEM == CAMERA_MEM_IONDMA) if((strcmp(gCamInfos[cameraId].driver,"uvcvideo") == 0) //uvc camera || (gCamInfos[cameraId].pcam_total_info->mHardInfo.mSensorInfo.mPhy.type == CamSys_Phy_end)// soc cif ) { gCamInfos[cameraId].pcam_total_info->mIsIommuEnabled = (IOMMU_ENABLED == 1)? true:false; }mCamMemManager = new IonDmaMemManager(gCamInfos[cameraId].pcam_total_info->mIsIommuEnabled); LOG1("%s(%d): Camera Hal memory is alloced from ION device",__FUNCTION__,__LINE__); #elif(CONFIG_CAMERA_MEM == CAMERA_MEM_PMEM) if(access(CAMERA_PMEM_NAME, O_RDWR) < 0) { LOGE("%s(%d): %s isn't registered, CameraHal_Mem current configuration isn't support ION memory!!!", __FUNCTION__,__LINE__,CAMERA_PMEM_NAME); } else { mCamMemManager = new PmemManager((char*)CAMERA_PMEM_NAME); LOG1("%s(%d): Camera Hal memory is alloced from %s device",__FUNCTION__,__LINE__,CAMERA_PMEM_NAME); } #endif mPreviewBuf = new PreviewBufferProvider(mCamMemManager); mVideoBuf = new BufferProvider(mCamMemManager); mRawBuf = new BufferProvider(mCamMemManager); mJpegBuf = new BufferProvider(mCamMemManager); char value[PROPERTY_VALUE_MAX]; property_get(/*CAMERAHAL_TYPE_PROPERTY_KEY*/"sys.cam_hal.type", value, "none"); if (!strcmp(value, "fakecamera")) { LOGD("it is a fake camera!"); mCameraAdapter = new CameraFakeAdapter(cameraId); } else { if((strcmp(gCamInfos[cameraId].driver,"uvcvideo") == 0)) { LOGD("it is a uvc camera!"); mCameraAdapter = new CameraUSBAdapter(cameraId); } else if(gCamInfos[cameraId].pcam_total_info->mHardInfo.mSensorInfo.mPhy.type == CamSys_Phy_Cif){ LOGD("it is a isp soc camera"); if(gCamInfos[cameraId].pcam_total_info->mHardInfo.mSensorInfo.mPhy.info.cif.fmt == CamSys_Fmt_Raw_10b) mCameraAdapter = new CameraIspSOCAdapter(cameraId); else mCameraAdapter = new CameraIspAdapter(cameraId); } else if(gCamInfos[cameraId].pcam_total_info->mHardInfo.mSensorInfo.mPhy.type == CamSys_Phy_Mipi){ LOGD("it is a isp camera"); mCameraAdapter = new CameraIspAdapter(cameraId); } else{ LOGD("it is a soc camera!"); mCameraAdapter = new CameraSOCAdapter(cameraId); } } //initialize { char *call_process = getCallingProcess(); if(strstr(call_process,"com.android.cts.verifier")) { mCameraAdapter->setImageAllFov(true); } else { mCameraAdapter->setImageAllFov(false); } } mDisplayAdapter = new DisplayAdapter(); mEventNotifier = new AppMsgNotifier(mCameraAdapter); mCameraAdapter->setEventNotifierRef(*mEventNotifier); mCameraAdapter->initialize(); updateParameters(mParameters); mCameraAdapter->setPreviewBufProvider(mPreviewBuf); mCameraAdapter->setDisplayAdapterRef(*mDisplayAdapter); mDisplayAdapter->setFrameProvider(mCameraAdapter); mEventNotifier->setPictureRawBufProvider(mRawBuf); mEventNotifier->setPictureJpegBufProvider(mJpegBuf); mEventNotifier->setVideoBufProvider(mVideoBuf); mEventNotifier->setFrameProvider(mCameraAdapter); //command thread mCommandThread = new CommandThread(this);mCommandThread->run("CameraCmdThread", ANDROID_PRIORITY_URGENT_DISPLAY);bool dataCbFrontMirror;bool dataCbFrontFlip;#if CONFIG_CAMERA_FRONT_MIRROR_MDATACB if (gCamInfos[cameraId].facing_info.facing == CAMERA_FACING_FRONT) {#if CONFIG_CAMERA_FRONT_MIRROR_MDATACB_ALL dataCbFrontMirror = true;#elseconst char* cameraCallProcess = getCallingProcess(); if (strstr(CONFIG_CAMERA_FRONT_MIRROR_MDATACB_APK,cameraCallProcess)) { dataCbFrontMirror = true; } else { dataCbFrontMirror = false; } if (strstr(CONFIG_CAMERA_FRONT_FLIP_MDATACB_APK,cameraCallProcess)) { dataCbFrontFlip = true; } else { dataCbFrontFlip = false; }#endif } else { dataCbFrontMirror = false; dataCbFrontFlip = false; }#else dataCbFrontMirror = false;#endif mEventNotifier->setDatacbFrontMirrorFlipState(dataCbFrontMirror,dataCbFrontFlip); // register for sensor events mSensorListener = new SensorListener(); if (mSensorListener.get()) { if (mSensorListener->initialize() == NO_ERROR) { mSensorListener->setCallbacks(gsensor_orientation_cb, this); mSensorListener->enableSensor(SensorListener::SENSOR_ORIENTATION); } else { LOGE("Error initializing SensorListener. not fatal, continuing"); mSensorListener.clear(); mSensorListener = NULL; } } LOG_FUNCTION_NAME_EXIT }
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android camera 架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Android Camera架构浅析
- Shell的RANDOM变量
- java web后台开发跟手机APP后台开发有什么不同
- WPS Office常用快捷键大全
- 从一道题看string的相关比较、排序、对应形参
- 用VC8编译器cl在控制台下编译
- 浅析Android Camera架构
- 系统去掉 Android 4.4.2 的StatusBar和NavigationBar
- Android顶部通知栏和系统通知栏的兼容问题
- 不能连接或打iTunes问题: 连接iPhone,下载应用,上架APP等(Resolve issues betwen iTunes and third-party security software)
- csdn 博客 舍 弃
- 千万级别以上的地图兴趣点(POI)的快速查找测试
- 深入探究VC —— 编译器cl.exe(1)
- mac版MySQL 密码重置 及 修改配置文件出错处理
- c++ 引用