Android IPC 概述

本文用到的代码::https://github.com/poecook/IPCDemo

开启进程

Android 开启多进程的方法很简单,四大组件在AndroidMenifest中给android:process属性指定值如下:

 ":remote"    android:name=".ServerService">复制代码

多进程带来的问题

一个应用中四大组件处于不同的进程中就意味着他们有独立的运行空间,虚拟机和Application,相应的会带来以下问题:

  • 单例模式、静态变量失效。
  • Application多次创建。
  • 线程同步失效
  • SharePreferences可靠性下降。

进程和线程的关系

  • 线程和进程是包含和被包含的关系。
  • 线程是CPU的最小调度单元。
  • 一个进程中可以包含多个线程,指一个执行单元

Android IPC 实现(AIDL)

Android的IPC方式有多种,但是毫无疑问功能性最强的就是AIDL。接下来我们就看如如何使用aidl实现进程间的通信。

整体的过程

服务端

  • 创建Service等待客户端发来连接请求。
  • 创建AIDL文件,将暴露给客户端使用的接口在这个文件中声明。
  • 在Service中实现这个接口。

客户端

  • 绑定服务端的Service
  • 绑定成功后讲返回的binder对象转化为AIDL接口所属的类型。
  • 使用AIDL中的方法

具体实现

我们模拟一个存放商店里耳机的行为,包括添加耳机,查看有什么耳机,有了耳机就进行通知这些功能。

AIDL接口创建

支持的数据类型

主要支持以下六种数据

  1. 基本数据类型
  2. String CharSequence
  3. Parcelable
  4. aidl文件
  5. List(只支持ArrayList)
  6. Map(只支持HashMap)

创建aidl注意事项

  • 其中ADIL和Parcelable必须显示的import不管他们是不是在一个包内
  • 如果aidl中用到了parceable对象,需要创建一个和它同名的aidl文件,并在文件中声明这个对象。
  • 除了基本数据类型外,其他的类型参数必要指定方向。in、out、或者inout。
  • aidl不支持静态常量。 //Headphones.java
package com.weshape3d.ipcdemo;import android.os.Parcel;import android.os.Parcelable;public class Headphones implements Parcelable {    public int id;    public String brand;    public String name;    public String price;    protected Headphones(Parcel in) {        id = in.readInt();        brand = in.readString();        name = in.readString();        price = in.readString();    }    public Headphones(int id, String brand, String name, String price) {        this.id = id;        this.brand = brand;        this.name = name;        this.price = price;    }    public static final Creator CREATOR = new Creator() {        @Override        public Headphones createFromParcel(Parcel in) {            return new Headphones(in);        }        @Override        public Headphones[] newArray(int size) {            return new Headphones[size];        }    };    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel parcel, int i) {        parcel.writeInt(id);        parcel.writeString(brand);        parcel.writeString(name);        parcel.writeString(price);    }}复制代码

//Headphones.aidl

package com.weshape3d.ipcdemo; parcelable Headphones;复制代码

// HeadphonesManager.aidl

package com.weshape3d.ipcdemo;import com.weshape3d.ipcdemo.Headphones;import  com.weshape3d.ipcdemo.IArrivedListener;interface HeadphonesManager{   void addHeadphones(in Headphones hp);    List getHeadphoneList();    void registListener(IArrivedListener listener);    void unRegistListener(IArrivedListener listener);}复制代码

// IArrivedListener.aidl

package com.weshape3d.ipcdemo;import com.weshape3d.ipcdemo.Headphones;interface IArrivedListener {   void onArrived(in Headphones hp);}复制代码

写完这些build一下就可以在app/build/generated/aidl/debug下看到aidl工具帮我们生成好的java类了。我们就可以使用。

服务端具体实现

服务端实现Binder.stub方法然后在service中返回。

解注册失效

原因是,我们通过Binder传递到服务端的时候会产生两个全新的对象。对象跨进程传输的本质是反序列化的过程。

系统给提供了RemoteCallBackList用于夸进程的listener解注册。

服务端注意事项

  • 在服务端实现的aidl方法会运行在服务端的线程池中,当多个客户端连接服务端的时候,需要注意线程安全。
  • 服务端的方法是运行在线程池当中的所以不怕开启耗时操作。
package com.weshape3d.ipcdemo;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.RemoteCallbackList;import android.os.RemoteException;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class ServerService extends Service {    private  CopyOnWriteArrayList headphonesCopyOnWriteArrayList = new CopyOnWriteArrayList<>();    private RemoteCallbackList arrivedListenerRemoteCallbackList = new RemoteCallbackList<>();    @Override    public IBinder onBind(Intent intent) {        return binder;    }    Binder binder = new HeadphonesManager.Stub() {        @Override        public void addHeadphones(Headphones hp) throws RemoteException {           haveNewHeadphones(hp);        }        @Override        public List getHeadphoneList() throws RemoteException {            return headphonesCopyOnWriteArrayList;        }        @Override        public void registListener(IArrivedListener listener) throws RemoteException {                arrivedListenerRemoteCallbackList.register(listener);        }        @Override        public void unRegistListener(IArrivedListener listener) throws RemoteException {            arrivedListenerRemoteCallbackList.unregister(listener);        }    };    /**     *添加新耳机     * @param headphones     */    private void haveNewHeadphones(Headphones headphones){        headphonesCopyOnWriteArrayList.add(headphones);        int n = arrivedListenerRemoteCallbackList.beginBroadcast();        for(int i = 0;i复制代码

客户端具体实现

package com.weshape3d.ipcdemo;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.IBinder;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.TextView;import java.util.List;public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private TextView tv_show;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.bt).setOnClickListener(this);        tv_show = findViewById(R.id.tv);        Intent intent = new Intent(this,ServerService.class);        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);    }    HeadphonesManager headphonesManager;    ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {             headphonesManager = HeadphonesManager.Stub.asInterface(iBinder);            try {                //注册监听                headphonesManager.registListener(new IArrivedListener.Stub() {                    @Override                    public void onArrived(Headphones hp) throws RemoteException {                        Log.d("drummor","有新加了书了老铁"+hp.brand);                    }                });            } catch (RemoteException e) {                e.printStackTrace();            }            try {               //添加DeathRecipient监听,BInder死亡时候触发                iBinder.linkToDeath(deathRecipient,0);                tv_show.setText("连接成功");            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName componentName) {        }    };    private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {        @Override        public void binderDied() {            if(headphonesManager==null)                return;            headphonesManager.asBinder().unlinkToDeath(deathRecipient,0);            headphonesManager = null;        }    };    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(serviceConnection);    }    @Override    public void onClick(View view) {        if(headphonesManager!=null){            Headphones headphones = new Headphones(1,"bose","","2018");            try {                headphonesManager.addHeadphones(headphones);               List arrayList = headphonesManager.getHeadphoneList();                tv_show.setText(arrayList.toString());            } catch (RemoteException e) {                e.printStackTrace();            }        }    }}复制代码

客户端注意事项

  • 调用远程方法的时候,需要注意不要放在主线程中容易一起ANR。
 ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {             headphonesManager = HeadphonesManager.Stub.asInterface(iBinder);            try {                //注册监听                headphonesManager.registListener(new IArrivedListener.Stub() {                    @Override                    public void onArrived(Headphones hp) throws RemoteException {                        Log.d("drummor","有新加了书了老铁"+hp.brand);                    }                });            } catch (RemoteException e) {                e.printStackTrace();            }            try {               //添加DeathRecipient监听,BInder死亡时候触发                iBinder.linkToDeath(deathRecipient,0);                tv_show.setText("连接成功");            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName componentName) {        }    };复制代码

向服务端注册监听

按照常规的注册监听的方式向服务端注册监听,会在解除监听的时候出现问题。原因在于Binder会把客户端传递过来的对象重新转换成新的对象,其实本质上跨进程传输就是反序列化的过程。 系统给我们提供了RemoteCallbackList,专门用于注册删除跨进程的listener。

使用RemoteCallbackList

RemoteCallbackList的使用必须beginBroadcast和finishBroadcast配对进行。如下

final int N =  mListenerList.beginBroadcast();for(int i=0;iif(l!=null){        l.callBack();//调用监听    }}mListenerList.finishBroadcast();复制代码

在我们demo中的具体使用可以上上面的代码实例或者github上的代码:https://github.com/poecook/IPCDemo

问题汇总小结

  • 当aidl下的文件结构跟主文件结构不一致的时候 aidl编译不通过
  • 对象的序列化使用的Parcelable
  • 监听远程使用RemoteCallbackList
  • 死亡监听使用DeathRecipient,当远程binder以外终止的时候会调用。
  • 远程方法的运行在服务端的线程池中,所以有可能会有耗时的可能性,所以客户端如果调用到远程的耗时操作时候会发生NAR。

更多相关文章

  1. AndroidStudio使用教程(第一弹)
  2. Cocos2d-x C++调用Android弹出提示框
  3. 解析Android如何利用Handler
  4. 详解Android系统中的root权限获得原理
  5. Android(安卓)View Animation Example
  6. android apk 为程序增加代码混淆
  7. android 避免Android开发中的ANR
  8. Android(安卓)基于Http协议的文件上传
  9. Android(安卓)Message机制的灵活应用

随机推荐

  1. 全面的区域科学研究数据获取途径汇总
  2. 0331作业
  3. Clad还是Tobit, 归并最小绝对偏差, 做Tob
  4. 我不是药神,我是印度仿制药,但我拯救了中
  5. CMS前世今生
  6. PSM, RDD, Heckman, Panel模型的操作程序
  7. 分位数回归, Oaxaca分解, Quaids模型, 非
  8. 社会科学空前大危机,堪比08年金融危机
  9. 计量经济圈圈友寻找到了“大米”, 从这里
  10. CHNS2015重磅来袭,中国健康领域研究者的