【IPC】Android中的进程间通信(IPC)详解

1 需要的知识点

1.1 进程与线程

要理解跨进程通信,首先需要理解以下几个知识点1

  • 进程:按照操作系统的描述,进程是资源分配的最小单位,一个进程可以包含多个线程
  • 线程:线程是CPU调度的最小单位,多线程需要考虑并发问题。

1.2 Android中的多进程

Android多进程指的是一个应用中存在多个进程的情况,在Android中,一般一个应用存在一个进程。多进程的情况2

  • 某个应用由于自身原因需要采用多进程模式实现
  • 为了加大一个应用可使用的内存,通过多进程来获取多份内存空间

开启多进程模式是通过在AndroidManifest中指定Android:process属性

<activity
	android:name=".SecondActivity"
	android:process=":twoprocess"></activity>
 <activity
 	android:name=".ThirdActivity"
 	android:process="com.yds.thirdprocess"></activity>
 	
  • “:”开头的进程属于应用的私有进程,其它应用的组件不可以与它跑在同一个进程中,而不以冒号开头的数据全局进程,其它应用通过shareUID方式可以和它跑在同一个进程中。
  • shareUID:Android系统会为每个程序分配一个不同级别的UID,如果需要相互调用,只能是UID相同才行,这就使得共享数据具有了一定的安全性,每个软件之间是不能随意获得数据的,而同一个Application只有一个UID,所以Application之间不存在访问权限的问题。

一般来说,使用多进程会存在以下几个问题:

  1. 静态成员与单例模式完全失效
  2. 线程同步机制完全失效
  3. SP(SharedPreference)可靠性会下降
  4. Application创建多次

1.3 数据共享方法

如果你需要做一个Application将某些服务的ServiceProvider或者Activity等的数据共享出去,有如下三个办法:

  • 完全暴露 :使用android:exported="true",一旦设置了true,则将会被完全暴露,可以被其它应用进程调用其暴露出去的数据。没有使用android:exportedServiceProvider或者Activity,其默认的exported值为false,但此时如果设置了intent filter,则其默认值为true.
  • 权限提示暴露:如果应用A设置了android:permission="xxx.xxx.xxx",则你必须在Manifest中使用use-permission才能访问应用A的东西。
  • 私有暴露:假如说一个公司做了两个产品,只想这两个产品之间可互相调用,那么这个时候就必须使用shareUserID将两个软件的Uid强制设置为一样的。这种情况下必须使用具有该公司签名的签名文档才能,如果使用一个系统自带软件的ShareUID,例如Contact,那么无须第三方签名。3

1.4 IPC基础概念介绍

SerialiazableParcelable

  • 序列化:讲对象转化为字节的过程
  • Serialiazable:Java提供的序列化接口
  • Parcelable:Android提供的序列化接口

SerialiazableParcelable的区别Serialiazable使用简单但是需要大量I/O操作,Parcelable使用较繁琐,主要用于内存序列化,效率高。

Binder
详见【IPC】Binder跨进程通信机制原理

2 实现IPC方式

实现IPC方式可以分为以下几种方式4

  1. 使用Bundle
  2. 使用文件共享
  3. 使用SharedPreferences
  4. 使用Messenger
  5. 使用AIDL
  6. 使用ContentProvider
  7. 使用Binder连接池
  8. Broadcast
  9. Socket
  10. 管道

2.1 使用Messenger

Messenger被称为信使,常与Message一起使用实现跨进程通信。底层只是对Binder的简单包装

  • 步骤
  1. 创建一个Service,在Service中的onBind里返回一个IBinder对象。
  2. AndroidManifest中声明服务,并将该服务放在另外的一个进程中
  3. 通过bindService绑定服务
  4. 在客户端创建ServiceConnection对象,使用服务端返回的IBinder对象创建一个Messenger,该Messenger是服务端的Messenger,可以通过该Messenger将客户端数据发送到服务端(该方法是通过客户端向服务端传递数据,如果想从服务端向客户端传递数据,可以通过在客户端创建一个Handler对象,然后通过该对象创建一个客户端Messenger,然后通过message.replyTo5将客户端的Messenger发送到服务端,然后在服务端获取客户端创建的Messenger,然后通过Messenger将数据发送给客户端,这样就实现了服务端向客户端传递数据)
  5. 通过mMessenger.send(message)来将数据发送给服务端,从而实现跨进程通信。
  • 代码实现
    创建一个Service,在Service中的onBind里返回一个IBinder对象。
/**
 * Created by yds
 * on 2020/4/13.
 */
public class MessengerService extends Service {
    //服务端自己的信使,为了获取客户端数据
    private Messenger mMessenger;
    //客户端信使对象,为了将数据发送到客户端
    private Messenger cMessenger;
    @Override
    public IBinder onBind(Intent intent) {
        mMessenger = new Messenger(new MyHandler());
        System.out.println("MessengerService onBind");
        return mMessenger.getBinder();
    }

    class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 0:
                    Message message = Message.obtain(null,1);
                    message.arg1 = 1000;
                    //获取客户端传递来的信使
                    cMessenger = msg.replyTo;
                    try {
                        //通过客户端信使发送服务端数据到客户端
                        cMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    System.out.println("MessengerService handleMessage");
                    break;
            }
        }
    }
}


AndroidManifest中声明服务,并将该服务放在另外的一个进程中

<service android:name=".MessengerService"
        android:process="com.yds.test.messengerservice"/>

使用Messenger实现客户端和服务端双向通信

public class MainActivity extends AppCompatActivity {
    //服务端信使,为了发送数据到客户端
    private Messenger mMessenger;
    //客户端自己的信使,为了获取服务端数据
    private Messenger cMessenger;

    private static class MyHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    System.out.println("Service Data: " + msg.arg1);
                    break;
            }
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
            cMessenger = new Messenger(new MyHandler());
            if (mMessenger != null) {
                Message msg = Message.obtain(null, 0);
                //将客户端自己的信使放在Message里传递到服务端
                msg.replyTo = cMessenger;
                try {
                    mMessenger.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMessenger = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
}

运行截图:
在这里插入图片描述

2.2 使用Bundle

Bundlefinal类型,不可被继承,它实现了Parcelable接口,是一个特殊的Map类型,支持进程间通信。
使用Bundle进行进程间通信,其实还是通过Messenger+Message来实现的,核心代码如下:

mMessenger = new Messenger(service);
Message msg = Message.obtain(null, 0);
Bundle bundle = new Bundle();
bundle.putString("IPC", "Bundle");
bundle.putInt("Number", 20);
msg.setData(bundle);
try {
    mMessenger.send(msg);
} catch (RemoteException e) {
    e.printStackTrace();
}

2.3 使用SharedPreferences

SharedPreferences并不适合存储大量数据和频繁改变数据,只能用于轻量的存储,高并发读/写时有可能会丢失数据。SharedPreferences存在以下性能问题6

  • 跨进程不安全。由于没有使用跨进程的锁,就算使用 MODE_MULTI_PROCESSSharedPreferences 在跨进程频繁读写有可能导致数据全部丢失。根据线上统计,SharedPreferences 大约会有万分之一的损坏率。
  • 加载缓慢SharedPreferences 文件的加载使用了异步线程,而且加载线程并没有设置优先级,如果这个时候读取数据就需要等待文件加载线程的结束。这就导致主线程等待低优先线程锁的问题,比如一个 100KBSP 文件读取等待时间大约需要 50 ~ 100ms,并且建议大家提前用预加载启动过程用到的 SP 文件。
  • 全量写入。无论是commit() 还是 apply(),即使我们只改动其中一个条目,都会把整个内容全部写到文件。而且即使我们多次写同一个文件,SP 也没有将多次修改合并为一次,这也是性能差的重要原因之一。
  • 卡顿。由于提供了异步落盘(拷贝到磁盘)的apply机制,在崩溃或者其它一些异常情况可能会导致数据丢失。所以当应用收到系统广播,或者被调用 onPause 等一些时机,系统会强制把所有的 SharedPreferences对象的数据落地到磁盘。如果没有落地完成,这时候主线程会被一直阻塞。这样非常容易造成卡顿,甚至是ANR,从线上数据来看 SP卡顿占比一般会超过 5%

由于以上原因,不建议使用SharedPreferences进行跨进程通信,特别是SharedPreferences进行跨进程通信不安全。

2.4 使用文件共享

利用多进程同时读写同个外部文件达到是数据交互的目的,存储形式没有限制:xml,文本,对象序列化等等。但其有着明显的缺点,由于Linux系统对文件并发读写没有限制,会导致数据不同步问题,所以该方式只适合于对数据同步要求不高的进程间通信

2.5 使用AIDL

为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。很多IPC通信方式都是基于AIDL的。如ServiceContentProvider

AIDL的简单使用可见:AIDL简单使用

待续

参考文章


  1. 带你了解android的IPC机制 ↩︎

  2. Android开发之android中的多进程模式 ↩︎

  3. Android中UID机制和共享进程 ↩︎

  4. Android夸进程通信机制四:使用 Bundle进行进程间通信 ↩︎

  5. Messenger:使用消息的跨进程通信 (Message.replyTo()的使用) ↩︎

  6. Android之SharedPreferences简介及使用说明 ↩︎

发布了139 篇原创文章 · 获赞 179 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/u013293125/article/details/105485223