Android中多进程与IPC的恩怨情仇

code · 2020-02-26

题记

可能对大部分人来讲,Android多进程通信貌似没什么用,很少有人使用过 android:process="" 这个属性,不过对于一些繁重的,甚至可以成为一个单独的App般的业务的module,放在主进程,不仅会互相影响到其稳定性(想想如果这个业务是外部的,那你们需要battle多久?)

跨进程通信的一般姿势

多进程中,我们常见的姿势有哪些呢?

引子:AIDL

AIDL(Android Interface Define Language)作为最基础,做常见的远程通信姿势,常常在各类的 基础、入门、甚至是说那些收费的文章中提及,其使用姿势常常有以下的步骤:

  • 1,定义AIDL文件:
  • 2,通信过程

没错,你一定很好奇,为什么我没有提到那些很多文章中都提到的 Service

不错,虽然Servie是AIDL(或者说是AIDL生成的Binder)最常见的载体,不过,此处我们应该理解的是,真正参与通信过程的是Binder,Parcel,而不是我们所说的 “四大组件”

Binder通信的流程,在别的文章中都已经说的很透彻了,此时我们再拿回来,讲讲其中的重要之处:

binder_ipc_arch.jpg

大部分人见到的binder应该都是这个样子的。正如图上图,binder也确实是这个样子了。可是大部分文章中都少提及了一点,Binder也是可以通过Bundle传输的
这就能发挥无限的想象力,比如:

我们常见的操作,ActivityOne --启动--> ActivityTwo, 然后two经过操作,在ActivityOne中接受结果。

ActivityOne
public class ActivityOne extends Activity
{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.test_al).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(ActivityOne.this,ActivityTwo.class);
                startActivityForResult(intent,0);
            }
        });
        
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode == RESULT_OK){
            switch (resultCode){
                ////do something
            }
        }
    }
}
ActivityTwo.java
public class ActivityTwo extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Handler().postDelayed(() -> {
            Intent intent = new Intent();
            intent.putExtra("status", "ok");
            setResult(RESULT_OK, intent);
        }, 2000);
    }
}

这是一般的使用姿势,当然,还会有更简单的方案比如EventBus\RxBus\LiveBus
等事件总线。也可以干脆写个静态的Listener来接受callback。总之,千奇百怪的姿势都有。

那么,你使用过下面这样的姿势吗?

ActivityOne
IView2Result.Stub stub = new IView2Result.Stub() {
    @Override
    public void onResult(String data) throws RemoteException {
        Log.e("onResult", data);
    }
};
Intent intent = new Intent(this,ActivityTwo.class);
Bundle bundle = new Bundle();
bundle.putBinder("binder",stub.asBinder());
intent.putExtra("data",bundle);
startActivity(intent);
ActivityTwo

IBinder binder = getIntent().getExtras().getBinder("binder");
IView2Result iView2Result = IView2Result.Stub.asInterface(binder);
iView2Result.onResult("data");
IView2Result.aidl
interface IView2Result {

     void onResult(String data);
}

没错,这样实现的回调也是可以使用的,相对之前时间总线的来说,这样的操作对跨进程的支持会更优。

后起之秀:Messenger

Messenger:作为一个不那么常见的IPC通信方式来说,有着以下的优点

  • 不需要重复的定义Aidl
  • 只需要使用handler来操作,大大的简化了ipc的通信流程

其通信模型如下:

+---------------+       +---------------+      +---------------+
|               |       |               |      |               |
|               |       |               |      |   Process2    |
|   Process1    |       |               |      |       +       |
|       +       +------>+               +----->+       |       |
|       |       |       |               |      |       |       |
|       |       |       |   Messenger   |      |       |       |
|       |       |       |               |      |       |       |
|       v       |       |               |      |       |       |
| +-----+-----+ |       |               |      |       v       |
| |  Handler  | <-------+               +<-----+ +-----+------ +
| +-----------+ |       |               |      | |  Handler  | |
|               |       |               |      | +------------ |
+---------------+       +---------------+      +---------------+

同样的,它也带来了一些显而易见的弊端:

  • 需要定义大量的handlemessage操作
  • 不支持远程通信的callback

当然,这些弊端我们也可以通过一些手段来规避,例如封装一些预置的,通用的操作来对外简化Api,可是对于分离进程常常需要的callback操作,却显得有些力不从心了。

那么有没有一种更优化的方案呢?

更秀的操作:ContentProvider

ContentProdiver作为Android中原生的组件支持,常常以一个内容提供者出现。用来封装一些需要跨进程共享的数据来说已经足以支持复杂的场景了。不过,作为一个provider,它也可以对外提供服务。如文章一开始的描述,参与跨进程通信的,是Binder和Parcel,而非组件。而任何可以参与Bundle数据交换的地方,我们都可以使用Binder进行通信。如此一来,出现了很多基于ContentProvider的远程通信方案:

常见的姿势是这样的

+--------------------+    +----------------------+
|                    |    |                      |
|                    |    |                      |
|       Process1     |    |      Process2        |
|                    |    |                      |
+---+-------^--------+    +------+------^--------+
    |       |                    |      |
    |       |                    |      |
    |       |                    |      |
    |       |                    |      |
    |       |                    |      |
    |       |                    |      |
+------------------------------------------------+
|   +-----------+               +-----------+    |
|   |   AIDL1   +-------------->+ AIDL2     |    |
|   |           |               |           |    |
|   +-----------+<--------------+-----------+    |
|                                                |
|                                                |
|              ContentProvider                   |
|                                                |
+------------------------------------------------+

Process1 和 Process2 之间使用了ContentProvider进行远程的调用,他们之间通常在Provider进程内还有一个缓存来存放各个进程对应的服务,等需要调用的进程发起调用的时,在Provider查询此服务,然后返回对应的Binder来实现通信。

其面临的弊端也是显而易见的,那就是需要反复的定义各种Aidl服务

以上就是目前阶段我总结的一些基于Binder的远程通信方案。现阶段的远程通信方案,或多或少的都面临着一些问题。这些问题虽然可以通过大量的编码解决,但往往催生一个新技术发展的是消除重复的劳动。所以,在多进程与IPC的恩怨情仇第二篇中,以上的这些问题都将一一解决,同时封装一个强大的,易于维护的远程通信方案!

Android IPC Binder
Theme Jasmine by Kent Liao