Android多进程与多进程通信

Android多进程开发

如何创建使用Android多进程?
在AndroidManifest.xml里面组件声明标签android:process=":xxx"或者在jin里面使用C函数fork进程,后者没试过,建议使用前者

Android:process

为组件(Activity、Service、Provider、receiver)设置process该组件会在新的进程下工作,process赋值有=“:xxx”和=“xxx”两种

  • =“:xxx” 此进程是私有进程,进程名为packagename:xxx
  • =“xxx” 公有进程,拥有相同sharedUserId的不同应用可以在这个进程使用,进程名为xxx

shareUID在以下定义使用:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.jz.testscroll"
    android:sharedUserId="com.xxx">

如果两个不同应用要在同一进程运行,除了上面sharedUerID一致,签名一致,并且要在Android.mk中添加LOCAL_CERTIFICATE,才能够安装成功,然后这两个应用进程都共享一个虚拟机,访问对方的资源

什么场景需要使用多进程?

App主进程内存使用到达可分配的内存瓶颈,很大程度上已经影响了App主进程的功能性能
需要执行特别复杂耗时的计算
独立进程的异常并不会引起主进程的奔溃异常

多进程会带来什么问题?

  1. Application会出现多次实例化及其声明周期调用,实质也是一个Android应用进程,当触发创建了新的进程,这个进程的内存空间独立,也有自己独立jvm环境,自然也需要一个Application,也需要对Application进行初始化,然而正常App主进程已经实例化Application了,这个时候要对进程名鉴别,防止非主线程对Application的生命周期方法调用正常的业务逻辑,可参考以下方法:
//pid传入android.os.Process.myPid()
public boolean isTrueMainApp(Context cxt, int pid) {
        ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
        if (am == null) {
            return false;
        }

        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
        if (runningApps != null && !runningApps.isEmpty()) {
            for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
                if(procInfo.pid == pid){
                    return getPackageName().equals(procInfo.processName);
                }
            }
        }
        return false;
    }
  1. sharedPreference不可靠,App运行时存在缓存,退出后才保存文件
  2. 进程间通信问题,由于Android的多进程内存空间是独立的,不能直接相互访问,所以要实现进程间通信

多进程通信

Android提供的多进程通信有很多种

  • 文件
  • 广播broadcast
  • contentProvider
  • socket
  • aidl (binder机制实现)
  • messenger (aidl实现)

前三种不做特别介绍

socket多进程通信

和正常的TCP socket通信是一样的,服务端进程内创建SocketServer,并监听端口,客户端创建本地localhost的Socket,并发起connect连接,连接成功后进行stream流的读写操作
本地通信这种方式比较低效,数据发送接收都要从网络模块跑一圈再回来,但我只是本地的两个进程而已啊

aidl和messenger通信

先大致说说binder机制

简单来说binder分为以下几个模块

  • client 客户端模块 – 发起服务调用
  • server模块 – 提供服务模块,具体的服务执行
  • ServiceManager – 管理各种服务模块
  • binder驱动 – 驱动连接各个模块

android 在不同的进程中启动 android多进程开发_android

AIDL

从上面binder机制可以看出,要想实现binder通信,必须的自己实现Service和client的功能,以下是使用studio工具来完成的aidl

  1. 定义服务端aidl接口格式
interface IremoteService {

    boolean connectActivity(int key, String vaalue);

    int add(int num1, int num2);

    UserBean userIn(in UserBean user);
    UserBean userOut(inout UserBean user);
}

编写好后,make project,会在build->generated->source生成接口具体实现

aidl支持的基础数据类型int、byte、char、float、double、boolean、long
引用类型:string、CharSequence
其他的引用类型必须要实现Parcelable才行
传递参数除基础类型外,引用类型必须设置tag: in、out、inout

  • in 对象是从客户端向服务端传入
  • out 数据时从服务端传递给客户端,客户端传递空对象(不是空指针),服务端接收空对象往里面写值,客户端对象会更新
  • inout 以上两种功能合并
  1. 定义服务端具体实现
public class MyService extends Service {

    private SparseArray<String> params;


    public MyService() {
        params = new SparseArray<>();
    }

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

    IremoteService.Stub stub = new IremoteService.Stub() {

        @Override
        public boolean connectActivity(int key, String vaalue) throws RemoteException {
            try {
                params.append(key, vaalue);
                return true;
            }catch (Exception e){
                return false;
            }

        }

        @Override
        public int add(int num1, int num2) throws RemoteException {
            Log.i("j_tag", "server pid " + Process.myPid());
            return num1+num2;
        }

        @Override
        public UserBean userIn(UserBean user) throws RemoteException {
            Log.i("j_tag", "userIn " +user );
            if(user != null){
                user.setName("userIn");
            }

            return user;
        }

        @Override
        public UserBean userOut(UserBean user) throws RemoteException {
            Log.i("j_tag", "userOut " + user );
            if(user != null){
                user.setName("userOut");
                user.setId(212);
            }else {
                user = new UserBean(1, "userOut");
            }

            return user;
        }
    };

}
  1. 客户端发起调用
public void initServer(){
        Intent intent = new Intent();
        intent.setAction("com.jz.service");
        intent.setPackage(getPackageName());
        bindService(intent, connection, BIND_AUTO_CREATE);
    }

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IremoteService.Stub stub = IremoteService.Stub.asInterface(service);
            if(stub == null){
                Log.i("j_tag", "stud null");
            }else {
                try {
                    if(stub.connectActivity(1, "connection")){
                        Log.i("j_tag", "add 1 +2" + stub.add(1,2));
                        Log.i("j_tag", "onServiceConnected client pid " + Process.myPid());
                    }
                    UserBean userBean = new UserBean();
                    Log.i("j_tag", stub.userIn(new UserBean(11, "recycle")).toString());
                    Log.i("j_tag", userBean.toString());
                    Log.i("j_tag", stub.userOut(userBean).toString());
                    Log.i("j_tag", userBean.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

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

studio结构图

android 在不同的进程中启动 android多进程开发_jvm_02


下图是运行的两个进程:

android 在不同的进程中启动 android多进程开发_java_03

aidl原理

客户端拿到服务端代理stub后如何实现对服务端的业务逻辑调用?

首先,先要明白make project后生成的中间aidl java类,以上面代码为例,生成的java代码主要有两个内部类:

Stub内部抽象类 implements Binder, IremoteService

Stub类是服务端提供具体服务的类,但在这个aidl.java里面只实现了Binder接口的逻辑,具体我们定义的IremoteService接口需要在Service里面实现,这也是为什么这是一个抽象类,没有实现IremoteService接口
Binder实现IBinder接口,实现该接口说明该类能提供跨进程访问能力,里面有重要的三个方法:

  • asInterface 返回下面的那个Proxy代理类,提供给客户端
  • onTransact 所以的业务接口调用都要经过这个方法,系统会把调用的接口方法封装在Parcel内,并传递一个方法的code码,transcat根据code码进行判断调用哪个方法

Proxy内部类

服务端提供服务的Binder类的静态代理类,实现了IremoteService接口,重写了接口方法,内部主要是对调用的方法进行封装为Parcel和方法code码,再调用真实的binder类的transact方法,进而由它在发起Binder底层逻辑,转入到服务端Server进程,所有方法都汇总到onTransact方法,再由他分发到具体的方法,这里有个疑问?从Proxy内部掉Proxy时就内部的被代理类binder已经是真是binder,它拥有最终的业务接口方法,为什么不直接调用接口,而要transact-onTransacr-具体方法, 原因就是进程隔离,在Proxy时在进程Client,进程Client和进程Server相互隔离的,不知道对方的任何东西,唯一知道的就是系统Binder驱动提供的transact,通过这个方法实现跨进行的调度,转到服务端进程执行;另一个角度,从Binder驱动角度去理解,我不管你们上层如何定义,我对你们都提供transact和onTransact接口,一个发起调用,一个分配调用,并且在我内部实现跨进程访问,上层你只管这么做就行了,
先解释下入日志:
第一条日志是服务端定义的Service对象的实例,
第二条是aidl生成的中间类的内部代理类Proxy中的方法日志,
第三条是aidl生成的中间类的内部类Stub的onTransact方法中,
第4条也是服务端的日志,
以及第6条是客户端Bind额Service成功后的日志;
下面我的截图也可以看出pid2184是client进程的Proxy对象(代码混淆的),它和serviceConnectted回调后的进程ID是一样,说明Proxy代理中的操作都还是在客户端的,和第6条pid相同说明同属客户端
而第三条aidl生成的中间类的内部类Stub的onTranact方法中pid又变成pid2225服务端进程去操作;
所以说明这Proxy-transact-stub-onTransact中间发生了进程切换,这就是binder提供的方法,也由binder来完成进程切换的

android 在不同的进程中启动 android多进程开发_java_04

Proxy内部类调用远端Server浅析

客户端调用远端Server接口方法时,会调用Proxy内部类的对应的方法接口,比如你定义的aidl是add方法,Proxy内部类会自动生成add方法,方法内主要会将函数名、参数等写到Parcel参数中,然后关键来了,以下这句完成跨进程调用:

mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);

_data:请求的参数
_reply:返回的参数
Stub.TRANSACTION_basicTypes:是方法码code,最后会根据这个参数决定调用服务端具体哪个方法;
最后一个0是决定独立进程参数,可以忽略;这里的跨进程的关键在于mRemote,它是Proxy内部类的成员,它是什么,从何而来,又从何而去?这里我简单阐述,mRemote是远端Binder服务端的真实客户端代理类,类似于Bpxx-Bnxx这种服务的Bp代理类,它通过ServiceManager的getService获取到远端服务类代理类,最后返回到Java层封装为BinderProxy类,所以这里的mRemote就是BinderProxy类型,其有一个long型mObject对象,它就是C++的指针存储,指向BpBinder(handler对应的服务),aidl调用任何方法都会汇总到BinderProxy的transact方法,进而传递到C++层的,然后执行BpBinder的transact方法中,最后在IPCThreadState中去,就涉及到底层数据转换等、进入Binder驱动完成跨进程访问了

总结:
理一下逻辑,

  1. aidl.java的内部抽象类Stub实现了Binder接口但没有实现aidl定义的接口,在服务端的Service中具体实现Stub类,并通过onBind方法返回真是Binder实例。
  2. 客户端在onServiceConnected方法中获取到第一步的Binder,但是这个Binder没有提供我们具体的服务接口(虽然它就是真是的Binder,服务端具体实现的Stub类,由于BInder类型是IBinder所以无法提供具体服务接口),需要调用Stub类的asInterface拿到服务代理类Proxy
  3. Proxy实现并封装了aidl定义的接口,使用它发起接口能力调用
    获取到Binder接口是BInder底层驱动提供能力,Intent封装了我们的服务请求,binder驱动将Intent转交给ServiceManger,ServiceManager会从自己注册Service查找到,并将服务的引用代理类返回给客户端,最后客户端就可以使用代理类发起服务调用了

Messenger

Messenger就是利用aidl接口实现的跨进程通信,它帮我们实现了aidl文件的编写以及Messager类的Parcel化,省去了中间的接口定义步骤,只需要在客户端和服务端声名Messenger及其使用即可

如果按照aidl方式自己实现Messenger步骤如下:

  1. 定义IMessenger.aidl和Messager.aidl,并声明接口方式send(in Message msg)
  2. 定义服务端send()方法具体的实现,大致如下
new IMessenger.Stub {
	private Hnadler target;
	//服务端自己的handler
	public IMessage(Handler a){
		target = a;
	}
	//具体接口实现
	void send(Message msg){
		//将客户端消息通过handler发送给服务端handler处理,处理后会根据Message制定的replyTo.send回去,replyTo实质也是一个IMessager
		target.sendMessage(msg);
	}
}
  1. client收到消息后,也通过自己定义的handler获取到数据

所以,Messenger使用的方法不难推断出:

  1. 服务端创建new Messenger(Handler)
  2. 客户段创建new Messenger(Handler)
  3. 客户端开始发送,并且在Message中制定replyTo回来的Messenger

binder简单理解,要从aidl内部几个类入手,在分析binder机制,客户端如何通过binder机制调取服务端的接口
binder较深入理解

最后在问个问题aidl通信是异步还是同步?思考思考,不要看答案

调用方式是跨进程调用的,算是同步,但是它调用服务端不能执行耗时操作,不然会报错;原因,查看源码得知,aidl发起调用后,系统会同时发送一个倒计时的handler消息,比如10s,如果10s内aidl返回了就没问题,10s后收到了handler消息而且aidl还没处理就会提示无响应

aidl的回调接口如何保存?

保存使用RemoteCallbackList接口,它是一个集合类,提供远端aidl的注册接口(register_aidl),可以自动过滤重复的aidl,同时自动绑定death接口,感知远端aidl死亡;
它是如何过滤重复的aidl,其内部是用了一个arrayMap接口,key是aidl,value为null,这种key自动过滤重复的接口