Android的AIDL跨进程通信

目录

导读

多进程

1、进程和线程的区别

2、Android的多线程

3、定义多进程

AIDL

1、Android studio创建AIDL

2、编写aidl代码

3、注册跨进程服务

4、编写跨进程service的代码

5、在com.j1进程中调用SenderUserService里面的相应方法

验证几个结论

1、每个进程保持各自的静态成员和单例

2、Application多次创建,每个虚拟机都会创建自己的Applicaition

3、注意在unbindService之前一定要检查是不是已经unbind

代码下载地址


导读

最近就是迷上了总结写博客,所以针对AIDL跨进程进行研究总结下。

多进程

1、进程和线程的区别

进程 系统进行资源分配和调度的一个独立单位。不只是程序的代码,还包括当前的活动
线程 进程的一个实体,是CPU调度和分配的基本单位,比进程更小的能独立运行的基本单位。

也就是说进程包含线程,同时一个进程可以包含多个线程。 

2、Android的多线程

Android操作系统是一个多用户的Linux系统,每一个应用就是一个不同的用户。默认情况下,系统会为每一个应用分配唯一的Linux用户的ID。默认情况下,每个应用都在自己的Linux进程中进行。

名词解释-DVM进程:dalivk的虚拟机。每一个Android应用都在自己的进程中进行,拥有独立的dalivk虚拟机实例。而每个DVM都是在Linux中的一个进程,分配一个单独的Linux id,所以DVM进程和Linux进程是一个进程。

Android为每个应用分配一个独立的虚拟机,确切的说是每个进程分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这样在不同的虚拟机(即进程)中访问同一个类的对象时就会产生多份副本,而这些副本之间是相互独立,互不影响。

在应用内,当新开一个进程时,由于系统需要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,启动的时候自然会创建一个新的Application,所以运行在不同进程中的组件是属于不同的虚拟机和Applicaition。

所以多进程也会引入一些问题:

1)每个进程保持各自的静态成员和单例

2)每个进程有自己的进程锁

3)SharedPreferences可靠性下降,不支持并发写

4)Application多次创建,不同的进程跑在不同的虚拟机上面,每个虚拟机都会创建自己的Applicaition

综上所述,

1)一个应用可以有多个进程,所以就会有多个虚拟机,多块内存空间

2)一个进程可以属于多个应用,多个应用可以共用同一个虚拟机,共享同一块内存空间。

3、定义多进程

默认的创建的APP运行在主进程中,其进程名为包名,那如何定义多进程呢?在AndroidManifest文件中声明组件的时候,使用android:process属性来指定。

不指定process 默认的为主进程,其进程名为包名

android:process=package:xxx/

android:process=xxx

将运行在package:remote进程中,属于全局进程,其他具有相同shareUID与签名的APP可以跑在该进程中
android:process=:xxx 运行在默认的包名:remote,是APP的私有进程,不允许其他APP访问

AIDL

AIDL通过定义服务端暴漏接口,供客户端使用。底层基于Binder机制来实现跨进程通信。

1、Android studio创建AIDL

在java同级目录(即main文件夹)中创建aidl文件夹,在main文件夹下从File->new->AIDL下创建aidl文件,会自动生成对应包名的aidl文件。

创建的文件内容如下:

// ISenderUserService.aidl
package com.j1;

// Declare any non-default types here with import statements

interface ISenderUserService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

2、编写aidl代码

我们去编写一个跨进程设置用户信息,并且取得最后用户的信息的一个例子。为了方便后面描述内容,将例子在详细一点,即在包名的进程(com.j1)中去调用私有进程userservice(新开的进程)来设置用户的信息,并从userserice进程中获取用户的信息。

1)创建设置User和读取User的aidl:ISenderUserService.aidl,代码如下:

// ISenderUserService.aidl
package com.j1;

// Declare any non-default types here with import statements
import com.j1.model.User;
import com.j1.IRemoteCallBack;

//一个进程中去给另外一个进程设置,然后从另外一个进程中读出用户信息
interface ISenderUserService {

     void setUserName(String name);
     String getUserName();

     void setUserAge(int age);
     int getUserAge();

     void setUserSex(String sex);
     String getUserSex();

     User getUser();

     void registerPushMessage(IRemoteCallBack callback);
}


AIDL文件中支持的数据类型包括:

基本数据类型、String、CharSequence、List中只支持ArrayList(其中泛型的元素必须被AIDL支持)、Map中只支持HashMap(其中泛型的元素必须被AIDL支持)、所有实现了Parcelable接口的对象、以及所有的AIDL接口。

2)因为我们在aidl中传递了自定义对象,所以该对象User也需要 定义一个aidl文件。其中里面的代码仅有如下即可:

// User.aidl
package com.j1.model;

// Declare any non-default types here with import statements

parcelable User;

这里有几点要注意的地方:

a)aidl文件的包名必须和java文件的包名保持一致。

即代码中的package com.j1.model;必须保持一致

b)如果java文件中定义的对象所在的包和其他aidl文件所在的包不一致,那么在aidl文件夹下,要为该User.aidl文件创建和java文件一致的包名,如图所示

否则在编译的时候会编译报错,如图所示: 

Process 'command '/Users/j1/Documents/android/sdk-macosx/build-tools/27.0.3/aidl'' finished with non-zero exit value 1

出现这种报错,就是由于在aild文件中引入的对象的包名不一致引起的。 

c)自定义的对象一定要实现Parcelable接口。

这个地方稍后会有文档在进行总结。

3)在java下相应的包名文件夹下创建User.java类,代码如下:

package com.j1.model;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by wenjing.liu on 18/10/8 in J1.
 *
 * @author wenjing.liu
 */
public class User implements Parcelable {

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 性别
     */
    private String sex;

    private User(Parcel source) {
        name = source.readString();
        age = source.readInt();
        sex = source.readString();
    }

    //......省略get/set方法,具体可以下载代码进行查看

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(sex);
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

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

    @Override
    public String toString() {
        String buff = "name = " + name + " , age = " + age + " , sex = " + sex;
        return buff;
    }
}

4)上述两种其实都是在com.j1进程中去设置userserice进程中的信息,同样userserice进程也可以主动去向com.j1进程中发送消息。

我们可以在这基础上在增加一个功能:在userserice的进程启动后10s,主动向com.j1进程发送"hello com.j1"的字符串,那么就需要我们在ISenderUserService.aidl中在增加一个接口方法:

  void registerPushMessage(IRemoteCallBack callback);

同样就要在ISenderUserService.aidl中引入import com.j1.IRemoteCallBack;并且还要定义IRemoteCallBack.aidl文件

// IRemoteCallBack.aidl
package com.j1;

// Declare any non-default types here with import statements
interface IRemoteCallBack {
   void pushCallBack(String hello);
}

3、注册跨进程服务

在AndroidManifest文件中注册service

        <service
            android:name=".service.SenderUserService"
            android:process=":userserice"/>

将该service注册成私有的进程。

4、编写跨进程service的代码

无非就是继承于Service,同时也要定义一个Binder类继承于ISenderUserService.Stub来编写相应的逻辑代码

package com.j1.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.j1.IRemoteCallBack;
import com.j1.ISenderUserService;
import com.j1.model.User;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by wenjing.liu on 18/10/8 in J1.
 *
 * @author wenjing.liu
 */
public class SenderUserService extends Service {

    ISenderUserBinder binder;
    private ScheduledThreadPoolExecutor executorExecutor;

    @Override
    public void onCreate() {
        super.onCreate();
        if (binder == null) {
            binder = new ISenderUserBinder();
        }
        //为了回调的时候开启一个定时器
        if (executorExecutor == null) {
            executorExecutor = new ScheduledThreadPoolExecutor(1);

        }
    }

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

    @Override
    public boolean onUnbind(Intent intent) {
        if (executorExecutor == null) {
            return super.onUnbind(intent);
        }
        executorExecutor.shutdown();
        return super.onUnbind(intent);
    }

    public class ISenderUserBinder extends ISenderUserService.Stub {
        String name = null;
        int age = -1;
        String sex = null;

        @Override
        public void setUserName(String name) throws RemoteException {
            this.name = name;
        }

        @Override
        public String getUserName() throws RemoteException {
            return name;
        }

        @Override
        public void setUserAge(int age) throws RemoteException {
            this.age = age;
        }

        @Override
        public int getUserAge() throws RemoteException {
            return age;
        }

        @Override
        public void setUserSex(String sex) throws RemoteException {
            this.sex = sex;
        }

        @Override
        public String getUserSex() throws RemoteException {
            return sex;
        }

        @Override
        public User getUser() throws RemoteException {
            if (name == null || age == -1 || sex == null) {
                throw new IllegalArgumentException("信息不完整");
            }
            return new User(name, age, sex);
        }

        @Override
        public void registerPushMessage(final IRemoteCallBack callback) throws RemoteException {
            if (executorExecutor == null) {
                return;
            }
            //延时10s之后,将"hello com.j1"返回
            executorExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    try {
                        callback.pushCallBack("hello com.j1");
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }, 10, TimeUnit.SECONDS);
        }
    }
}

5、在com.j1进程中调用SenderUserService里面的相应方法

 ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //获得接口类
            userService = ISenderUserService.Stub.asInterface(service);
            //com.j1进程去设置userservice进程中的相关内容
            try {
                userService.registerPushMessage(callBack);
                userService.setUserName("张三");
                userService.setUserAge(20);
                userService.setUserSex("male");
                //经过userservice进程进行计算,得到User对象   
                tvUser.setText(userService.getUser().toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    /**
     * userservice进程主动向com.j1发送信息
     */
    private IRemoteCallBack.Stub callBack = new IRemoteCallBack.Stub() {
        @Override
        public void pushCallBack(String hello) throws RemoteException {
            tvCallBack.setText(hello);
        }
    };

我们看下在SenderUserService没有启动之前,该应用中只有一个com.j1的进程

当服务启动之后,会增加com.j1:userservice进程 

最后运行之后,发现自己想要的信息也可以显示到界面上

验证几个结论

1、每个进程保持各自的静态成员和单例

验证代码:打印单例所在的进程号和实例

    public static synchronized SingleInstance getInstance() {
        if (instance == null) {
            Log.d("SingleInstance", "pid = " + Process.myPid() + " , instance = " + instance);
            instance = new SingleInstance();
            return instance;
        }
        Log.d("SingleInstance", "pid = " + Process.myPid() + " , instance = " + instance);
        return instance;
    }

具体在com.j1进程和 com.j1:userservice进程中调用getInstance()

//com.j1进程
10-09 16:35:38.791 3961-3961/com.j1 D/SingleInstance: pid = 3961 , instance = null
10-09 16:35:45.566 3961-3961/com.j1 D/SingleInstance: pid = 3961 , instance = com.j1.SingleInstance@7d6cf39

//com.j1:userservice进程
10-09 16:35:45.622 3986-3986/? D/SingleInstance: pid = 3986 , instance = null
10-09 16:35:45.631 3986-3998/? D/SingleInstance: pid = 3986 , instance = com.j1.SingleInstance@173ba7d

从打印的日志可以看到每个进程中保持各自的单例。 

2、Application多次创建,每个虚拟机都会创建自己的Applicaition

//com.j1进程
10-09 16:19:22.482 3212-3212/com.j1 D/AidlApplication: pid = 3212
10-09 16:20:22.733 3212-3212/com.j1 V/MainActivity:  onServiceConnected pid = 3212
10-09 16:20:32.738 3212-3225/com.j1 V/MainActivity:  IRemoteCallBack pid = 3212

//com.j1:userservice进程
10-09 16:20:22.728 3295-3295/? D/AidlApplication: pid = 3295
10-09 16:20:22.734 3295-3307/? W/SenderUserService:  registerPushMessage pid = 3295
10-09 16:20:22.736 3295-3308/? W/SenderUserService: setUserName pid = 3295

从打印出来的pid可以看出,每创建一个进程,都会加载AidlApplication,所以如果在AidlApplication做一些初始化的逻辑,例如初始化第三方的sdk等时候,需要进行特殊处理下。

3、注意在unbindService之前一定要检查是不是已经unbind

否则会抛出以下异常

 java.lang.RuntimeException: Unable to destroy activity {com.j1/com.j1.MainActivity}: java.lang.IllegalArgumentException: Service not registered: com.j1.MainActivity$2@6cf0cb6

可以使用下面的方式进行避免:

    private void stopService() {
        if (connection == null || !isBinderService) {
            return;
        }
        unbindService(connection);
        connection = null;
        isBinderService = false;
    }

其中isBinderService在bindService的时候进行赋值

isBinderService = bindService(intent, connection, Context.BIND_AUTO_CREATE);

代码下载地址

里面涉及到的代码下载地址:

https://download.csdn.net/download/nihaomabmt/10709078

猜你喜欢

转载自blog.csdn.net/nihaomabmt/article/details/82906937