第二章-IPC机制

IPC基础-Linux 中的进程间通信机制

  • pipe 管道:内核管理的一个缓冲区,只能传递无
  • FIFO 命名管道
  • 共享内存
  • Socket
  • signal
  • Message queues
  • Semaphore

Android 中的多进程方式

Andorid 中使用多进程需要在AndroidMainfest中给4大组件添加android:process属性

  • andorid:process=":remote"
    • 私有进程,别的组件不可以和它跑在一起
  • andorid:process="com.android.remote"
    • 公有进程,别的组件可以通过使用ShareUID的方式和它跑在一起(当然应用签名还必须一样)

多进程会导致如下问题

  • 单例模式,静态变量完全失效
  • Application会创建多次:一个进程分配一个虚拟机,一个应用就是一个进程,启动一个进程就相当于重新启动了一个应用程序
  • SharePreferencess 可靠性下降(多个进程并发读写同一个xml文件很大概率出现问题)
  • 线程同步机制完全失效

序列化与反序列化

实现Serizlizable接口

serialVersionUID 序列化时系统会将该ID也一同写入序列化的文件,在反序列化时会通过比较该id和反序列化的类的id,如果不同会报异常。
如果不指定该值,在反序列化时如果反序列化的类中删除或多出了某些字段就会crash。(不指定,序列化和反序列化系统会计算hash值来赋值到该字段上,所以修改字段或删除后会改变hash从而造成crash)。如果指定了serialVersionUID ,因为serialVersionUID 都是相同的那么在反序列化后系统会尽可能的帮助我们恢复数据

实现Parcelable接口

AIDL 生成的java代码分析

  • aidl中声明了几个方法,就会有几个整形id用来标识这些方法,用于标识在transact过程中客户端使用的是哪个方法。
  • 内部类Stub就是一个Binder。当客户端与服务端位于同一进程时,不会走进程的transact过程,当客户端与服务端位于不同进程时,需要走内部代理类Proxy中的ransact过程
  • Stub中onTransact在服务端执行,该方法运行在服务端的Binder线程池中,当客户端发起跨进程请求系统底层会将请求封装后调用该方法。如果服务端与客户端位于同一进程,那么不会调用该方法(因为客户端获得的Binder是服务端本身,是同一个类)。如果服务端与客户端位于不同进程,由于客户端获得的Binder是一个Proxy,调用方法通过Proxy发送消息,服务端通过onTransact处理消息。
  • Proxy在客户端执行

注:AIDL文件只是系统给我们一个快速生成Binder的工具。

服务端状态的监听

客户端可以通过DeathRecipient接口来监听服务端状态

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    try {
        bookManager = IBookManager.BookManagerImpl.asInterface(service);
        bookManager.asBinder().linkToDeath(deathRecipient,0);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

在绑定服务获取Binder对象时设置监听

private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        bookManager.asBinder().unlinkToDeath(deathRecipient,0);
    }
};

在回调中取消监听

Android中的IPC方式

  • 通过Bundle,四大组件中Activity,Service,Receiver都支持在Intent中传递Bundle数据。Bundle实现了Parcelable接口因此很容易在各进程中传递。
  • 通过共享文件,两个进程通过读/写同一文件进行数据交换
    • 优点
      • 数据格式自定义化程度较高,只要读写双方约定好数据格式即可。
    • 缺点
      • 并发读写可能会出现数据丢失,数据不是最新问题

总结:文件共享方式适合在对数据同步要求不高的进程间进行通信,并且开发人员要妥善处理并发问题

  • 通过Messenger

服务端

private Messenger messenger = new Messenger(new MessageHandle());
@Override
public IBinder onBind(Intent intent) {
    return messenger.getBinder();
}

服务端直接返回Messenger中内部Binder对象

客户端

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    mService = new Messenger(service);
}

客户端绑定成功后实例化,该构造函数中会调用IMessenger.Stub.asInterface(target)将IBinder转化成服务端Messenger的代理对象。之后就可以通过mService.send(Message msg)方法向服务端发送消息。

客户端与服务端双向通信

客户端绑定成功后在组装Message消息时需要向message.replyTo赋值接收消息的Messenger

Message message = Message.obtain();
message.setData(bundle);
message.what =1;
message.replyTo = mMessengerClient; //赋值接收消息的Messenger
mService.send(message);

服务端获取该Messenger后即可向客户端发送消息

private static class MessageHandle extends Handler{·
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                try {
                    Messenger replyClient = msg.replyTo;
                    Bundle reciverBundle = msg.getData();
                    reciverBundle.setClassLoader(Book.class.getClassLoader());
                   com.hdingmin.dev.Book book = reciverBundle.getParcelable("data");
                    if (replyClient != null) {
                        Message replyMsg = Message.obtain();
                        replyMsg.what = 1001;
                        Bundle bundle = new Bundle();
                        bundle.putString("reply", "我已经收到消息了,这是我收到的消息:" + book.toString());
                        replyMsg.setData(bundle);
                        replyClient.send(replyMsg);
                    }
                }catch (RemoteException e){
                    e.printStackTrace();
                }
                break;
            default:
                    break;
        }
    }
}

注意:通过Messenger向服务端发送包含实现Parcelable接口数据的Bundle时由于是跨进程调用导致Bundle中ClassLoder丢失,所以在getXXX数据前必须先setClassLoader否则会报错。同时Messenger是串行处理的请求不适合存在大量并发请求,同时也不支持调用服务端方法。

  • 通过AIDL
    • AIDL支持的数据类型
      • 基本数据类型(int、long、char、boolean、double等)
      • String和CharSequence
      • List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
      • Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和vale
      • Parcelable:所有实现了Parcelable接口的对象
      • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

注意

  • Parcelable对象或AIDL对象必须显示的import出来
  • 自定义的Parcelable对象必须有同名的AIDL文件,如XXX.java,那么必须有XXX.aidl
  • 除了基本数据类型(默认是in),其他类型参数必须标上方向:in,out,inout
    • in :数据只能由客户端流向服务端
    • out :数据只能由服务端流向客户端
    • inout :数据可在服务端与客户端之间双向流通
      客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的参数为空的对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
  • 通过ContentProvider

使用步骤:

  • 继承ContentProvider抽象类实现抽象方法
  • 在AndroidManifest中配置provider和authorities
    ContentProvider 中方法参数的含义
    /**
     * 查询
     * @param uri 标识、定位任何资源的字符串
     * @param projection 需要筛选的列字段
     * @param selection 筛选条件相当于WHERE后的内容“name=?”或“name=张三”
     * @param selectionArgs  如果selection中使用了占位符“name=?”,那么该参数不能为空
     * @param sortOrder 排序
     * @return
     * Cursor cursor = getContentResolver().query(uri,new String[]{"name"},"name=?",new String[]{"张三"}, null);
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }
  • 通过Socket
    服务端
serverSocket = new ServerSocket(8688);
//阻塞方法,调用该方法后线程进入阻塞状态直到接收到客户端的连接
final Socket client = serverSocket.accept();

客户端

socket = new Socket("192.168.12.1",8688);
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (!SocketActivity.this.isFinishing()){
        //阻塞方法,调用该方法后线程进入阻塞状态直到接收到服务端返回的消息
        String msg = br.readLine();
        mHandler.obtainMessage(HAS_RECEIVE_MSG,msg).sendToTarget();
}

客户端发送消息

private void sendMessage (){
      String content = msgEidtText.getText().toString();
      if(!TextUtils.isEmpty(content)){
          try {
                if(!socket.isOutputShutdown()) {
                    //加上\n是因为服务端读取客户端的时候使用了readLine(),所以加上换行符才能被服务端读取
                    bw.write(content+"\n");
                    bw.flush();
                    Toast.makeText(this, "已发送:" + content, Toast.LENGTH_SHORT).show();
              }
          }catch (IOException ex){
                ex.printStackTrace();
          }
     }else {
            Toast.makeText(this,"发送消息不能为空",Toast.LENGTH_SHORT).show();
     }
}

IPC 方式的优缺点和适用场景

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程通信
文件共享 简单易用 不支持高并发,无法做到进程间即时通信 无并发访问,交换简单的数据实时性不高的场景
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用复杂,需要处理线程同步 一对多通信,有RPC需求
Messenger 支持一对多串行通信,支持实时通信 不能很好处理高并发的情况,不支持RPC,数据通过Messenger传输只能传Bundle支持的数据类型 低并发,一对多即时通信,无需RPC或无需返回结果的RPC需求
ContentProvider 在数据源访问方面功能强大,支持一对多并发操作,可通过Call方法扩展其他操作 可理解为收约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间的数据共享
Socket 功能强大,可通过网络传输字节流,支持一对多并发实时通信 使用起来复杂,不支持直接的RPC 网络数据交换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,100评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,862评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,993评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,309评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,303评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,421评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,830评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,501评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,689评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,506评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,564评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,286评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,826评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,875评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,114评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,705评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,269评论 2 341

推荐阅读更多精彩内容