Android Binder 机制初步学习 笔记(四,完结)—— Binder 简单应用示例
来源:互联网 发布:js删除数组中元素 编辑:程序博客网 时间:2024/06/05 15:23
- NOTE
- Binder 通信实践
- 为虚拟字符设备 Freg 编写驱动
- 1 fregh
- 2 fregc
- 3 Kconfig
- 4 Makefile
- 5 编译内核驱动模块
- Binder 实例 Common 模块
- 1 IFregServiceh
- 2 IFregServicecpp
- Binder 实例 Server 模块
- 1 FregServercpp
- 2 Androidmk
- Binder 实例 Client 模块
- 1 FregClientcpp
- 2 Androidmk
- 测试结果
- 为虚拟字符设备 Freg 编写驱动
NOTE
- 源码版本:Android 7.1.2。
- 内核版本:android-goldfish-3.4
- 内核下载:
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
(清华镜像站) - 以下分析思路均来自老罗的《Android 系统源代码情景分析(修订版)》。
Binder 通信实践
- 基于之前介绍过的
Binder
通信库,我们可以写一个简单的应用实例来熟悉它的使用方法。 - 在参考书中,这个关于
Binder
的例子里实现了一个Service
组件,这个组件负责管理一个虚拟的硬件设备freg
,这个虚拟的设备是作者在介绍HAL
层时实现的。这里我们只需要用到这个设备的内核驱动部分,所以首先我们要实现这个虚拟设备。 - 完成设备的驱动程序后,就要开始写我们的
Binder
实例了。这个实例分为三个模块:common
:- 实现硬件访问服务接口
IFregService
。 - 实现
Binder
本地对象类BnFregService
与Binder
代理对象类BpFregService
。
- 实现硬件访问服务接口
server
:- 实现了
Server
进程,其中包含了一个Service
组件FregService
。
- 实现了
client
:- 实现了一个
Client
进程,它通过一个BpFregService
代理对象去访问运行在Server
进程中的Service
组件FregService
所提供的服务。
- 实现了一个
1. 为虚拟字符设备 Freg 编写驱动
- 在
/kernel/goldfish/drivers
下新建一个文件夹freg
:mkdir freg
1.1 freg.h
- 定义四个字符串常量,分别描述
freg
在设备文件系统中的名称。 - 定义结构体
fake_reg_dev
描述虚拟设备freg
:val
:描述一个虚拟寄存器。sem
:信号量,用于同步访问寄存器。dev
:标准 Linux 字符设备结构体变量,用于标志freg
为字符设备类型。
#ifndef _FAKE_REG_H_#define _FAKE_REG_H_#include <linux/cdev.h>#include <linux/semaphore.h>#define FREG_DEVICE_NODE_NAME "freg"#define FREG_DEVICE_FILE_NAME "freg"#define FREG_DEVICE_PROC_NAME "freg"#define FREG_DEVICE_CLASS_NAME "freg"struct fake_reg_dev { int val; struct semaphore sem; struct cdev dev;};#endif
1.2 freg.c
- 向用户空间提供三个访问设备
freg
的寄存器val
的接口:proc
文件系统接口。- 传统设备文件系统接口。
devfs
文件系统接口。
- 首先定义一些相关变量以及函数原型:
#include <linux/init.h>#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/proc_fs.h>#include <linux/device.h>#include <asm/uaccess.h>#include "freg.h"/* the major/minor device number */static int freg_major = 0;static int freg_minor = 0;/* types and struct of the device */static struct class* freg_class = NULL;static struct fake_reg_dev* freg_dev = NULL;/* traditional operations */static int freg_open(struct inode* inode, struct file* filp);static int freg_release(struct inode* inode, struct file* filp);static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);/* traditional operations list */static struct file_operations freg_fops = { .owner = THIS_MODULE, .open = freg_open, .release = freg_release, .read = freg_read, .write = freg_write,};/* device properties operation of filesystem 'devfs' */static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr, char* buf);static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);/* devfs filesystem's device properties */static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, freg_val_show, freg_val_store);
- 实现传统设备文件操作:
freg_open
/freg_release
:打开 / 关闭设备。freg_read
/freg_write
:读取 / 写入val
。
/* open this device */static int freg_open(struct inode* inode, struct file* filp){ struct fake_reg_dev* dev; /* save our struct to the private_data of file pointer */ dev = container_of(inode->i_cdev, struct fake_reg_dev, dev); filp->private_data = dev; return 0;}/* release this device */static int freg_release(struct inode* inode, struct file* filp){ return 0;}/* read the value from the device */static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos){ ssize_t err = 0; struct fake_reg_dev* dev = filp->private_data; /* synchronize */ if (down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } if (count < sizeof(dev->val)) { goto out; } /* copy value to the buffer */ if (copy_to_user(buf, &(dev->val), sizeof(dev->val))) { err = -EFAULT; goto out; } err = sizeof(dev->val);out: up(&(dev->sem)); return err;}/* write value to the device */static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos){ ssize_t err = 0; struct fake_reg_dev* dev = filp->private_data; /* synchronize */ if (down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } if (count != sizeof(dev->val)) { goto out; } /* copy buffer to the value */ if (copy_from_user(&(dev->val), buf, count)) { err = -EFAULT; goto out; } err = sizeof(dev->val);out: up(&(dev->sem)); return err;}
- 定义用于访问设备的
devfs
文件系统接口:- 将寄存器
val
当做设备的一个属性,通过读写熟悉达到访问目的。 freg_val_show
:读取val
。freg_val_store
:写入val
。- 为方便编写
proc
文件系统接口,将一些共通的操作提取出来,写成两个内部函数__freg_get_val
与__freg_set_val
。
- 将寄存器
/* read dev->val to buffer */static ssize_t __freg_get_val(struct fake_reg_dev* dev, const char* buf){ int val = 0; if (down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } val = dev->val; up(&(dev->sem)); return snprintf(buf, PAGE_SIZE, "%d\n", val);}/* write buffer value to dev->val */static ssize_t __freg_set_val(struct fake_reg_dev* dev, const char* buf, size_t count){ int val = 0; /* str to int */ val = simple_strtol(buf, NULL, 10); if (down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } dev->val = val; up(&(dev->sem)); return count;}/* read device properties value */static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr, char* buf){ struct fake_reg_dev* hdev = (struct fake_reg_dev*) dev_get_drvdata(dev); return __freg_get_val(hdev, buf);}/* write value to device properties */static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count){ struct fake_reg_dev* hdev = (struct fake_reg_dev*) dev_get_drvdata(dev); return __freg_set_val(hdev, buf, count);}
- 定义
proc
文件系统接口:freg_proc_read
/freg_proc_write
:寄存器读写操作。freg_create_proc
/freg_remove_proc
:创建 / 删除/proc/freg
文件。- 注意在
creat
的时候,原本有一句entry->owner = THIS_MODULE
的,但是在这个版本里不需要。
/* read dev->val to page buffer */static ssize_t freg_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data){ if (off > 0) { *eof = 1; return 0; } return __freg_get_val(freg_dev, page);}/* save buffer value to dev->val */static ssize_t freg_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data){ int err = 0; char* page = NULL; if (len > PAGE_SIZE) { printk(KERN_ALERT"The buff is too large: %lu.\n", len); return -EFAULT; } page = (char*) __get_free_page(GFP_KERNEL); if (!page) { printk(KERN_ALERT"Failed to alloc page.\n"); return -ENOMEM; } /* copy user buffer to kernel buffer */ if (copy_from_user(page, buff, len)) { printk(KERN_ALERT"Failed to copy buff from user.\n"); err = -EFAULT; goto out; } err = __freg_set_val(freg_dev, page, len);out: free_page((unsigned long) page); return err;}/* create file 'proc/freg' */static void freg_create_proc(void){ struct proc_dir_entry* entry; entry = create_proc_entry(FREG_DEVICE_PROC_NAME, 0, NULL); if (entry) { entry->read_proc = freg_proc_read; entry->write_proc = freg_proc_write; }}/* remove file 'proc/freg' */static void freg_remove_proc(void){ remove_proc_entry(FREG_DEVICE_PROC_NAME, NULL);}
- 定义驱动的模块加载与卸载函数:
freg_init
:注册与初始化freg
。freg_exit
:反注册与释放freg
。- 注意在
setup
中,采用了sema_init(&(dev->sem), 1)
来初始化信号量,而在源码分析的原文中,这里用的是init_MUTEX(&(dev->sem))
,但目前这个操作已经被弃用了。
/* init device */static int __freg_setup_dev(struct fake_reg_dev* dev){ int err; dev_t devno = MKDEV(freg_major, freg_minor); memset(dev, 0, sizeof(struct fake_reg_dev)); /* init cdev */ cdev_init(&(dev->dev), &freg_fops); dev->dev.owner = THIS_MODULE; dev->dev.ops = &freg_fops; /* register cdev */ err = cdev_add(&(dev->dev), devno, 1); if (err) { return err; } /* init freg_dev */ sema_init(&(dev->sem), 1); dev->val = 0; return 0;}/* load module */static int __init freg_init(void){ int err = -1; dev_t dev = 0; struct device* temp = NULL; printk(KERN_ALERT"Initializing freg device.\n"); /* dynamic allocation of the major and minor number */ err = alloc_chrdev_region(&dev, 0, 1, FREG_DEVICE_NODE_NAME); if (err < 0) { printk(KERN_ALERT"Failed to alloc char dev region.\n"); goto fail; } freg_major = MAJOR(dev); freg_minor = MINOR(dev); /* alloc struct freg */ freg_dev = kmalloc(sizeof(struct fake_reg_dev), GFP_KERNEL); if (!freg_dev) { err = -ENOMEM; printk(KERN_ALERT"Failed to alloc freg device.\n"); goto unregister; } /* init device */ err = __freg_setup_dev(freg_dev); if (err) { printk(KERN_ALERT"Failed to setup freg device: %d.\n", err); goto cleanup; } /* create freg_class dir 'freg' in '/sys/class' */ freg_class = class_create(THIS_MODULE, FREG_DEVICE_CLASS_NAME); if (IS_ERR(freg_class)) { err = PTR_ERR(freg_class); printk(KERN_ALERT"Failed to create freg device class.\n"); goto destroy_cdev; } /* create dir 'freg' in '/dev/' and 'sys/class/freg' */ temp = device_create(freg_class, NULL, dev, NULL, "%s", FREG_DEVICE_FILE_NAME); if (IS_ERR(freg_class)) { err = PTR_ERR(temp); printk(KERN_ALERT"Falied to create freg device.\n"); goto destroy_class; } /* create properties dir 'val' in 'sys/class/freg/freg' */ err = device_create_file(temp, &dev_attr_val); if (err < 0) { printk(KERN_ALERT"Failed to create attribute val of freg device.\n"); goto destroy_device; } dev_set_drvdata(temp, freg_dev); /* create 'proc/freg' */ freg_create_proc(); printk(KERN_ALERT"Succeeded to initialize freg device.\n"); return 0;destroy_device: device_destroy(freg_class, dev);destroy_class: class_destroy(freg_class);destroy_cdev: cdev_del(&(freg_dev->dev));cleanup: kfree(freg_dev);unregister: unregister_chrdev_region(MKDEV(freg_major, freg_minor), 1);fail: return err;}/* unload module */static void __exit freg_exit(void){ dev_t devno = MKDEV(freg_major, freg_minor); printk(KERN_ALERT"Destroy freg device.\n"); /* delete '/proc/freg' */ freg_remove_proc(); /* destroy device and its class */ if (freg_class) { device_destroy(freg_class, MKDEV(freg_major, freg_minor)); class_destroy(freg_class); } /* delete cdev and release mem */ if (freg_dev) { cdev_del(&(freg_dev->dev)); kfree(freg_dev); } /* release devno */ unregister_chrdev_region(devno, 1);}MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Fake Register Driver");module_init(freg_init);module_exit(freg_exit);
1.3 Kconfig
- 驱动程序写完后,需要继续编写一些编译配置文件。
- 这个文件定义了驱动的编译选项。
config FREG tristate "Fake Register Driver" default n help This is the freg driver for android system.
- 在
freg
文件夹下写好这个文件后,还要去修改内核的Kconfig
文件,否则无法编译我们加入的新内核驱动。 kernel/goldfish/drivers/Kconfig
中,加入:source "drivers/freg/Kconfig"
1.4 Makefile
- 这个驱动的编译脚本很简单,只有一行:
obj-$(CONFIG_FREG) += freg.o
- 相应地,我们也需要修改内核的 Makefile 文件:
drivers/Makefile
- 添加语句:
obj-$(CONFIG_FREG) += freg/
1.5 编译内核驱动模块
- 指令
make menuconfig
配置编译方式:- 用上下键选择
Device Drivers
项,按下Enter
。 - 选择
Fake Register Driver
项,按下Y
。 - 保存配置,退出。
- 用上下键选择
- 执行
make
,成功后会有提示:Kernel: arch/arm/boot/zImage is ready
- 之后启动模拟器
emulator
的时候,加上-kernel kernel/goldfish/arch/arm/boot/zImage
,就能使用编译好的内核了。
2. Binder 实例 —— Common 模块
- 在
./external/binder
文件夹下创建common
文件夹:mkdir common
- 进入
cd common
2.1 IFregService.h
- 宏
FREG_SERVICE
描述了该Service
组件注册到Service Manager
的名称。 - 定义硬件访问服务接口
IFregService
:getVal
:读取硬件设备freg
中寄存器val
的值。setVal
:写入val
值。
- 定义
Binder
本地对象类BnFregService
:- 实现成员函数
onTransact
,用于处理收到的信息。
- 实现成员函数
- 宏
DECLARE_META_INTERFACE
:- 作用是声明
IFregService
类的元接口。 - 定义一个静态成员变量
descriptor
,用于描述接口名。 - 定义成员函数
getInterfaceDescriptor
,用于获取接口名。 - 定义静态成员函数
asInterface
,用于将一个IBinder
对象转换为IFregService
接口。 - 定义了
IFregService
的构造与析构函数。
- 作用是声明
#ifndef IFREGSERVICE_H_#define IFREGSERVICE_H_#include <utils/RefBase.h>#include <binder/IInterface.h>#include <binder/Parcel.h>#define FREG_SERVICE "stone.FregService"using namespace android;class IFregService : public IInterface{public : DECLARE_META_INTERFACE(FregService); virtual int32_t getVal() = 0; virtual void setVal(int32_t val) = 0;};class BnFregService : public BnInterface<IFregService>{public : virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);};#endif
2.2 IFregService.cpp
- 定义枚举变量,表示进程间通信代码:
GET_VAL
:对应成员函数getVal
。SET_VAL
:对应成员函数setVal
。
- 定义
Binder
代理对象类BpFregService
,它实现了IFregService
接口:getVal
:- 将需要传输的数据封装到
Parcel
对象中。 - 通过
remote
函数获得一个BpBinder
代理对象。 - 通过代理对象的成员函数
transact
来请求运行在Server
进程中的一个Binder
本地对象执行一个GET_VAL
操作。 - 操作的返回结果是一个整数,封装在另一个
Parcel
对象中返回。
- 将需要传输的数据封装到
setVal
:- 将传递的数据封装在一个
Parcel
对象中。 - 同样获取
BpBinder
代理对象。 - 通过代理的成员函数
transact
请求SET_VAL
操作。 - 通过
SET_VAL
操作将一个整数写入到设备freg
的寄存器中。
- 将传递的数据封装在一个
- 宏
IMPLEMENT_META_INTERFACE
:- 与头文件中的宏是对应的,它实现了
IFregService
类的元接口。 - 将接口名设置为
"stone.IFregService"
。 - 实现
IFregService
类的构造,析构函数(空实现)。 - 实现成员函数
getInterfaceDescriptor
。 - 实现成员函数
asInterface
:- 其参数
obj
应指向一个类型为BnFregService
的Binder
本地对象,或一个类型为BpBinder
的Binder
代理对象,否则其返回值为NULL
。 - 若指向本地对象,则调用成员函数
queryLocalInterface
直接返回一个IFregService
接口。 - 若指向代理对象,则成员函数
queryLocalInterface
返回NULL
,随后将其封装成一个BpFregService
对象,并将它的IFregService
接口返回。
- 其参数
- 与头文件中的宏是对应的,它实现了
- 实现了
BnFregService
类成员函数onTransact
:- 负责将
GET_VAL
与SET_VAL
请求分发给其子类的成员函数getVal
与setVal
处理。 BnFregService
的子类为FregService
,其具体实现了getVal
与setVal
。
- 负责将
#define LOG_TAG "IFregService"#include <utils/Log.h>#include "IFregService.h"using namespace android;enum{ GET_VAL = IBinder::FIRST_CALL_TRANSACTION, SET_VAL};class BpFregService : public BpInterface<IFregService>{public : BpFregService(const sp<IBinder>& impl) : BpInterface<IFregService>(impl) { } int32_t getVal() { Parcel data; data.writeInterfaceToken(IFregService::getInterfaceDescriptor()); Parcel reply; remote()->transact(GET_VAL, data, &reply); int32_t val = reply.readInt32(); return val; } void setVal(int32_t val) { Parcel data; data.writeInterfaceToken(IFregService::getInterfaceDescriptor()); data.writeInt32(val); Parcel reply; remote()->transact(SET_VAL, data, &reply); }};IMPLEMENT_META_INTERFACE(FregService, "stone.IFregService");status_t BnFregService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){ switch(code) { case GET_VAL: { CHECK_INTERFACE(IFregService, data, reply); int32_t val = getVal(); reply->writeInt32(val); return NO_ERROR; } case SET_VAL: { CHECK_INTERFACE(IFregService, data, reply); int32_t val = data.readInt32(); setVal(val); return NO_ERROR; } default: { return BBinder::onTransact(code, data, reply, flags); } }}
3. Binder 实例 —— Server 模块
- 在
./external/binder
文件夹下创建common
文件夹:mkdir server
- 进入
cd server
3.1 FregServer.cpp
- 首先实现了一个
Service
组件类FregService
:- 继承了
BnFregService
,并具体实现了IFregService
接口。 - 构造函数中,调用
open
函数来打开设备文件/dev/freg
,并将得到的文件描述符保存在成员变量fd
中。 - 相应地,析构函数中则会关闭设备文件。
getVal
与setVal
都会通过打开的设备文件对其寄存器进行读写操作。- 静态成员函数
instantiate
负责将FregService
组件注册到Service Manager
中,并将注册名设置为"stone.FregService"
,如此一来,Client
进程就能通过该名称获取这个FregService
组件的一个代理对象了。
- 继承了
main
函数即是该Server
进程的主函数:- 调用
instantiate
注册组件。 - 调用
ProcessState
对象成员函数startThreadPool
启动Binder
线程池。 - 调用主线程
IPCThreadState
对象的成员joinThreadPool
将主线程添加到进程Binder
线程池中,用于处理来自Client
的通信请求。
- 调用
#define LOG_TAG "FregServer"#include <stdlib.h>#include <fcntl.h>#include <utils/Log.h>#include <binder/IServiceManager.h>#include <binder/IPCThreadState.h>#include "../common/IFregService.h"#define FREG_DEVICE_NAME "/dev/freg"class FregService : public BnFregService{public : FregService() { fd = open(FREG_DEVICE_NAME, O_RDWR); if (-1 == fd) { ALOGE("Failed to open device %s.\n", FREG_DEVICE_NAME); } } virtual ~FregService() { if (fd != -1) { close(fd); } }public : static void instantiate() { defaultServiceManager()->addService(String16(FREG_SERVICE), new FregService()); } int32_t getVal() { int32_t val = 0; if (fd != -1) { read(fd, &val, sizeof(val)); } return val; } void setVal(int32_t val) { if (fd != -1) { write(fd, &val, sizeof(val)); } }private : int fd;};int main(int argc, char** argv){ FregService::instantiate(); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); return 0;}
3.2 Android.mk
server
模块的编译脚本。
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := optionalLOCAL_SRC_FILES := ../common/IFregService.cpp \ FregServer.cppLOCAL_SHARED_LIBRARIES := libcutils libutils libbinderLOCAL_MODULE := FregServerinclude $(BUILD_EXECUTABLE)
4. Binder 实例 —— Client 模块
- 在
./external/binder
文件夹下创建common
文件夹:mkdir server
- 进入
cd server
4.1 FregClient.cpp
- 首先调用
defaultServiceManager()
获取Service Manager
的代理对象。 - 调用代理对象成员函数
getService
获取名为"stone.FregService"
的Service
组件的一个类型为BpBinder
的代理对象。 - 将
BpBinder
代理对象封装成BpFregService
代理对象,获取其IFregService
接口,保存在变量service
中。 - 通过
service->getVal()
获取寄存器的当前值,并打印。 - 通过
service->setVal()
设置寄存器的值,这个值为当前值加一。 - 再次通过
service->getVal()
获取寄存器值并打印。 - 可以预见到的结果是,运行
FregClient
后屏幕上应有的输出应该是:n
与n + 1
。 - 由于寄存器值初始化为
0
,所以第一次运行FregClient
时输出应为0
和1
。 - 可以预见到的是,第二次运行
FregClient
时输出为1
和2
。
#define LOG_TAG "FregClient"#include <utils/Log.h>#include <binder/IServiceManager.h>#include "../common/IFregService.h"int main(){ sp<IBinder> binder = defaultServiceManager()->getService(String16(FREG_SERVICE)); if (binder == NULL) { ALOGE("Failed to get freg service: %s.\n", FREG_SERVICE); return -1; } sp<IFregService> service = IFregService::asInterface(binder); if (service == NULL) { ALOGE("Failed to get freg service interface.\n"); return -2; } printf("Read original value from FregService:\n"); int32_t val = service->getVal(); printf(" %d.\n", val); printf("Add value 1 to FregService.\n"); val += 1; service->setVal(val); printf("Read the value from FregService again:\n"); val = service->getVal(); printf(" %d.\n", val); return 0;}
4.2 Android.mk
client
模块的编译脚本。
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := optionalLOCAL_SRC_FILES := ../common/IFregService.cpp \ FregClient.cppLOCAL_SHARED_LIBRARIES := libcutils libutils libbinderLOCAL_MODULE := FregClientinclude $(BUILD_EXECUTABLE)
5. 测试结果
- 回到
Android
根目录下:mmm ./external/binder/server/
mmm ./external/binder/client/
make snod
- 编译成功后:
emulator -kernel kernel/goldfish/arch/arm/boot/zImage &
adb shell
- 接下来的操作与输出如下:
- 启动
FregServer
模块。 - 启用一次
FregClient
观察到两个值:0
和1
。 - 启用第二次
FregClient
观察到两个值:1
和2
。
- 启动
- 实测结果与 4.1 中的分析一致,说明这个实例已经成功运作了。
generic:/ # FregServer &
[1] 1139
generic:/ # FregClient
Read original value from FregService:
0.
Add value 1 to FregService.
Read the value from FregService again:
1.
generic:/ # FregClient
Read original value from FregService:
1.
Add value 1 to FregService.
Read the value from FregService again:
2.
阅读全文
0 0
- Android Binder 机制初步学习 笔记(四,完结)—— Binder 简单应用示例
- Android Binder 机制初步学习 笔记(二)—— Binder 设备基本操作实现
- Android Binder 机制初步学习 笔记(三)—— Binder 进程通讯库简介
- Android Binder 机制初步学习 笔记(一)—— 概述及数据结构介绍
- Android Binder 通信机制学习(四)
- android binder机制之——(我是binder实例)
- android binder机制之——(创建binder服务)
- android binder机制之——(我是binder实例)
- android binder机制之——(我是binder实例)
- android binder机制之——(创建binder服务)
- Android Binder 机制学习
- Android Binder机制学习
- android Binder 机制学习
- Binder机制学习笔记-Binder框架
- Android Binder机制学习笔记之一
- Android IPC机制学习笔记(三) Binder
- Android内核学习笔记—Binder分析
- android学习笔记--binder
- Oracle工具函数总结
- kaldi问题:queue.pl: error submitting jobs to queue
- linux 安装telnet服务
- mybatis 总结
- View的绘制流程
- Android Binder 机制初步学习 笔记(四,完结)—— Binder 简单应用示例
- Notification 的 Flag 设置
- 【OpenCV入门教程】图像加噪
- 自动生成代码类
- 谷歌的AI码农写出了比真码农还有效的机器学习代码!
- 远程连接mysql
- 我和这对最近融资2.32亿美元的创业夫妻档聊了聊 ...
- 如何基于区块链做数据分析并赚钱? | 硅谷线下活动
- 关于axios和promise的理解