Android 多进程详解

  进程间通信即IPC。首先我们要理解什么是进程?什么是线程?线程是cpu的调度的最小资源,同时是一种有限的资源。进程则是一个执行单元,在Android中一般指一个应用程序(也有多进程的程序)。一个进程中可以有多个线程,是包含关系。在多进程的应用程序中,就出现多进程通信的方式。对于Android来说多进程通信一般有Binder、messager、socket、文件、ContentProvider等方式。

多进程开启方式

  Android中的四大组件(Activity、BoardcastReceiver、Service、ContentProvider)都可以使用多进程。Android中的多进程实现只有通过在AndroidMenfiest中指定android:process属性。来指定进程。还有一个中特殊方法即在jni中通过native方法fork一个进程。下面示例开启多进程

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".DefaultActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".BRemoteActivity"
            android:process="com.demo.launchmode.remote" />

        <activity
            android:name=".ARemoteActivity"
            android:process=":remote" />
    </application>

以上开启多进程中可以看到我们为ARemoteActivity和BRemoteActivity分别指定了单独的进程。进程名称分别为":remote""com.demo.launchmode.remote"分别开启两个activity在ddms中看到如下三个进程:

ddms进程列表.png

三个进程名不相同。需要注意的是":remote""com.demo.launchmode.remote"的区别。其中以开头的进程为当前应用的私有进程。其他应用不可以和他在同一进程中。而不以开头,指定完整进程名的为全局进程其他应用可以通过shareUid泡在相同的进程中去。

多进程的特点

  开启多进程很简单,但是在使用中会发现多进程中间的数据不共享等问题。Android会为每个进程分配一个独立的虚拟机。不同的虚拟机分配不同的内存和地址空间。这会导致多进程中访问同一个类会产生多个副本。

一、优点
  1. 分配不同的内存,使得同一个应用可以使用的内存增大,
  2. 可以执行一些耗时操作而不影响主进程。
二、缺点:一般来说多进程会造成:
  1. 单例模式和静态成员变量完全失效。
    原因:
  2. sharedpreferences可靠性降低。
  3. 线程同步机制失效。
  4. application会创建多次。
多进程通信方式
  • intent

  • 文件

  • ContentProvider

  • messager
    messager切记是串行的

  • aidl

  因为通常直接使用aidl进行通信,而且ContentProvider、Messenger的底层都是通过binder实现的。我们下面详细介绍一下Binder:

Binder基础

一、数据传递格式要求

AIDL支持的数据格式有以下几种:

  1. 基本数据类型(int、double、float、boolean、long、char等)
  2. String和CharSequence
  3. Parcelable型数据
  4. list只支持ArrayList,Map只支持HashMap,其中包含的数据类型是以上种类型
  5. AIDL接口类型

使用Parcelable和AIDL类型要导包,另外AIDL中使用Parcelable数据类型必须生成相应的aidl文件。

// Book.aidl
package com.demo.remote.beans;
parcelable Book ;
二、定向Tag

在使用AIDL中使用数据,除基本数据和AIDL之外,都要为输入参数添加定向tag。定向Tag包含in、out、inout三种。它们区别如下:

  • in 表示输入型参数。即void addBook(in Book book);说明当客户端调用的addBook(book)时,传入的参数book,如果service服务端对book对象的属性改变是不会改变客户点的book对象的属性.
  • out 输出型参数。即void addBook(out Book book);当客户端调用addBook(book)时,传入的参数到达服务端是,是一个新的空对象,当服务端修改了该对象时,客户端本地的book对象也会随之改变。
  • inout 输入输出型参数。及in和out两种方式的集合体。即void addBook(inout Book book);说明当客户端调用的addBook(book)时,传入的参数book对象,会完整到达服务端,当服务端修改传入的book对象时,客户端的book对象也会随之改变。
    总结:aidl的定向tag只能作用与aidl方法中的输入参数,返回参数不能使用定向tag。区别就是输入的参数能否完整到达服务端,并且服务端对入参的修改,是否会影响到客户端。具体如下表:
参数名称 参数能否完整到达服务端 服务端修改是否印象客户端
in
out
inout
三、RemoteCallbackList

  当我们需要监听service端某个数据发生变化,我们一般需要在service端注册监听完成。当我们不需要时,取消监听注册。然而实际运用过程中发现不是这样的,注册监听可以成功,但是取消注册时,会因为无法找到该listener而解注册失败。因为当我们在跨进程调用的时候服务端接收到的已经是一个新生成的对象。用原有的对象去解除注册肯定找不到对象而失败。这个时候我们要用RemoteCallbackList ,定义声明如下:
class RemoteCallbackList<E extends IInterface>它适用于管理任何aidl接口。以下是RemoteCallbackList核心源码分析

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            // Flag unusual case that could be caused by a leak. b/36778087
            logExcessiveCallbacks();
            //获取注册监听本身的binder 对象
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
              //将回调本身的binder对象作为key值,回调作为value存入map集合中,缓存起来。
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }

在RemoteCallbackList的注册方法中,会将回调listener的binder对象作为key存入map集合中。我们知道在跨进程中binder是我们跨进程的基础。当我们移除注册的时候,根据回调本身的binder对象找出新生成的回调对象,将至移除,完成解注册过程。

四、binder在多进程中的区别

  如上调用示例所示,通过binder进行通信,区分service进程是否在单独的进程中,如果在单独的进程中话,binder对象是同一个数据对象,如果在多进程中那么两者是不同进程那两者则是不同的对象。
同进程binder对比


不同进程binderhashcode

源码分析

  /**
         * Cast an IBinder object into an com.demo.remote.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.demo.remote.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.demo.remote.IBookManager))) {
                return ((com.demo.remote.IBookManager) iin);
            }
            return new com.demo.remote.IBookManager.Stub.Proxy(obj);
        }

上层是调用IBookManager.Stub.asInterface(service)获取binder对象,通过查看源码发现其中如果是同进程则直接返回,如果不是则通过生成一个代理对象返回。

调用示例

//客户端
public class AidlTestActivity extends Activity {
    private int count = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base_test_remote);
    }

    private void bindTestService() {
        Intent intent = new Intent(this, AIDLTestService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    private IBookManager iBookManager;
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    public void addBook(View view) {
        if (iBookManager == null) {
            bindTestService();
            Log.e("AIDLTest","waite for service bind success");
            return;
        }
        try {
            count++;
            iBookManager.addBook(new Book("activity" + count, count));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    public void showBookList(View view) {
        if (iBookManager == null) {
            bindTestService();
            Log.e("AIDLTest","waite for service bind success");
            return;
        }
        try {
            List<Book> bookList = iBookManager.getBookList();
            if (bookList==null){
                return;
            }
            for (Book book : bookList) {
                Log.e("AIDLTest","book: "+book);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

service端

public class AIDLTestService extends Service {
    private List<Book> mBookList = new ArrayList<>();

    Binder iBookManager = new IBookManager.Stub() {

        @Override
        public void addBook(Book book) throws RemoteException {
            if (mBookList == null) {
                mBookList = new ArrayList<>();
            }
            mBookList.add(book);
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList == null ? new ArrayList<Book>() : mBookList;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book("binder", 1));
        mBookList.add(new Book("binder", 2));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return iBookManager;
    }
}

aidl文件


// IBookManager.aidl
package com.demo.remote;
import com.demo.remote.beans.Book;
// Declare any non-default types here with import statements

interface IBookManager {
  void addBook(in Book book);
  List<Book> getBookList();
}

调用方法执行线程

  客户端调用服务端的方法是运行在Service的Binder线程池中,同时客户端线程会被挂起,要注意如果服务端的检查比较耗时,会导致客户端长时间挂起,要注意导致主线程ANR。反之,服务端回调客户端的方法则执行在客户端的binder线程池中。因为调用方法。结论:无论任何甲端调用对方的方法,乙端会在自己进程的binder线程池中运行,甲会挂起,要注意防止出现ANR,并且需要因为每次调用都在自己的线程中,所以如果涉及数据问题要考虑数据同步问题!!!

Binder健壮性

  Binder 因为可能是会意外死亡的,所以为了健壮性我们需要当binder死亡时,重连服务。有两种方式:

  1. DeathRecipient
 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
           //重连服务器,在binder线程池中执行
            bindTestService();
        }
    };
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                service.linkToDeath(deathRecipient,0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            iBookManager = IBookManager.Stub.asInterface(service);


            Log.e("binder_hash", "onServiceConnected  binder : " + iBookManager);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
  1. onServiceDisconnected()方法重连执行在UI线程中
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容