Android中AIDL的使用(一) 之 AIDL经典示例

在Android开发中IPC(Inter-Process Communication的缩写)多进程通信的使用场景很多。一般地由于某些功能模块需要特殊原因运行在单独的进程中,又或者为了加大一个应用可使用的内存,因为Android对单个应用使用的最大内存是有限制的,又或者需要做一些非常耗内存而又不好回收的事情,在事情完成后直接结束掉进程,等。开启多进程模式很简单,就是给四大组件指定android:process属性即可。IPC的方式有多种,在日常开发中涉及进程间通信时的首选应该就是AIDL了。我们就来以示例的方式学习AIDL的使用。

示例

第一步,新建一个People的类,使它继承Parcelable,代码如下:

package com.zyx.aidldemo;

import android.os.Parcel;
import android.os.Parcelable;

public class People implements Parcelable {

    public int pId;
    public String pName;

    public People() {

    }

    public People(int pId, String pName) {
        this.pId = pId;
        this.pName = pName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(pId);
        dest.writeString(pName);
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public People createFromParcel(Parcel in) {
            return new People(in);
        }

        public People[] newArray(int size) {
            return new People[size];
        }
    };

    private People(Parcel in) {
        pId = in.readInt();
        pName = in.readString();
    }

    @Override
    public String toString() {
        return String.format("pId:%s, pName:%s", pId, pName);
    }
}

说明:

  1. 在Android的AIDL中,仅支持的数据类型有:
    1. 基本数据类型(int、long、char、boolean、double等);
    2. String和CharSequence;
    3. List:只支持ArrayList,里面的每个元素都必须能够被AIDL支持;
    4. Map:只支持HashMap,里面的每个元素都必须能够被AIDL支持;
    5. Parcelable:所有实现了Parcelable接口的对象;        
    6. AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
  2. Parcelable的作用是序列化对象,因为跨进程通信传输对象必须是以序列化和反序列化的方式进行。Parcelable的实现有些繁琐,但性能效率很高。
  3. 序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的。
  4. 反序列化功能由CREATEOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。
  5. 内容描述功能由describeContents方法来完成,几乎在所有情况下这方法都应该返回0,仅当当前对象中存在文件描述符时才返回1。

这里就不进行Parcelable详细的介绍了,大家在使用方面按照上例来一般都不会有问题。

 

第二步,创建AIDL接口文件People.aidl

package com.zyx.aidldemo;


parcelable People;

说明:

  1. AIDL文件中用到的自定义的Parcelable对象,必须新建一个和它同名的AIDL文件,而且要在其中声明它为parcelable类型

 

第三步,创建AIDL接口文件IPeopleManager.aidl

package com.zyx.aidldemo;

import com.zyx.aidldemo.People;

interface IPeopleManager {
    List getPeopleList();
    void addPeople(in People people);
}

IPeopleManager.adil中定义了一个接口,它的功能很简单就是获取列表对象和添加对象

注意:

  1. 注意import com.zyx.aidldemo.People;这句代码,因为自定义的Parcelable对象和AIDL对象不管是否和当前的AIDL文件位于同一个包内,也必须要显式import进来。
  2. addPeople方法的参数中有in关键字,因为aidl除了基本数据类型,其它类型的参数必须标上方向:in、out或者inout,我们需根据实现需要去指定参数类型,因为这在底层实现是有开销的。
  3. AIDL接口中只支持方法,不支持声明静态常量

 

第四步,此时,准备功能已经完成了,先对工程进行初始的编译,因为只要在编译后才可以将aidl文件转化成IPeopleManager.java文件:build\generated\source\aidl\debug\com\zyx\aidldemo\IPeopleManager.java

 

第五步,新建一个服务PeopleManagerService.java,并在AndroidManifest.xml中特意指定它在不同的进程android:process=":remote"代码如:

package com.zyx.aidldemo;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class PeopleManagerService extends Service {
    // 是ArrayList 的一个线程安全的变体
    private CopyOnWriteArrayList mPeopleList = new CopyOnWriteArrayList();

    private Binder mBinder = new IPeopleManager.Stub() {
        @Override
        public List getPeopleList() throws RemoteException {
            return mPeopleList;
        }

        @Override
        public void addPeople(People people) throws RemoteException {
            mPeopleList.add(people);
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

说明

  1. CopyOnWriteArrayListArrayList 的一个线程安全的变体,它支持并发读/写,可以自动的线程同步,并且最终返回一个新的ArrayList传递给客户端。
  2. IPeopleManager.Stub返回一个Binder对象,它的实现就是上面第四步中的IPeopleManager.java里面自动生成

 

第六步,在MainActivity中绑定PeopleManagerService服务,并在绑定回调中进行跨进程添加对象和获得列表对象:

package com.zyx.aidldemo;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            IPeopleManager peopleManager = IPeopleManager.Stub.asInterface(service);
            try {
                People people = new People(1, "子云心");
                // 添加对象
                peopleManager.addPeople(people);
                // 获取对象列表
                List list = peopleManager.getPeopleList();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        public void onServiceDisconnected(ComponentName className) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, PeopleManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}

就这样,一次完整的使用AIDL进行IPC实例就完成。

示例扩展1

在实际开发中远不止上例如此简单,假如现在需要在客户端增加一个监听,好让每次列表更新后就会进行一次通知,也就是一个典型的观察者模式。所以就有了以下的扩展。

第一步,因为AIDL中无法使用普通接口,所以得创建IOnNewPeopleListener.aidl文件:

package com.zyx.aidldemo;

import com.zyx.aidldemo.People;

interface IOnNewPeopleListener {
    void onNewPeople(in People newPeople);
}

第二步,修改IPeopleManager.adil,加两个接口:

package com.zyx.aidldemo;

import com.zyx.aidldemo.People;
import com.zyx.aidldemo.IOnNewPeopleListener;

interface IPeopleManager {
    List getPeopleList();
    void addPeople(in People people);
    // 扩展新增代码:新增接口
    void registerListener(IOnNewPeopleListener listener);
    void unregisterListener(IOnNewPeopleListener listener);
}

第三步,在执行编译后生成新的IPeopleManager.java后,进行PeopleManagerService的修改:

package com.zyx.aidldemo;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class PeopleManagerService extends Service {

    // 是ArrayList 的一个线程安全的变体
    private CopyOnWriteArrayList mPeopleList = new CopyOnWriteArrayList();

    // 扩展新增代码:用于存储回调监听的接口列表
    private CopyOnWriteArrayList mListenerList = new CopyOnWriteArrayList();

    private Binder mBinder = new IPeopleManager.Stub() {
        @Override
        public List getPeopleList() throws RemoteException {
            return mPeopleList;
        }
        @Override
        public void addPeople(People people) throws RemoteException {
            mPeopleList.add(people);
            // 扩展新增代码:新加对象后执行监听回调
            for (int i = 0; i < mListenerList.size(); i++) {
                IOnNewPeopleListener listener = mListenerList.get(i);
                listener.onNewPeople(people);
            }
        }
        // 扩展新增代码:添加监听接口到列表
        @Override
        public void registerListener(IOnNewPeopleListener listener) throws RemoteException {
            if (!mListenerList.contains(listener)) {
                mListenerList.add(listener);
            }
        }
        // 扩展新增代码:移除监听列表中的监听接口
        @Override
        public void unregisterListener(IOnNewPeopleListener listener) throws RemoteException {
            if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);
            }
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

最后一步,就是修改客户端MainActivity.java文件,客户端要注册IOnNewPeopleListener

package com.zyx.aidldemo;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    IPeopleManager mPeopleManager;

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mPeopleManager = IPeopleManager.Stub.asInterface(service);
            try {
                // 扩展新增代码:注册监听
                mPeopleManager.registerListener(mIOnNewPeopleListener);

                People people = new People(1, "子云心");
                mPeopleManager.addPeople(people);

                List list = mPeopleManager.getPeopleList();

            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        public void onServiceDisconnected(ComponentName className) {
        mPeopleManager = null;
        }
    };

    // 扩展新增代码:定义监听接口
    private IOnNewPeopleListener mIOnNewPeopleListener = new IOnNewPeopleListener.Stub() {
        @Override
        public void onNewPeople(People newPeople) throws RemoteException {
            // TODO 这里子线程,如若需要修改UI请使用 Handler
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, PeopleManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        // 扩展新增代码:移除监听
        if (mPeopleManager != null && mPeopleManager.asBinder().isBinderAlive()) {
            try {
                mPeopleManager.unregisterListener(mIOnNewPeopleListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

示例扩展2

从扩展1中,其实存在一个bug,因为客户端MainActivity的移除监听根本就没有成功。why?因为客户端注册和移除注册过程中使用的虽是同一个客户端对象,但通过Binder传递到服务端后,产生了两个不同的对象,因为对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程。所以就有了扩展2的修改:

第一步,修改服务端PeopleManagerService.java的几个方法代码:

// 扩展新增代码:用于存储回调监听的接口列表
// private CopyOnWriteArrayList mListenerList = new CopyOnWriteArrayList();
// 新扩展修改代码:将 CopyOnWriteArrayList 替换掉变成 RemoteCallbackList
private RemoteCallbackList mListenerList = new RemoteCallbackList();

@Override
public void addPeople(People people) throws RemoteException {
    mPeopleList.add(people);
    // 扩展新增代码:新加对象后执行监听回调
    // for (int i = 0; i < mListenerList.size(); i++) {
    //     IOnNewPeopleListener listener = mListenerList.get(i);
    //     listener.onNewPeople(people);
    // }
    // 新扩展修改代码
    final int N = mListenerList.beginBroadcast();
    for (int i = 0; i < N; i++) {
        IOnNewPeopleListener listener = mListenerList.getBroadcastItem(i);
        if (listener != null) {
            try {
                listener.onNewPeople(people);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    mListenerList.finishBroadcast();

}
// 扩展新增代码:添加监听接口到列表
@Override
public void registerListener(IOnNewPeopleListener listener) throws RemoteException {
    // if (!mListenerList.contains(listener)) {
    //     mListenerList.add(listener);
    // }
    // 新扩展修改代码
    boolean success = mListenerList.register(listener);
    final int N = mListenerList.beginBroadcast();
    mListenerList.finishBroadcast();
}
// 扩展新增代码:移除监听列表中的监听接口
@Override
public void unregisterListener(IOnNewPeopleListener listener) throws RemoteException {
    // if (mListenerList.contains(listener)) {
    //     mListenerList.remove(listener);
    // }
    // 新扩展修改代码
    boolean success = mListenerList.unregister(listener);
    final int N = mListenerList.beginBroadcast();
    mListenerList.finishBroadcast();
}

说明

  1. RemoteCallbackList是系统专门提供的用于删除跨进程listener接口,它是一个泛型,支持管理任意AIDL接口。另外它内部自动实现了线程同步的功能。
  2. beginBroadcast和finishBroadcast必须要配对使用,哪怕仅仅获取RemoteCallbackList中的元素个数。

 

点击下载示例源码

 

——本文部分内容参考自《Android开发艺术探索》

 

 

你可能感兴趣的:(Android入门与基础)