Android Fk: PKMS(3)之installd及LocalSocket实现Java层与Native层通信
来源:互联网 发布:php 点击链接下载文件 编辑:程序博客网 时间:2024/06/05 21:02
LOCAL_CLANG := true#Android Fk: PKMS(3)之installd及LocalSocket实现Java层与Native层通信
一、installd的概述
从上一篇介绍应用安装与卸载的学习文档中知道PKMS在实现部分包管理功能时需要借助installd去完成,关于调用的详细流程可以参考这篇博客,Android7.0 PackageManagerService (5) installd,作者详细介绍了installd的初始化及调用方法的流程,查看android 7.1.1的源码,这部分代码和博主所述大致一致,本人就不赘述了。
1.installd的启动
installd是个native的服务,在system/bin下,开机时由init启动:
看installd的rc文件:
service installd /system/bin/installd class main socket installd stream 600 system system
可以看到在起installd的时候创建了一个名为installd的socket文件,查看如下:
看到dev/socket下还有其他服务创建的socket文件;
由上面提到的博客分析得知 installd启动后,获取作为服务端的socket “installd”; 然后,监听”installd”,等待Java层installer服务的连接及命令的到来:
2.installd的调用方试
作为客户端的PKMS使用Intaller中封装好的用于socket通信的InstallerConnection对应向对应的socket”installd”发生操作指令;
PKMS调到installd的大致流程总结如图(详细流程参考上面提到的博客):
PKMS调用installd的方式是通过localsocket的方式实现的,socket实现了从Java层到Native层的通信,下面将通过一个demo学习来使用下这种socket通信方式;
二、Socket方式实现Java层与Native层通信
1.模仿installd写一个开机启动的native服务
1.1 native服务源码
在framework/base/cmd下新建一个demo文件夹命名为socket_test,或者在installd的模块目录framework/native/cmd下建项目目录也可以,然后新建c++文件,socket_test.cpp,如下:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/wait.h>#include <sys/un.h>#include <cutils/sockets.h>#include <utils/Log.h>#include <android/log.h>#define SOCKET_NAME "socket_test"#define LOG_TAG "SOCKET_TEST_SERVER"#define LOGD(...) __android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)int main(){ char log[200]; LOGD("main"); int connect_number = 6; int fdListen = -1, new_fd = -1; int ret; struct sockaddr_un peeraddr; socklen_t socklen = sizeof (peeraddr); int numbytes ; char buff[256]; //获取SOCKET_NAME的socket文件描述符 fdListen = android_get_control_socket(SOCKET_NAME); if (fdListen < 0) { sprintf(log,"Failed to get socket '" SOCKET_NAME "' errno:%d , main listen will exit!", errno); LOGD(log); exit(-1); } //监听客户端连接,最多连接connect_number个 ret = listen(fdListen, connect_number); sprintf(log,"Listen result %d",ret); LOGD(log); if (ret < 0) { perror("listen"); exit(-1); } //获取客户端的连接 new_fd = accept(fdListen, (struct sockaddr *) &peeraddr, &socklen); sprintf(log,"Accept_fd: %d",new_fd); LOGD(log); if (new_fd < 0 ) { sprintf(log,"fd<0 error %d",errno); LOGD(log); exit(-1); } while(1) { LOGD("Waiting for Client ..."); if((numbytes = recv(new_fd,buff,sizeof(buff),0))==-1) { sprintf(log,"%d",errno); LOGD(log); continue; } else { sprintf(log,"Server Received: %s",buff); LOGD(log); } //将收到的buff信息再send回给client端 if(send(new_fd,buff,strlen(buff),0)==-1) { close(new_fd); LOGD("send error!"); exit(0); } else { LOGD("Server sendback succuess!"); } } LOGD("main close "); close(new_fd); close(fdListen); return 0;}
主要流程和installd类似,大概的操作如下:
启动后获取对应的socket文件描述符作为socket的server端,然后监听是否有client连接,client连接后接受client发送的消息,然后将消息再通过socket方式send回给client端;
1.2 Android.mk文件
然后同目录下新建Android.mk文件,如下:
#frameworks/base/cmds/socket_test/Android.mkLOCAL_PATH:= $(call my-dir)common_src_files := socket_test.cppinclude $(CLEAR_VARS)LOCAL_SRC_FILES := $(common_src_files)LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPESLOCAL_SHARED_LIBRARIES := \ libcutils \ liblog \ libandroidfw \ libutils \ libselinux LOCAL_MODULE := socket_testLOCAL_INIT_RC := socket_test.rcinclude $(BUILD_EXECUTABLE)
其中值得注意的是LOCAL_INIT_RC这个标识,由于需要让这个demo服务通过init启动,因此需要给socket_test写自己的rc文件,有LOCAL_INIT_RC标识,在编该模块的时候会将该rc文件拷贝到system/etc/init目录下,init会去解析这个目录下的所以rc文件然后做对应的操作;
1.3 socket_test.rc文件
接着定义socket_test的rc文件:
service socket_test /system/bin/socket_test class main socket socket_test stream 660 system system#保证开机结束后启动on property:sys.boot_completed=1 start socket_test
在这里方便验证,直接将这个socket_init.rc文件push到system/etc/init/下面,但是需要在重启前将rc文件权限改为和其他rc文件一致,否则可能导致服务起不来,甚至无法开机,可以看到权限改为644:
adb rootadb remountadb push XX/socket_test.rc system/etc/init/adb shellchmod 644 system/etc/init/socket_test.rc
到这一步我们可以尝试make socket_test -j8是否生成了对应的服务,这一步会在”out/target/product/pollux/system/bin/”生成socket_test服务,另外会将socket_test.rc更新到”out/target/product/pollux/system/etc/init/”目录下,全编的时候会将这个目录打包到手机对应的system/etc/init/目录下,然后开机去解析;
将该服务push到system/bin/下,然后更改权限,
adb push out/target/product/pollux/system/bin/socket_test system/binadb chmod 755 socket_test
1.4 小问题解决
1.4.1 SELinux domain未定义导致socket_test未启动
满怀激动的重启后,发现socket_test并未启动,而且dev/socket下也没有生成rc中定义好的socket文件,
查看开机log发现如下:
03-08 10:00:19.212502 0 0 E init : Service socket_test does not have a SELinux domain defined.
说明没有定义SELinux domain,导致服务无法自启动。需按如下方式修改或添加sepolicy文件:
a. 在system/sepolicy/file_contexts文件末尾添加
############################## socket_test# System files/system/bin/socket_test u:object_r:socket_test_exec:s0
b.在system/sepolicy/文件夹下新建socket_test.te文件,内容如下:
type socket_test, domain;type socket_test_exec, exec_type, file_type;init_daemon_domain(socket_test)
c.编译bootimage,烧录bootimage,执行如下命令后再重启查看socket_test进程是否起来:
adb rootadb remountadb shell restorecon system/bin/xxxadb reboot
1.4.2 selinux问题导致socket文件未创建,然后创建了socket文件server又无法获取等问题
但是看到dev/socket/下socket_test可能还是没有被创建,这里其实是selinux的问题了,因此需要根据log里显示被拒权限相应的添加这些权限;
类似于如下的log:
03-08 10:31:04.219000 3738 3738 I auditd : type=1400 audit(0.0:645): avc: denied { create } for comm="init" name="socket_test" scontext=u:r:init:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=103-08 10:31:08.719000 4181 4181 W socket_test: type=1400 audit(0.0:650): avc: denied { read write } for path="/dev/oeminfo" dev="tmpfs" ino=17725 scontext=u:r:socket_test:s0 tcontext=u:object_r:oeminfo_device:s0 tclass=chr_file permissive=103-08 11:24:27.919 3711 3711 I auditd : type=1400 audit(0.0:666): avc: denied { setattr } for comm="init" name="socket_test" dev="tmpfs" ino=35596 scontext=u:r:init:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=0
后面还会有其他奇怪的问题,比如socket文件创建好了,单socket_test服务无法获取dev/socket/下的socket_test文件作为服务端,log显示:
03-08 11:11:31.384 0 0 E init : Failed to lchown socket '/dev/socket/socket_test': Permission denied看到该log前面一点出现03-08 11:24:27.919 3711 3711 W init : type=1400 audit(0.0:666): avc: denied { setattr } for name="socket_test" dev="tmpfs" ino=35596 scontext=u:r:init:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=1
所以这同样是SElinux问题,添加selinux权限的方法如下,在system/sepolicy/文件夹下添加的socket_test.te末尾添加权限:
#allow scontext tcontext:tclass { perm1 perm2 } 大致的添加方式allow init socket_device:sock_file{ create unlink link setattr};allow socket_test oeminfo_device:chr_file { read write };allow socket_test rootfs:lnk_file { getattr setattr };
selinux也是修改了system/sepolicy中的文件,同样如上编bootimage,烧录,restorecon操作,重启,
现在终于看到dev/socket/下有socket_test文件了,system/bin/下的socket_test服务同样也起来了,而且也已成功获取socket_test文件作为服务端,此时正在监听client端连接。
至此模仿installd,完成了native层的服务端,下面来完成java层的client短。
2. 写个Apk作为Client端向Native 服务 socket_test进行通信
2.1 apk核心代码
为了操作方便直接写成apk作为client端,一个消息输入框,一个发送按钮,一个显示server发回的消息;
生成系统签名的该apk,push到system/app下,给apk以系统的shareUid:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.demo.mysocketclient" android:sharedUserId="android.uid.system">
主要代码:
public class MainActivity extends AppCompatActivity { Button btn_send; EditText etxt; TextView txtRev; private final String SOCKET_NAME = "socket_test"; LocalSocket client; LocalSocketAddress address; private InputStream mIn; private OutputStream mOut; BufferedReader in; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); client = new LocalSocket(); address = new LocalSocketAddress(SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); btn_send = (Button) findViewById(R.id.btn_send); etxt = (EditText) findViewById(R.id.etxt_msg); txtRev = (TextView) findViewById(R.id.txt_receved); try { client.connect(address); } catch (IOException e) { e.printStackTrace(); } try { mOut = client.getOutputStream(); mIn = client.getInputStream(); in = new BufferedReader(new InputStreamReader(mIn)); } catch (IOException e) { e.printStackTrace(); } btn_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //这里需要在末尾加个换行符,否则在server发送返回回来的时候下面的in.readLine()会阻塞住 String msg = etxt.getText().toString() + "\n"; String rev = SendMsg(msg); if (rev != null) { txtRev.setText(rev.toString()); } else { Log.d("DCYY", "rev is null!"); } } }); } @Override protected void onStop() { super.onStop(); if (client != null && client.isConnected()) { try { client.close(); mOut.close(); mIn.close(); in.close(); } catch (IOException e) { e.printStackTrace(); } } } private String SendMsg(String s) { final byte[] message = s.getBytes(); final int len = message.length; if ((len < 1)) return "empty msg!"; try { //向socket服务端发送消息 mOut.write(message); SystemClock.sleep(1000); return in.readLine(); } catch (IOException e) { e.printStackTrace(); } return "send failed!"; }}
2.2 小问题解决
这里selinux权限还有冲突,比如app需要获取连接到dev/socket/下面的socket_test文件用于socket通信,但是却被Selinux权限拒绝了,每次要进行socket连接的时候总是报错,显示的log如下:
08-06 19:19:29.654 5964-5964/com.demo.mysocketclient W/.mysocketclient: type=1400 audit(0.0:1035): avc: denied { write } for name="socket_test" dev="tmpfs" ino=519 scontext=u:r:system_app:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=008-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err: java.io.IOException: Permission denied08-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err: at android.net.LocalSocketImpl.connectLocal(Native Method)08-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err: at android.net.LocalSocketImpl.connect(LocalSocketImpl.java:292)08-06 19:19:29.665 5964-5964/com.demo.mysocketclient W/System.err: at android.net.LocalSocket.connect(LocalSocket.java:131)08-06 19:19:29.665 5964-5964/com.demo.mysocketclient W/System.err: at com.demo.mysocketclient.MainActivity.onCreate(MainActivity.java:40)
加上WRITE_EXTERNAL_STORAGE这个权限都不管用,这是selinux问题,对照上面添加上selinux权限:
allow system_app socket_device:sock_file{ read write };allow system_app socket_test:unix_stream_socket{ connectto };
可是发现编bootimage又编不过了,原来app.te规定了appdomain是不允许有这个权限的,冲突导致编不过bootimage了。
system/sepolicy/app.te# Sockets under /dev/socket that are not specifically typed.neverallow appdomain socket_device:sock_file write;
所以还是先偷个懒把selinux关了,日后再找方法解决这个问题吧:
adb rootadb remountadb shell setenforce 0
3. 从Java应用层通过socket方式与Native层service进行通信
ok,下面所有selinux问题都不用管了,大胆尝试:
在输入框中写上要发送的消息,然后点击发送,从log中看的出,此时socket_test是收到了client端发来的消息了:
从Client端apk界面显示来看,也收到了由native服务socket_test发回的信息:
至此,成功的完成从java应用层发送消息给native层的服务,并打印出消息,同时也实现了从native服务层发送消息到达java应用层;
三、总结
1.看到PKMS是通过localsocket的方式与installd进行通信的,install在收到消息后根据消息的指令及数据进行接下来的功能实现,比较灵活。
2.localsocket是对linux中的socket的封装,日后再看它其他重要的使用方式已经socket通信方式的重点;
3.模仿installd写了一个本地服务,并通过localsocket方式实现了java应用层与native层的通信,其实细细想想,这样的方法可以用来做很多事情,实现自己需要的功能,socket方式也是一种很好的方式。
4.还有没有搞定的地方,如果是需要应用在开发中,如何避免selinux禁止appdomain的权限问题,看到网上说可以把socket文件生成在data/app/com.xxx.app/下,回头要好好试试。
参考博客:
Android7.0 PackageManagerService (5) installd
http://blog.csdn.net/Gaugamela/article/details/52769139
Service xxx does not have a SELinux domain defined
http://blog.csdn.net/l460133921/article/details/72891678
android 6.0 Java层和native守护进程socket通信
http://blog.csdn.net/u012439416/article/details/72974388
解决avc-denied之设置SELinux策略
http://blog.csdn.net/eliot_shao/article/details/51859083
- Android Fk: PKMS(3)之installd及LocalSocket实现Java层与Native层通信
- Android的LocalSocket实现及SELinux权限设置.编译(应用层和native通信)
- Android java层与C层通过localsocket通信、通信协议制定与解析。
- Android java层与C层通过localsocket通信、通信协议制定与解析。
- Android Java层与Native通信踩过的坑
- Android localsocket与native的通信
- Android Fk-PKMS(2) PackageManagerService之应用的安装与卸载
- Android Fk:PKMS(1)-PackageManagerService的Binder架构及初始化
- android native层进程通信
- Android------Binder java层如何与native层交互
- Android中java层使用LocalSocket和底层进行通讯
- android 6.0 Java层和native守护进程socket通信
- 【PhoneGAP学习】Android PhoneGap框架(3)--重要知识点的预先学习 (JS层与 Native 层之间通信)
- Android Java层,Native层,Lib层打印Log简介
- Android Native层进程间通信
- Android中在native层对java层应用程序发送广播方法及原理
- Android开发Bitmap在Native层与Java层内存的两种生成方式
- Android开发Bitmap在Native层与Java层内存的两种生成方式
- adb wifi调试
- 【Java学习笔记】String相关语句
- 斐波那契数列
- mysql数据库优化总结(心得)
- JQuery解决jsonp问题,后台采用java
- Android Fk: PKMS(3)之installd及LocalSocket实现Java层与Native层通信
- 8.1
- Java基础之反射的三种实现方式
- 执行maven命令 Error: JAVA_HOME is not defined correctly executing maven
- 1-13yum安装和更新软件包
- OpenSessionInViewFilter原理以及为什么要用OpenSessionInViewF
- Spring和MyBatis的整合的查询小案例
- Java模块 -- BigDecimal 高精度数字计算
- 推荐算法的可扩展性之hadoop篇(待续...)