Android Sensor Input类型 (五) Sensor HAL NativeSensorManager

2024-04-18 10:58

本文主要是介绍Android Sensor Input类型 (五) Sensor HAL NativeSensorManager,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

NativeSensorManager

代码路径:

code/hardware/qcom/sensors/NativeSensorManager.cpp
code/hardware/qcom/sensors/NativeSensorManager.h

NativeSensorManager类定义

class NativeSensorManager : public Singleton<NativeSensorManager> {friend class Singleton<NativeSensorManager>;NativeSensorManager();~NativeSensorManager();struct sensor_t sensor_list[MAX_SENSORS]; //!< sensorlist数组 >! NoteBy: yujixuanstruct SensorContext context[MAX_SENSORS]; //!< context数组 >! NoteBy: yujixuanstruct SensorEventMap event_list[MAX_SENSORS]; //!< 用于上报的evnet 数组 >! NoteBy: yujixuanstatic const struct SysfsMap node_map[]; //sysfs 设备节点名static const struct sensor_t virtualSensorList[];static char virtualSensorName[][SYSFS_MAXLEN];  //虚拟sensor相关int mSensorCount;bool mScanned;int mEventCount;DefaultKeyedVector<int32_t, struct SensorContext*> type_map;DefaultKeyedVector<int32_t, struct SensorContext*> handle_map;DefaultKeyedVector<int, struct SensorContext*> fd_map;void compositeVirtualSensorName(const char *sensor_name, char *chip_name, int type);int getNode(char *buf, char *path, const struct SysfsMap *map);int getSensorListInner();int getDataInfo();int registerListener(struct SensorContext *hw, struct SensorContext *virt);int initCalibrate(const SensorContext *list);int initVirtualSensor(struct SensorContext *ctx, int handle, struct sensor_t info);int addDependency(struct SensorContext *ctx, int handle);int getEventPath(const char *sysfs_path, char *event_path);int getEventPathOld(const struct SensorContext *list, char *event_path);
public:int getSensorList(const sensor_t **list);  //!< 获取sensor列表 >! NoteBy: yujixuaninline SensorContext* getInfoByFd(int fd) { return fd_map.valueFor(fd); };inline SensorContext* getInfoByHandle(int handle) { return handle_map.valueFor(handle); };inline SensorContext* getInfoByType(int type) { return type_map.valueFor(type); };int getSensorCount() {return mSensorCount;}void dump();int hasPendingEvents(int handle);int activate(int handle, int enable);int setDelay(int handle, int64_t ns);int readEvents(int handle, sensors_event_t *data, int count); //!< 读取events >! NoteBy: yujixuanint calibrate(int handle, struct cal_cmd_t *para);int batch(int handle, int64_t sample_ns, int64_t latency_ns);int flush(int handle);
};

class NativeSensorManager 继承了Singleton,单例模式,只存在一个实例对象,即在SensorHal 中被多次访问使用的sm;

通过NativeSensorManager class的定义可知 主要有以下内容:

  • 定义维护了 在SensorHAL 中由 sensor_t 组成的 sensor_list 数组;
  • 定义由SensorContext 组成的 Context数组;
  • 定义了SensorEventMap 组成的 event_list 数组;
  • 定义了用于记录sensor 在sys中设备节点路径的 SysfsMap

SysfsMap node_map

struct SysfsMap {int offset;const char *node;int type;int required;
};
const struct SysfsMap NativeSensorManager::node_map[] = {{offsetof(struct sensor_t, name), SYSFS_NAME, TYPE_STRING, 1},{offsetof(struct sensor_t, vendor), SYSFS_VENDOR, TYPE_STRING, 1},{offsetof(struct sensor_t, version), SYSFS_VERSION, TYPE_INTEGER, 1},{offsetof(struct sensor_t, type), SYSFS_TYPE, TYPE_INTEGER, 1},{offsetof(struct sensor_t, maxRange), SYSFS_MAXRANGE, TYPE_FLOAT, 1},{offsetof(struct sensor_t, resolution), SYSFS_RESOLUTION, TYPE_FLOAT, 1},{offsetof(struct sensor_t, power), SYSFS_POWER, TYPE_FLOAT, 1},
......
};

SensorEventMap

struct SensorEventMap {char data_name[80];char data_path[PATH_MAX];
};

继承Singleton 单例模式

前面可知,NativeSensorManager : public Singleton,集成了Sigleton, 是单例模式;

Singleton模板类,指定了 template的类型,T为 NativeSensorManager; (note:C++模板)

在NativeSensorManager.cpp的最开始,创建了实例;

ANDROID_SINGLETON_STATIC_INSTANCE(NativeSensorManager);

代码路径:system/core/include/utils/Singleton.h

template <typename TYPE>
class ANDROID_API Singleton
{
public:static TYPE& getInstance() {Mutex::Autolock _l(sLock);TYPE* instance = sInstance;if (instance == 0) {instance = new TYPE();sInstance = instance;}//如果实例对象不存在,实例化一个对象, 已存在直接返回;//TYPE == NativeSensorManagerreturn *instance;}static bool hasInstance() {Mutex::Autolock _l(sLock);return sInstance != 0;}
protected:~Singleton() { };Singleton() { };
private:Singleton(const Singleton&);Singleton& operator = (const Singleton&);static Mutex sLock;static TYPE* sInstance;
};
#define ANDROID_SINGLETON_STATIC_INSTANCE(TYPE)                 \template<> ::android::Mutex  \(::android::Singleton< TYPE >::sLock)(::android::Mutex::PRIVATE);  \template<> TYPE* ::android::Singleton< TYPE >::sInstance(0);  \template class ::android::Singleton< TYPE >;

由上分析可知,NativeSensorManager未实例化在第一次调用时有 instance = new TYPE(); 即 new NativeSensorManager();

以下是 NativeSensorManager类构造器的实现:

NativeSensorManager::NativeSensorManager():mSensorCount(0), mScanned(false), mEventCount(0), type_map(NULL), handle_map(NULL), fd_map(NULL)
{int i;memset(sensor_list, 0, sizeof(sensor_list));memset(context, 0, sizeof(context));type_map.setCapacity(MAX_SENSORS);handle_map.setCapacity(MAX_SENSORS);fd_map.setCapacity(MAX_SENSORS);for (i = 0; i < MAX_SENSORS; i++) {context[i].sensor = &sensor_list[i];sensor_list[i].name = context[i].name;sensor_list[i].vendor = context[i].vendor;list_init(&context[i].listener);list_init(&context[i].dep_list);}if(getDataInfo()) {ALOGE("Get data info failed\n");}dump();
}

通过getDataInfo 初始化sensor list数组,sensor context数组,对NativeSensorManager 做实际的数据填充;

dump 显示当前NativeSensorManager的主要内容;

getDataInfo 填充数据

NativeSensorManager是管理sensor HAL 处理的核心,getDataInfo是对填充构建内部细节的实际实现,一下是其主要内容:

int NativeSensorManager::getDataInfo() {int i, j;struct SensorContext *list;int has_acc = 0;int has_compass = 0;int has_gyro = 0;int has_light = 0;int has_proximity = 0;struct sensor_t sensor_mag;struct sensor_t sensor_acc;struct sensor_t sensor_light;struct sensor_t sensor_proximity;struct sensor_t sensor_gyro;mSensorCount = getSensorListInner();//1:完成SensorContext list 获取和填充 包含sensor_tfor (i = 0; i < mSensorCount; i++) {struct SensorRefMap *item;list = &context[i];list->is_virtual = false;item = new struct SensorRefMap;item->ctx = list;/* hardware sensor depend on itself */list_add_tail(&list->dep_list, &item->list);if (strlen(list->data_path) != 0)list->data_fd = open(list->data_path, O_RDONLY | O_CLOEXEC | O_NONBLOCK);//open event 路径,获取文件描述符if (list->data_fd > 0) {fd_map.add(list->data_fd, list);// DefaultKeyedVector<int, struct SensorContext*> fd_map;// 添加到sm的fd_map中;}type_map.add(list->sensor->type, list);//  DefaultKeyedVector<int32_t, struct SensorContext*> type_map;// 添加到sm的type_map中;handle_map.add(list->sensor->handle, list);//  DefaultKeyedVector<int32_t, struct SensorContext*> handle_map;// 添加到sm的handle_map中;//根据不同的sensor type,创建不同的SensorBase子类实例;switch (list->sensor->type) {case SENSOR_TYPE_ACCELEROMETER:has_acc = 1;list->driver = new AccelSensor(list);//假设,type是acc,new一个 AccelSensor; 一个SensorBase子类实例sensor_acc = *(list->sensor);break;case SENSOR_TYPE_MAGNETIC_FIELD:has_compass = 1;list->driver = new CompassSensor(list);sensor_mag = *(list->sensor);break;case SENSOR_TYPE_PROXIMITY:has_proximity = 1;//......list->driver = new LightSensor(list);sensor_light = *(list->sensor);break;//......default:list->driver = NULL;ALOGE("No handle %d for this type sensor!", i);break;}initCalibrate(list);//校准相关}//virtual sensors //......
}

校准相关,virtual sensor相关暂不分析; 通过以上代码,可知在getDataInfo 中主要实现了:

  • 通过getSensorListInner 完成SensorContext list 获取和填充 包含sensor_t
  • 通过for循环,对每一个sensor做处理,打开其event patch,记录得到的文件描述符fd到map中;
  • 判断sensor type 根据返回的结果 创建基于SensorBase的sensor driver实例;

getSensorListInner获取

通过前面的blog可知,sensor kernel driver 注册了sensor class类,提供了若干api 用于往sensor class注册;

sensor driver的具体实现中注册了 input device;通过adb, 可以直观的相关的device node如下:

msm8909:/sys/class/sensors # ls
MPU6050-accel MPU6050-gyro mmc3416x-mag stk3x1x-light stk3x1x-proximitymsm8909:/sys/class/sensors/MPU6050-accel # ls
calibrate enable_wakeup             flags  max_delay   min_delay  power      sensor_power uevent
device    fifo_max_event_count      flush  max_latency name       resolution subsystem    vendor
enable    fifo_reserved_event_count handle max_range   poll_delay self_test  type         versionmsm8909:/sys/class/sensors/MPU6050-accel/device # ls
MPU6050-accel capabilities enable id       name poll_delay properties subsystem uniq
addr          device       event4 modalias phys power      reg        uevent    writemsm8909:/sys/class/sensors/MPU6050-accel/device # cat uevent
PRODUCT=18/0/0/0
NAME="MPU6050-accel"
PROP=0
EV=9
ABS=100 7
MODALIAS=input:b0018v0000p0000e0000-e0,3,kra0,1,2,28,mlsfw

通过adb 可以直接与这些device node做交互,控制sensor,读取event数据等, 在sensor hal的代码中如何完成这些操作?

前面的内容提到,getDataInfo 填充NativeSensorManager,其中关键是getSensorListInner 完成SensorContext list 获取和填充,下面是它的具体实现:

int NativeSensorManager::getSensorListInner()
{int number = 0;int err = -1;const char *dirname = SYSFS_CLASS;//1: SYSFS_CLASS == "/sys/class/sensors/"char devname[PATH_MAX];char *filename;char *nodename;DIR *dir;struct dirent *de;struct SensorContext *list;unsigned int i;dir = opendir(dirname);strlcpy(devname, dirname, PATH_MAX);filename = devname + strlen(dirname);//2:filename指针指在SYSFS_CLASS之后,即/sys/class/sensors/ 后while ((de = readdir(dir))) {if(de->d_name[0] == '.' &&(de->d_name[1] == '\0' ||(de->d_name[1] == '.' && de->d_name[2] == '\0')))continue; //3:去掉. .. 等无关目录list = &context[number];//4:发现一个目标目录,实现一个list 元素;strlcpy(filename, de->d_name, PATH_MAX - strlen(SYSFS_CLASS));//5:把读到目录名赋值到file name指针的位置;即填充了 devname[PATH_MAX]中 /sys/class/sensors/ 后的内容nodename = filename + strlen(de->d_name);//6:同样的,nodename 指针指向 filename后的位置*nodename++ = '/';for (i = 0; i < ARRAY_SIZE(node_map); i++) {strlcpy(nodename, node_map[i].node, PATH_MAX - strlen(SYSFS_CLASS) - strlen(de->d_name));//7:把node_map中的 预设的name 传入err = getNode((char*)(list->sensor), devname, &node_map[i]);if (err) {ALOGE("Get node for %s failed.\n", devname);break;}//8;过程与上相同,获取每个node的具体路径}list->sensor->handle = SENSORS_HANDLE(number);//#define SENSORS_HANDLE(x) (SENSORS_HANDLE_BASE + x + 1)strlcpy(nodename, "", SYSFS_MAXLEN);strlcpy(list->enable_path, devname, PATH_MAX);/* initialize data path */strlcpy(nodename, "device", SYSFS_MAXLEN);//路径指向:  /sys/class/sensors/MPU6050-accel/deviceif (getEventPath(devname, list->data_path) == -ENODEV) {getEventPathOld(list, list->data_path);}//9:获取event 节点路径,用来读取数据, 比如 MPU6050-accel, 注册的input event路径是:///dev/input/event4number++;}closedir(dir);return number;
}

在/code/hardware/qcom/sensors/sensors.h 中已经预设了 若干信息,例如:

#define SYSFS_CLASS         "/sys/class/sensors/"
#define SYSFS_NAME          "name"
#define SYSFS_VENDOR        "vendor"                                                                                              #define SYSFS_VERSION       "version"

getSensorListInner 完成对sys中sensor class下每一个sensor的处理;

通过getNode把每一个sensor下的节点数据获取到,并填充保存到相应的数据结构中;

通过getEventPatch 获取到sensor driver驱动中注册的 input device在 input 下的路径;

比如通过adb 可以看到/sys/class/sensors/MPU6050-accel/device

下event的编号是event4, 则记录input device的device node 是/dev/input/event4, 后续读取sensor data 则是通过这个路径。

以下是getNode的实现:

getNode
int NativeSensorManager::getNode(char *buf, char *path, const struct SysfsMap *map) {char * fret;ssize_t len = 0;int fd;char tmp[SYSFS_MAXLEN];//......memset(tmp, 0, sizeof(tmp));fd = open(path, O_RDONLY);//......len = read(fd, tmp, sizeof(tmp) - 1);//......tmp[len - 1] = '\0';if (tmp[strlen(tmp) - 1] == '\n')tmp[strlen(tmp) - 1] = '\0';//把p 指向sensor_t map->offset 的位置,以name为例,指向指向sensor_t 的nameif (map->type == TYPE_INTEGER) {int *p = (int *)(buf + map->offset);*p = atoi(tmp);} else if (map->type == TYPE_STRING) {char **p = (char **)(buf + map->offset);strlcpy(*p, tmp, SYSFS_MAXLEN);} else if (map->type == TYPE_FLOAT) {float *p = (float*)(buf + map->offset);*p = atof(tmp);} else if (map->type == TYPE_INTEGER64) {int64_t *p = (int64_t *)(buf + map->offset);*p = atoll(tmp);}//read node中的内容,并根据node map中预设的类型 做好转换,把每个sesnor_t 的内容填充上close(fd);return 0;
}

getNode把每一个sensor下的节点数据获取到,根据map->type 做好指的转换并记录下;

node_map 前面提过,它是SysfsMap元素组成的 预设的数组

struct SysfsMap {int offset;const char *node;int type;int required;
};
const struct SysfsMap NativeSensorManager::node_map[] = {{offsetof(struct sensor_t, name), SYSFS_NAME, TYPE_STRING, 1},{offsetof(struct sensor_t, vendor), SYSFS_VENDOR, TYPE_STRING, 1},{offsetof(struct sensor_t, version), SYSFS_VERSION, TYPE_INTEGER, 1},{offsetof(struct sensor_t, type), SYSFS_TYPE, TYPE_INTEGER, 1},{offsetof(struct sensor_t, maxRange), SYSFS_MAXRANGE, TYPE_FLOAT, 1},{offsetof(struct sensor_t, resolution), SYSFS_RESOLUTION, TYPE_FLOAT, 1},{offsetof(struct sensor_t, power), SYSFS_POWER, TYPE_FLOAT, 1},{offsetof(struct sensor_t, minDelay), SYSFS_MINDELAY, TYPE_INTEGER, 1},{offsetof(struct sensor_t, fifoReservedEventCount), SYSFS_FIFORESVCNT, TYPE_INTEGER, 0},{offsetof(struct sensor_t, fifoMaxEventCount), SYSFS_FIFOMAXCNT, TYPE_INTEGER, 0},
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
#if defined(__LP64__){offsetof(struct sensor_t, maxDelay), SYSFS_MAXDELAY, TYPE_INTEGER64, 0},{offsetof(struct sensor_t, flags), SYSFS_FLAGS, TYPE_INTEGER64, 0},
#else{offsetof(struct sensor_t, maxDelay), SYSFS_MAXDELAY, TYPE_INTEGER, 0},{offsetof(struct sensor_t, flags), SYSFS_FLAGS, TYPE_INTEGER, 0},
#endif
#endif
};

SensorBase

在前面的getDataInfo处理过程中,其中判断sensor type 根据返回的结果 创建基于SensorBase的sensor driver实例是很关键的一步;

HAL中完成框架性的处理,实际操作需要根据每个sensor的特性对不同的sensor种类做不同的定义处理,以下是以acc为例:

在getDataInfo中,new了一个AccelSensor:

        switch (list->sensor->type) {case SENSOR_TYPE_ACCELEROMETER:has_acc = 1;list->driver = new AccelSensor(list);

AccelSensor类的定义如下:

class AccelSensor : public SensorBase {InputEventCircularReader mInputReader;sensors_event_t mPendingEvent;bool mHasPendingEvent;int64_t mEnabledTime;int setInitialState();
public:AccelSensor();AccelSensor(char *name);AccelSensor(struct SensorContext *context);virtual ~AccelSensor();virtual int readEvents(sensors_event_t* data, int count);virtual bool hasPendingEvents() const;virtual int setDelay(int32_t handle, int64_t ns);virtual int enable(int32_t handle, int enabled);virtual int calibrate(int32_t handle, struct cal_cmd_t *para,struct cal_result_t *cal_result);virtual int initCalibrate(int32_t handle, struct cal_result_t *cal_result);
};

它继承自SensorBase类, SensorBase也是各类sensor类的父类,以下是SensorBase的类的定义:

class SensorBase {
protected:const char* dev_name;const char* data_name;const sensor_cal_algo_t*    algo;char        input_name[PATH_MAX];int     dev_fd;int     data_fd;int64_t report_time;bool mUseAbsTimeStamp;sensors_meta_data_event_t   meta_data;char input_sysfs_path[PATH_MAX];int input_sysfs_path_len;int mEnabled;int mHasPendingMetadata;int64_t sysclk_sync_offset;int openInput(const char* inputName);static int64_t getTimestamp();static int64_t getClkOffset();static int64_t timevalToNano(timeval const& t) {return t.tv_sec*1000000000LL + t.tv_usec*1000;}int open_device();int close_device();
public:SensorBase(const char* dev_name, const char* data_name,const struct SensorContext* context = NULL);virtual ~SensorBase();virtual int readEvents(sensors_event_t* data, int count) = 0;virtual int injectEvents(sensors_event_t* data, int count);virtual bool hasPendingEvents() const;virtual int getFd() const;virtual int setDelay(int32_t handle, int64_t ns);virtual int enable(int32_t handle, int enabled) = 0;virtual int calibrate(int32_t handle, struct cal_cmd_t *para, struct cal_result_t *cal_result);virtual int initCalibrate(int32_t handle, struct cal_result_t *cal_result);virtual int setLatency(int32_t handle, int64_t ns);virtual int flush(int32_t handle);
};

SensorBase类中定义了如,readEvents,enable,flush等操作函数,AccelSensor继承了这些虚函数,并且完成了重写;

前文提到,读取poll_poll是调用了NativeSensorManager::readEvents,

list->driver->readEvents(data, count); 即是取出SensorBase指针指向的对象,调用它的readEvents函数;(note:多态的应用,通过指向父类类型对象,调用虚函数,实际调用到子类重写的具体的readEvents函数)

InputEventCircularReader

AccelSensor类定义的首个元素是:mInputReader,它是InputEventCircularReader 类对象;如其名称,输入事件循环读取,它主要用于对sensor event data的获取;

InputEventCircularReader 类定义如下:

class InputEventCircularReader
{struct input_event* const mBuffer;struct input_event* const mBufferEnd;struct input_event* mHead;struct input_event* mCurr;ssize_t mFreeSpace;
public:InputEventCircularReader(size_t numEvents);~InputEventCircularReader();ssize_t fill(int fd);ssize_t readEvent(input_event const** events);void next();
};
ssize_t InputEventCircularReader::fill(int fd)
{size_t numEventsRead = 0;if (mFreeSpace) {const ssize_t nread = read(fd, mHead, mFreeSpace * sizeof(input_event));if (nread<0 || nread % sizeof(input_event)) {// we got a partial event!!return nread<0 ? -errno : -EINVAL;}numEventsRead = nread / sizeof(input_event);if (numEventsRead) {mHead += numEventsRead;mFreeSpace -= numEventsRead;if (mHead > mBufferEnd) {size_t s = mHead - mBufferEnd;memcpy(mBuffer, mBufferEnd, s * sizeof(input_event));mHead = mBuffer + s;}}}return numEventsRead;
}
ssize_t InputEventCircularReader::readEvent(input_event const** events)
{*events = mCurr;ssize_t available = (mBufferEnd - mBuffer) - mFreeSpace;return available ? 1 : 0;
}
void InputEventCircularReader::next()
{mCurr++;mFreeSpace++;if (mCurr >= mBufferEnd) {mCurr = mBuffer;}
}

InputEventCircularReader 提供了fill,和 readEvent函数;

AccelSensor中的readEvents函数是使用它们来完成;以下是 readEvents的实现,这也是 读取HAL读取event data的最终实现的部分;

int AccelSensor::readEvents(sensors_event_t* data, int count)
{if (count < 1)return -EINVAL;if (mHasPendingEvent) {mHasPendingEvent = false;mPendingEvent.timestamp = getTimestamp();*data = mPendingEvent;return mEnabled ? 1 : 0;}if (mHasPendingMetadata) {mHasPendingMetadata--;meta_data.timestamp = getTimestamp();*data = meta_data;return mEnabled ? 1 : 0;}ssize_t n = mInputReader.fill(data_fd);if (n < 0)return n;int numEventReceived = 0;input_event const* event;
#if FETCH_FULL_EVENT_BEFORE_RETURN
again:
#endifwhile (count && mInputReader.readEvent(&event)) {int type = event->type;if (type == EV_ABS) {float value = event->value;if (event->code == EVENT_TYPE_ACCEL_X) {mPendingEvent.data[0] = value * CONVERT_ACCEL_X;} else if (event->code == EVENT_TYPE_ACCEL_Y) {mPendingEvent.data[1] = value * CONVERT_ACCEL_Y;} else if (event->code == EVENT_TYPE_ACCEL_Z) {mPendingEvent.data[2] = value * CONVERT_ACCEL_Z;}} else if (type == EV_SYN) {switch (event->code){case SYN_TIME_SEC:{mUseAbsTimeStamp = true;report_time = event->value*1000000000LL;}break;case SYN_TIME_NSEC:{mUseAbsTimeStamp = true;mPendingEvent.timestamp = report_time+event->value;}break;case SYN_REPORT:{if(mUseAbsTimeStamp != true) {mPendingEvent.timestamp = timevalToNano(event->time);}mPendingEvent.timestamp -= sysclk_sync_offset;if (mEnabled) {*data++ = mPendingEvent;numEventReceived++;count--;}}break;}} else {ALOGE("AccelSensor: unknown event (type=%d, code=%d)",type, event->code);}mInputReader.next();}
#if FETCH_FULL_EVENT_BEFORE_RETURN/* if we didn't read a complete event, see if we can fill andtry again instead of returning with nothing and redoing poll. */if (numEventReceived == 0 && mEnabled == 1) {n = mInputReader.fill(data_fd);if (n)goto again;}
#endifreturn numEventReceived;
}

这篇关于Android Sensor Input类型 (五) Sensor HAL NativeSensorManager的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/914589

相关文章

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Redis的Hash类型及相关命令小结

《Redis的Hash类型及相关命令小结》edisHash是一种数据结构,用于存储字段和值的映射关系,本文就来介绍一下Redis的Hash类型及相关命令小结,具有一定的参考价值,感兴趣的可以了解一下... 目录HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSET

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Python中异常类型ValueError使用方法与场景

《Python中异常类型ValueError使用方法与场景》:本文主要介绍Python中的ValueError异常类型,它在处理不合适的值时抛出,并提供如何有效使用ValueError的建议,文中... 目录前言什么是 ValueError?什么时候会用到 ValueError?场景 1: 转换数据类型场景

C# dynamic类型使用详解

《C#dynamic类型使用详解》C#中的dynamic类型允许在运行时确定对象的类型和成员,跳过编译时类型检查,适用于处理未知类型的对象或与动态语言互操作,dynamic支持动态成员解析、添加和删... 目录简介dynamic 的定义dynamic 的使用动态类型赋值访问成员动态方法调用dynamic 的

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影