Android和Docker的一致架构设计(4)
-- <分合自如>的设计模式(Design Pattern)
ee ee
[Back]
Sec-2、Android的进程(集装箱)概念
2.1 简介
基于Linux的安全限制,不同进程的地址空间是独立的,让不同的App可以分别摆(或布署)在各自的独立进程里(执行)。这是基于安全考虑,一个进程是一个独立的执行空间,不会被正在其它进程里的程序所侵犯。这种保护方法是Android的重要安全机制。其实,Android的进程管理模式与Docker集装箱思维具有异曲同工之妙。
因此,不同的App通常会各自摆在不同的进程里。如果一个App包含多个类别时,这些类别可以分布在不同进程里执行,或挤在一个进程里执行。那么,分别摆在不同进程里执行的App之间又如何相互沟通(通信)呢? 那就是「跨进程」(Inter-Process Communication,简称IPC)通信机制了。换句话说,由于不同进程的地址空间不同,两支 App之间不能使用一般的函数调用(Function Call)途径来通信,所以就设计一个IPC通信机制了。
2.2 Android的IPC通信机制
如果你对Android的进程概念不孰悉的话,也没关系,我在这里来帮你做个初步的介绍。当我们启动某一支App时,Android的Zygote服务孵化(Fork)一个新进程给它,然后将它(即App)加载到新进程里。基于Linux的安全限制,以及进程的基本特性(例如,不同进程的地址空间是独立的),如果两个类(或其对象)在同一个进程里执行时,两者通信方便也快速。但是,当它们分别在不同的进程里执行时,两者沟通就属于跨进程通信了,不如前者方便,也慢些。当Zygote创建新进程时,会替新进程产生一个主线程,以及创建一个虚拟机(Virtual Machine,简称VM)的对象,可执行Java代码,也引导JNI本地程序的执行,实现Java与C/C++程序之间的通信,如下图:
图-14、Android的进程(Process)
在创建进程的时刻,还会替主线程创建它专用的Message Queue和Looper。
图-15、不同进程的地址空间是独立的
一支Android的App常常含有许多个模块,或是类(Class)。例如,有一支App内含三个类:FirstActivity、LoadActivity和LoadService。在Android的AndroidManifest.xml文件就是App的布署配置指示。由这个配置文件内容来指示Android如何布署这些类(模块)。如果我们撰写这个App的AndroidManifest.xml文件内容如下:
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.misoo.pkm">
<uses-permission xmlns:android="http://schemas.android.com/apk/res/android"
android:name="android.permission.INTERNET">
</uses-permission>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".FirstActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </activity>
<activity android:name=".LoadActivity">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter> </activity>
<service android:name=".LoadService" android:process=":remote">
<intent-filter>
<action android:name="com.misoo.pkm.REMOTE_SERVICE" />
</intent-filter> </service>
</application>
</manifest>
Android就依据这个文档里的指示来将这些类布署于两个进程里运行。其中,FirstActivity和LoadActivity两个类别会加载预设的进程里。而LoadService则会加载于名为“remote”的独立进程里。如下图:
图-16、一支AP跨两个进程
其中,FirstActivity和LoadActivity两个类别会加载预设的进程里。而LoadService则会加载于名为“remote”的独立进程里。
请留意上述文件内容:
<service android:name=".LoadService" android:process=":remote">
<intent-filter>
<action android:name="com.misoo.pkm.REMOTE_SERVICE" />
</intent-filter> </service>
如果做个修改:将其中的红色文字部分删除掉,便成为:
<service android:name=".LoadService">
<intent-filter>
<action android:name="com.misoo.pkm.REMOTE_SERVICE" />
</intent-filter> </service>
并且重新布署之后,三个类都会在同一个进程里运行了。
图-17、合并到一个进程里
其中最亮丽的设计是:这种重新布署,并不需要更改三个类的代码,至需要更改配置脚本即可。
2.3 IPC通信的三步骤
如果你想要理解上述<分合自如>的设计,就得先了解Android的IPC通信机制和步骤了。在Android里,当两个类都在同一个进程里执行时,两者之间的沟通,只要采取一般的函数调用(Function Call)就行了,既快速又方便。一旦两个类分别在不同的进程里执行时,两者之间的沟通,就不能采取一般的函数调用途径了。只好采取IPC通信途径。Android框架的IPC通信仰赖单一的IBinder接口。此时Client端调用IBinder接口的transact()函数,透过IPC机制而调用到远方(Remote)的onTransact()函数。例如,下述范例程序代码。兹建立Android开发项目:
此程序的执行画面如下:
= => 请看完整源代码
在表面上,myActivity调用IBinder接口的transact()函数,进而调用到myBinder的onTranscat()函数。
图-18、Android的IPC角色
然而,在实践机制里,myActivity与myService之调用是透过Linux底层的Binder Driver来达成的,它们之间进行数据交换时,Binder Driver就有机会进行Marshalling动作,而达成IPC通信了。请看下述程序码片段:
// myActivity.java
public class myActivity extends Activity implements OnClickListener {
……..
public void onCreate(Bundle icicle) {
……..
startService(new Intent("com.misoo.pk01.REMOTE_SERVICE"));
……..
}}}
// myService.java
public class myService extends Service {
private IBinder mb = null;
………..
@Override public void onStart() {
mb = new myBinder();
}}
当myActivity调用startService()时,就调用Service.onStart()函数,执行到指令:
mb = new myBinder()
接着,调用myBinder()构造式(Constructor);进而调用父类别Binder()构造式,转而调用JNI本地的init()函数。此刻执行init()函数时,会在C/C++层里创建一个JavaBBinderHolder类的对象,并且将这JavaBBinderHolder对象的指针存入到myBinder对象里,让myBinder对象指向JavaBBinderHolder对象。如下图:
图-19、startService()函数的动作
目前,已经执行完startService()函数了。接着,myActivity继续调用bindService()函数,想去绑定Service服务。如果找到该服务,且它尚未被任何Client所绑定的话,就会调用myService的onBind()函数。此时,执行到指令:return mb。如下述的程序码片段:
// myActivity.java
public class myActivity extends Activity implements OnClickListener {
IBinder mb = null;
……..
public void onCreate(Bundle icicle) {
……..
startService(new Intent("com.misoo.pk01.REMOTE_SERVICE"));
bindService(new Intent("com.misoo.pk01.REMOTE_SERVICE"), mConnection, Context.BIND_AUTO_CREATE);
……..
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder ibinder) {
mb = ibinder;
}};
…….
public void onClick(View v) {
……
mb.transact(…);
……
}}}
// myService.java
public class myService extends Service {
private IBinder mb = null;
………..
@Override public void onStart() { mb = new myBinder(); }
@Override public IBinder onBind(Intent intent) { return mb; }
}
这onBind()函数将mb(即myBinder对象的IBinder接口)回传Android框架。其实这项绑定Service的活动都是由框架里的AMS(ActivityManagerService)所主控的。
图-20、onBind()函数的动作
当 AMS接到回传来的myBinder对象指针(即其IBinder接口)时,就可以找到其在C/C++层所对映的JavaBBinderHolder对象。接着,右调用JavaBBinderHolder的get()函数去创建一个JavaBBinder对象。接着,AMS在Client端进程的java层里创建一个BinderProxy对象来代表JavaBBinder的分身,也就是代表了myBinder的分身。最后将BinderProxy的IBinder接口回传给myActivity。这个动作就是在执行下述指令时所发生的:
public void onServiceConnected(
ComponentName className, IBinder ibinder) {
mb = ibinder;
}
此时完成了跨进程的服务绑定(Bind),如下图:
图-21、JavaBBinder类别的角色
所谓建好了服务绑定(Bind)之后,就如同建好了跨进程的桥梁。之后,就能随时透过这桥梁而进行从myActivity调用到myService的跨进程IPC通信。绑定了服务之后,就能从myActivity调用BinderProxy(透过IBinder接口)的IBinder接口,执行了transact()函数。如下图:
图-22、IPC的调用路径
在上图里,从JNI本地模块拉了一条红色虚线,表示这并非直接的通信途径。也就是,实际上是透过底层Binder Driver才调用到BBinder的IBinder接口。如下图:
图-23、IPC依赖底层的Binder Driver驱动
以上完整地叙述了Android的跨进程IPC机制。
~ End ~
[Back]