Android中IPC机制详解

本文部分内容参照《Android开发艺术探索》

IPC是什么?

IPC全称为Inter-Process Communication,译为“跨进程通信”,在这里要着重提一下,进程(Process)和线程(Thread)是完全不一样的概念,两者不能混淆。在操作系统的概念里,进程是CPU的调度单元,而线程是CPU的最小调度单位,一个进程包含一个或一组线程,两者为包含关系,具体两者的区别还请查阅相关资料。

为什么要使用多进程?

  1. 增加App能使用的内存大小,缓解OOM问题
  2. 应用保活策略
  3. 其他软件本身需求
我们为以上的几个原因进行一一解释:

一:增加App能使用的内存大小,缓解OOM问题
Android为了保证整个系统的稳定性,确保在一个应用崩溃时不会影响到其他正在运行的应用,Android会为每一个应用都会分配一个虚拟机(DVM),更加确切的说,Android其实为每一个进程都分配了一个虚拟机,当应用所在的进程崩溃时不会影响到其他的进程。
为了保证Android内存不被某一个应用进程所在的虚拟机大量占用,导致其他应用没有可用的内存资源,Android系统会对进程能够分配的内存大小进行限制,这个数值每个手机厂商的标准都有所不同。
你可以通过这样获取你的可用内存大小

ActivityManager activityManager = (ActivityManager) this.getSystemService(Activity.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
        activityManager.getMemoryInfo(info);
        System.out.println(info.availMem);

你也可以通过在清单中添加属性,调整可以申请到的内存,但仍不能超出已规定的限制

android:largeHeap="true"

当某个进程所占用的内存超出了这个内存限制之后,就会因为申请不到足够的内存而出现OOM错误。但如果将一个应用分到几个进程上运行以后,就相当于增加了应用可使用的内存大小,避免出现OOM错误。
二:应用保活策略
为了保证Android系统能够平稳的运行,保证有足够的剩余内存分配,Android系统必须及时杀死无用进程或者优先级较低的进程,来省出稀缺的内存空间。这就像Java的垃圾收集一样,只不过Java是回收垃圾,Android是杀死进程。
而某些应用则为了自身的需求需要保证一直运行,不能简单的被杀死,那就需要一定的保活策略。其中的几种常用策略就需要用到多进程,如双进程守护策略、关联服务进程策略等,想要深入了解进程保活相关知识,可以阅读此文章关于 Android 进程保活,你所需要知道的一切

怎样开启多进程?

Android想要开启多进程非常简单,只需要在清单里,为需要运行在另外进程上的四大组件指定属性

android:process=":name"  <!--开启的进程名为 包名:name-->

也可以这样设置

android:process="com.aze.demo.name"  <!--开启的进程名为com.aze.demo.name-->

虽然开启多进程简单,但是使用起来却并不是那么简单。
他会导致以下问题:

  1. 静态成员和单例模式失效(进程所在内存不一致,不能共享)
  2. 线程同步机制失效(锁的对象不是同一个对象)
  3. SharePreferences变得不可靠(底层靠的是读写XML文件,系统为了加快读写速度有缓存策略,所以虽然有Linux系统的对文件的并发处理策略,但多进程同时读/写或同时写仍有可能会造成数据丢失)
  4. Application会多次创建(启动另一个进程的时候,相当于把应用开启了一个副本)

假设有两个Activity,其中一个Activity运行在另一个进程上,因为又开启了一个进程,所以系统分别为两个进程开启了一个虚拟机,那么你完全可以把这两个进程看成不同的两个应用,因为它们运行在不同的内存块上,尽管它们是同一个应用的两个部分。
而这两个Activity调用的Java类虽然看起来是相同的,但是其实两个Activity调用的只是同一个类的副本,数据是不能以正常手段传输的。但要想整个应用平稳高效的运行,每个进程不可能只单干,而不相互交流。要想实现这个交流就要用到IPC机制。

实现IPC的方式

  1. Bundle:通过传输Parcelable对象
  2. Socket:C/S(Client-Service)模型
  3. Messenger:底层是通过Binder
  4. ContentProvider:底层通过Binder
  5. AIDL:底层通过Binder
  6. 文件共享:两进程读取同一文件

一、Bundle

Bundle可以翻译为“捆,束”,它可以绑定“一捆”Parcelable数据,通过Intent在四大组件之间传输,但是它是通过什么原理实现跨进程传输的呢?
Parcelable是Android基于自身系统的特性设计的对象序列化接口,能够将对象序列化为二进制字节流,存储到系统的共享内存区,需要这个对象的进程从共享内存区将其取出后,经过反序列化,将二进制字节流转化为对象。
以下图示显示了这一过程:

二、Socket方式

一个进程作为Server端,另一个进程作为Client端,两者预先协商好一个端口,通过TCP或UDP方式互相传送各种数据流,通过网络的途径实现跨进程通信。

三:Binder方式

Messenger、ContentProvider、AIDL这几种主要的IPC方式,底层都是通过Binder实现的,那么Binder是什么呢?
我们首先来看Google提供的Android系统的平台架构图:
在这里插入图片描述
可以看出Binder在Android平台架构的Linux驱动层,Binder是一个没有对应的硬件设备,专门为IPC设计的底层驱动,它的位置为:

/dev/binder

Binder是一个非常深入的话题,想要更加了解它,可以去看Gityuan的Binder概述,本文只从使用的角度上去介绍Binder。

我们以Android接口定义语言(AIDL)来分析Binder:
AIDL可以稍加配置由IDE自动生成,本文不再介绍AIDL的生成方式。
前面提到了Binder是基于C/S架构的,那么把服务端与客户端分开介绍。
服务端:

package com.aze.demo;
public interface IAddInterface extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.aze.demo.IAddInterface {
    	//DESCRIPTOR 是区分Binder的唯一标识
        private static final java.lang.String DESCRIPTOR = "com.aze.demo.IAddInterface";
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * 将IBinder对象转换为IAddInterface接口
         * 如果服务端与客户端不在同一进程则使用代理
         */
        public static com.aze.demo.IAddInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.aze.demo.IAddInterface))) {
                return ((com.aze.demo.IAddInterface) iin);
            }
            return new com.aze.demo.demo.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface(descriptor);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.aze.demo.IAddInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

			/**
			* 返回当前的Binder对象
			*/
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int add(int a, int b) throws android.os.RemoteException;
}

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法特别说一下,第一个参数可以分辨出客户端是调用的哪一个方法,具体可以参见

   static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

第二个参数是需要向调用方法中传递的参数,执行完毕后如果有返回值,就写入第三个参数reply中。

我们实现接口以后,我们还需要向客户端暴露这个接口,这里扩展Service并实现onBinder使其返回Binder对象。

public class IAddService extends Service {
    public final IAddInterface.Stub binder=new IAddInterface.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a+b;
        }
    };

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

客户端:
当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的Binder的对象实例。
注意:如果服务端设置了检查权限的需求,客户端首先要有对AIDL的访问权限,一般需要在配置清单里添加相关权限。

public class BinderActivity extends AppCompatActivity {
    IAddInterface iAddInterface;
    private ServiceConnection serviceConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iAddInterface=IAddInterface.Stub.asInterface(service);
            try {
                iAddInterface.add(1,2);   //调用远程方法
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent=new Intent(this,IAddService.class);
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
    }
}

整个过程为:

Client进程绑定服务,获取返回的Binder对象,传入想要调用的方法与需要的参数,Binder将这些数据传输给Service进程,Service进程验证Client权限后,通过onTransact方法验证传入的数据是否合法,验证无误后调用相应方法,将任务提交给线程池,任务完成后将结果返回给Binder,让Binder转交给Client。至此,Client与Service进程通过Binder完成了IPC。

四、文件共享

两个进程共同访问同一个物理硬盘上的文件,把这个文件作为信息传输的“中转站”,可以通过序列化对象到该文件,另一个进程对文件的数据反序列化获得相应对象。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值