导读
多进程
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跨进程进行研究总结下。
进程 | 系统进行资源分配和调度的一个独立单位。不只是程序的代码,还包括当前的活动 |
线程 | 进程的一个实体,是CPU调度和分配的基本单位,比进程更小的能独立运行的基本单位。 |
也就是说进程包含线程,同时一个进程可以包含多个线程。
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)一个进程可以属于多个应用,多个应用可以共用同一个虚拟机,共享同一块内存空间。
默认的创建的APP运行在主进程中,其进程名为包名,那如何定义多进程呢?在AndroidManifest文件中声明组件的时候,使用android:process属性来指定。
不指定process | 默认的为主进程,其进程名为包名 |
android:process=package:xxx/ android:process=xxx |
将运行在package:remote进程中,属于全局进程,其他具有相同shareUID与签名的APP可以跑在该进程中 |
android:process=:xxx | 运行在默认的包名:remote,是APP的私有进程,不允许其他APP访问 |
AIDL通过定义服务端暴漏接口,供客户端使用。底层基于Binder机制来实现跨进程通信。
在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);
}
我们去编写一个跨进程设置用户信息,并且取得最后用户的信息的一个例子。为了方便后面描述内容,将例子在详细一点,即在包名的进程(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 CREATOR = new Parcelable.Creator() {
@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);
}
在AndroidManifest文件中注册service
将该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);
}
}
}
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进程
最后运行之后,发现自己想要的信息也可以显示到界面上
验证代码:打印单例所在的进程号和实例
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
从打印的日志可以看到每个进程中保持各自的单例。
//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等时候,需要进行特殊处理下。
否则会抛出以下异常
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