Android Camera 运行流程

首先既然Camera是利用binder通信,它肯定要将它的service注册到ServiceManager里面,以备后续Client引用,那么这一步是在哪里进行的呢?细心的人会发现,在frameworks\base\media\mediaserver\Main_MediaServer.cpp下有个main函数,可以用来注册媒体服务。没错就是在这里,CameraService完成了服务的注册,相关代码如下:

int main(int argc, char** argv)
{
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
LOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}

可是我们到CameraService文件里面却找不到instantiate()这个函数,它在哪?继续追到它的一个父类BinderService,

CameraService的定义在frameworks/base/services/camera/libcameraservice/CameraService.h中

class CameraService :
public BinderService<CameraService>,
public BnCameraService
{
class Client;
friend class BinderService<CameraService>;
public:
static char const* getServiceName() { return "media.camera"; }
.....

.....

}

从以上定义可以看出CameraService 继承于BinderService,所以CameraService::instantiate(); 其实是调用BinderService中的instantiate

BinderService的定义在frameworks/base/include/binder/BinderService.h中

// ---------------------------------------------------------------------------
namespace android {

template<typename SERVICE>
class BinderService
{
public:
static status_t publish() {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE());
}

static void publishAndJoinThreadPool() {
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SERVICE::getServiceName()), new SERVICE());
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}

static void instantiate() { publish(); }

static status_t shutdown() {
return NO_ERROR;
}
};

}; // namespace android
// ---------------------------------------------------------------------------
可以发现在publish()函数中,CameraService完成服务的注册 。这里面有个SERVICE,源码中有说明

template<typename SERVICE>
这表示SERVICE是个模板,这里是注册CameraService,所以可以用CameraService代替
return sm->addService(String16(CameraService::getServiceName()), new CameraService());
好了这样,Camera就在ServiceManager完成服务注册,提供给client随时使用。
Main_MediaServer主函数由init.rc在启动是调用,所以在设备开机的时候Camera就会注册一个服务,用作binder通信。

Binder服务已注册,那接下来就看看client如何连上server端,并打开camera模块。咱们先从camera app的源码入手。在onCreate()函数中专门有一个open Camera的线程

camera app的源码文件在以下目录packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/camera.java
@Override
public void onCreate(Bundle icicle){
super.onCreate(icicle);
getPreferredCameraId();
String[] defaultFocusModes = getResources().getStringArray(
R.array.pref_camera_focusmode_default_array);
mFocusManager = new FocusManager(mPreferences, defaultFocusModes);

/*
* To reduce startup time, we start the camera open and preview threads.
* We make sure the preview is started at the end of onCreate.
*/
mCameraOpenThread.start();

PreferenceInflater inflater = new PreferenceInflater(this);
PreferenceGroup group =
(PreferenceGroup) inflater.inflate(R.xml.camera_preferences);

ListPreference gbce = group.findPreference(CameraSettings.KEY_GBCE);
if (gbce != null) {
mGBCEOff = gbce.findEntryValueByEntry(getString(R.string.pref_camera_gbce_entry_off));
if (mGBCEOff == null) {
mGBCEOff = "";
}
}

ListPreference autoConvergencePreference = group.findPreference(CameraSettings.KEY_AUTO_CONVERGENCE);
if (autoConvergencePreference != null) {
mTouchConvergence = autoConvergencePreference.findEntryValueByEntry(getString(R.string.pref_camera_autoconvergence_entry_mode_touch));
if (mTouchConvergence == null) {
mTouchConvergence = "";
}
mManualConvergence = autoConvergencePreference.findEntryValueByEntry(getString(R.string.pref_camera_autoconvergence_entry_mode_manual));
if (mManualConvergence == null) {
mManualConvergence = "";
}
}

ListPreference exposure = group.findPreference(CameraSettings.KEY_EXPOSURE_MODE_MENU);
if (exposure != null) {
mManualExposure = exposure.findEntryValueByEntry(getString(R.string.pref_camera_exposuremode_entry_manual));
if (mManualExposure == null) {
mManualExposure = "";
}
}

ListPreference temp = group.findPreference(CameraSettings.KEY_MODE_MENU);
if (temp != null) {
mTemporalBracketing = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_temporal_bracketing));
if (mTemporalBracketing == null) {
mTemporalBracketing = "";
}

mExposureBracketing = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_exp_bracketing));
if (mExposureBracketing == null) {
mExposureBracketing = "";
}

mZoomBracketing = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_zoom_bracketing));
if (mZoomBracketing == null) {
mZoomBracketing = "";
}

mHighPerformance = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_hs));
if (mHighPerformance == null) {
mHighPerformance = "";
}

mHighQuality = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_hq));
if (mHighQuality == null) {
mHighQuality = "";
}

mHighQualityZsl = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_zsl));
if (mHighQualityZsl == null) {
mHighQualityZsl = "";
}
}

getPreferredCameraId();
mFocusManager = new FocusManager(mPreferences,
defaultFocusModes);
mTouchManager = new TouchManager();

mIsImageCaptureIntent = isImageCaptureIntent();
setContentView(R.layout.camera);
if (mIsImageCaptureIntent) {
mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
} else {
mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
mThumbnailView.enableFilter(false);
mThumbnailView.setVisibility(View.VISIBLE);
}

mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
mCaptureLayout = getString(R.string.pref_camera_capture_layout_default);

mPreferences.setLocalId(this, mCameraId);
CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());

mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);

// we need to reset exposure for the preview
resetExposureCompensation();

Util.enterLightsOutMode(getWindow());

// don't set mSurfaceHolder here. We have it set ONLY within
// surfaceChanged / surfaceDestroyed, other parts of the code
// assume that when it is set, the surface is also set.
SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
SurfaceHolder holder = preview.getHolder();
holder.addCallback(this);

s3dView = new S3DViewWrapper(holder);

holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

// Make sure camera device is opened.
try {
mCameraOpenThread.join();
mCameraOpenThread = null;
if (mOpenCameraFail) {
Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
return;
} else if (mCameraDisabled) {
Util.showErrorAndFinish(this, R.string.camera_disabled);
return;
}
} catch (InterruptedException ex) {
// ignore
}
mCameraPreviewThread.start();

if (mIsImageCaptureIntent) {
setupCaptureParams();
} else {
mModePicker = (ModePicker) findViewById(R.id.mode_picker);
mModePicker.setVisibility(View.VISIBLE);
mModePicker.setOnModeChangeListener(this);
mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
}

mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
mOnScreenIndicators = (Rotatable) findViewById(R.id.on_screen_indicators);
mLocationManager = new LocationManager(this, this);

// Wait until the camera settings are retrieved.
synchronized (mCameraPreviewThread) {
try {
mCameraPreviewThread.wait();
} catch (InterruptedException ex) {
// ignore
}
}

// Do this after starting preview because it depends on camera
// parameters.
initializeIndicatorControl();
mCameraSound = new CameraSound();

// Make sure preview is started.
try {
mCameraPreviewThread.join();
} catch (InterruptedException ex) {
// ignore
}
mCameraPreviewThread = null;
}
再看看mCameraOpenThread

Thread mCameraOpenThread = new Thread(new Runnable(){
public void run() {
try {
mCameraDevice = Util.openCamera(Camera.this, mCameraId);
} catch (CameraHardwareException e) {
mOpenCameraFail = true;
} catch (CameraDisabledException e) {
mCameraDisabled = true;
}
}
});

继续追Util.openCamera ,Util类的定义在以下目录:packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/Util.java
public static android.hardware.Camera openCamera(Activity activity, int cameraId)
throws CameraHardwareException, CameraDisabledException {
// Check if device policy has disabled the camera.
DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
Context.DEVICE_POLICY_SERVICE);
if (dpm.getCameraDisabled(null)) {
throw new CameraDisabledException();
}

try {
return CameraHolder.instance().open(cameraId);
} catch (CameraHardwareException e) {
// In eng build, we throw the exception so that test tool
// can detect it and report it
if ("eng".equals(Build.TYPE)) {
throw new RuntimeException("openCamera failed", e);
} else {
throw e;
}
}
}
又来了个CameraHolder,该类用一个实例openCamera

CameraHolder的定义在以下目录:packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/CameraHolder.java

public synchronized android.hardware.Camera open(int cameraId)
throws CameraHardwareException {
Assert(mUsers == 0);
if (mCameraDevice != null && mCameraId != cameraId) {
mCameraDevice.release();
mCameraDevice = null;
mCameraId = -1;
}
if (mCameraDevice == null) {
try {
Log.v(TAG, "open camera " + cameraId);
mCameraDevice = android.hardware.Camera.open(cameraId);
mCameraId = cameraId;
} catch (RuntimeException e) {
Log.e(TAG, "fail to connect Camera", e);
throw new CameraHardwareException(e);
}
mParameters = mCameraDevice.getParameters();
} else {
try {
mCameraDevice.reconnect();
} catch (IOException e) {
Log.e(TAG, "reconnect failed.");
throw new CameraHardwareException(e);
}
mCameraDevice.setParameters(mParameters);
}
++mUsers;
mHandler.removeMessages(RELEASE_CAMERA);
mKeepBeforeTime = 0;
return mCameraDevice;
}

在这里就开始进入framework层了,调用frameworks\base\core\java\android\hardware\Camera.java类的open方法 。

public static Camera open(int cameraId) {
return new Camera(cameraId);
}
这里调用了Camera的构造函数,在看看构造函数
Camera(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
mPreviewCallback = null;
mPostviewCallback = null;
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;
}

native_setup(new WeakReference<Camera>(this), cameraId);
}
好,终于来到JNI了


继续看camera的JNI文件:frameworks/base/core/jni# gedit android_hardware_Camera.cpp

由于前面Camera的构造函数里调用了native_setup(new WeakReference<Camera>(this), cameraId);

那么native_setup()的定义在那里呢

通过我的查看,在frameworks/base/core/jni# gedit android_hardware_Camera.cpp中有这样一个定义,

我认为通过这个定义,使得native_setup和android_hardware_Camera_native_setup关联起来

static JNINativeMethod camMethods[] = {
{ "getNumberOfCameras",
"()I",
(void *)android_hardware_Camera_getNumberOfCameras },
{ "getCameraInfo",
"(ILandroid/hardware/Camera$CameraInfo;)V",
(void*)android_hardware_Camera_getCameraInfo },
{ "native_setup",
"(Ljava/lang/Object;I)V",
(void*)android_hardware_Camera_native_setup },
{ "native_release",
"()V",
(void*)android_hardware_Camera_release },
{ "setPreviewDisplay",
"(Landroid/view/Surface;)V",
(void *)android_hardware_Camera_setPreviewDisplay },
{ "setPreviewTexture",
"(Landroid/graphics/SurfaceTexture;)V",
(void *)android_hardware_Camera_setPreviewTexture },
{ "startPreview",
"()V",
(void *)android_hardware_Camera_startPreview },
{ "_stopPreview",
"()V",
(void *)android_hardware_Camera_stopPreview },
{ "previewEnabled",
"()Z",
(void *)android_hardware_Camera_previewEnabled },
{ "setHasPreviewCallback",
"(ZZ)V",
(void *)android_hardware_Camera_setHasPreviewCallback },
{ "_addCallbackBuffer",
"([BI)V",
(void *)android_hardware_Camera_addCallbackBuffer },
{ "native_autoFocus",
"()V",
(void *)android_hardware_Camera_autoFocus },
{ "native_cancelAutoFocus",
"()V",
(void *)android_hardware_Camera_cancelAutoFocus },
{ "native_takePicture",
"(I)V",
(void *)android_hardware_Camera_takePicture },
{ "native_setParameters",
"(Ljava/lang/String;)V",
(void *)android_hardware_Camera_setParameters },
{ "native_getParameters",
"()Ljava/lang/String;",
(void *)android_hardware_Camera_getParameters },
{ "reconnect",
"()V",
(void*)android_hardware_Camera_reconnect },
{ "lock",
"()V",
(void*)android_hardware_Camera_lock },
{ "unlock",
"()V",
(void*)android_hardware_Camera_unlock },
{ "startSmoothZoom",
"(I)V",
(void *)android_hardware_Camera_startSmoothZoom },
{ "stopSmoothZoom",
"()V",
(void *)android_hardware_Camera_stopSmoothZoom },
{ "setDisplayOrientation",
"(I)V",
(void *)android_hardware_Camera_setDisplayOrientation },
{ "_startFaceDetection",
"(I)V",
(void *)android_hardware_Camera_startFaceDetection },
{ "_stopFaceDetection",
"()V",
(void *)android_hardware_Camera_stopFaceDetection},
};

所以,native_setup(new WeakReference<Camera>(this), cameraId);这个调用即是对下面android_hardware_Camera_native_setup这个函数的调用

// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
jobject weak_this, jint cameraId)
{
sp<Camera> camera = Camera::connect(cameraId);

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(thiz);
camera->setListener(context);

// save context in opaque field
env->SetIntField(thiz, fields.context, (int)context.get());
}
JNI函数里面,我们找到Camera C/S架构的客户端了,它调用connect函数向服务器发送连接请求。JNICameraContext这个类是一个监听类,用于处理底层Camera回调函数传来的数据和消息
看看客户端的connect函数有什么,connect定义在以下路径frameworks/base/libs/camera/camera.cpp

sp<Camera> Camera::connect(int cameraId)
{
LOGV("connect");
sp<Camera> c = new Camera();
const sp<ICameraService>& cs = getCameraService();
if (cs != 0) {
c->mCamera = cs->connect(c, cameraId);
}
if (c->mCamera != 0) {
c->mCamera->asBinder()->linkToDeath(c);
c->mStatus = NO_ERROR;
} else {
c.clear();
}
return c;
}

const sp<ICameraService>& cs =getCameraService();获取CameraService实例。

进入getCameraService()中

// establish binder interface to camera service
const sp<ICameraService>& Camera::getCameraService()
{
Mutex::Autolock _l(mLock);
if (mCameraService.get() == 0) {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
binder = sm->getService(String16("media.camera"));
if (binder != 0)
break;
LOGW("CameraService not published, waiting...");
usleep(500000); // 0.5 s
} while(true);
if (mDeathNotifier == NULL) {
mDeathNotifier = new DeathNotifier();
}
binder->linkToDeath(mDeathNotifier);
mCameraService = interface_cast<ICameraService>(binder);
}
LOGE_IF(mCameraService==0, "no CameraService!?");
return mCameraService;
}
CameraService实例通过binder获取的,mCameraService即为CameraService的实例。

回到sp<Camera> Camera::connect(int cameraId)中
c->mCamera = cs->connect(c, cameraId);
即:执行server的connect()函数,并且返回ICamera对象,赋值给Camera的mCamera,服务端connect()返回的是他内部类的一个实例。
server的connect()函数定义在以下路径:frameworks/base/services/camera/libcameraservice/CameraService.cpp

sp<ICamera> CameraService::connect(
const sp<ICameraClient>& cameraClient, int cameraId) {
int callingPid = getCallingPid();
sp<CameraHardwareInterface> hardware = NULL;

LOG1("CameraService::connect E (pid %d, id %d)", callingPid, cameraId);

if (!mModule) {
LOGE("Camera HAL module not loaded");
return NULL;
}

sp<Client> client;
if (cameraId < 0 || cameraId >= mNumberOfCameras) {
LOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).",
callingPid, cameraId);
return NULL;
}

char value[PROPERTY_VALUE_MAX];
property_get("sys.secpolicy.camera.disabled", value, "0");
if (strcmp(value, "1") == 0) {
// Camera is disabled by DevicePolicyManager.
LOGI("Camera is disabled. connect X (pid %d) rejected", callingPid);
return NULL;
}

Mutex::Autolock lock(mServiceLock);
if (mClient[cameraId] != 0) {
client = mClient[cameraId].promote();
if (client != 0) {
if (cameraClient->asBinder() == client->getCameraClient()->asBinder()) {
LOG1("CameraService::connect X (pid %d) (the same client)",
callingPid);
return client;
} else {
LOGW("CameraService::connect X (pid %d) rejected (existing client).",
callingPid);
return NULL;
}
}
mClient[cameraId].clear();
}

if (mBusy[cameraId]) {
LOGW("CameraService::connect X (pid %d) rejected"
" (camera %d is still busy).", callingPid, cameraId);
return NULL;
}

struct camera_info info;
if (mModule->get_camera_info(cameraId, &info) != OK) {
LOGE("Invalid camera id %d", cameraId);
return NULL;
}

char camera_device_name[10];
snprintf(camera_device_name, sizeof(camera_device_name), "%d", cameraId);

hardware = new CameraHardwareInterface(camera_device_name);
if (hardware->initialize(&mModule->common)!= OK) {
hardware.clear();
return NULL;
}

client = new Client(this, cameraClient, hardware, cameraId, info.facing, callingPid);
mClient[cameraId] = client;
LOG1("CameraService::connect X");
return client;
}
实例化Camera Hal接口 hardware,hardware调用initialize()进入HAL层打开Camear驱动。
CameraHardwareInterface中initialize()定义在以下路径:frameworks/base/services/camera/libcameraservice/CameraHardwareInterface.h

代码如下:

status_t initialize(hw_module_t *module)
{
LOGI("Opening camera %s", mName.string());
int rc = module->methods->open(module, mName.string(),
(hw_device_t **)&mDevice);
if (rc != OK) {
LOGE("Could not open camera %s: %d", mName.string(), rc);
return rc;
}
#ifdef OMAP_ENHANCEMENT_CPCAM
initHalPreviewWindow(&mHalPreviewWindow);
initHalPreviewWindow(&mHalTapin);
initHalPreviewWindow(&mHalTapout);
#else
initHalPreviewWindow();
#endif
return rc;
}
此处通过module->method->open()方法真正打开Camera设备,

其中module的定义在以下路径:

class CameraService :
public BinderService<CameraService>,
public BnCameraService
{

class Client : public BnCamera
{
public:
......

private:

.....

};

camera_module_t *mModule;

};

此处还必须找到camera_module_t的定义,以更好的理解整个运行流程,通过追根溯源找到了camera_module_t定义,

camera_module_t的定义在以下路径:hardware/libhardware/include/hardware/camera.h中,定义如下

typedef struct camera_module {
hw_module_t common;
int (*get_number_of_cameras)(void);
int (*get_camera_info)(int camera_id, struct camera_info *info);
} camera_module_t;
其中包含get_number_of_cameras方法和get_camera_info方法用于获取camera info

另外hw_module_t common;这个选项十分重要,此处应重点关注,因为是使用hw_module_t结构体中的open()方法打开设备文件的

继续找到hw_module_t 结构体的定义.在以下路径:hardware/libhardware/include/hardware/hardware.h,代码如下:

struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;

/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;

/** major version number for the module */
uint16_t version_major;

/** minor version number of the module */
uint16_t version_minor;

/** Identifier of module */
const char *id;

/** Name of this module */
const char *name;

/** Author/owner/implementor of the module */
const char *author;

/** Modules methods */
struct hw_module_methods_t* methods;

/** module's dso */
void* dso;

/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];

} hw_module_t;

同样,找到hw_module_methods_t这个结构体的定义,代码如下:

typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;

hw_module_methods_t 结构体中只有open()一个方法,用于打开camera driver,实现与硬件层的交互

到此为止,很容易看出:

Android中Camera的调用流程可分为以下几个层次:
Package->Framework->JNI->Camera(cpp)--(binder)-->CameraService->Camera HAL->Camera Driver

待续。。。。。

Android Camera 运行流程

首先既然Camera是利用binder通信,它肯定要将它的service注册到ServiceManager里面,以备后续Client引用,那么这一步是在哪里进行的呢?细心的人会发现,在frameworks\base\media\mediaserver\Main_MediaServer.cpp下有个main函数,可以用来注册媒体服务。没错就是在这里,CameraService完成了服务的注册,相关代码如下:

int main(int argc, char** argv)
{
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
LOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}

可是我们到CameraService文件里面却找不到instantiate()这个函数,它在哪?继续追到它的一个父类BinderService,

CameraService的定义在frameworks/base/services/camera/libcameraservice/CameraService.h中

class CameraService :
public BinderService<CameraService>,
public BnCameraService
{
class Client;
friend class BinderService<CameraService>;
public:
static char const* getServiceName() { return "media.camera"; }
.....

.....

}

从以上定义可以看出CameraService 继承于BinderService,所以CameraService::instantiate(); 其实是调用BinderService中的instantiate

BinderService的定义在frameworks/base/include/binder/BinderService.h中

// ---------------------------------------------------------------------------
namespace android {

template<typename SERVICE>
class BinderService
{
public:
static status_t publish() {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE());
}

static void publishAndJoinThreadPool() {
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SERVICE::getServiceName()), new SERVICE());
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}

static void instantiate() { publish(); }

static status_t shutdown() {
return NO_ERROR;
}
};

}; // namespace android
// ---------------------------------------------------------------------------
可以发现在publish()函数中,CameraService完成服务的注册 。这里面有个SERVICE,源码中有说明

template<typename SERVICE>
这表示SERVICE是个模板,这里是注册CameraService,所以可以用CameraService代替
return sm->addService(String16(CameraService::getServiceName()), new CameraService());
好了这样,Camera就在ServiceManager完成服务注册,提供给client随时使用。
Main_MediaServer主函数由init.rc在启动是调用,所以在设备开机的时候Camera就会注册一个服务,用作binder通信。

Binder服务已注册,那接下来就看看client如何连上server端,并打开camera模块。咱们先从camera app的源码入手。在onCreate()函数中专门有一个open Camera的线程

camera app的源码文件在以下目录packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/camera.java
@Override
public void onCreate(Bundle icicle){
super.onCreate(icicle);
getPreferredCameraId();
String[] defaultFocusModes = getResources().getStringArray(
R.array.pref_camera_focusmode_default_array);
mFocusManager = new FocusManager(mPreferences, defaultFocusModes);

/*
* To reduce startup time, we start the camera open and preview threads.
* We make sure the preview is started at the end of onCreate.
*/
mCameraOpenThread.start();

PreferenceInflater inflater = new PreferenceInflater(this);
PreferenceGroup group =
(PreferenceGroup) inflater.inflate(R.xml.camera_preferences);

ListPreference gbce = group.findPreference(CameraSettings.KEY_GBCE);
if (gbce != null) {
mGBCEOff = gbce.findEntryValueByEntry(getString(R.string.pref_camera_gbce_entry_off));
if (mGBCEOff == null) {
mGBCEOff = "";
}
}

ListPreference autoConvergencePreference = group.findPreference(CameraSettings.KEY_AUTO_CONVERGENCE);
if (autoConvergencePreference != null) {
mTouchConvergence = autoConvergencePreference.findEntryValueByEntry(getString(R.string.pref_camera_autoconvergence_entry_mode_touch));
if (mTouchConvergence == null) {
mTouchConvergence = "";
}
mManualConvergence = autoConvergencePreference.findEntryValueByEntry(getString(R.string.pref_camera_autoconvergence_entry_mode_manual));
if (mManualConvergence == null) {
mManualConvergence = "";
}
}

ListPreference exposure = group.findPreference(CameraSettings.KEY_EXPOSURE_MODE_MENU);
if (exposure != null) {
mManualExposure = exposure.findEntryValueByEntry(getString(R.string.pref_camera_exposuremode_entry_manual));
if (mManualExposure == null) {
mManualExposure = "";
}
}

ListPreference temp = group.findPreference(CameraSettings.KEY_MODE_MENU);
if (temp != null) {
mTemporalBracketing = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_temporal_bracketing));
if (mTemporalBracketing == null) {
mTemporalBracketing = "";
}

mExposureBracketing = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_exp_bracketing));
if (mExposureBracketing == null) {
mExposureBracketing = "";
}

mZoomBracketing = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_zoom_bracketing));
if (mZoomBracketing == null) {
mZoomBracketing = "";
}

mHighPerformance = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_hs));
if (mHighPerformance == null) {
mHighPerformance = "";
}

mHighQuality = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_hq));
if (mHighQuality == null) {
mHighQuality = "";
}

mHighQualityZsl = temp.findEntryValueByEntry(getString(R.string.pref_camera_mode_entry_zsl));
if (mHighQualityZsl == null) {
mHighQualityZsl = "";
}
}

getPreferredCameraId();
mFocusManager = new FocusManager(mPreferences,
defaultFocusModes);
mTouchManager = new TouchManager();

mIsImageCaptureIntent = isImageCaptureIntent();
setContentView(R.layout.camera);
if (mIsImageCaptureIntent) {
mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
} else {
mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
mThumbnailView.enableFilter(false);
mThumbnailView.setVisibility(View.VISIBLE);
}

mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
mCaptureLayout = getString(R.string.pref_camera_capture_layout_default);

mPreferences.setLocalId(this, mCameraId);
CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());

mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);

// we need to reset exposure for the preview
resetExposureCompensation();

Util.enterLightsOutMode(getWindow());

// don't set mSurfaceHolder here. We have it set ONLY within
// surfaceChanged / surfaceDestroyed, other parts of the code
// assume that when it is set, the surface is also set.
SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
SurfaceHolder holder = preview.getHolder();
holder.addCallback(this);

s3dView = new S3DViewWrapper(holder);

holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

// Make sure camera device is opened.
try {
mCameraOpenThread.join();
mCameraOpenThread = null;
if (mOpenCameraFail) {
Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
return;
} else if (mCameraDisabled) {
Util.showErrorAndFinish(this, R.string.camera_disabled);
return;
}
} catch (InterruptedException ex) {
// ignore
}
mCameraPreviewThread.start();

if (mIsImageCaptureIntent) {
setupCaptureParams();
} else {
mModePicker = (ModePicker) findViewById(R.id.mode_picker);
mModePicker.setVisibility(View.VISIBLE);
mModePicker.setOnModeChangeListener(this);
mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
}

mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
mOnScreenIndicators = (Rotatable) findViewById(R.id.on_screen_indicators);
mLocationManager = new LocationManager(this, this);

// Wait until the camera settings are retrieved.
synchronized (mCameraPreviewThread) {
try {
mCameraPreviewThread.wait();
} catch (InterruptedException ex) {
// ignore
}
}

// Do this after starting preview because it depends on camera
// parameters.
initializeIndicatorControl();
mCameraSound = new CameraSound();

// Make sure preview is started.
try {
mCameraPreviewThread.join();
} catch (InterruptedException ex) {
// ignore
}
mCameraPreviewThread = null;
}
再看看mCameraOpenThread

Thread mCameraOpenThread = new Thread(new Runnable(){
public void run() {
try {
mCameraDevice = Util.openCamera(Camera.this, mCameraId);
} catch (CameraHardwareException e) {
mOpenCameraFail = true;
} catch (CameraDisabledException e) {
mCameraDisabled = true;
}
}
});

继续追Util.openCamera ,Util类的定义在以下目录:packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/Util.java
public static android.hardware.Camera openCamera(Activity activity, int cameraId)
throws CameraHardwareException, CameraDisabledException {
// Check if device policy has disabled the camera.
DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
Context.DEVICE_POLICY_SERVICE);
if (dpm.getCameraDisabled(null)) {
throw new CameraDisabledException();
}

try {
return CameraHolder.instance().open(cameraId);
} catch (CameraHardwareException e) {
// In eng build, we throw the exception so that test tool
// can detect it and report it
if ("eng".equals(Build.TYPE)) {
throw new RuntimeException("openCamera failed", e);
} else {
throw e;
}
}
}
又来了个CameraHolder,该类用一个实例openCamera

CameraHolder的定义在以下目录:packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/CameraHolder.java

public synchronized android.hardware.Camera open(int cameraId)
throws CameraHardwareException {
Assert(mUsers == 0);
if (mCameraDevice != null && mCameraId != cameraId) {
mCameraDevice.release();
mCameraDevice = null;
mCameraId = -1;
}
if (mCameraDevice == null) {
try {
Log.v(TAG, "open camera " + cameraId);
mCameraDevice = android.hardware.Camera.open(cameraId);
mCameraId = cameraId;
} catch (RuntimeException e) {
Log.e(TAG, "fail to connect Camera", e);
throw new CameraHardwareException(e);
}
mParameters = mCameraDevice.getParameters();
} else {
try {
mCameraDevice.reconnect();
} catch (IOException e) {
Log.e(TAG, "reconnect failed.");
throw new CameraHardwareException(e);
}
mCameraDevice.setParameters(mParameters);
}
++mUsers;
mHandler.removeMessages(RELEASE_CAMERA);
mKeepBeforeTime = 0;
return mCameraDevice;
}

在这里就开始进入framework层了,调用frameworks\base\core\java\android\hardware\Camera.java类的open方法 。

public static Camera open(int cameraId) {
return new Camera(cameraId);
}
这里调用了Camera的构造函数,在看看构造函数
Camera(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
mPreviewCallback = null;
mPostviewCallback = null;
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;
}

native_setup(new WeakReference<Camera>(this), cameraId);
}
好,终于来到JNI了


继续看camera的JNI文件:frameworks/base/core/jni# gedit android_hardware_Camera.cpp

由于前面Camera的构造函数里调用了native_setup(new WeakReference<Camera>(this), cameraId);

那么native_setup()的定义在那里呢

通过我的查看,在frameworks/base/core/jni# gedit android_hardware_Camera.cpp中有这样一个定义,

我认为通过这个定义,使得native_setup和android_hardware_Camera_native_setup关联起来

static JNINativeMethod camMethods[] = {
{ "getNumberOfCameras",
"()I",
(void *)android_hardware_Camera_getNumberOfCameras },
{ "getCameraInfo",
"(ILandroid/hardware/Camera$CameraInfo;)V",
(void*)android_hardware_Camera_getCameraInfo },
{ "native_setup",
"(Ljava/lang/Object;I)V",
(void*)android_hardware_Camera_native_setup },
{ "native_release",
"()V",
(void*)android_hardware_Camera_release },
{ "setPreviewDisplay",
"(Landroid/view/Surface;)V",
(void *)android_hardware_Camera_setPreviewDisplay },
{ "setPreviewTexture",
"(Landroid/graphics/SurfaceTexture;)V",
(void *)android_hardware_Camera_setPreviewTexture },
{ "startPreview",
"()V",
(void *)android_hardware_Camera_startPreview },
{ "_stopPreview",
"()V",
(void *)android_hardware_Camera_stopPreview },
{ "previewEnabled",
"()Z",
(void *)android_hardware_Camera_previewEnabled },
{ "setHasPreviewCallback",
"(ZZ)V",
(void *)android_hardware_Camera_setHasPreviewCallback },
{ "_addCallbackBuffer",
"([BI)V",
(void *)android_hardware_Camera_addCallbackBuffer },
{ "native_autoFocus",
"()V",
(void *)android_hardware_Camera_autoFocus },
{ "native_cancelAutoFocus",
"()V",
(void *)android_hardware_Camera_cancelAutoFocus },
{ "native_takePicture",
"(I)V",
(void *)android_hardware_Camera_takePicture },
{ "native_setParameters",
"(Ljava/lang/String;)V",
(void *)android_hardware_Camera_setParameters },
{ "native_getParameters",
"()Ljava/lang/String;",
(void *)android_hardware_Camera_getParameters },
{ "reconnect",
"()V",
(void*)android_hardware_Camera_reconnect },
{ "lock",
"()V",
(void*)android_hardware_Camera_lock },
{ "unlock",
"()V",
(void*)android_hardware_Camera_unlock },
{ "startSmoothZoom",
"(I)V",
(void *)android_hardware_Camera_startSmoothZoom },
{ "stopSmoothZoom",
"()V",
(void *)android_hardware_Camera_stopSmoothZoom },
{ "setDisplayOrientation",
"(I)V",
(void *)android_hardware_Camera_setDisplayOrientation },
{ "_startFaceDetection",
"(I)V",
(void *)android_hardware_Camera_startFaceDetection },
{ "_stopFaceDetection",
"()V",
(void *)android_hardware_Camera_stopFaceDetection},
};

所以,native_setup(new WeakReference<Camera>(this), cameraId);这个调用即是对下面android_hardware_Camera_native_setup这个函数的调用

// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
jobject weak_this, jint cameraId)
{
sp<Camera> camera = Camera::connect(cameraId);

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(thiz);
camera->setListener(context);

// save context in opaque field
env->SetIntField(thiz, fields.context, (int)context.get());
}
JNI函数里面,我们找到Camera C/S架构的客户端了,它调用connect函数向服务器发送连接请求。JNICameraContext这个类是一个监听类,用于处理底层Camera回调函数传来的数据和消息
看看客户端的connect函数有什么,connect定义在以下路径frameworks/base/libs/camera/camera.cpp

sp<Camera> Camera::connect(int cameraId)
{
LOGV("connect");
sp<Camera> c = new Camera();
const sp<ICameraService>& cs = getCameraService();
if (cs != 0) {
c->mCamera = cs->connect(c, cameraId);
}
if (c->mCamera != 0) {
c->mCamera->asBinder()->linkToDeath(c);
c->mStatus = NO_ERROR;
} else {
c.clear();
}
return c;
}

const sp<ICameraService>& cs =getCameraService();获取CameraService实例。

进入getCameraService()中

// establish binder interface to camera service
const sp<ICameraService>& Camera::getCameraService()
{
Mutex::Autolock _l(mLock);
if (mCameraService.get() == 0) {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
binder = sm->getService(String16("media.camera"));
if (binder != 0)
break;
LOGW("CameraService not published, waiting...");
usleep(500000); // 0.5 s
} while(true);
if (mDeathNotifier == NULL) {
mDeathNotifier = new DeathNotifier();
}
binder->linkToDeath(mDeathNotifier);
mCameraService = interface_cast<ICameraService>(binder);
}
LOGE_IF(mCameraService==0, "no CameraService!?");
return mCameraService;
}
CameraService实例通过binder获取的,mCameraService即为CameraService的实例。

回到sp<Camera> Camera::connect(int cameraId)中
c->mCamera = cs->connect(c, cameraId);
即:执行server的connect()函数,并且返回ICamera对象,赋值给Camera的mCamera,服务端connect()返回的是他内部类的一个实例。
server的connect()函数定义在以下路径: frameworks/base/services/camera/libcameraservice/ CameraService.cpp

sp<ICamera> CameraService::connect(
const sp<ICameraClient>& cameraClient, int cameraId) {
int callingPid = getCallingPid();
sp<CameraHardwareInterface> hardware = NULL;

LOG1("CameraService::connect E (pid %d, id %d)", callingPid, cameraId);

if (!mModule) {
LOGE("Camera HAL module not loaded");
return NULL;
}

sp<Client> client;
if (cameraId < 0 || cameraId >= mNumberOfCameras) {
LOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).",
callingPid, cameraId);
return NULL;
}

char value[PROPERTY_VALUE_MAX];
property_get("sys.secpolicy.camera.disabled", value, "0");
if (strcmp(value, "1") == 0) {
// Camera is disabled by DevicePolicyManager.
LOGI("Camera is disabled. connect X (pid %d) rejected", callingPid);
return NULL;
}

Mutex::Autolock lock(mServiceLock);
if (mClient[cameraId] != 0) {
client = mClient[cameraId].promote();
if (client != 0) {
if (cameraClient->asBinder() == client->getCameraClient()->asBinder()) {
LOG1("CameraService::connect X (pid %d) (the same client)",
callingPid);
return client;
} else {
LOGW("CameraService::connect X (pid %d) rejected (existing client).",
callingPid);
return NULL;
}
}
mClient[cameraId].clear();
}

if (mBusy[cameraId]) {
LOGW("CameraService::connect X (pid %d) rejected"
" (camera %d is still busy).", callingPid, cameraId);
return NULL;
}

struct camera_info info;
if (mModule->get_camera_info(cameraId, &info) != OK) {
LOGE("Invalid camera id %d", cameraId);
return NULL;
}

char camera_device_name[10];
snprintf(camera_device_name, sizeof(camera_device_name), "%d", cameraId);

hardware = new CameraHardwareInterface(camera_device_name);
if (hardware->initialize(&mModule->common)!= OK) {
hardware.clear();
return NULL;
}

client = new Client(this, cameraClient, hardware, cameraId, info.facing, callingPid);
mClient[cameraId] = client;
LOG1("CameraService::connect X");
return client;
}
实例化Camera Hal接口 hardware,hardware调用initialize()进入HAL层打开Camear驱动。
CameraHardwareInterface中initialize()定义在以下路径:frameworks/base/services/camera/libcameraservice/CameraHardwareInterface.h

代码如下:

status_t initialize(hw_module_t *module)
{
LOGI("Opening camera %s", mName.string());
int rc = module->methods->open(module, mName.string(),
(hw_device_t **)&mDevice);
if (rc != OK) {
LOGE("Could not open camera %s: %d", mName.string(), rc);
return rc;
}
#ifdef OMAP_ENHANCEMENT_CPCAM
initHalPreviewWindow(&mHalPreviewWindow);
initHalPreviewWindow(&mHalTapin);
initHalPreviewWindow(&mHalTapout);
#else
initHalPreviewWindow();
#endif
return rc;
}
此处通过module->method->open()方法真正打开Camera设备,

其中module的定义在以下路径:

class CameraService :
public BinderService<CameraService>,
public BnCameraService
{

class Client : public BnCamera
{
public:
......

private:

.....

};

camera_module_t *mModule;

};

此处还必须找到camera_module_t的定义,以更好的理解整个运行流程,通过追根溯源找到了camera_module_t定义,

camera_module_t的定义在以下路径:hardware/libhardware/include/hardware/camera.h中,定义如下

typedef struct camera_module {
hw_module_t common;
int (*get_number_of_cameras)(void);
int (*get_camera_info)(int camera_id, struct camera_info *info);
} camera_module_t;
其中包含get_number_of_cameras方法和get_camera_info方法用于获取camera info

另外hw_module_t common;这个选项十分重要,此处应重点关注,因为是使用hw_module_t结构体中的open()方法打开设备文件的

继续找到hw_module_t 结构体的定义.在以下路径:hardware/libhardware/include/hardware/hardware.h,代码如下:

struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;

/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;

/** major version number for the module */
uint16_t version_major;

/** minor version number of the module */
uint16_t version_minor;

/** Identifier of module */
const char *id;

/** Name of this module */
const char *name;

/** Author/owner/implementor of the module */
const char *author;

/** Modules methods */
struct hw_module_methods_t* methods;

/** module's dso */
void* dso;

/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];

} hw_module_t;

同样,找到hw_module_methods_t这个结构体的定义,代码如下:

typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;

hw_module_methods_t 结构体中只有open()一个方法,用于打开camera driver,实现与硬件层的交互

到此为止,很容易看出:

Android中Camera的调用流程可分为以下几个层次:
Package->Framework->JNI->Camera(cpp)--(binder)-->CameraService->Camera HAL->Camera Driver

待续。。。。。

Android Camera open运行流程相关推荐

  1. Android Studio程序运行流程(大白话迅速入门)

    Android Studio程序运行流程 前面我们说过了,开发过程中程序员需要关注的三处地方:第一处:activity Java类.第二是:模块中res文件下的AndroidManifest.xml文 ...

  2. Android camera预览流程

    前面已经简单介绍了,在Android系统中open camera的流程,但是,它又是怎么预览.怎么配置流,如何最终操作到camera HAL的呢.接下来以android原生相机应用,android9, ...

  3. Android Camera HAL3 - 框架流程预览

    前面说了 HAL3 是一个总线型的设计结构,本文就先对 HAL3 的控制流进行一个提纲挈领式的概述,主要理解整个 HAL3 的主干框架,以便对后续深入各个细节. 主干流程 以下全部都是摘抄 Googl ...

  4. Android Camera 通过V4L2与kernel driver的完整交互过程

    Android Camera 通过V4L2与kernel driver的完整交互过程 之前在  Android Camera 的执行流程   http://blog.chinaunix.net/uid ...

  5. Android Camera简单整理(一)-Camera Android架构(基于Q)

    Camera整体架构简单整理 一.Android Camera整体架构简述 1.1 Android Camera 基本分层 1.2 Android Camera工作大体流程 二. Camera App ...

  6. Android Camera 架构

    和你一起终身学习,这里是程序员 Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以下内容: 一.Android Camera整体架构简述 二. Came ...

  7. Android Camera模块(一)

    Android Camera模块(一) 一.Android Camera架构 1.1 整体架构 架构参考Android Camera简单整理(一)-Camera Android架构(基于Q) goog ...

  8. Android Camera:从零开发一款相机APP Day01:前景

    一.Android Camera开发前景: 1)camera相关应用的领域2)相关岗位介绍:3)市场招聘介绍:4)发展前景介绍: 二.学习这门课的重要性: 1)适合的人群:2)熟悉和了解Android ...

  9. Android Camera:从零开发一款相机APP

    从零开发一款相机APP Day 1: 前言 一.Android Camera开发前景: 1)camera相关应用的领域 2)相关岗位介绍: 3)市场招聘介绍: 4)发展前景介绍: 二.学习这门课的重要 ...

最新文章

  1. Spring Boot 监听 Redis Key 失效事件实现定时任务
  2. SiamMask算法详解
  3. LeetCode 345. Reverse Vowels of a String
  4. mysql 批量建表_mysql 如何实现循环批量插入?
  5. C++编程语言中类的静态成员介绍
  6. 火了!GitHub标星三万,Python抢票神器
  7. win10下Clion的安装与配置
  8. 大学excel题库含答案_2017excel试题库附答案.doc
  9. 从达特茅斯会议到图灵奖---人工智能学习分享
  10. [Tableau] 销售团队绩效分析与相关策略指导
  11. 【基础】603- 深入分析Session和Cookie
  12. 【音视频工具】前端屏幕录制工具 + 录制<video>标签内容
  13. 如何搭建符合企业数字化电商解决方案之理解建议
  14. 怎么把所有图片变成一样的大小
  15. 百度云免费SSL证书申请攻略
  16. linux cpu 使用10个进程,linux下获取占用CPU资源最多的10个进程
  17. 安装vs 2013 与打包项目生成安装包
  18. 修改IE为默认浏览器
  19. 企业微信朋友圈可以删除吗?删除后别人还能看到吗?
  20. 猪肉价格跌至去年最低水平 区块链养猪成热点

热门文章

  1. 在前端团队的那些日子(初见)
  2. Windows 10开机可以跳过锁屏界面的操作方法
  3. 恭迎万亿级营销(圈人)潇洒的迈入毫秒时代 - 万亿user_tags级实时推荐系统数据库设计...
  4. 浮动元素横排居中显示及浏览器兼容性处理
  5. mysql 设置密码
  6. 更改select里面的值
  7. 通过文件锁 Lockfile/flock 让脚本单实例运行
  8. EVEREST Ultimate Edition 4.50 Build 1330 Final
  9. 项目管理: 软件质量的可靠保证
  10. 为什么要学习Linux操作系统?