上一次讲解了一下CameraService的启动过程,今天梳理一下Camera预览的过程

StartPreview过程

首先,我们还是从应用层的使用入手

Camera.java (packages\apps\legacycamera\src\com\android\camera)

  1. Thread mCameraPreviewThread = new Thread(new Runnable() {
  2. public void run() {
  3. initializeCapabilities(); //初始化参数
  4. startPreview(); //启动预览
  5. }
  6. });

针对相机应用,采用了单独的线程来处理预览,猜测是为了加快预览显示的速度

  1. private void startPreview() {
  2. ......
  3. // If we're previewing already, stop the preview first (this will blank
  4. // the screen).
  5. if (mCameraState != PREVIEW_STOPPED) stopPreview();
  6. setPreviewDisplay(mSurfaceHolder); //设置SurfaceHolder
  7. setDisplayOrientation(); //设置显示方向
  8. ......
  9. setCameraParameters(UPDATE_PARAM_ALL); //设置参数
  10. // Inform the mainthread to go on the UI initialization.
  11. if (mCameraPreviewThread != null) {
  12. synchronized (mCameraPreviewThread) {
  13. mCameraPreviewThread.notify();
  14. }
  15. }
  16. try {
  17. Log.v(TAG, "startPreview");
  18. mCameraDevice.startPreview(); //启动预览,若失败,关闭Camera
  19. } catch (Throwable ex) {
  20. closeCamera();
  21. throw new RuntimeException("startPreview failed", ex);
  22. }
  23. ......
  24. }

这里就是APP中启动预览的过程,过程必然会是

java -> JNI -> cpp,

然后通过Binder机制执行到CameraClient中的

CameraClient.cpp (av\services\camera\libcameraservice\api1)

  1. status_t CameraClient::startPreview() {
  2. LOG1("startPreview (pid %d)", getCallingPid());
  3. return startCameraMode(CAMERA_PREVIEW_MODE);
  4. }

这里传入的是CAMERA_PREVIEW_MODE,枚举类型是在CameraClient.h中定义的

// camera operation mode

enum camera_mode {

CAMERA_PREVIEW_MODE = 0, // frame automatically released

CAMERA_RECORDING_MODE = 1, // frame has to be explicitly released by releaseRecordingFrame()

};

第一种是针对普通的预览,第二种是针对录像

  1. // start preview or recording
  2. status_t CameraClient::startCameraMode(camera_mode mode) {
  3. LOG1("startCameraMode(%d)", mode);
  4. Mutex::Autolock lock(mLock);
  5. status_t result = checkPidAndHardware();
  6. if (result != NO_ERROR) return result;
  7. switch(mode) {
  8. case CAMERA_PREVIEW_MODE:
  9. if (mSurface == 0 && mPreviewWindow == 0) {
  10. LOG1("mSurface is not set yet.");
  11. // still able to start preview in this case.
  12. }
  13. return startPreviewMode(); //开始预览模式
  14. case CAMERA_RECORDING_MODE:
  15. if (mSurface == 0 && mPreviewWindow == 0) {
  16. ALOGE("mSurface or mPreviewWindow must be set before startRecordingMode.");
  17. return INVALID_OPERATION;
  18. }
  19. return startRecordingMode(); //开始录像模式
  20. default:
  21. return UNKNOWN_ERROR;
  22. }
  23. }

这里我们走的是预览模式

  1. status_t CameraClient::startPreviewMode() {
  2. LOG1("startPreviewMode"); //LOG1,一直忘记说了,这是有log开关用过setprop可以使用
  3. status_t result = NO_ERROR;
  4. // if preview has been enabled, nothing needs to be done
  5. if (mHardware->previewEnabled()) { //如果已经启动预览,不必重复
  6. return NO_ERROR;
  7. }
  8. if (mPreviewWindow != 0) {
  9. //适配显示窗口的大小
  10. native_window_set_scaling_mode(mPreviewWindow.get(),
  11. NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
  12. //调整帧数据的方向
  13. native_window_set_buffers_transform(mPreviewWindow.get(),
  14. mOrientation);
  15. }
  16. mHardware->setPreviewWindow(mPreviewWindow); //设置mPreviewWindow为显示窗口
  17. result = mHardware->startPreview(); //HAL层启动预览
  18. return result; //返回结果
  19. }

这里面涉及到native_window_set_scaling_mode,native_window_set_buffers_transform,直接跟代码,看注释就可以理解,这部分涉及到显示的一些内容,这里暂时不做讲解,native_window_set_scaling_mode设置模式为NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,native_window_set_buffers_transform是用来调整方向。

这里有一个问题,mPreviewWindow是从何而来的呢?

还记得我们在应用层的startPreview()方法中会有这么一个过程

  1. setPreviewDisplay(mSurfaceHolder);
  2. setDisplayOrientation();

这里的setPreviewDisplay(mSurfaceHolder),中间的过程大家可以自己跟一下,最后会执行到

  1. status_t CameraClient::setPreviewTarget(
  2. const sp<IGraphicBufferProducer>& bufferProducer) {
  3. ......
  4. sp<IBinder> binder;
  5. sp<ANativeWindow> window;
  6. if (bufferProducer != 0) {
  7. binder = bufferProducer->asBinder();
  8. // Using controlledByApp flag to ensure that the buffer queue remains in
  9. // async mode for the old camera API, where many applications depend
  10. // on that behavior.
  11. window = new Surface(bufferProducer, /*controlledByApp*/ true); //这个家伙
  12. }
  13. return setPreviewWindow(binder, window);
  14. }
  1. status_t CameraClient::setPreviewWindow(const sp<IBinder>& binder,
  2. const sp<ANativeWindow>& window) {
  3. Mutex::Autolock lock(mLock);
  4. ......
  5. if (window != 0) {
  6. result = native_window_api_connect(window.get(), NATIVE_WINDOW_API_CAMERA);
  7. if (result != NO_ERROR) {
  8. ALOGE("native_window_api_connect failed: %s (%d)", strerror(-result),
  9. result);
  10. return result;
  11. }
  12. }
  13. // If preview has been already started, register preview buffers now.
  14. if (mHardware->previewEnabled()) {
  15. if (window != 0) {
  16. native_window_set_scaling_mode(window.get(),
  17. NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
  18. native_window_set_buffers_transform(window.get(), mOrientation);
  19. result = mHardware->setPreviewWindow(window);
  20. }
  21. }
  22. if (result == NO_ERROR) {
  23. // Everything has succeeded. Disconnect the old window and remember the
  24. // new window.
  25. disconnectWindow(mPreviewWindow);
  26. mSurface = binder;
  27. mPreviewWindow = window; //这里便是赋值的操作了,后面我们操作的mPreviewWindow
  28. } else {
  29. // Something went wrong after we connected to the new window, so
  30. // disconnect here.
  31. disconnectWindow(window);
  32. }
  33. return result;
  34. }

是不是看的很眼熟,和startPreviewMode()的过程有点相似。这就是mPreviewWindow的赋值过程。

回到setPreviewMode()函数中,其中主要的过程是这两个

mHardware->setPreviewWindow(mPreviewWindow);

result = mHardware->startPreview();

这里都是HAL层的处理,将窗口传下去,然后启动预览,最后数据就可以投射到这个预览窗口上了。

我们继续往下看一下,

CameraHardwareInterface.h (av\services\camera\libcameraservice\device1)

  1. /** Set the ANativeWindow to which preview frames are sent */
  2. status_t setPreviewWindow(const sp<ANativeWindow>& buf)
  3. {
  4. ALOGV("%s(%s) buf %p", __FUNCTION__, mName.string(), buf.get());
  5. if (mDevice->ops->set_preview_window) {
  6. mPreviewWindow = buf;
  7. mHalPreviewWindow.user = this;
  8. ALOGV("%s &mHalPreviewWindow %p mHalPreviewWindow.user %p", __FUNCTION__,
  9. &mHalPreviewWindow, mHalPreviewWindow.user);
  10. return mDevice->ops->set_preview_window(mDevice,
  11. buf.get() ? &mHalPreviewWindow.nw : 0);
  12. }
  13. return INVALID_OPERATION;
  14. }
  1. /**
  2. * Start preview mode.
  3. */
  4. status_t startPreview()
  5. {
  6. ALOGV("%s(%s)", __FUNCTION__, mName.string());
  7. if (mDevice->ops->start_preview)
  8. return mDevice->ops->start_preview(mDevice);
  9. return INVALID_OPERATION;
  10. }

这是一个空壳,我们去看具体的实现,这里我们看下android5.1源码中Qcom的实现,由于针对HAL层的不同厂商有不同的处理方式,在这里我们就随便找个目录下的进行分析,旨在看流程,理解一些基础的内容,

QCamera2Hal.cpp (\device\asus\flo\camera\qcamera2\hal)

  1. #include "QCamera2Factory.h"
  2. static hw_module_t camera_common = {
  3. tag: HARDWARE_MODULE_TAG,
  4. module_api_version: CAMERA_MODULE_API_VERSION_1_0,
  5. hal_api_version: HARDWARE_HAL_API_VERSION,
  6. id: CAMERA_HARDWARE_MODULE_ID,
  7. name: "QCamera Module",
  8. author: "Qualcomm Innovation Center Inc",
  9. methods: &qcamera::QCamera2Factory::mModuleMethods,
  10. dso: NULL,
  11. reserved: {0},
  12. };
  13. camera_module_t HAL_MODULE_INFO_SYM = {
  14. common: camera_common,
  15. get_number_of_cameras: qcamera::QCamera2Factory::get_number_of_cameras,
  16. get_camera_info: qcamera::QCamera2Factory::get_camera_info,
  17. set_callbacks: NULL,
  18. get_vendor_tag_ops: NULL,
  19. open_legacy: NULL,
  20. reserved: {0}
  21. };

这里提一下HAL_MODULE_INFO_SYM这个东西,本身就是一个定义在hardware.h下的一个宏,看注释,意思很明显

  1. define HAL_MODULE_INFO_SYM HMI
  2. //.so中将一个符号HMI,获取此符号的地址,就获取到了对应的hw_module_t地址

HAL_MODULE_INFO_SYM,这个是HAL 编译生成的so的入口,CameraService会获取这个来操作so

camera_common是针对HAL规范定义的一些内容。

Camera的open指向的是&qcamera::QCamera2Factory::mModuleMethods中的open方法,如下

  1. struct hw_module_methods_t QCamera2Factory::mModuleMethods = {
  2. open: QCamera2Factory::camera_device_open,
  3. };

这个方法中打开设备节点,我们可以看到HAL层中open的过程是很有讲究的,也不能这么说,应为HAL的处理方式基本上都是如此。

  1. int QCamera2Factory::camera_device_open(
  2. const struct hw_module_t *module, const char *id,
  3. struct hw_device_t **hw_device)
  4. {
  5. if (module != &HAL_MODULE_INFO_SYM.common) {
  6. ALOGE("Invalid module. Trying to open %p, expect %p",
  7. module, &HAL_MODULE_INFO_SYM.common);
  8. return INVALID_OPERATION;
  9. }
  10. if (!id) {
  11. ALOGE("Invalid camera id");
  12. return BAD_VALUE;
  13. }
  14. return gQCamera2Factory.cameraDeviceOpen(atoi(id), hw_device);
  15. }
  1. int QCamera2Factory::cameraDeviceOpen(int camera_id,
  2. struct hw_device_t **hw_device)
  3. {
  4. int rc = NO_ERROR;
  5. if (camera_id < 0 || camera_id >= mNumOfCameras)
  6. return BAD_VALUE;
  7. //到这里才是真正的HAL层的创建,可见HAL层的创建和open操作是相关的
  8. QCamera2HardwareInterface *hw = new QCamera2HardwareInterface(camera_id);
  9. if (!hw) {
  10. ALOGE("Allocation of hardware interface failed");
  11. return NO_MEMORY;
  12. }
  13. rc = hw->openCamera(hw_device);
  14. if (rc != NO_ERROR) {
  15. delete hw;
  16. }
  17. return rc;
  18. }

之前在CameraService过程中提到的CameraHardwareInterface空壳就是为这个QCamera2HardwareInterface准备的,具体实现全部都在这个类中。

关于QCamera2HardwareInterface的内容我们在后面会讲到,这里暂且先放一下,接着上面的

mHardware->setPreviewWindow(mPreviewWindow);

result = mHardware->startPreview();

经过CameraHardwareInterface后

mDevice->ops->set_preview_window(mDevice, buf.get() ? &mHalPreviewWindow.nw : 0);

mDevice->ops->start_preview(mDevice);

然后经过QCamera2HardwareInterface中的mCameraOps函数指针对应表

set_preview_window: QCamera2HardwareInterface::set_preview_window

start_preview: QCamera2HardwareInterface::start_preview

所以会调用到

  1. int QCamera2HardwareInterface::set_preview_window(struct camera_device *device,
  2. struct preview_stream_ops *window)
  3. {
  4. int rc = NO_ERROR;
  5. QCamera2HardwareInterface *hw =
  6. reinterpret_cast<QCamera2HardwareInterface *>(device->priv);
  7. if (!hw) {
  8. ALOGE("%s: NULL camera device", __func__);
  9. return BAD_VALUE;
  10. }
  11. hw->lockAPI();
  12. rc = hw->processAPI(QCAMERA_SM_EVT_SET_PREVIEW_WINDOW, (void *)window);
  13. if (rc == NO_ERROR) {
  14. hw->waitAPIResult(QCAMERA_SM_EVT_SET_PREVIEW_WINDOW);
  15. rc = hw->m_apiResult.status;
  16. }
  17. hw->unlockAPI();
  18. return rc;
  19. }

这里会经过一轮状态机,暂时先不讲,然后执行到

int QCamera2HardwareInterface::setPreviewWindow(

struct preview_stream_ops *window)

{

mPreviewWindow = window;

return NO_ERROR;

}

同理,startPreview也会执行到

int QCamera2HardwareInterface::startPreview()

{

int32_t rc = NO_ERROR;

ALOGD(“%s: E”, func);

// start preview stream

if (mParameters.isZSLMode() && mParameters.getRecordingHintValue() !=true) {

rc = startChannel(QCAMERA_CH_TYPE_ZSL);

} else {

rc = startChannel(QCAMERA_CH_TYPE_PREVIEW);

}

ALOGD(“%s: X”, func);

return rc;

}

这里开启通道,可以理解成数据通道,ZSL是之前没有的,所谓ZSL就是触发拍照后不停止预览。

这里看到会根据当前是都支持ZSL模式而进入不同的通道,我们这里就看QCAMERA_CH_TYPE_PREVIEW,startChannel

  1. int32_t QCamera2HardwareInterface::startChannel(qcamera_ch_type_enum_t ch_type)
  2. {
  3. int32_t rc = UNKNOWN_ERROR;
  4. if (m_channels[ch_type] != NULL) {
  5. rc = m_channels[ch_type]->start();
  6. }
  7. return rc;
  8. }

m_channels是不同的通道的实例的数组,这里如果没有PREVIEW的channel就直接return,岂不是无法启动预览,这个流程感觉有点不对劲。但是这整个过程跟下来也没有看到m_channels相关的初始化过程。

这个问题出在我刚才从CameraHardwareInterface跟到QCamera2HardwareInterface的时候跳过的一个内容—–状态机,在状态机中会执行一个preparePreview()的操作

  1. int32_t QCamera2HardwareInterface::preparePreview()
  2. {
  3. int32_t rc = NO_ERROR;
  4. if (mParameters.isZSLMode() && mParameters.getRecordingHintValue() !=true) {
  5. rc = addChannel(QCAMERA_CH_TYPE_ZSL); //这里我们就添加了一个channel,当然这里是ZSL的
  6. if (rc != NO_ERROR) {
  7. return rc;
  8. }
  9. } else {
  10. bool recordingHint = mParameters.getRecordingHintValue(); //recording
  11. if(recordingHint) {
  12. rc = addChannel(QCAMERA_CH_TYPE_SNAPSHOT); //录像中是可以拍照的,需要snapshot channel
  13. if (rc != NO_ERROR) {
  14. return rc;
  15. }
  16. rc = addChannel(QCAMERA_CH_TYPE_VIDEO); //video channel
  17. if (rc != NO_ERROR) {
  18. delChannel(QCAMERA_CH_TYPE_SNAPSHOT);
  19. return rc;
  20. }
  21. }
  22. rc = addChannel(QCAMERA_CH_TYPE_PREVIEW); //添加preview channel
  23. if (rc != NO_ERROR) {
  24. if (recordingHint) {
  25. delChannel(QCAMERA_CH_TYPE_SNAPSHOT);
  26. delChannel(QCAMERA_CH_TYPE_VIDEO);
  27. }
  28. return rc;
  29. }
  30. }
  31. return rc;
  32. }

在addchannel()的过程中会根据不同的channel类型创建不同的实例,这里我们直接看从addChannel()转到的addPreviewChannel()函数

  1. int32_t QCamera2HardwareInterface::addPreviewChannel()
  2. {
  3. int32_t rc = NO_ERROR;
  4. QCameraChannel *pChannel = NULL; //初始化一个QCameraChanel,后面要使用
  5. if (m_channels[QCAMERA_CH_TYPE_PREVIEW] != NULL) {
  6. // if we had preview channel before, delete it first
  7. delete m_channels[QCAMERA_CH_TYPE_PREVIEW]; //如果之前preview channel存在,干掉
  8. m_channels[QCAMERA_CH_TYPE_PREVIEW] = NULL;
  9. }
  10. pChannel = new QCameraChannel(mCameraHandle->camera_handle,
  11. mCameraHandle->ops);
  12. //new 一个新的channel
  13. .....
  14. // meta data stream always coexists with preview if applicable
  15. rc = addStreamToChannel(pChannel, CAM_STREAM_TYPE_METADATA,
  16. metadata_stream_cb_routine, this);
  17. //添加metadata stream cb
  18. if (rc != NO_ERROR) {
  19. ALOGE("%s: add metadata stream failed, ret = %d", __func__, rc);
  20. delete pChannel;
  21. return rc;
  22. }
  23. if (isNoDisplayMode()) { //判断是否为不需要显示的模式
  24. rc = addStreamToChannel(pChannel, CAM_STREAM_TYPE_PREVIEW,
  25. nodisplay_preview_stream_cb_routine, this);
  26. } else {
  27. //这里添加preview stream cb到channel中
  28. rc = addStreamToChannel(pChannel, CAM_STREAM_TYPE_PREVIEW,
  29. preview_stream_cb_routine, this);
  30. }
  31. if (rc != NO_ERROR) {
  32. ALOGE("%s: add preview stream failed, ret = %d", __func__, rc);
  33. delete pChannel;
  34. return rc;
  35. }
  36. m_channels[QCAMERA_CH_TYPE_PREVIEW] = pChannel; //维护m_channels数据
  37. return rc;
  38. }

这里注册的preview_stream_cb_routine回调,这之后的过程我们暂时先不去看,了解这部分之后,回到之前chanel 的start(),最后会执行到QCameraChannel::start()方法,这里往下的内容我们暂时不往下看,知道这个过程中会执行数据采集,然后返回给HAL层就行了,HAL针对底层返回的数据,我们在哪里获取,做什么对应的处理呢?找到之前注册的Callback.

QCamera2HWICallbacks.cpp (\device\asus\flo\camera\qcamera2\hal)

  1. void QCamera2HardwareInterface::preview_stream_cb_routine(mm_camera_super_buf_t *super_frame,
  2. QCameraStream * stream,
  3. void *userdata)
  4. {
  5. ALOGD("[KPI Perf] %s : BEGIN", __func__);
  6. int err = NO_ERROR;
  7. QCamera2HardwareInterface *pme = (QCamera2HardwareInterface *)userdata;
  8. QCameraGrallocMemory *memory = (QCameraGrallocMemory *)super_frame->bufs[0]->mem_info;
  9. ......
  10. mm_camera_buf_def_t *frame = super_frame->bufs[0];
  11. ......
  12. if (!pme->needProcessPreviewFrame()) {
  13. ALOGE("%s: preview is not running, no need to process", __func__);
  14. stream->bufDone(frame->buf_idx);
  15. free(super_frame);
  16. return;
  17. }
  18. if (pme->needDebugFps()) {
  19. pme->debugShowPreviewFPS();
  20. }
  21. int idx = frame->buf_idx;
  22. pme->dumpFrameToFile(frame->buffer, frame->frame_len,
  23. frame->frame_idx, QCAMERA_DUMP_FRM_PREVIEW);
  24. //这里的注释很明显,displayer buffer而这个buffer就是我们需要投射到屏幕上的数据
  25. // Display the buffer.
  26. int dequeuedIdx = memory->displayBuffer(idx); //这部分涉及到显示的过程,这里不做赘述
  27. if (dequeuedIdx < 0 || dequeuedIdx >= memory->getCnt()) {
  28. ALOGD("%s: Invalid dequeued buffer index %d from display",
  29. __func__, dequeuedIdx);
  30. } else {
  31. // Return dequeued buffer back to driver
  32. err = stream->bufDone(dequeuedIdx);
  33. if ( err < 0) {
  34. ALOGE("stream bufDone failed %d", err);
  35. }
  36. }
  37. //针对上层设置的datacallback过程做些处理
  38. // Handle preview data callback
  39. if (pme->mDataCb != NULL && pme->msgTypeEnabledWithLock(CAMERA_MSG_PREVIEW_FRAME) > 0) {
  40. ......
  41. qcamera_callback_argm_t cbArg;
  42. memset(&cbArg, 0, sizeof(qcamera_callback_argm_t));
  43. cbArg.cb_type = QCAMERA_DATA_CALLBACK;
  44. cbArg.msg_type = CAMERA_MSG_PREVIEW_FRAME;
  45. cbArg.data = data;
  46. if ( previewMem ) {
  47. cbArg.user_data = previewMem;
  48. cbArg.release_cb = releaseCameraMemory;
  49. }
  50. cbArg.cookie = pme;
  51. pme->m_cbNotifier.notifyCallback(cbArg); //封装完之后往上甩
  52. }
  53. free(super_frame);
  54. ALOGD("[KPI Perf] %s : END", __func__);
  55. return;
  56. }

这就是在addPreviewChannel的过程中添加的preview stream callback,当然还有metadata的,暂时先看preview的这个。这里面作的操作就是显示预览数据到窗口中,然后对设置下面的preview callback做对应的callback处理.

讲到这里,Camera的预览过程基本上就结束了,关于底层如果采集数据以及HAL中一些其他的内容,在这里没有讲解,主要是要理解这个过程,之后再每一个过程中在往下学习。

本文中代码使用的是Android5.1原始代码,欢迎大家留言交流。

版权声明:本文为博主原创文章,未经博主允许不得转载。

<Android Framework 之路>Android5.1 Camera Framework(二)的更多相关文章

  1. <Android Framework 之路>Android5.1 Camera Framework(一)

    Android5.0 Camera Framework 简介 CameraService启动 CameraService是在MediaServer启动过程中进行的 main_mediaserver.c ...

  2. <Android Framework 之路>Android5.1 Camera Framework(三)

    上一次讲解了一下startPreview过程,主要是为了画出一条大致的从上到下的线条,今天我们看一下Camera在Framework的sendCommand和dataCallback,这部分属于衔接过 ...

  3. <Android Framework 之路>Android5.1 Camera Framework(四)——框架总结

    前言 从之前的几篇文件,可以基本弄清楚 Camera从APK,经过framework的衔接,与HAL层进行交互,最终通过驱动完成Camera的一些动作. Camera层次分析 APP层 Framewo ...

  4. <Android Framework 之路>Android5.1 MediaScanner

    前言 MediaScanner是Android系统中针对媒体文件的扫描过程,将储存空间中的媒体文件通过扫描的方式遍历并存储在数据库中,然后通过MediaProvider提供接口使用,在Android多 ...

  5. android菜鸟之路-事件分发机制总结(二)

    ViewGroup事件分发机制 自己定义一个LinearLayout,ImageView和Button,小二,上代码 <LinearLayout xmlns:android="http ...

  6. Android学习之路——简易版微信为例(一)

    这是“Android学习之路”系列文章的开篇,可能会让大家有些失望——这篇文章中我们不介绍简易版微信的实现(不过不是标题党哦,我会在后续博文中一步步实现这个应用程序的).这里主要是和广大园友们聊聊一个 ...

  7. Android高薪之路-Android程序员面试宝典

    Android高薪之路-Android程序员面试宝典

  8. android从应用到驱动之—camera(2)---cameraHAL的实现

    本文是camera系列博客,上一篇是: android从应用到驱动之-camera(1)---程序调用流程 本来想用这一篇博客把cameraHAL的实现和流程都给写完的.搞了半天,东西实在是太多了.这 ...

  9. 小猪的Android入门之路 Day 3 - part 3

    小猪的Android入门之路 Day 3 - part 3 各种UI组件的学习 Part 3 本节引言: 在前面两个部分中我们对Android中一些比較经常使用的基本组件进行了一个了解, part 1 ...

随机推荐

  1. 定时器篇---java.util.TimerTask和quartz

    最近项目中出现了定时执行任务的东西,研究了一下,觉得挺不错的,以后还用得到,就总结了下. 这里只介绍两种java.util.Timer 和 quartz java.util.Timer java自带的 ...

  2. Debian 6 , 十个串口为什么只识别到了 6个 剩下4 个被禁止了

    0.946441] Serial: 8250/16550 driver, 6 ports, IRQ sharing enabled [    0.946533] serial8250: ttyS0 a ...

  3. 插入排序InsertSort

    插入排序:从第二个数开始  一直和前面的数组比较 获得排序定位 ​ 代码 /** *插入排序 */ public class InsertSort { public static void inser ...

  4. 为什么java io流必须得关闭

    当我们new一个java流对象之后,不仅在计算机内存中创建了一个相应类的实例对象.而且,还占用了相应的系统资源,比如:文件句柄.端口.数据库连接等.在内存中的实例对象,当没有引用指向的时候,java垃 ...

  5. node——四种注册路由方式

      app.get和app.post 1.请求的方法必须是get/post2.请求的路径的pathname必须等于(====)路径 app.use 1.在进行路由匹配的时候不限定方法,什么请求方法都可 ...

  6. MySQL 单表查询多表查询

    一 单表查询 表准备 create table emp( id int not null unique auto_increment, name varchar(20) not null, sex e ...

  7. 网站出现502 bad getway

    最近项目之余,领导叫解决下系统网站经常出现502的问题,作为小头头的我,怎能不顶上. 流程开始走起,先查nginx,嗯,配置是大众的.是不是缓存溢出了呢.调节buffer的值 .貌似也没什么影响啊.5 ...

  8. 【BZOJ2733】【HNOI2012】永无乡 - 线段树合并

    题意: Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通 ...

  9. django rest-farme-work 的使用(3)

    请求和响应 Requests and Responses 从这一片来说,我们将真正开始覆盖REST框架的核心.我们来介绍一些基本的构建块 Request objects REST框架引入了一个Requ ...

  10. Swoole 同步模式与协程模式的对比

    在现代化 PHP 高级开发中,Swoole 为 PHP 带来了更多可能,如:常驻内存.协程,关于传统的 Apache/FPM 模式与常驻内存模式(同步)的巨大差异,之前我做过测试,大家能直观的感受到性 ...