AndroidDocker一致架构设计(4)

-- <分合自如>的设计模式(Design Pattern)

ee                                             ee

[Back] 

 

Sec-2Android的进程(集装箱)概念

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]