Android(安卓)AIDL进程间通信(IPC)
IPC是Inter-Process Communication的缩写,意思是进程间通信(如果不清楚什么是进程,请左转问度娘后在回转来看)。Android中IPC的方式有很多种,今天先说最灵活也是最常用的一种,即AIDL(Android Interface Definition Language)方式。
创建多进程的方式有两种,一,给四大组件指定android:process属性,二,通过JNI在native层fork一个新的进程。今天Demo以第一种为例,为了方便查看此Demo没有写两个工程,而是在同一个应用内给SecondActivity指定
android:process="com.dyk.aidltest.SecondActivity"这样就相当于SecondActivity是另一个应用的Activity。下面开始讲解Demo。
Demo下载
工程大纲:
首先新建一个实现了Parcelable的Book类,用作测试Bean。
package com.example.aidl;import android.os.Parcel;import android.os.Parcelable;public class Book implements Parcelable{public int bookId;public String bookName;public Book(int bookId, String bookName) {super();this.bookId = bookId;this.bookName = bookName;}@Overridepublic int describeContents() {return 0;}/**序列化*/@Overridepublic void writeToParcel(Parcel out, int flags) {out.writeInt(bookId);out.writeString(bookName);}private Book(Parcel source){bookId = source.readInt();bookName = source.readString();}/**反序列化*/public static final Creator<Book> CREATOR = new Creator<Book>() {@Overridepublic Book createFromParcel(Parcel source) {return new Book(source);}@Overridepublic Book[] newArray(int size) {return new Book[size];}};}
接着新建需要的aidl文件
package com.example.aidl;parcelable Book;
package com.example.aidl;import com.example.aidl.Book;interface IBookManager{List<Book> getBookList();void addBook(in Book book);}
注意:Book必须实现Parcelable接口(序列化,反序列化),参数in代表输入型参数,out代表输出型参数,inout代表输入输出型参数,否则aidl不支持。就算Book和aidl文件在同一包下,也需要导包。如果AIDL用到了自定义的parcelable对象,则必须建立一个和它同名的aidl文件,并在其中声明它为parcelable类型。AIDL接口中只支持方法,不支持静态常量,这一点区别于传统的接口。
新建上述文件夹后可以在gen/package name目录下可以查找到系统自动为我们生成的IBookManager。乍一看这个类有点乱,format后可以发现这个类并没有想象的那么复杂。在此简单分析下这个类,不感兴趣的可以略过这一阶段,直接进入下一分割线。
--------------------------------------------------------------华丽丽的分割线--------------------------------------------
首先这个IBookManager继承了IInterface接口,里面有一个抽象类Stub和两个抽象方法。
public static abstract class Stub extends android.os.Binder implements com.example.aidl.IBookManager
<span style="white-space:pre"></span>public java.util.List<com.example.aidl.Book> getBookList() throws android.os.RemoteException;public void addBook(com.example.aidl.Book book) throws android.os.RemoteException;S tub里面有几个比较重要的方法:asInterface,asBinder,onTransact和一个代理类
private static class Proxy implements com.example.aidl.IBookManagerasInterface:将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种对象区分进程,若在统一进程,返回服务端的Stub本身,否则返回封装后的Stub.proxy对象。
if (((iin != null) && (iin instanceof com.example.aidl.IBookManager))) {return ((com.example.aidl.IBookManager) iin);}return new com.example.aidl.IBookManager.Stub.Proxy(obj);asBinder:返回当前的Binder对象
onTransact:服务端通过参数code确定客户端请求的目标方法,接着从data中取出目标方法所需的参数,然后执行目标方法,最后向reply中写入返回值。
<span style="white-space:pre"></span>public boolean onTransact(int code, android.os.Parcel data,android.os.Parcel reply, int flags)throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_getBookList: {data.enforceInterface(DESCRIPTOR);java.util.List<com.example.aidl.Book> _result = this.getBookList();reply.writeNoException();reply.writeTypedList(_result);return true;}case TRANSACTION_addBook: {data.enforceInterface(DESCRIPTOR);com.example.aidl.Book _arg0;if ((0 != data.readInt())) {_arg0 = com.example.aidl.Book.CREATOR.createFromParcel(data);} else {_arg0 = null;}this.addBook(_arg0);reply.writeNoException();return true;}case TRANSACTION_registerListener: {data.enforceInterface(DESCRIPTOR);com.example.aidl.IOnNewBookArrivedListener _arg0;_arg0 = com.example.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());this.registerListener(_arg0);reply.writeNoException();return true;}case TRANSACTION_unregisterListener: {data.enforceInterface(DESCRIPTOR);com.example.aidl.IOnNewBookArrivedListener _arg0;_arg0 = com.example.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());this.unregisterListener(_arg0);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}
-------------------------------------------------------------------华丽丽的分割线-------------------------------------------------------------------------
接下来就可以写我们的服务端了,这里使用一个service充当服务端。先上代码
package com.example.service;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;import android.app.Service;import android.content.Intent;import android.content.pm.PackageManager;import android.os.IBinder;import android.os.RemoteCallbackList;import android.os.RemoteException;import android.util.Log;import com.example.aidl.Book;import com.example.aidl.IBookManager.Stub;import com.example.aidl.IOnNewBookArrivedListener;public class MyService extends Service {public static final String TAG = "MyService";// CopyWriteArrayList支持并发读/写// AIDL方法是在服务端的Binder线程池中执行的,// 因此当多个客户端同时连接时,会存在多个线程同时访问的情形// 类似的还有ConcurrentHashMap/** 书籍列表 */private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();/** Stub继承Binder类实现了IBookManager接口 */public class MyServiceImpl extends Stub {@Overridepublic List<Book> getBookList() throws RemoteException {for (int i = 0; i < 10; i++) {Book mBook = new Book(i, "平凡的世界");mBookList.add(mBook);}return mBookList;}@Overridepublic void addBook(Book book) throws RemoteException {mBookList.add(book);@Overridepublic IBinder onBind(Intent intent) {return new MyServiceImpl();}}在onBind中返回继承Stub的内部类MyServiceImpl。上文也提到过Stub继承Binder类实现了IBookManager接口并且本身是一个抽象类。所以非抽象子类需要重写getBookList()和addBook(Book book)方法。
服务器端暂时到此结束,接下来就到客户端了。
package com.example.aidltest;import com.example.aidl.Book;import com.example.aidl.IBookManager;import com.example.aidl.IOnNewBookArrivedListener;import android.app.Activity;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity implements OnClickListener {public static final String TAG = "MainActivity";private Button bindService;private Button startSecondActivity;private TextView tvShow;private IBookManager mIBookManager;private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {//<span style="font-family: Arial, Helvetica, sans-serif;">onServiceDisconnected</span><span style="font-family: Arial, Helvetica, sans-serif;">和onServiceConnected方法都在UI线程中运行</span>//所以不要在它们里面直接调用服务器端的耗时方法}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// service就是com.example.service.MyService$MyServiceImpl// Log.i(TAG,TAG+"******"+service.getClass().getName());// 面向接口编程mIBookManager = IBookManager.Stub.asInterface(service);//为了避免ANR下面的代码最好新开线程中运行,这里为了方便就不麻烦了try {// 父类调用子类方法tvShow.setText(mIBookManager.getBookList().toString());Book newBook = new Book(888,"监听测试***书籍");mIBookManager.addBook(newBook);} catch (RemoteException e) {e.printStackTrace();}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}@Overrideprotected void onDestroy() {//解除绑定unbindService(mServiceConnection);super.onDestroy();}private void initView() {bindService = (Button) findViewById(R.id.bindService);startSecondActivity = (Button) findViewById(R.id.startSecondActivity);tvShow = (TextView) findViewById(R.id.tvShow);bindService.setOnClickListener(this);startSecondActivity.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.bindService:Intent intent = new Intent("com.example.service.MyService");// MyService类的onBind方法返回的是MyService的内部类MyServiceImplboolean isBind = bindService(intent, mServiceConnection,Context.BIND_AUTO_CREATE);if (isBind) {Log.i(TAG, TAG + "********绑定成功***********");} else {Log.i(TAG, TAG + "********绑定失败***********");}break;case R.id.startSecondActivity:Intent intent2 = new Intent(MainActivity.this, SecondActivity.class);startActivity(intent2);break;default:break;}}}流程: A.bindService()--->S.onCreate--->S.onBind---->>A.onServiceConnected绑定成功,并获得Service对象
结束绑定按钮的监听事件-->>unbindService(conn)关闭连接对象-->>S.destory()销毁该service。
PS:SecondActivity和Maintivity代码完全一样,只是在AndroidMainfest多了一个属性,可自行创建。
android:process="com.dyk.aidltest.SecondActivity"点击“绑定服务”后效果如图: 可以看到已经获取到Book的信息了。
get。
客户端到调用服务器端的方法到此结束。如果有这么一个需求:服务端有数据变动需要立即通知客户端,那么应该怎么做呢?没错,接口。需要说明的是这里的接口不是普通的接口。因为AIDL中无法使用普通接口,所以我们需要写一个aidl接口。
package com.example.aidl;import com.example.aidl.Book;interface IOnNewBookArrivedListener{void onNewBookArrived(in Book newBook);}
在IBookManaget里增加两个抽象方法
void registerListener(IOnNewBookArrivedListener listener);void unregisterListener(IOnNewBookArrivedListener listener);由于IBookManager增加两个抽象方法,所以相应的MyServiceImpl需要重写这两个方法。
<span style="white-space:pre"></span>@Overridepublic void registerListener(IOnNewBookArrivedListener listener)throws RemoteException {mListenerList.register(listener);Log.i(TAG, "register listener:" + listener);}@Overridepublic void unregisterListener(IOnNewBookArrivedListener listener)throws RemoteException {// 其实就是从Map中移除相应的键值对(listener是key)mListenerList.unregister(listener);Log.i(TAG, "unregister listener:" + listener);}对象是不能跨进程直接传输的,对象跨进程传输本质都是反序列化的过程。使用普通的ArrayList后Binder会把客户端传输过来的对象重新转化并生成一个对象,虽然我们注册和反注册使用的同一个客户端对象,但是这样通过Binder传递到服务器端后会产生两个全新的对象。使用RemoteCallbackList可以解决这个问题,具体优点如下:
// RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口// 当客户端进程终止后,它能自动移除客户端所注册的listener// 内部自动实现了线程同步的功能// 它不是list 内部采用Map接口保存AIDL的回调/** 监听器列表 */private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
在addBook中调用这个监听方法
<span style="white-space:pre"></span>// 遍历通知每个监听者int len = mListenerList.beginBroadcast();for (int i = 0; i < len; i++) {IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);// 如果listener.onNewBookArrived(book)比较耗时,最好新开线程,防止ANRlistener.onNewBookArrived(book);}mListenerList.finishBroadcast();接下来在MainActivity或SecondActivity中实现这个接口,并重写两个方法registerListener和unregisterListener。
<span style="white-space:pre"></span>IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {@Overridepublic void onNewBookArrived(Book newBook) throws RemoteException {//书籍增加后的做法Log.i(TAG, "newBook="+newBook.bookId+"\t"+newBook.bookName);}};在 MainActivity或SecondActivity的onServiceConnected中注册监听
<span style="white-space:pre"></span>//注册为监听者mIBookManager.registerListener(mIOnNewBookArrivedListener);
在MainActivity或SecondActivity的onDestroy中反注册监听
<span style="white-space:pre"></span>if (mIBookManager!=null&&mIBookManager.asBinder().isBinderAlive()) {try {//解除监听mIBookManager.unregisterListener(mIOnNewBookArrivedListener);} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
到这里基本算大功告成了,最后的最后还有一个小缺陷需要我们修复。默认情况下我们的服务器端谁都可以连接,这可不一定是我们想要的。没错,加入限权。
首先在AndroidMainfest中声明所需的限权
<!-- 自定义MyService的限权,在OnBind中验证限权 --> <permission android:name="com.example.service.permission.MyService" android:protectionLevel="normal" > </permission>
<span style="white-space:pre"></span><service android:name="com.example.service.MyService" android:permission="com.example.service.permission.MyService" > <intent-filter> <action android:name="com.example.service.MyService" /> </intent-filter> </service>
<span style="white-space:pre"></span><!-- 使用限权 --> <uses-permission android:name="com.example.service.permission.MyService" />
最后在onBinder中验证限权,没有限权返回空,无法绑定
<span style="white-space:pre"></span>@Overridepublic IBinder onBind(Intent intent) {//限权验证 ,没有限权返回空,无法绑定int check = checkCallingOrSelfPermission("com.example.service.permission.MyService");if (check==PackageManager.PERMISSION_DENIED) {return null;}return new MyServiceImpl();}
缕清思路加写作一共花了3个多小时,终于完成了!这一大套流程下来可真不容易,希望对你有所帮助。
Demo下载地址:http://download.csdn.net/detail/qq_17250009/9252577
更多相关文章
- 浅谈Java中Collections.sort对List排序的两种方法
- 类和 Json对象
- Python list sort方法的具体使用
- python list.sort()根据多个关键字排序的方法实现
- Android提交数据到服务器的两种方式四种方法
- 初探Android中的binder机制
- android面试题总结
- Android中SyncTask的使用
- android 预装第三方apk的方法