学习笔记之IPC - Android进程通信

Posted 我叫白小飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记之IPC - Android进程通信相关的知识,希望对你有一定的参考价值。

IPC

IPC就是 inter-process communication的缩写,含义是 跨进程通信,在学习这之前,我们需要聊什么是进程。进程是系统运行程序的最小单位,是程序的一个运行实例。一个进程的创建是通过Process.start()方法来完成的,其中的参数可以定制启动后最先执行的线程,通常是指定一个ActivityThread(主线程),process会通过socket把创建进程的请求发送给zygote,最终由zygote来fork一个新的进程,也就是我们创建的APP的实例。具体的进程分析可以参考以下两篇文章:Android process 的启动流程Android进程系列第二篇—Zygote进程的启动流程

安卓中的多进程

为什么要使用多进程
  1. 我们开发应用时也遇到过需要常驻后台的应用,例如:跑步、音乐、手机管家之类的软件,核心服务不会因为主应用被后台清理后功能停止;
  2. 我们如果要做一个大而全的应用,例如里边有地图、大图浏览、webview、监控服务、下载服务等吃内存的大户,这个时候内存分配给单一进程的空间可能会不够,因为安卓分配给进程的阈值可能是48M、24M、16m等,超过阈值可能会导致主进程OOM。
    合理使用多进程,我们可以很大程度上避免应用的奔溃以及提升用户体验;
使用的风险

多进程固然效果不错,但是如果使用不当,也会造成很多麻烦,例如:

  • 多虚拟机潜在问题:多进程创建之后,会创建独立的运行空间,并且有独立的虚拟机,很多Java的特性会在多进程中失效:
  • 静态变量和单利模式完全失效:因为不同进程之间的内存空间是不同的,所以虚拟机的方法区内的静态变量也是相互独立的,因为单例模式是基于静态变量的,所以单利也会失效,所以在访问多进程的相同变量时值可能不同;
  • 线程同步可能完全失效:多进程是不同的虚拟机的,而线程同步是通过虚拟机调度完成的,所以会失效;
  • Application会多次创建

进程的创建等于创建一个新的应用实例,所以会再创建一个新的application,onCreate方法会被调用多次,所以不要在application中创建过多的静态变量,这样回导致内存增加;

  • 文件读写问题

文件读写是泛指,其中包括:数据库、文件、sp等,由于Java中文件锁和队列机制都是虚拟机级别的,所有不同进程访问同一个文件时,锁是不管用的。

实例

安卓中常用的创建对进程的方式为在androidManifest文件中,给activity、service、brodcastreceiver、contentprovider等四大组件设置process属性,其实设置属性也有两种写法,分别是":remote" 和直接写进程名:process=“com.xxxxx.xxx:remote”; process=“com.xxxx.xxx”,如下:

<activity android:name=".SecondActivity"
            android:process=":remote">
        </activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

项目运行之后,从mainactivity跳转到secondactivity,然后通过命令:adb shell ps -ef|grep com.xxxx.xxxx 查看当进程:

  • 这两种命名方式的区别:最明显的应该就是名字上了,直接 :remote 这样的命名方式最简单,其次就是用冒号这种形式创建的进程是属于私有进程的,其他应用的组件不可以运行在该进程;

IPC概念介绍

接下来看一下IPC,前面已经大概说了什么是IPC,也就是概念,现在我们聊聊ipc中涉及到的几个概念:Serializable、Parcelable以及Binder。为什么要了解Serializable和Parcelable接口呢?因为我们知道Binder是用来通信的,通信就涉及到信息传递(数据传递),我们传递的信息不是无规则的,相传什么就传什么,而Binder的数据传输就用到上述两个接口,咱们一个个来看:

  • Serializable接口
    它是java提供的一个序列化接口,什么又是序列化?请看文章:对Java Serializable(序列化)的理解和总结,其实大概意思就是将一个对象转换为字节序列的过程就是序列化。序列化和反序列化过程就是将对象I/O流的读写。我们再实现Serializable接口的时候一般系统会提示生成一个serialVersionUID的变量,这个变量的作用就是防止对象结构变化之后反序列化失败。
  • Parcelable接口
    实现Parcelable接口是另一种序列化方式,安卓独有的,它的序列化过程比较繁琐,具体请看代码:
public class User implements Parcelable 
    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;

    protected User(Parcel in) 
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
    

    @Override
    public void writeToParcel(Parcel dest, int flags) 
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
    

    @Override
    public int describeContents() 
        return 0;
    

    public static final Creator<User> CREATOR = new Creator<User>() 
        @Override
        public User createFromParcel(Parcel in) 
            return new User(in);
        

        @Override
        public User[] newArray(int size) 
            return new User[size];
        
    ;

我们注意到继承完之后自动重写了writeToParcel方法,这个方法就是序列化的主要方法,Parcel 这个对象内部包装了可序列化的数据,可以在Binder中任意传输。

区别
实现Serializable接口的方式进行了I/O操作,而Parcelable则是在内存中序列化,直接降对象序列化到磁盘或者序列化之后通过网络传输,这是其一,其二,Serializable使用简单,只需要继承继承该接口即可,而Parcelable使用起来比较麻烦。

  1. Binder
    Binder是一个继承了IBinder接口的一个类,它是Android中一种跨进程通信的方式。Android系统的虚拟地址内存分为用户空间和内核空间,用户空间为私有空间,内核空间为共享空间,Binder的机制就是通过共享内核空间实现进程通信;Binder把进程A生成的IPC数据(在用户空间生成),传递给BinderDriver,Binder Driver在内核空间运行,之后Binder Driver再把IPC数据传递给进程B。IPC数据由4部分组成,Handle、RPC数据、RPC代码、Binder协议,Handle是服务号,用来区分不同的服务,RPC代码和RPC数据分别是B应用待调用的函数和函数的参数,Binder协议表示IPC数据的处理方法,包括两种,从IPC层传递到Binder Driver和从Binder Driver传递到IPC层。

安卓中的IPC方式

Android中实现IPC的方式有很多种,实质上其实多进程之间的数据传递,只要实现了这个目的就等于实现了IPC,一下就介绍几种方式:

  1. Bundle 这是最简单的方式,因为前边我们介绍了如何在安卓开启多进程,那我们启动多进程时,可以直接带上数据,这样就可以简单实现多进程的通信。
startActivity(new Intent(...).putExtra("data",bundle));
  1. 文件共享: 我们可以让不同的进程访问同一个文件(xml、文本、序列化对象等)实现进程通信,但是我们也知道不同进程之间是不能保证文件锁的一致的,这样会导致数据不一致的问题,所以不推荐使用。
  2. 使用消息机制 Massager:其实这种实现的方式底层也是AIDL:

客户端消息处理:

public class ClientHandler extends Handler 
    @Override
    public void handleMessage(@NonNull Message msg) 
        super.handleMessage(msg);
        if (msg.what == 2) 
            // 获取到服务端返回的信息并打印出来
            String str = msg.getData().getString("reply");
            if (BuildConfig.DEBUG) Log.e("ClientMHandler", str);
        
    

服务端消息处理

public class ServiceHandler extends Handler 
    @Override
    public void handleMessage(@NonNull Message msg) 
        super.handleMessage(msg);
        if (msg.what == 1) 
            Log.e("ServiceHandler",msg.getData().getString("msg"));
            // 获取客户端的信使实例
            Messenger client = msg.replyTo;
            // 新建返回消息
            Message message = Message.obtain();
            message.what = 2;
            Bundle bundle = new Bundle();
            bundle.putString("reply", "收到客户端的消息了!!");
            message.setData(bundle);
            try 
                // 给客户端发送消息
                client.send(message);
             catch (RemoteException e) 
                e.printStackTrace();
            

        
    


服务端

public class MessengerService extends Service 
    // service启动时就创建一个信使
    Messenger messenger = new Messenger(new ServiceHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        // service和client的绑定
        return messenger.getBinder();
    

客户端

public class MainActivity extends AppCompatActivity 

    private ServiceConnection conn = new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            // 通过与服务端绑定,获取Binder,也就是service,然后通过service创建一个和service通信的信使
            Messenger messenger = new Messenger(service);
            //创建消息
            Message message = Message.obtain();
            message.what = 1;
            Bundle bundle = new Bundle();
            bundle.putString("msg", "你好服务端!");
            message.setData(bundle);
            // 创建一个客户端的信使,传过去,然后服务端才能根据这个信使给客户端发消息
            message.replyTo = new Messenger(new ClientHandler());
            try 
                messenger.send(message);
             catch (RemoteException e) 
                e.printStackTrace();
            

        

        @Override
        public void onServiceDisconnected(ComponentName name) 

        
    ;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_start_service).setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, MessengerService.class);
                bindService(intent,conn, Service.BIND_AUTO_CREATE);
            
        );
    



以上是binder的简单使用,我们可以再结合AIDL的只是对其进行优化,稍后专门做一下AIDL的用法;
5. ContentProvider :其实ContentProvider 也是IPC机制,底层也是通过AIDL实现的多进程通信,不过由于ContentProvider 的特殊机制,它的职责计较专一,就是为其他程序提供数的。
6. socket: 学名“套接字”,是网络通信中的概念,它分为流式套接字和数据报套接字相中,分别对应网络的传输控制层中的TCP和UDP协议,TCP是基于“三次握手”建立的连接,可提供稳定的双向传输,而UDP是无连接的单向传输没有像TCP一样的超时重传的机制,所以他的连接可能会出现数据丢失的问题,并且连接也不够稳定,但是因为没有握手机制,所以它的效率是要高于TCP的。因为是基于网络层的,所以不受限于应用的用户空间,所以它也是跨进程通信的手段之一,后续会详细对socket做介绍;

参考链接

Android Binder机制

以上是关于学习笔记之IPC - Android进程通信的主要内容,如果未能解决你的问题,请参考以下文章

Android:安卓学习笔记之进程间通信方式(IPC)的简单理解和使用

Android进程间通信(IPC)机制Binder简要介绍和学习计划

转Android进程间通信(IPC)机制Binder简要介绍和学习计划

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android 之 IPC 进程通信全解析