Android单个app的aidl跨进程调用

先介绍背景:最近在做一个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跨进程调用。


首先是项目结构

Android单个app的aidl跨进程调用_第1张图片

如果需要想要使用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;
    }
}

接着是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 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;
    }
}

关于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中将服务声明为远程服务:

        

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

Android单个app的aidl跨进程调用_第2张图片


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

Android单个app的aidl跨进程调用_第3张图片

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


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


最后,整个Sample App在https://github.com/yhling93/android上的AidlSample中,有兴趣的可以下载看看。

你可能感兴趣的:(Android)