扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
本篇文章针对向ServiceManager注册服务 和 获取服务两个流程来做总结。在这两个过程中,ServiceManager都扮演的是服务端,与客户端之间的通信也是通过Binder IPC。
我们提供的服务有:成都网站制作、网站设计、外贸网站建设、微信公众号开发、网站优化、网站认证、华宁ssl等。为成百上千企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的华宁网站制作公司
在此之前先了解下Binder的进程与线程的关系:
用户空间 :ProcessState描述一个进程,IPCThreadState对应一个进程中的一个线程。
内核空间 :binder_proc描述一个进程,统一由binder_procs全局链表保存,binder_thread对应进程的一个线程。
ProcessState与binder_proc是一一对应的。
Binder线程池 :每个Server进程在启动时会创建一个binder线程池,并向其中注册一个Binder线程;之后Server进程也可以向binder线程池注册新的线程,或者Binder驱动在探测到没有空闲binder线程时会主动向Server进程注册新的的binder线程。对于一个Server进程有一个最大Binder线程数限制15,(#define DEFAULT_MAX_BINDER_THREADS 15)。对于所有Client端进程的binder请求都是交由Server端进程的binder线程来处理的。我的理解是:binder线程是进程进行binder ipc时的一条数据处理路径。
MediaPlayerService向ServiceManager注册过程如下:
相关类:
整个过程总结如下:
1 获取BpServiceManager 与 BpBinder
由defaultServiceManager()返回的是BpServiceManager,同时会创建ProcessState对象和BpBinder对象。然后通过BpBinder执行transact,把真正工作交给IPCThreadState来处理。
2 BpBinder transact
Binder代理类调用transact()方法,真正工作还是交给IPCThreadState来进行transact工作。
3 通过IPCThreadState 包装并转换数据并进行transact事务处理
每个线程都有一个IPCThreadState,每个IPCThreadState中都有一对Parcel变量:mIn、mOut。相当于两根数据管道:
最后执行talkWithDriver。
writeTransactionData:将BC Protocol + binder_transaction_data结构体 写入mOut, 然后执行waitForResponse:
由talkWithDriver将数据进一步封装到binder_write_read结构体,通过ioctl(BINDER_WRITE_READ)与驱动通信。同时等待驱动返回的接收BR命令,从mIn取出返回的数据。
mIn包装的数据结构(注册服务handle = 0 ,code 为ADD_SERVICE_TRANSACTION):
4 Binder Driver
把binder_write_read结构体write_buffer里数据取出来,分别得到BC命令和封装好数据的事务binder_transaction_data, 然后根据handler,在当前binder_proc中,找到相应的binder_ref,由binder_ref再找到目标binder_node实体,由目标binder_node再找到目标进程binder_proc。然后就是插入数据:当binder驱动可以找到合适的线程,就会把binder_transaction节点插入到servciemanager的线程的todo队列中,如果找不到合适的线程,就把节点之间插入servciemanager的binder_proc的todo队列。
5 ServiceManager
经过Binder Driver的处理,数据已经到了ServiceManager进程,在BR_TRANSACTION的引导下,在binder_loop()中执行binder_parser()取出数据,执行do_add_service()操作,最终向 svcinfo 列表中添加已经注册的服务(没有数据的返回)。最后发送 BR_REPLY 命令唤醒等待的线程,通知注册成功。结束MediaPlayerService进程 waitForResponse()的状态,整个注册过程结束。
获取服务的过程与注册类似,首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令携带 CHECK_SERVICE_TRANSACTION 命令,同时获取服务的线程进入等待状态 waitForResponse()。Binder 驱动收到请求命令向 ServiceManager 的发送 BC_TRANSACTION 查询已注册的服务,会区分请求服务所属进程情况。
查询到直接响应 BR_REPLY 唤醒等待的线程。若查询不到将与 binder_procs 链表中的服务进行一次通讯再响应。
以startService为例来简单总结下执行流程:
3.1 从方法执行流程来看:
Client :
1 AMP.startService 标记方法以及通过Parcel包装数据;
2 BinderProxy.transact 实际调用native的 android_os_BinderProxy_transact 传递数据;
3 获取BpServiceManager 与 BpBinder 同时会创建ProcessState。然后通过BpBinder执行transact,把真正工作交给IPCThreadState来处理;
4 IPC.transact 主要执行writeTransactionData,将上层传来的数据重新包装成binder_transaction_data,并将BC Protocol + binder_transaction_data结构体 写入mOut;
5 IPC waitForResponse talkWithDriver + 等待返回数据;
6 talkWithDriver 将数据进一步封装成binder_write_read,通过ioctl(BINDER_WRITE_READ)与驱动通信;
Kernel :
7 binder ioctl 接收BINDER_WRITE_READ ioctl命令;
8 binder_ioctl_write_read 把用户空间数据ubuf拷贝到内核空间bwr;
9 binder_thread_write 当bwr写缓存有数据,则执行binder_thread_write;当写失败则将bwr数据写回用户空间并退出;
10 binder_transaction 找到目标进程binder_proc并插入数据到目标进程的线程todo队列,最终执行到它
时,将发起端数据拷贝到接收端进程的buffer结构体;
11 binder_thread_read 根据binder_transaction结构体和binder_buffer结构体数据生成新的binder_transaction_data结构体,写入bwr的read_buffer,当bwr读缓存有数据,则执行binder_thread_read;当读失败则再将bwr数据写回用户空间并退出;最后,把内核数据bwr拷贝到用户空间ubuf。
12 binder_thread_write + binder_ioctl BR命令和数据传递
Server:
13 IPC.executeCommand 解析kernel传过来的binder_transaction_data数据,找到目标BBinder并调用其transact()方法;
14 IPC.joinThreadPool 采用循环不断地执行getAndExecuteCommand()方法, 处理事务。当bwr的读写buffer都没有数据时,则阻塞在binder_thread_read的wait_event过程. 另外,正常情况下binder线程一旦创建则不会退出.
15 BBinder.transact 到Binder.exeTransact 调用 AMN.onTransact
16 AMN.onTransact 把数据传递到AMS.starService去执行
17 AMS.starService Server处理了Client的请求了
然后原路replay回去,talkWithDriver 到Kernel ,然后找到Client进程,把数据拷贝到read_buffer里,最终唤醒IPC,把反馈传递回AMP.startService。完成启动服务。
3.2 从通信协议流程来看:
非oneWay:
oneway:
oneway与非oneway区别: 都是需要等待Binder Driver的回应消息BR_TRANSACTION_COMPLETE. 主要区别在于oneway的通信收到BR_TRANSACTION_COMPLETE则返回,而不会再等待BR_REPLY消息的到来. 另外,oneway的binder IPC则接收端无法获取对方的pid.
3.3 从数据流来看
从用户空间开始:
进入驱动后:
回到用户空间:
参考:
在内部存储卡的的data/com.android.provides.contacts里面。
以三星的s8为例
找到通讯录以及通话记录方法:
1、打开三星的s8手机,在系统界面找到“我的文件”。
2、进入到三星的s8手机“我的文件”里面找到“内部存储”点击进入。
3、进入“内部存储”下翻,在列表中找到“data”文件夹,点击打开。
4、进入“data”文件夹后找到“com.android.provides.contacts”文件夹,点击打开。
5、在“com.android.provides.contacts”文件夹里面,把CSV文件导出,就可以看到通讯录文件了。
1.打开串口。
2.串口处于监听状态
3.想串口写入数据,串口接收到数据返回数据
SerialPort类所在的包一定要和上图包名一直,因为串口通讯需要使用jni中的函数。
package android_serialport_api;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.fx.serialporttest.L;
public class SerialPort {
/*
* Do not remove or rename the field mFd: it is used by native method
* close();
*/
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
/**
* 构造方法
* @param device 串口地址
* @param baurate 波特率
* @param flags
* @throws IOException
* @throws InterruptedException
*/
public SerialPort(File device,int baudrate,int flags) {
/*
* 检测是否有访问权限
*/
if(!device.canRead()||!device.canWrite()){
//如果没有读写权限,尝试chmod命令这个文件
L.tag("没有读写权限");
Process su;
try {
su = Runtime.getRuntime().exec("/system/bin/su");//获取root读写权限
String cmd = "chmod 777"+device.getAbsolutePath()+"\n"+"exit\n";
su.getOutputStream().write(cmd.getBytes()); //向此路径文件写入命令
if((su.waitFor()!=0||!device.canRead()||!device.canWrite())){
throw new SecurityException();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
mFd = open(device.getAbsolutePath(),baudrate,flags);
if(mFd==null){
mFd = open(device.getAbsolutePath(),baudrate,flags);
L.tag("native open return null");
}
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
}
public FileInputStream getmFileInputStream() {
return mFileInputStream;
}
public void setmFileInputStream(FileInputStream mFileInputStream) {
this.mFileInputStream = mFileInputStream;
}
public FileOutputStream getmFileOutputStream() {
return mFileOutputStream;
}
public void setmFileOutputStream(FileOutputStream mFileOutputStream) {
this.mFileOutputStream = mFileOutputStream;
}
//JNI
private native static FileDescriptor open(String path,int baudrate,int flags);
public native void close();
static {
System.loadLibrary("serial_port");
}
}
package android_serialport_api;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.fx.serialporttest.L;
public class SerialPortFinger {
private static ReadThread readThread;
private static FileInputStream mFileInputStream;
private static FileOutputStream mFileOutputStream;
static String path = "/dev/ttyS0";//设备主板的串口地址,地址有所不同
public void startListener(){
SerialPort serialPort = new SerialPort(new File(path), 9600, 0);//9600是波特率,这个也是有所不同,具体要看设备
mFileInputStream = serialPort.getmFileInputStream();
mFileOutputStream = serialPort.getmFileOutputStream();//获取串口写入流
readThread = new ReadThread();
readThread.start();//开启监听
}
/**
* 发送指令到串口
*
* @param cmd
* @return
*/
public boolean sendCmds(String cmd) {
boolean result = true;
byte[] mBuffer = (cmd+"\r\n").getBytes();
try {
if (mFileOutputStream != null) {
mFileOutputStream.write(mBuffer);
} else {
result = false;
}
} catch (IOException e) {
e.printStackTrace();
result = false;
}
return result;
}
static class ReadThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
int len;
StringBuffer sb = new StringBuffer("");
while(true){ //循环监听串口,读取返回的数据
byte[] buffer = new byte[1024];
if(mFileInputStream==null){
return;
}
try {
len = mFileInputStream.read(buffer);
if(len0){
sb.append(new String(buffer, 0, len));
}
if(!sb.toString().equals(""))
{
L.tag(sb.toString());//收到串口的返回数据,在日志中打印出来
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
L.tag("接受完成");
}
}
}
}
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流