最近工作上有碰到sensor的相关问题,正好分析下其流程作个笔记。

这个笔记分三个部分:

  1. sensor硬件和驱动的工作机制
  2. sensor 上层app如何使用
  3. 从驱动到上层app这中间的流程是如何

Sensor硬件和驱动的工作机制

先看看Accerometer +Gyro Sensor的原理图:

总结起来分四个部分(电源,地,通信接口,中断脚)。电源和地与平台和芯片本身有关系,与我们分析的没有多少关系,根据sensor的特性保证sensor正常工作的上电时序。关于通信接口,sensor与ap之间通信一般有两种接口(I2C/SPI)。因sensor数据量不大,I2C的速度足矣,目前使用I2C的居多。SDA是I2C的数据线,SCL是I2C的clock线。关于中断脚就是INT。Sensor有两个工作模式。一种是主动上报数据(每时每刻将获取到的数据上报给系统),另个一种是中断模式(当数据的变化大于了之前设置的触发条件),比如手机翻转大于45度,就会将当前的变化及当前数据上报给系统。

Sensor上层app的使用

先要注册指定sensor的事件监听,然在在有事件上报上来时,获取上报的数据。

具体代码如下:

 1 SensorManager mSensorManager = (SensorManager)mContext.getSystemService(Context.SENSOR_SERVICE);2 Sensor mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);3 4 mSensorManager.registerListener(mSensorListener, mSensor, SensorManager.SENSOR_DELAY_GAME);5 /*6     public static final int SENSOR_DELAY_FASTEST = 0;7     public static final int SENSOR_DELAY_GAME = 1;8     public static final int SENSOR_DELAY_UI = 2;9     public static final int SENSOR_DELAY_NORMAL = 3;
10 上报的速度可以根据需求来选择
11 */
12
13 SensorEventListener mSensorListener = new SensorEventListener(){
14       public void onAccuracyChanged(Sensor arg0, int arg1){
15  } 16 17 public void onSensorChanged(SensorEvent event){ 18 if(event.sensor == null){ 19 return; 20  } 21 Log.d(TAG, "onSensorChanged"); 22 if(Sensor.TYPE_ACCELEROMETER == event.sensor.getType()) { 23 mGsensor = (float)event.values[SensorManager.DATA_Z]; 24 mSensorManager.unregisterListener(this); 25 Log.e(TAG, "mgsensor = " + mGsensor); 26 mOnSensorChangedFlag = false; 27  } 28  } 29 }

从驱动到上层App这中间的流程如何

前面二段分别说了驱动上报数据和app读取数据,但中间的流程是如何的呢,这个是此篇博客的重点了。

驱动层上报数据后,HAL层怎么处理呢?这个属于input hal层的接收和分发了。来,我们来啃啃这个骨头:

frameworks/base/core/java/android/app/SystemServiceRegistry.java

419         registerService(Context.SENSOR_SERVICE, SensorManager.class,
420                 new CachedServiceFetcher<SensorManager>() {
421             @Override
422             public SensorManager createService(ContextImpl ctx) {
423                 return new SystemSensorManager(ctx.getOuterContext(),
424                   ctx.mMainThread.getHandler().getLooper());
425             }});

mContext.getSystemService(Context.SENSOR_SERVICE) 返回的就是SystemSensorManager 的对象(也是继承SensorManager 类)。

frameworks/base/core/java/android/hardware/SensorManager.java
790     public Sensor getDefaultSensor(int type) {
......................................................................
841         List<Sensor> l = getSensorList(type);
842         boolean wakeUpSensor = false;846         if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||847                 type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||848                 type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||849                 type == Sensor.TYPE_WRIST_TILT_GESTURE) {850             wakeUpSensor = true;851         }852 //返回支持唤醒的sensor853         for (Sensor sensor : l) {854             if (sensor.isWakeUpSensor() == wakeUpSensor) return sensor;855         }
}

我们再看看getSensorList这里面有啥玩意。。。。

    public List<Sensor> getSensorList(int type) {
.......................................................................final List<Sensor> fullList = getFullSensorList();//然后再种所有sensor中找出对应的sensorfor (Sensor i : fullList) {if (i.getType() == type)list.add(i);}return list;
}

getFullSensorList这个函数返回的是mFullSensorsList。mFullSensorList是SystemSensorManager 遍历所有的sensor得到的集合。下一步我们再来看看registerListener是怎么回事。frameworks/base/core/java/android/hardware/SystemSensorManager.java
    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {synchronized (mSensorListeners) {//先查看下此sensor的监听队列是否已经存在,如果不存在,就重新new个SensorEventQueue queue = mSensorListeners.get(listener);if (queue == null) {        queue = new SensorEventQueue(listener, looper, this, fullClassName);mSensorListeners.put(listener, queue);return true;} else {return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);}
}

到这里就明显是一个消息队列回调的问题了,肯定是发现消息队列里有消息时就会回调具体的事件。我们继续撸代码。

static final class SensorEventQueue extends BaseEventQueue {protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,long timestamp) {
......................................................// call onAccuracyChanged() only if the value changesfinal int accuracy = mSensorAccuracies.get(handle);if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {mSensorAccuracies.put(handle, t.accuracy);mListener.onAccuracyChanged(t.sensor, t.accuracy);}mListener.onSensorChanged(t);}
}

从这里就可以看我们listener里实现的onAccuracyChanged,onSensorChanged是怎么被调用。

frameworks/base/core/java/android/hardware/SensorEventListener.java

public interface SensorEventListener {public void onSensorChanged(SensorEvent event);public void onAccuracyChanged(Sensor sensor, int accuracy);
}

就是一个接口,里面声明两个函数。

看到回调是在dispatchSensorEvent里做的,看看是谁调用的。。。

frameworks/base/core/jni/android_hardware_SensorManager.cpp

class Receiver : public LooperCallback {virtual int handleEvent(int fd, int events, void* data) {ASensorEvent buffer[16];while ((n = q->read(buffer, 16)) > 0) {for (int i=0 ; i<n ; i++) {if (buffer[i].type == SENSOR_TYPE_META_DATA) {// This is a flush complete sensor event. Call dispatchFlushCompleteEvent// method.if (receiverObj.get()) {env->CallVoidMethod(receiverObj.get(),gBaseEventQueueClassInfo.dispatchFlushCompleteEvent,buffer[i].meta_data.sensor);}} else {if (receiverObj.get()) {env->CallVoidMethod(receiverObj.get(),gBaseEventQueueClassInfo.dispatchSensorEvent,buffer[i].sensor,mScratch,status,buffer[i].timestamp);}}}}
}

读到的数据,根据数据的类型去回调不同的接口。dispatchSensorEvent就是在这里被调用的。

handleEvent这个是一个典型的eventQueue这事件处理,具体就不在这里分析了。

 回调这些都有分析了,那事件是哪里加入到消息队列中的,那些消息又是怎么来的呢,话说问题问对了,就能找到往下查的路了。。哈哈


理论这些肯定会有sensor服务在开机的时候启动的,那服务在哪里,是怎么启动的呢。。。

frameworks/base/services/java/com/android/server/SystemServer.java

private void startBootstrapServices() {
...................................................
startSensorService();
}

这个startSensorService是个jni函数,调用的是:

frameworks/base/services/core/jni/com_android_server_SystemServer.cpp

static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) {
//创建一个线程做sensorinit的工作
pthread_create( &sensor_init_thread, NULL, &sensorInit, NULL);
}void* sensorInit(void *arg) {SensorService::instantiate();
}

sensorService服务就做初始化了,服务启动时会做threadLoop(),

bool SensorService::threadLoop()
{ALOGD("nuSensorService thread starting...");const size_t minBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;const size_t numEventMax = minBufferSize / (1 + mVirtualSensorList.size());//device初始化SensorDevice& device(SensorDevice::getInstance());const size_t vcount = mVirtualSensorList.size();const int halVersion = device.getHalDeviceVersion();do {
//调用device.pollssize_t count = device.poll(mSensorEventBuffer, numEventMax);if (count < 0) {ALOGE("sensor poll failed (%s)", strerror(-count));break;}
}

再看看SensorDevice 里初始化和poll里做了啥 :

SensorDevice::SensorDevice():  mSensorDevice(0),mSensorModule(0)
{  //get HAL modulestatus_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,(hw_module_t const**)&mSensorModule);ALOGE_IF(err, "couldn't load %s module (%s)",SENSORS_HARDWARE_MODULE_ID, strerror(-err));if (mSensorModule) {     //open HAL moduleerr = sensors_open_1(&mSensorModule->common, &mSensorDevice);
...................................................
}

SensorDevice初始化做了两个动作,一个是获取sensor HAL module,紧接着打开sensor hal module。

再一起看年poll里做啥了,

ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) {if (!mSensorDevice) return NO_INIT;ssize_t c;do {c = mSensorDevice->poll(reinterpret_cast<struct sensors_poll_device_t *> (mSensorDevice),buffer, count);} while (c == -EINTR);return c;
}

poll也是调用 的是Hal module里的poll。

那sensor HAL里做了啥呢,模块做了啥呢?

sensor hal路径:hardware/libhardware/modules/sensors/

hardware/libhardware/modules/sensors/multihal.cpp

624 static int open_sensors(const struct hw_module_t* hw_module, const char* name,
625         struct hw_device_t** hw_device_out) {
626     ALOGV("open_sensors begin...");
627 //初始化加载高通的库
628     lazy_init_modules();
629
630     // Create proxy device, to return later.
631     sensors_poll_context_t *dev = new sensors_poll_context_t();
632     memset(dev, 0, sizeof(sensors_poll_device_1_t));
633     dev->proxy_device.common.tag = HARDWARE_DEVICE_TAG;
634     dev->proxy_device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
635     dev->proxy_device.common.module = const_cast<hw_module_t*>(hw_module);
636     dev->proxy_device.common.close = device__close;
637     dev->proxy_device.activate = device__activate;
638     dev->proxy_device.setDelay = device__setDelay;
639     dev->proxy_device.poll = device__poll;
640     dev->proxy_device.batch = device__batch;
641     dev->proxy_device.flush = device__flush;
.......................................
}

我们看看lazy_init_modules()这个,是把指定的的hal so加载起来。。

481 /*
482  * Ensures that the sub-module array is initialized.
483  * This can be first called from get_sensors_list or from open_sensors.
484  */
485 static void lazy_init_modules() {
486     pthread_mutex_lock(&init_modules_mutex);
487     if (sub_hw_modules != NULL) {
488         pthread_mutex_unlock(&init_modules_mutex);
489         return;
490     }
491     std::vector<std::string> *so_paths = new std::vector<std::string>();
481 /*
482  * Ensures that the sub-module array is initialized.
483  * This can be first called from get_sensors_list or from open_sensors.
484  */
485 static void lazy_init_modules() {
486     pthread_mutex_lock(&init_modules_mutex);
487     if (sub_hw_modules != NULL) {
488         pthread_mutex_unlock(&init_modules_mutex);
489         return;
490     }
491     std::vector<std::string> *so_paths = new std::vector<std::string>();
492     get_so_paths(so_paths);
493
494     // dlopen the module files and cache their module symbols in sub_hw_modules
495     sub_hw_modules = new std::vector<hw_module_t *>();
496     dlerror(); // clear any old errors
497     const char* sym = HAL_MODULE_INFO_SYM_AS_STR;
498     for (std::vector<std::string>::iterator it = so_paths->begin(); it != so_paths->end(); it++) {
499         const char* path = it->c_str();
500         void* lib_handle = dlopen(path, RTLD_LAZY);
501         if (lib_handle == NULL) {
502             ALOGW("dlerror(): %s", dlerror());
503         } else {
504             ALOGI("Loaded library from %s", path);
505             ALOGV("Opening symbol \"%s\"", sym);
506             // clear old errors
507             dlerror();
508             struct hw_module_t* module = (hw_module_t*) dlsym(lib_handle, sym);
509             const char* error;
510             if ((error = dlerror()) != NULL) {
511                 ALOGW("Error calling dlsym: %s", error);
512             } else if (module == NULL) {
513                 ALOGW("module == NULL");
514             } else {
515                 ALOGV("Loaded symbols from \"%s\"", sym);
516                 sub_hw_modules->push_back(module);
517             }
518         }
519     }
520     pthread_mutex_unlock(&init_modules_mutex);
521 }//获取的要加载so库的路径:/system/etc/sensors/hals.conf
492     get_so_paths(so_paths);
493
494     // dlopen the module files and cache their module symbols in sub_hw_modules
495     sub_hw_modules = new std::vector<hw_module_t *>();
496     dlerror(); // clear any old errors
497     const char* sym = HAL_MODULE_INFO_SYM_AS_STR;
498     for (std::vector<std::string>::iterator it = so_paths->begin(); it != so_paths->end(); it++) {
499         const char* path = it->c_str();
500         void* lib_handle = dlopen(path, RTLD_LAZY);
501         if (lib_handle == NULL) {
502             ALOGW("dlerror(): %s", dlerror());
503         } else {
504             ALOGI("Loaded library from %s", path);
505             ALOGV("Opening symbol \"%s\"", sym);
506             // clear old errors
507             dlerror();
508             struct hw_module_t* module = (hw_module_t*) dlsym(lib_handle, sym);
509             const char* error;
510             if ((error = dlerror()) != NULL) {
511                 ALOGW("Error calling dlsym: %s", error);
512             } else if (module == NULL) {
513                 ALOGW("module == NULL");
514             } else {
515                 ALOGV("Loaded symbols from \"%s\"", sym);
516                 sub_hw_modules->push_back(module);
517             }
518         }
519     }
520     pthread_mutex_unlock(&init_modules_mutex);
521 }

这个路径下就一个库:sensors.ssc.so

再来看看poll看名字就能猜到是从数据队列里等数据,看代码:

330 int sensors_poll_context_t::poll(sensors_event_t *data, int maxReads) {
331     ALOGV("poll");
332     int empties = 0;
333     int queueCount = 0;
334     int eventsRead = 0;
335
336     pthread_mutex_lock(&queue_mutex);
337     queueCount = (int)this->queues.size();
338     while (eventsRead == 0) {
339         while (empties < queueCount && eventsRead < maxReads) {
340             SensorEventQueue* queue = this->queues.at(this->nextReadIndex);
341             sensors_event_t* event = queue->peek();

确实是消息队列。。。

再来年看看加载的so库这个是高通的sensor hal库。

代码路径:vendor/qcom/proprietary/sensors/dsps/libhalsensors

看先从哪里插入数据的:

vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/Utility.cpp

bool Utility::insertQueue(sensors_event_t const *data_ptr){
..........................if (q_head_ptr == NULL) {/* queue is empty */q_tail_ptr = q_ptr;q_head_ptr = q_ptr;} else {/* append to tail and update tail ptr */q_tail_ptr->next = q_ptr;q_tail_ptr = q_ptr;}
}

那看调用的有哪些呢?

Orientation.cpp (src): if (Utility::insertQueue(&la_sample)) {
PedestrianActivityMonitor.cpp (src): if (Utility::insertQueue(&sensor_data)) {
Pedometer.cpp (src): if (Utility::insertQueue(&la_sample)) {
PickUpGesture.cpp (src): if (Utility::insertQueue(&sensor_data)) {
QHeart.cpp (src): if (Utility::insertQueue(&la_sample)) {
RelativeMotionDetector.cpp (src): if (Utility::insertQueue(&sensor_data)) {
RotationVector.cpp (src): if (Utility::insertQueue(&la_sample)) {
Sensor.cpp (src): if (Utility::insertQueue(&flush_evt)){

....................................................................

都在各类sensor的processInd 这个函数中,每种sensor类型根据自身数据的特点,对其做数据结构做指定封装。也就是所谓的工厂模式。

有一个调用比较特别:SMGRSensor.cpp 中processReportInd函数,这个函数中

void SMGRSensor::processReportInd(Sensor** mSensors, sns_smgr_periodic_report_ind_msg_v01* smgr_ind){
...............................handle = getHandleFromInd(smgr_ind->ReportId, smgr_data->DataType,smgr_data->SensorId);if (handle == -1 ) {HAL_LOG_ERROR(" %s: ReportId = %d  DataType = %d SensorId = %d ", __FUNCTION__,smgr_ind->ReportId, smgr_data->DataType, smgr_data->SensorId);goto error;}/* Corresponds to screen orientation req, fill in the right type */if ((handle == HANDLE_ACCELERATION) && (smgr_ind->ReportId == HANDLE_MOTION_ACCEL)) {sensor_data.type = SENSOR_TYPE_SCREEN_ORIENTATION;sensor_data.sensor = HANDLE_MOTION_ACCEL;}if (mSensors[handle] != NULL) {(static_cast<SMGRSensor*>(mSensors[handle]))->processReportInd(smgr_ind, smgr_data, sensor_data);}
................................if (Utility::insertQueue(&sensor_data)) {Utility::signalInd(data_cb);}
}

这里根据smgr_data->DataType又做了一次工厂模式的分发处理:

GyroscopeUncalibrated.cpp (src):  FUNCTION:  processReportInd
GyroscopeUncalibrated.cpp (src):void GyroscopeUncalibrated::processReportInd(
GyroscopeUncalibrated.cpp (src):    HAL_LOG_DEBUG("GyroscopeUncalibrated::processReportInd");
GyroscopeUncalibrated.h (inc):  FUNCTION:  processReportInd
GyroscopeUncalibrated.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
HallEffect.cpp (src):  FUNCTION:  processReportInd
HallEffect.cpp (src):void HallEffect::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
HallEffect.h (inc):  FUNCTION:  processReportInd
HallEffect.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Humidity.cpp (src):  FUNCTION:  processReportInd
Humidity.cpp (src):void Humidity::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Humidity.h (inc):  FUNCTION:  processReportInd
Humidity.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
IRGesture.cpp (src):  FUNCTION:  processReportInd
IRGesture.cpp (src):void IRGesture::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
IRGesture.h (inc):  FUNCTION:  processReportInd
IRGesture.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Light.cpp (src):  FUNCTION:  processReportInd
Light.cpp (src):void Light::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Light.h (inc):  FUNCTION:  processReportInd
Light.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Magnetic.cpp (src):  FUNCTION:  processReportInd
Magnetic.cpp (src):void Magnetic::processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind,
Magnetic.h (inc):  FUNCTION:  processReportInd
Magnetic.h (inc):    void processReportInd(sns_smgr_periodic_report_ind_msg_v01* smgr_ind

又是根据类型不同,做了另一批类型sensor的处理。继续反向推导,processReportInd其它这个也是processBufferingInd 调用的,processBufferingInd也是processInd 调用的。这就和其它的sensor到统一战线上了。都是processInd处理的。关键就是这processInd了,这个是一个回调SMGRSensor_sensor1_cb函数里处理的。那这个回调是谁注册,又是什么调用的呢?vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/SensorsContext.cpp这个里面会在SensorContext实例化时注册。
SensorsContext::SensorsContext(): active_sensors(0),is_accel_available(false),is_gyro_available(false),is_mag_available(false),is_prox_available(false),smgr_version(0)
{
。。。。。。。。。。。。。。。。。。。err = sensor1_open(&sensor_info_sensor1_cb->sensor1_handle, &context_sensor1_cb, (intptr_t)this);
。。。。。。。。。
}

那得去撸代码啊,不然不知道啥时候回调context_sensor1_cb这个函数啊。。。

这个函数在另一个库中了libsensor1。。

这个函数做的事情比较多,分三部分:

sensor1_open( sensor1_handle_s **hndl,sensor1_notify_data_cb_t data_cbf,intptr_t cb_data )
{
.........................sensor1_init();
............................
sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) strlcpy(address.sun_path, SENSOR_CTL_SOCKET, UNIX_PATH_MAX);
connect(sockfd, (struct sockaddr *)&address, len)
.....................................
libsensor_add_waiting_client(&cli_data);libsensor_add_client( &new_cli, false )
.....................................
}

那第一步先看看sensor1_init做了啥?

相当于创建了一个读线程,一直在poll读消息队列里的消息,读到消息后,会封装数据,然后发一个信息去唤醒另外一个线程,唤醒的线程后面再说。

第二步再看看,创建了一个socket(一个客户端socket),去连接服务端的socket(服务端的socket又是什么东东),上个读线程读到的消息就是从socket读到的消息(libsensor_read_socket),那一定是服务端socket发送过来的嘛。。。

第三步增加client,再看看这个又做了啥?

这里又创建了一个回调线程,等有消息来时,唤醒本线程,然后回调sensor.ssc.库里的context_sensor1_cb。这个线程就谁唤醒的呢,哈哈,大家就能想到就是init中的那个读线程嘛。

总结sensor1_open就是创建一个读线程从socket客户端中读数据,读到数据后,就回调sensor.ssc库中的context_sensor1_cb,进而上报数据做进一步回调。

那问题来了,那个socket服务端又是怎么回事呢。。。慢慢接近真相了。。。。


这时又出现了一个服务SensorDaemon:

代码路径:vendor/qcom/proprietary/sensors/dsps/sensordaemon

sns_main_setup 里创建了socket服务器端,然后监听客户端socket的监听,那什么时候往socket里写东西呢?

这就涉及另一个回调函数了sns_main_notify_cb。这个回调函数则好就是sensor1_open里注册的。这个sensor1_open 与 libsensor1里的sensor1_open不是同一个。

ok,那问题又来了,啥时候做的回调,和之前很类似,有一个读线程,初始化后处理polling状态,当收到消息时,就回调这个回调函数。

这个读线程的数据是从哪里来的呢,这就涉及到QMI service了。QMI service这部分代码就不是AP这边了,此份代码就在modem的adsp代码中了。

找时间再来续modem这边的adsp。

 

转载于:https://www.cnblogs.com/jack2010/p/6182329.html

android 6.0 高通平台sensor 工作机制及流程(原创)相关推荐

  1. android 6.0 高通平台sensor 工作机制及流程

    最近工作上有碰到sensor的相关问题,正好分析下其流程作个笔记. 这个笔记分三个部分: sensor硬件和驱动的工作机制 sensor 上层app如何使用 从驱动到上层app这中间的流程是如何 Se ...

  2. Android 7.0 高通平台-telephony-机器无Sim卡情况下,获取SIM卡状态方法getSimState偶现为6,而不是1

    TelephonyManager.java -------->getSimState接口 /** * Returns a constant indicating the state of the ...

  3. 高通平台sensor学习

    刚入行驱动时最先接触调试的外设模块便是sensor,一直都是零零散散的记录,这次终于下定决心对自己所学做一个系统的总结. sensor作为一款常用的外设,虽不起眼但是很多功能确实离不开它.比如我们手机 ...

  4. 高通平台msm8917蓝牙mac地址流程

    高通平台msm8917蓝牙mac地址流程 Where is the BD address? BD address can be saved in following places: modem NV ...

  5. android lcd调试 高通平台lcd调试深入分析总结(mipi和rgb接口)

    各位网友:最近发现我这篇文章转载的到处都是,有的则以原创存在,转载时请注明出处,还有文中错误的地方请指正!谢谢合作. 一:点亮lcd in kernel 其实点亮lcd很简单必须保证以后几个步骤正确: ...

  6. Android6.0 高通平台 is 32-bit instead of 64-bit 问题

    做高通项目时碰高一个问题:有些apk在32位平台上运行没问题,但是在64位平台上出现crash,出错信息如下: java.lang.UnsatisfiedLinkError: dlopen faile ...

  7. android camera (2) ---高通平台camera开发

    1. 设置摄像头方向 2. 打开线程与预览线程 3. 设置参数 4. Camera外设按键 5. 自动对焦与触摸对焦 6. 拍照 7. 人脸检测 8. 位置管理 9. 旋转管理 10. 变焦 11. ...

  8. 高通平台 Sensor 调试技巧 01

    MSM8953 查看 modem 的版本号 8953_MODEM_P\MPSS.TA.3.0\modem_proc\build\ms\XXX_CUSTOM\xxxcust_prj.h   #defin ...

  9. android ROM ---(1)高通平台 Android O 升级学习

    1. Android Project Treble 与iOS相比,安卓系统有一个致命弱点,那就是新版本系统升级太慢,除了谷歌Nexus和Pixel等亲儿子机型,其他OEM商的机型更新新系统需要用户漫长 ...

最新文章

  1. css制作圆角矩形,CSS绘制圆角矩形图形的效果
  2. Linux文件系统之dd
  3. 【转】用nohup命令让Linux下程序永远在后台执行
  4. ROS: Ubuntu16.04安装ROS-kinetic
  5. 【POI word】使用POI实现对Word的读取以及生成
  6. AngularJS 日期格式化
  7. Spring Boot中使用模板引擎引用资源
  8. 让数据大白于天下:GCC插件实现代码分析和安全审计
  9. .net ef 字段不区分大小写_第六节:框架搭建之EF的Fluent Api模式的使用流程
  10. Spring框架的xml出错Cannot resolve reference to bean txPointcut while setting bean property pointcut
  11. 《基于MFC的OpenGL编程》Part 18 Reading objects from the OBJ File Format
  12. 扫雷源代码(HTML)
  13. python的encode()和decode()的用法及实例
  14. 360一键root工具 v5.1.3 pc绿色版
  15. ppspp android编译,PPSSPP模拟器通用设置,伪福利
  16. 每日算法 - 列出24点游戏的所有解法
  17. LFWA人脸属性数据集解析
  18. 点击“加入QQ群”链接打开电脑QQ扫码后发现登录地点不是本地
  19. 2021年河南高考--各高校在河南录取分数线预测(本科二批——理科)
  20. 动力煤浅析+今日交易记录 2021.10.18

热门文章

  1. 面试笔记二(总结的答案可能有误,感谢批评指正)
  2. 网易轻舟微服务大升级,突破在线业务中台的异构挑战
  3. 利用XCode进行iOS模拟定位
  4. 特殊纸张如何设置打印格式
  5. win7下双显示器的顺序调整
  6. 第2章 集成MySQL数据库
  7. win10 wifi 时断时续 无internet 安全 解决办法
  8. 面经手册 · 第11篇《StringBuilder 比 String 快?空嘴白牙的,证据呢!》
  9. 平板android版本4.4.2,流畅更好用,台电平板国内首发正式版安卓4.4.2
  10. [附源码]Node.js计算机毕业设计坝上长尾鸡养殖管理系统Express