应用层开发者想要在Android端更上一层,Android基础知识、启动过程、编译打包apk过程、安卓framework层等实现原理都是一定要掌握的,熟悉安卓四大组件的深层通信过程及原理和Android构建工具gradle的实现原理也都逐渐成为必要技能,这就是好比步入应用层中高级工程师的一道门,这道门对于应用层初学者来说却无比坚实。
本篇旨在为我们学习Android运行机制奠定基础,因为包括启动流程(了解之后可以做启动优化)、四大组件的交互、Hanlder都会关联到。通过这些学习,你的应用可以实现最直观的启动优化。例如:很多应用后续使用过程,除了默认进程外会再启动一些服务类进程(有三方的类似推送,也有可能是我们自己创建的其他服务类进程),当应用从launch桌面启动(AS性能检测可以看到,或者自己debug),系统创建应用默认进程(与applicationId保持一致),然后在应用进程中做一些必要的初始化工作,大部分需要初始化的工作,都是默认进程所必须的,非默认进程只需要针对自身做必要的部分初始化工作即可。也就是说,当我们做好所有的单进程(ApplicationId同名进程)初始化等工作,安全又快,也就实现了应用从最初的4-5s左右到后来的2-3s,纯原生鱼雷基本不到2s(自动登陆接口响应需要1s多),如果再配合一些其他的优化手段,画面太好。你觉得提升2s没啥用?那是没见过做启动页开屏广告十几家广告一起来,至于显示我们不关心,但是你肯定是都要初始化的,要是代码比较任性,超10s那也常见(初始化必要工作太多的不在讨论范围),正常到3s这个差别还是很大的。本来只在默认进行做的工作,你每个进程都做;而且多进程和多线程一样,操作数据也要考虑到安全性。在正式开始Binder通信之前,我们先进行如下学习:
(1)对AIDL的操作进行简单介绍(也是我们平时使用的方式):
1.创建aidl文件MakeProject(C/S两端一样)
interface IMathAidl {
double add(double a,double b);//加法
double sub(double a,double b);//减法
void play(String path);//路径信息
}
2.服务端创建Service,别忘记注册
public class MyService extends Service {`
private IMathAidl.Stub mStub = new IMathAidl.Stub() {
@Override
public double add(double a, double b) throws RemoteException {
return a + b;
}
@Override
public double sub(double a, double b) throws RemoteException {
return a - b;
}
@Override
public void play(String path) throws RemoteException {
Message message = Message.obtain();
message.obj = path;
mHandler.sendMessage(message);
}
};
@Override
public IBinder onBind(Intent intent) {
return mStub;
}
}
<service
android:name=".MyService">
<intent-filter>
<action android:name="com.sinitek.aidl.service"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
3.Client端进行关联调用Server端
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.sinitek.aidl.service");
intent.setPackage("com.sinitek.transactionserver");//minSdkVersion 21
bindService(intent, mConn, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMathAidl mathAidl = IMathAidl.Stub.asInterface(service);
try {
double result = mathAidl.add(1, 1);
Toast.makeText(MainActivity.this, "计算结果为:" + result, Toast.LENGTH_SHORT).show();
mathAidl.play("请观看服务端播放:'西京一村夫'SINITEK冲刺之路.mp4");
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
}
以上代码放在:(https://github.com/BuilderPattern/AidlTransactionServer.git)(https://github.com/BuilderPattern/AidlTransactionClient.git)
(2)如果你是一个比较有求知欲的键盘侠,你通过一顿操作在IMathAidl.java中就会发现,该AIDL会生成java类,实际的交互都这里完成。问题来了,是不是可以不用定义aidl接口,直接采用IMathAidl.java类中的方式去实现呢?答案是:肯定可以!因为,本质上就是aidl通过构建生成对应的IMathAidl.java文件来实现具体操作,如下:
1.Server端创建一个Service并注册:
public class NoAidlService extends Service {
public IBinder onBind(Intent t) {
return mBinder;
}
private NormalBinder mBinder = new NormalBinder();
private class NormalBinder extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {//0加,1乘
switch (code) {
case 0: {
data.enforceInterface("NoAidlService");//检测标识
int _arg0 = data.readInt();
int _arg1 = data.readInt();
int _result = _arg0 + _arg1;
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case 1: {
data.enforceInterface("NoAidlService");
int _arg0 = data.readInt();
int _arg1 = data.readInt();
int _result = _arg0 * _arg1;
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
}
}
<service android:name=".NoAidlService">
<intent-filter>
<action android:name="com.sinitek.noaidl.myservice" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
2.Client端连接Server端,发送收据/等待回复:
public class MainActivity extends AppCompatActivity {
IBinder mBinder;
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.activity_main_operate_tv);
Intent intent = new Intent("com.sinitek.noaidl.myservice");
intent.setPackage("com.sinitek.transactionservernoaidl");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
initEvent();
}
private void initEvent() {
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRcvSnd();
}
});
}
public void mRcvSnd() {
if (mBinder == null) {
return;
}
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _code = (int) (Math.random() * 6) % 2;
int _result;
try {
_data.writeInterfaceToken("NoAidlService");//客户端标识
_data.writeInt(6);
_data.writeInt(6);
mBinder.transact(_code, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
Toast.makeText(MainActivity.this, "收到回复:" + _result, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
有关该交互过程比较容易理解,依次:创建Server》Client连接Service》Client向Service发送数据》Service收到Client数据进行一系列分析操作,做出回应》Client拿到Service的响应结果,如下图所示:
下面,我们正式开始对Binder通信机制和过程涉及的相关内容进行阐述(如本文有理解误区,万望读者不吝赐教):
(一)进程间通信有多种方式,这里主要介绍Android进程间通信Binder特点及实现过程:
在 Android 中,一个进程通常无法访问另一个进程的内存。为了进行通信,需将其对象分解成可供操作系统理解的原语,并将其编组为可供开发人员操作的对象。如果开发人员直接编写执行该操作的代码较为繁琐,因此Android引入AIDL实现进程通信。aidl(Android Interface Definition Language)接口定义语言,可以定义客户端与服务均认可的编程接口,实现进程通信。学习Binder前,强烈建议先了解aidl的实现过程,通过学习aidl的工作过程,我们会对进程通信的流程有大致的概念,有利于底层执行的命令、调用接口和执行上层写入/读取相对应的理解,后面学习底层的过程就好比验证每个过程的更深层实现。比如:C/S端通信transact/onTransact方法内实现数据交互,消息源筛选、数据类型及数据的读写,特点一目了然;鉴于篇幅,有关aidl的实现过程请自行学习,尽量熟练掌握(可以学习Android开发艺术探索和张鸿洋的博客)。
特点:安全、传输性能高和操作相对简单;安全性,对比socket/管道通信方式,Binder通信过程中,系统每创建一个进程会分配对应的PID和UID(此特点还可以用于多进程项目的某些方向区分优化),进程之间通过进程id匹配确认消息来源;传输性能高,传输数据不需要tcp协议的三次握手,传输过程只需一次拷贝,操作简单;共享内存虽然不需要拷贝,但实现起来要复杂很多。
(二)了解下内存映射函数mmap和读写数据接口ioctl:
mmap用于建立用户空间和内核空间的映射关系,实现映射关系内存空间共享数据;建立映射关系的不同内存空间的数据操作,会映射到建立关系的其他内存空间,对应关系如下图:
ioctl是底层和应用层之间定义的接口协议(用于操作写入/读取数据),不提供单独的read/write接口,一次调用实现写入读取数据,满足数据交互同步。假设需要进行写入/读取数据(write_size和read_size都大于0),在操作写入和读取的过程,先将write_buffer里面的数据写入Binder;然后同步等待数据返回,从Binder中读取数据存入read_buffer。因为BINDER_WRITE_READ命令包含写入和读取数据两步操作,且是否进行写入和读取操作都是根据Binder的write_size和read_size确定的。假如只写入,设置Binder的write_size大于0,read_size等于0即可;其他情况同理。
(三)接下来对进程通信C/S端采用Binder通信过程的write/read及数据传递进行介绍:
在aidl操作的过程,有单独的write和read方法,在底层命令是一次执行,写入/读取根据顺序判断是否需要执行(write_size/read_size)对应操作。比如Server/Client端执行write/read完成后,进入等待接收数据状态,接收到数据之后做相应操作并回应reply。
Binder写入/读取的数据结构体是binder_transaction_data,该数据体包含了上述,比如:进程id、用户id用于接收方确认发送方,data_size缓存区存放的数据长度(由发送方设置,用于接收方确定接收到的数据大小)。
系统为每个应用分配一定的内存(虽然应用可以自行配置,但不能超过系统可分配的最大值),且每次处理完之后会执行BUFFER_FREE命令释放内存。上面我们提到mmap()映射函数,在Binder通信的写入/读取过程中,采用的是接收数据缓存的动态分配和释放。程序处理完某缓存区数据之后,底层会调用命令释放缓存区,否则会导致缓存区耗尽而无法接收数据。
关于Binder的C/S端通信,总结性描述:Client端将函数参数打包,在transact()中通过服务端Binder向Server发送数据包请求/等待数据返回,Server端在onTransact()中通过客户端Binder获取binder_transaction_data数据,取出进程id及其他数据做相应处理,应答Client端。很多讲解这块儿内容的时候,都提到用对方Client/Server的Binder进行数据操作,实现数据交互。在底层本质上是,进程通过ServiceManager(简称SMgr下面会详细解释)注册,用的时候直接在注册表查找,就可以实现两端都持有彼此的引用(可以暂时理解为对象的引用遍布各处),由于应用层实现不需要修改底层代码,所以这也是便于理解的一种说法。在Client和Server通信的过程,SMgr类似于域名服务器,当客户端向服务端发起请求的时候,通过SMgr找到初始进程对应Binder引用(类比在域名服务器中找到域名对应的主机)进行写入/读取;SMgr进程的转化及其Binder创建,如下图:
通信主要由四部分组成:Server、Client、SMgr和Binder驱动;前两个是需要实现通信的两端,驱动负责把Server端Binder相关信息通过数据包形式发送到SMgr,SMgr收到数据包之后解析数据注册Server端的Binder引用,后续SMgr还会负责在Binder的登记表里查找每个进程对应的Binder引用。大致过程为:Server端创建Binder实体之后,通过驱动以transaction_data数据包的形式发送到SMgr,通过SMgr将对应引用注册到内核Binder注册表。以上发送及注册引用的具体过程为:Binder实体创建之后,驱动会在内核创建其对应的实体节点和用于传递给SMgr的Binder引用,然后把实体信息和刚刚创建的Binder内核引用等信息传递给SMgr,SMgr进程解析数据将引用插入Binder注册表,Client端就可以在表中查找该引用执行write_buffer/read_buffer等操作;其他进程发送数据,SMgr接收数据并执行注册过程,如下图:
实际上要完成上面的过程,首先Server和SMgr也涉及到进程间通信,SMgr可以理解成系统默认进程,其他进程和SMgr通信的时候,SMgr都是作为Server端;那么SMgr和Server端通信时,SMgr中的Binder引用到底是谁传过来的,都要它注册,那他自己的Binder谁注册?比方SMgr进程是项目经理,其他进程是项目组织成员,项目经理给每个人的权力(Binder)去完成工作,那么项目经理分配权限,项目经理的权力哪里来?肯定需要管理层赋予他职责。接下来解释下SMgr的Binder引用哪里来,当某个进程执行BINDER_SET_CONTEXT_MGR命令将自身注册成SMgr进程时,驱动会为该进程创建好Binder实体(0号引用),并且其他所有进程都能获取到该引用,系统只能有一个SMgr进程。后续任何进程向SMgr注册Binder时,都要通过该引用(SMgr的Binder实体引用)和SMgr进程进行通信。进程注册及进程间通信整个过程,大致理解为SMgr作为主导注册/查找对应Binder对象进行写入/读取,如下图:
所以,进程间通信就可以分为:SMgr和其他进程,Server、Client和SMgr两种。本质上都是用对应进程的Binder对象实现写入/读取等操作。区别在于进程对应的Binder对象:前者系统注册的时候就会定为0号引用;后者需要SMgr(Binder对象管理中心)实现注册,后续在注册表中根据定义的key取出对应进程的Binder引用执行操作。
此处,我们延伸一下通信过程中线程的交互(后面写关于Handler的内容会更具体解释),如下图:
本文参考资料:
1. https://blog.csdn.net/augfun/article/details/82343249
2. https://baike.baidu.com/item/mmap/1322217?fr=aladdin
3. https://baike.baidu.com/item/ioctl/6392403?fr=aladdin
4. https://developer.android.google.cn/guide/components/aidl.html?hl=zh-cn
5. http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0319/2619.html
6. https://www.zhihu.com/question/20122137/answer/14049112