Android多进程模式
一个应用使用多进程模式,就是给四大组件(Activity,Service,Receiver,ContentProvider)在清单文件中指定android:process属性。无法给一个线程或者一个实体类指定运行时的进程
开启多进程的配置
在清单文件中配置process属性
<activity android:name=".ThirdActivity"
android:process="com.wzh.activitydemo.remote"
/>
<activity android:name=".SecondActivity"
android:process=":remote"
/>
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-
:remote
冒号+名称,运行时,会加上当前包名,com.wzh.activitydemo:remote,这是一种简写,会自动带上包名,这种表示当前应用的私有进程,其他应用的组件不可以和它跑在同一进程中 -
完整包名+remote
com.wzh.activitydemo.remote,这种属于全局进程,其他应用通过ShareUID方式可以和它跑在同一进程中 -
默认进程
如果不指定process属性,则运行在当前应用的进程中,默认是当前包名
多进程模式的运行机制
每个进程都分配一个独立虚拟机,不同的虚拟机在内存分配上有不同地址空间,导致在不同虚拟机访问同一个类的对象产生多份副本
一般来说,多进程会造成的问题:
- 静态成员和单例完全失效
- 线程同步机制完全失效
- SharePreferences的可靠性下降
- Application会创建多次
IPC基础概念
主要包含三方面的内容:Serializable接口、Parcelable接口以及Binder。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable
Serializable接口
java提供的一个序列化接口,空接口
private static final long serialVersionUID = 1239893457234L; // 系统自动生成的id或者使用1L,两者没有区别
有serialVersionUID在很大程度上避免反序列化过程的失败,在删除某个变量或增加新的成员变量,这时反序列化过程依然能够成功。如果类结构发生了非常规性的改变,对类结构有毁灭性的改变,序列化依然失败
Parcelable接口
android提供的序列化接口
public class Book implements Parcelable {
public int id;
public String name;
public String author;
public Book(int id, String name, String author) {
this.id = id;
this.name = name;
this.author = author;
}
protected Book(Parcel in) {
id = in.readInt();
name = in.readString();
author = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {//反序列化
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {//内容描述,几乎都是返回0
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {//序列化
dest.writeInt(id);
dest.writeString(name);
dest.writeString(author);
}
}
Serializable和Parcelable如何选择?
serializable使用简单但是开销大,序列化和反序列化过程需要大量I/O操作。
Parcelable效率高,但是使用比较麻烦。
建议还是使用Serializable
Binder
Binder是Android的一个类,实现了IBinder接口,是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟物理设备,设备驱动/dev/binder,该通信方式在Linux中没有。
Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager)和相应ManagerService的桥梁。
进程间的通信,一个进程不是对另一个进程进行操作的,而是通过Binder工具进行进程间通信的
Android开发中,Binder主要用在Service中,包括AIDL和Messenger(底层是AIDL),普通的service中的Binder不涉及进程间通信。
使用AIDL分析Binder
- 创建三个文件Book.java,Book.aidl,IBookManager.aidl
Book实现了Parcelable接口
Book.aidl是Book类在AIDL中声明
IBookManager.aidl是定义的接口,里面可以自定义方法
- Book.aidl
package com.wzh.activitydemo;
parcelable Book;
- IBookManager.aidl
// IBookManager.aidl
package com.wzh.activitydemo;
import com.wzh.activitydemo.Book;//需要引入Parcelable类
// Declare any non-default types here with import statements
interface IBookManager {
/**
* 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);
List<Book> getBookList();
void addBook(in Book book);//注意这里参数中需要写in或out
}
AIDL 支持的数据类型如下:
-
基本数据类型
-
String 和charsequence
-
ArrayList和HashMap, 并且其中包含的元素也必须被AIDL 支持
-
实现Parcelable 接口的对象。
-
其他AIDL 接口。
这里我要传递的是我自定义的类型, 所以必须自定义类必须实现Parcelable接口。
实现Parcelable 接口必须定义对象生成器CREATOR(注意只能命名为CREATOR),和实现以下方法:
public int describeContents()
public void writeToParcel(Parcel dest, int flags)
- 编译后生成java类
编译后生成的Java类
例如上面生成的IBookManager接口,继承IInterface接口,所有可以在Binder中传输的接口都需要继承IInterface接口。
IBookManger接口的结构核心实现是:IBookManager内部静态抽象类Stub和Stub中的静态内部类Proxy
- 在IBookManager中声明了一个内部类Stub
public static abstract class Stub extends android.os.Binder implements com.wzh.activitydemo.IBookManager{
//Binder的唯一标识,一般用当前Binder类类名表示
private static final java.lang.String DESCRIPTOR = "com.wzh.activitydemo.IBookManager";
//用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。
//这种转换是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象
public static com.wzh.activitydemo.IBookManager asInterface(android.os.IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//判断客户端和服务端是否是同一进程
if (((iin!=null)&&(iin instanceof com.wzh.activitydemo.IBookManager))) {
return ((com.wzh.activitydemo.IBookManager)iin);
}
return new com.wzh.activitydemo.IBookManager.Stub.Proxy(obj);
}
//返回当前对象
@Override public android.os.IBinder asBinder()
{
return this;
}
/**
*
* @param code 服务端根据code确定客户端所请求的目标方法是什么
* @param data 取出目标方法所需的参数
* @param reply 执行完成后,向reply写入返回值
* @param flags
* @return 返回false时,客户端请求失败
* @throws android.os.RemoteException
*/
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{}
}
- Stub中定义了Proxy类
private static class Proxy implements com.wzh.activitydemo.IBookManager
{
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.wzh.activitydemo.Book> getBookList() throws android.os.RemoteException{
//输入型对象
android.os.Parcel _data = android.os.Parcel.obtain();
//输出型对象
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.wzh.activitydemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//RPC远程请求,调用服务端的onTransact方法
//当前线程会被挂起直至服务端进程返回数据,不能使用UI线程发起此远程请求
boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getBookList();
}
_reply.readException();
//从_reply中取出结果,并返回
_result = _reply.createTypedArrayList(com.wzh.activitydemo.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.wzh.activitydemo.Book book) throws android.os.RemoteException{}
}
当客户端和服务端都位于同一进程,不会调用transact过程,当两者位于不同进程时,才会执行
IPC方式
Bundle
由于Bundle实现了Parcelable接口,可以在不同进程间传输,可以在Bundle中附加需要传输的信息,通过Intent发送数据,前提是数据类型必须是Bundle支持的
文件共享
两个进程通过读/写同一个文件夹交换数据。文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
从本质上说,SharedPreferences也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPrefences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,有很大几率会丢失数据,不建议在进程间通信使用。
Messenger
Messenger翻译为信使,通过它可以在不同进程中传递Message对象。是一种轻量级的IPC方案,底层实现是AIDL。由于它一次处理一个请求,因此在服务端我们不用考虑线程同步问题
构造函数
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
服务端和客户端交互
- 创建Service服务端
/**
* create by wzh
* 服务端
*/
public class MessageService extends Service {
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(@NonNull @NotNull Message msg) {
if (msg.what == Contants.MSG_FROM_CLIENT) {
Bundle bundle = msg.getData();
String clientMsg = bundle.getString("client msg");
System.out.println("服务端收到信息----:" + clientMsg);
//获取客户端的信使
Messenger replyTo = msg.replyTo;
if (replyTo != null) {
Message replyMsg = Message.obtain();
replyMsg.what = Contants.MSG_FROM_SERVER;
replyMsg.arg1 = 888;
Bundle replyBundle = new Bundle();
replyBundle.putString("service msg", "我是服务端,我收到你发送的消息了");
replyMsg.setData(replyBundle);
//发送给客户端
try {
replyTo.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
//创建信使对象
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {//返回Binder对象
return mMessenger.getBinder();
}
}
- 服务端配置为另一个进程
<service android:name="com.wzh.messenger.MessageService"
android:process=":remote"
/>
- 客户端绑定服务
//接收服务端发送的消息
private val replyHandler :Handler by lazy {
Handler(Looper.getMainLooper(),object :Handler.Callback{
override fun handleMessage(msg: Message): Boolean {
if (msg.what ==Contants.MSG_FROM_SERVER){//收到服务端发送过来的信息
val arg1 = msg.arg1
val data = msg.data
val serviceMsg = data.getString("service msg")
println("客户端收到信息----:${arg1},${serviceMsg}")
}
return true
}
})
}
//接收服务端发送消息的信使
private val replyMessenger:Messenger by lazy {
Messenger(replyHandler)
}
private val serviceConnection by lazy {
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
//创建信使
val messenger = Messenger(service)
//封装信息
val message = Message.obtain(null, Contants.MSG_FROM_CLIENT)
val bundle = Bundle()
bundle.putString("client msg","我是客户端,跨进程发送信息")
//可以用Bundle,arg1,arg2,what,replyTo传递数据,不能使用obj
message.data = bundle
//如果需要服务端发送信息到客户端时,需要设置replyTo
message.replyTo = replyMessenger
try {
//发送
messenger.send(message)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
}
private fun startMessenger() {
val intent = Intent(this, MessageService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
上面的操作即可实现客户端和服务端在不同进程中进行交互。
Messenger中进行数据传递必须将数据放入Message中,Messenger和Message都实现了Parcelable接口,因此可以跨进程传输。Message中能使用的载体有what,arg1,arg2,Bundle以及replyTo,不支持使用obj。
Messenger是以串行的方式处理客户端发来的消息,如果有大量的并发请求,那么Messenger就不合适了。Messenger主要为了传递消息,如果需要跨进程调用服务端的方法,Messenger就无法做到了
使用AIDL
- AIDL支持的数据类型
- 其中自定义Parcelable对象和AIDL对象必须显示import。
如果AIDL文件中用到了自定义Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型
package com.wzh.activitydemo;
parcelable Book;
- AIDL中除了基本数据类型,其他类型的参数必须标上方向:in(输入参数),out(输出参数)或者inout(输入输出型参数)
- AIDL中只支持方法,不支持声明静态常量,与传统接口有区别,AIDL中无法使用普通接口,只能是有AIDL接口
- 如果需要使用观察者模式时,定义的存储观察者列表使用RemoteCallbackList(本质不是List),因为普通的List,通过跨进程后,都是新的对象,无法进行remove相同的观察者
开发步骤
- 创建aidl文件
例如IBookManager.aidl
// IBookManager.aidl
package com.wzh.activitydemo;
//记得显式导入Parcelable类
import com.wzh.activitydemo.Book;
// Declare any non-default types here with import statements
interface IBookManager {
/**
* 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);
//自定义的方法,自定义Book类型,需要创建同名的Book.aidl文件
//在Book.aidl文件中标注parcelable类型
List<Book> getBookList();
void addBook(in Book book);
}
- 新建与Parcelable对象的同名文件Book.aidl
package com.wzh.activitydemo;
parcelable Book;
- 新建服务端,BookService
public class BookService extends Service {
private IBookManager.Stub mBinder = new IBookManager.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public List<Book> getBookList() throws RemoteException {
//编写相应的逻辑
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;//返回Binder对象
}
}
- 客户端的实现
绑定service,在ServiceConnection中获取IBookManager对象
private val serviceConnection by lazy {
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val bookManager: IBookManager = IBookManager.Stub.asInterface(service)
//调用bookManager相应方法,如果调用方法,服务端耗时过久,会导致ANR,耗时的方法需要在非UI线程中调用
}
ContentProvider
抽象类,底层实现是Binder,系统预置了很多ContentProvider,比如通讯录,日程信息等。子类需要实现6个方法。创建时机在Application执行onCreate之前
public class BookProvider extends ContentProvider {
//contentProvider创建
@Override
public boolean onCreate() {
//主线程
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
//Binder线程池中
return null;
}
/**
* 用来返回一个uri请求所对应的MIME类型,比如图片,视频等
* 如果不关心这个类型,可以返回null或*/*
* @param uri
* @return
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
//Binder线程池中
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
//Binder线程池中
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
//Binder线程池中
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
//Binder线程池中
return 0;
}
}
ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,行代表一条记录,列代表字段,和数据库类似。除了表格数据外,ContentProvider还支持文件数据,比如图片,视频等,文件数据和表格数据的结构不同。Android系统提供的MediaStore就是文件类型的ContentProvider。
ContentProvider对底层数据的存储方式,既可以使用SQLite数据库,文件存储,内存存储等
清单文件注册
<provider
android:authorities="com.wzh.activitydemo.provider"
android:name="com.wzh.contentProvider.BookProvider"
android:permission="com.wzh.PROVIDER"
android:process=":wzh_provider"
/>
authorities是contentProvider的唯一标识,必须唯一
permission标识provider的权限,访问此provider必须带上权限才能方法,否则异常
Socket
socket是网络通信的概念,对应网络传输控制的TCP和UDP协议
Binder连接池
AIDL是一种最常用的进程间通信方式