先介绍背景:最近在做一个app,app里有一个组件,由于该组件需要操作sqlite数据库,因此组件其实是依赖于context的,而且组件初始化比较耗时(有一些文件要解压读取),所以我们希望这个组件在一开始初始化好之后就可以一直用着。我们的app有一个功能是调用系统相册应用选取照片,在调用之后发现我们自己的app会被系统回收(我们的app内置了一个in-memory database,因此挺占内存的),组件为null导致app crash。这个问题在RAM较小(1G RAM以下)和系统较低(Android4.4及以下)的手机上出现频繁,不得不去解决。
首先分析问题,为什么会被crash,我们知道在android的进程优先级中(进程优先级可自行在android官方文档找到),前台app,前台service的优先级是最高的,即系统不会去回收当前用户正在交互的activity或者是service,然而当我们在调用系统相册应用时,我们的app变成了后台进程,系统发现内存不够用了便会去回收我们的app。而且由于我们的组件是和app紧耦合在一起的(在同一个进程中),因此当app被回收的时候,这个组件也将一同被回收。
然后尝试解决问题,首先是优化app内存的使用,其次是将组件放置到一个前台service中,发现依然会被系统回收。接着我们只能试试在将组件放在一个单独的service process中,提升service优先级尽量避免被杀死。网上教程大部分都是两个app之间的跨进程调用,找到一篇单个app的跨进程调用却是用非常麻烦的handler messenger机制实现,官方文档也很模糊,因此本文说的就是如何在单个app中实现aidl跨进程调用。
首先是项目结构
如果需要想要使用aidl远程调用,需要声明aidl接口,在本例中IMyAidlInterface.aidl就是接口文档,接口文档如何写,支持什么类型请参考android官方文档,下面给出我的例子,里面import的两个自定义类型会在稍后解释。
package sample.adam.aidlsample;
// Declare any non-default types here with import statements
import sample.adam.aidlsample.model.MyObject;
import sample.adam.aidlsample.model.MySubObject;
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
// The "in" tag means the data flow can just be from client to server, the object of the client won't be changed
void setObject(in MyObject obj);
// The "out" tag means the data flow can just be from server to client,
// the object of the client is commonly null, the client can notices the changes of the object
void getObjectMethod1(out MyObject obj);
MyObject getObjectMethod2();
// There is also "inout" tag which means the flow is two-way, either client or server can notice the changes of the object
}
其次,我的接口参数为自定义类型,若想要aidl传递自定义类型,则该类型一定要实现Parcelabel接口,而且需要定义该类型的aidl文件且目录结构要一致。
下面具体介绍,首先是MySubObject类型:
package sample.adam.aidlsample.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
/**
* Created by yhling on 9/10/16.
*/
public class MySubObject implements Parcelable {
private int[] array;
private List list1;
private List list2;
public MySubObject() {}
protected MySubObject(Parcel in) {
array = in.createIntArray();
/********************************************************
*
* Attention!
* You should new the List instance first,
* otherwise you'll get an NullPointerException when calling readList method
*
********************************************************/
list1 = new ArrayList<>();
list2 = new ArrayList<>();
in.readList(list1, Integer.class.getClassLoader());
in.readList(list2, Integer.class.getClassLoader());
}
public static final Creator CREATOR = new Creator() {
@Override
public MySubObject createFromParcel(Parcel in) {
return new MySubObject(in);
}
@Override
public MySubObject[] newArray(int size) {
return new MySubObject[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeIntArray(array);
parcel.writeList(list1);
parcel.writeList(list2);
}
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
public List getList1() {
return list1;
}
public void setList1(List list1) {
this.list1 = list1;
}
public List getList2() {
return list2;
}
public void setList2(List list2) {
this.list2 = list2;
}
}
package sample.adam.aidlsample.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
/**
* Created by yhling on 9/10/16.
*/
public class MyObject implements Parcelable {
private int byteLen;
private String str;
private byte[] bytes;
private List list;
public MyObject() {}
protected MyObject(Parcel in) {
byteLen = in.readInt();
str = in.readString();
/********************************************************
*
* Attention!
* You should new the List And Array instance first,
* otherwise you'll get an NullPointerException when calling readList method
*
********************************************************/
bytes = new byte[byteLen];
list = new ArrayList<>();
in.readByteArray(bytes);
//bytes = in.createByteArray();
in.readList(list, MySubObject.class.getClassLoader());
}
public static final Creator CREATOR = new Creator() {
@Override
public MyObject createFromParcel(Parcel in) {
return new MyObject(in);
}
@Override
public MyObject[] newArray(int size) {
return new MyObject[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(byteLen);
parcel.writeString(str);
parcel.writeByteArray(bytes);
parcel.writeList(list);
}
public void readFromParcel(Parcel in) {
byteLen = in.readInt();
str = in.readString();
/********************************************************
*
* Attention!
* You should new the List And Array instance first,
* otherwise you'll get an NullPointerException when calling readList method
*
********************************************************/
bytes = new byte[byteLen];
list = new ArrayList<>();
in.readByteArray(bytes);
//bytes = in.createByteArray();
in.readList(list, MySubObject.class.getClassLoader());
}
public int getByteLen() {
return byteLen;
}
public void setByteLen(int byteLen) {
this.byteLen = byteLen;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
}
至此我们的实体类型已经定义好了,记住这两个东西的所在目录 app/src/main/java/sample/adam/aidlsample/model/
接着我们定义实体类型的aidl文件:
// MyObject.aidl
package sample.adam.aidlsample.model;
// Declare any non-default types here with import statements
parcelable MyObject;
// MySubObject.aidl
package sample.adam.aidlsample.model;
// Declare any non-default types here with import statements
parcelable MySubObject;
这两个文件,一定要放在aidl目录下与java目录下相同的结构中!即 app/src/main/aidl/sample/adam/aidlsample/model/
一个前面是app/src/main/java,另一个前面是app/src/main/aidl,后面结构必须一样,不然编译时aidl会提示无法找到实现了Parcelable接口的类。
当自定义类写完,aidl文件写完,我们就可以build project(Android Studio Toolbar -> Build -> Make Module 'app'来生成相对应的Interface java文件,生成的java接口文件在如图所示目录中:
在build/generated/source/aidl/debug/sample.adam.aidlsample中,我们将IMyAidlInterface文件拷贝到我们的项目中,具体可看上面的项目结构图。
接口生成好之后,就是在Service中实现接口了:
package sample.adam.aidlsample.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import sample.adam.aidlsample.model.MyObject;
/**
* Created by yhling on 9/10/16.
*/
public class MyService extends Service {
private MyObject myObject;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mServiceBinder;
}
private final IMyAidlInterface.Stub mServiceBinder = new IMyAidlInterface.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
try {
return super.onTransact(code, data, reply, flags);
} catch (RuntimeException e) {
Log.e(MyService.class.getSimpleName(), "Exception!", e);
}
return super.onTransact(code, data, reply, flags);
}
@Override
public void setObject(MyObject obj) throws RemoteException {
myObject = obj;
}
@Override
public void getObjectMethod1(MyObject obj) throws RemoteException {
obj.setByteLen(myObject.getByteLen());
obj.setBytes(myObject.getBytes());
obj.setStr(myObject.getStr());
obj.setList(myObject.getList());
}
@Override
public MyObject getObjectMethod2() throws RemoteException {
return MyService.this.myObject;
}
};
}
然后就是在MainActivity中调用远程服务了,这里仅仅贴出核心代码:
private IMyAidlInterface mRemoteService;
protected void onCreate(Bundle savedInstanceState) {
//...
bindService(new Intent(this, MyService.class), mServiceConnection, BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mRemoteService = IMyAidlInterface.Stub.asInterface(iBinder);
Toast.makeText(MainActivity.this, "Service is connected", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
我们在Android Monitor中可以看到两个process的存在,并且远程服务的进程名字就是刚才在AndroidManifest文件中声明的。
运行App,也能够通过远程服务设置和获取对象。
当然,回到我们最初的问题,其实这样做也无法避免远程服务进程被回收掉,我们所做的只不过是尽量提升进程的优先级使其不被回收,如果真的被回收了,我们还是需要去重新执行初始化组件的操作。个人感觉有些第三方ROM对内存管理做得还不错,同时android系统版本越高,内存回收机制更优。不过内存管理这东西,我觉得我们程序员自己还是要尽力管管好,不能完全依赖OS。
最后,整个Sample App在https://github.com/yhling93/android上的AidlSample中,有兴趣的可以下载看看。