在Android中,有几种方式可以和native层进行通讯,根据权限不同可以适当选择沟通的方式:
1.基于JNI在上层java代码中直接调用native code中的C API,这个所有第三方权限都可以,即所谓的NDK编程。
2.基于socket stream进行通讯,在native c层建立一个server task,通过socket连接与上层进行通信。-----需要系统权限
3.基于驱动模块进行通讯,即自定义一个驱动模块,将相关native code写成驱动模块,通过读写设备流进行通信。---需要系统权限
本文以Android源代码来说明第二种通讯方式,android source code中installer模块就是通过socket stream通讯的典型例子,通过分析这个例子可以了解如何设计socket通讯方式。
一、底层server侧native代码(socket通讯就是典型的c/s模型)
源代码文件路径:
android_4.1_src\frameworks\base\cmds\installd
1. installd.h
定义驻留程序server的socketname,在客户端需要通过这个socketname来与server建立连接。
#define SOCKET_PATH "installd"
2.installd.c
定义一个无穷循环,等待客户端的连接,与普通的socket编程并无多大区别:
- int main(const int argc, const char *argv[]) {
- char buf[BUFFER_MAX];
- struct sockaddr addr;
- socklen_t alen;
- int lsocket, s, count;
- //首先获取有效的socketname
- lsocket = android_get_control_socket(SOCKET_PATH);
- if (lsocket < 0) {
- LOGE("Failed to get socket from environment: %s\n", strerror(errno));
- exit(1);
- }
- if (listen(lsocket, 5)) { //开始监听这个socket path
- LOGE("Listen on socket failed: %s\n", strerror(errno));
- exit(1);
- }
- fcntl(lsocket, F_SETFD, FD_CLOEXEC);
- for (;;) {
- alen = sizeof(addr);
- s = accept(lsocket, &addr, &alen);//等待客户端连接上,也就是上层代码请求连接
- if (s < 0) {
- LOGE("Accept failed: %s\n", strerror(errno));
- continue;
- }
- fcntl(s, F_SETFD, FD_CLOEXEC);
- LOGI("new connection\n");
- for (;;) {
- unsigned short count;
- if (readx(s, &count, sizeof(count))) { //读取客户端的请求内容
- LOGE("failed to read size\n");
- break;
- }
- if ((count < 1) || (count >= BUFFER_MAX)) {
- LOGE("invalid size %d\n", count);
- break;
- }
- if (readx(s, buf, count)) {
- LOGE("failed to read command\n");
- break;
- }
- buf[count] = 0;
- if (execute(s, buf)) break;//根据请求内容执行相应的代码
- }
- LOGI("closing connection\n");
- close(s);
- }
- return 0;
- }
关于读取客户端的请求命名,然后执行不同的代码和返回数据,请查看installd.c源代码即可。
如果自己需要设计类似的功能,模仿修改即可。
3.Android.mk
由于需要编译成驻留程序,因此需要是直接可执行程序,请参考Android.mk内容,
其中
include $(BUILD_EXECUTABLE) 表示编译成可执行程序。
4.编译:
如果是自己定义的模块,可以将源代码放置在 development目录下,创建如 myserver 的目录,编写自己的native server代码.
模仿 上述代码即可。
编译(根据当前环境自己修改):
./mbldenv.sh
mm development/myserver
请根据当前的环境生成system.img
5.生成的可执行文件一般在 out/target/product/xxx/system/xbin/目录下,根据环境不同位置不同。
6.将该可执行文件复制到对应的目录下,如mtk环境下参考如下:
vendor\mediatek\konka73_gb\artifacts\out\target\product\konka73_gb\system\bin
7.在init.rc增加相应的配置,让其开机自动启动:如: \mediatek\config\konka73_gb\init.rc
在zygote启动后加载你的驻留程序:
service zygote /system/bin/app_process -Xzygote/system/bin --zygote --start-system-server
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
#这里yoursocketname必须与server中定义的一致,系统是通过这个找到你的服务的。
#yoursocketdaemon是刚才编译的可执行文件
service yourservicename /system/bin/yoursocketdaemon
socket yoursocketname stream 600 system system
user root
group root
oneshot
如installd的配置: service installd /system/bin/installd
class main
socket installd stream 600 system system
关于init.rc的配置规则,可以参考 /system/cor/init/readme.txt
二、上层framework的代码
还是以installer.java为例说明(位于:android_4.1_src\frameworks\base\services\java\com\android\server\pm下)
上层就比较简单了,直接发起相应的连接请求即可:
客户端调用execute执行相应的命令即可,相关的命令定义与installd.c定义的命令一致,也就是如果需要扩展相应的分别增加相应的命名执行和调用的代码即可。
- struct cmdinfo cmds[] = { //in installd.c
- { "ping", 0, do_ping },
- { "install", 3, do_install },
- { "dexopt", 3, do_dexopt },
- { "movedex", 2, do_move_dex },
- { "rmdex", 1, do_rm_dex },
- { "remove", 2, do_remove },
- { "rename", 2, do_rename },
- { "freecache", 1, do_free_cache },
- { "rmcache", 1, do_rm_cache },
- { "protect", 2, do_protect },
- { "getsize", 4, do_get_size },
- { "rmuserdata", 2, do_rm_user_data },
- { "movefiles", 0, do_movefiles },
- { "linklib", 2, do_linklib },
- { "unlinklib", 1, do_unlinklib },
- { "mkuserdata", 3, do_mk_user_data },
- { "rmuser", 1, do_rm_user },
- };
- private int execute(String cmd) {//in installer.java
- String res = transaction(cmd);
- try {
- return Integer.parseInt(res);
- } catch (NumberFormatException ex) {
- return -1;
- }
- }
返回信息可以通过reply参数写回给客户端。
请同时阅读android 上述提到的相关源代码以深入了解具体实现。