您的位置:首页 > 移动开发

Android单个app的aidl跨进程调用

2016-09-11 22:32 267 查看
先介绍背景:最近在做一个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<Integer> list1;
private List<Integer> 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<MySubObject> CREATOR = new Creator<MySubObject>() {
@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<Integer> getList1() {
return list1;
}

public void setList1(List<Integer> list1) {
this.list1 = list1;
}

public List<Integer> getList2() {
return list2;
}

public void setList2(List<Integer> list2) {
this.list2 = list2;
}
}


接着是MyObject类型:

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<MySubObject> 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<MyObject> CREATOR = new Creator<MyObject>() {
@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<MySubObject> getList() {
return list;
}

public void setList(List<MySubObject> list) {
this.list = list;
}
}


关于Parcelable和Parcel相关的知识点请查找官方android文档,这里不是讲述重点,关于Parcel和Parcelable的一些坑我也用注释写了出来
至此我们的实体类型已经定义好了,记住这两个东西的所在目录 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;
}
};
}


这里重写了onTransact方法是为了能够输出远程服务的错误信息,便于定位错误和调试。

然后就是在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) {

}
};


最后在AndroidManifest.xml中将服务声明为远程服务:

<service android:name=".service.MyService"
android:process=":remote"
android:exported="false"/>


至此大功告成,下面让我们来看一下运行效果:





我们在Android Monitor中可以看到两个process的存在,并且远程服务的进程名字就是刚才在AndroidManifest文件中声明的。



运行App,也能够通过远程服务设置和获取对象。

当然,回到我们最初的问题,其实这样做也无法避免远程服务进程被回收掉,我们所做的只不过是尽量提升进程的优先级使其不被回收,如果真的被回收了,我们还是需要去重新执行初始化组件的操作。个人感觉有些第三方ROM对内存管理做得还不错,同时android系统版本越高,内存回收机制更优。不过内存管理这东西,我觉得我们程序员自己还是要尽力管管好,不能完全依赖OS。

最后,整个Sample App在https://github.com/yhling93/android上的AidlSample中,有兴趣的可以下载看看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息