IPC 机制(二)
来源:互联网 发布:有哪些网络创意的 编辑:程序博客网 时间:2024/05/01 23:10
一、Android中的IPC方式
上一篇文章中已经介绍了IPC 的基础知识,接下来是IPC的夸进程方式。IPC的方式有很对,可以通过Intent中附加extras来传递信息,或者通过共享文件的方式共享数据,还有可以才用Binder方式来夸进程,另外,ContentProvider天生就是支持夸进程访问的,因此我i门也可以才用它们来进行IPC。此外,通过网络通信也可以实现数据传递的,所以Socket也可以实现IPC。
1.使用Bundle
四大组件中的三大组件(Activity、Service、Receiver)主要支持在Intent 中传递Bundle数据的,由于Bundle实现了Paecelable接口,所以它可以方便地在不同的进程间传递。基于这一点,我们在这个进程中启动了另一个进程的Activity、Service和Receiver,我们可以在Bundle中附加我们需要传输给另一个进程的信息并通过Intent发送出去。
但是,传输的内容必须被序列化或者Bundle支持的类型。Bundle不支持的类型可以通过在另一个进程中启动一个Service来获取我们要传递的值(比如计算)。
2.使用Messenger
Messenger可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC对象,底层实现是AIDL,在源码中可以看出:
/** * Reference to a Handler, which others can use to send messages to it. * This allows for the implementation of message-based communication acrossmessenger * processes, by creating a Messenger pointing to a Handler in one process, * and handing that Messenger to another process. * * <p>Note: the implementation underneath is just a simple wrapper around * a {@link Binder} that is used to perform the communication. This means * semantically you should treat it as such: this class does not impact process * lifecycle management (you must be using some higher-level component to tell * the system that your process needs to continue running), the connection will * break if your process goes away for any reason, etc.</p> */public final class Messenger implements Parcelable { private final IMessenger mTarget; /** * Create a new Messenger pointing to the given Handler. Any Message * objects sent through this Messenger will appear in the Handler as if * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had * been called directly. * * @param target The Handler that will receive sent messages. */ public Messenger(Handler target) { mTarget = target.getIMessenger(); } /** * Send a Message to this Messenger's Handler. * * @param message The Message to send. Usually retrieved through * {@link Message#obtain() Message.obtain()}. * * @throws RemoteException Throws DeadObjectException if the target * Handler no longer exists. */ public void send(Message message) throws RemoteException { mTarget.send(message); } /** * Retrieve the IBinder that this Messenger is using to communicate with * its associated Handler. * * @return Returns the IBinder backing this Messenger. */ public IBinder getBinder() { return mTarget.asBinder(); } /** * Comparison operator on two Messenger objects, such that true * is returned then they both point to the same Handler. */ public boolean equals(Object otherObj) { if (otherObj == null) { return false; } try { return mTarget.asBinder().equals(((Messenger)otherObj) .mTarget.asBinder()); } catch (ClassCastException e) { } return false; } public int hashCode() { return mTarget.asBinder().hashCode(); } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeStrongBinder(mTarget.asBinder()); } public static final Parcelable.Creator<Messenger> CREATOR = new Parcelable.Creator<Messenger>() { public Messenger createFromParcel(Parcel in) { IBinder target = in.readStrongBinder(); return target != null ? new Messenger(target) : null; } public Messenger[] newArray(int size) { return new Messenger[size]; } }; /** * Convenience function for writing either a Messenger or null pointer to * a Parcel. You must use this with {@link #readMessengerOrNullFromParcel} * for later reading it. * * @param messenger The Messenger to write, or null. * @param out Where to write the Messenger. */ public static void writeMessengerOrNullToParcel(Messenger messenger, Parcel out) { out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder() : null); } /** * Convenience function for reading either a Messenger or null pointer from * a Parcel. You must have previously written the Messenger with * {@link #writeMessengerOrNullToParcel}. * * @param in The Parcel containing the written Messenger. * * @return Returns the Messenger read from the Parcel, or null if null had * been written. */ public static Messenger readMessengerOrNullFromParcel(Parcel in) { IBinder b = in.readStrongBinder(); return b != null ? new Messenger(b) : null; } /** * Create a Messenger from a raw IBinder, which had previously been * retrieved with {@link #getBinder}. * * @param target The IBinder this Messenger should communicate with. */ public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }}
Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以很简单地进行线程间通信。同时,由于它一次可以处理一个请求,因此在服务端我们不需要考虑线程同步的问题,这是因为服务端不存在并发的情形。实现一个Messenger有下面几个步骤,分别是服务端和客户端。
(1)服务端进程
在服务端创建一个Service来处理客户端的链接请求,同时创建一个Handler并通过他来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder.
(2)客户端进程
客户端进程,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送信息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。下面是例子的代码:
服务端:
MessengerHandler 用于处理客户端发送的消息,从消息中取出客户端发来的文本信息。messenger是一个Messenger对象,它和MessengerHandler相关联,并在onBind方法中返回它里面的Binder对象,可以看出,这里Messenger的作用是将客户端发送的消息传递给MessengerHandler处理
public class MessengerService extends Service { private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_CLIENT: L.d("receive msg from Client:" + msg.getData().getString("msg")); break; default: super.handleMessage(msg); } } } public MessengerService() { } private final Messenger messenger= new Messenger(new MessengerHandler()); @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); }}
注册service,让其运行在单独的进程中
<service android:name=".messenger.MessengerService" android:process=":remote" />
客户端:
客户端首先需要绑定远程进程的MessengerService,绑定成功后,根据服务端返回的binder对象创建Messenger对象并使用此对象向服务端发送消息。
public class MessengerActivity extends AppCompatActivity { private Messenger mService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = new Messenger(service); Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString("msg","hello, this is client"); msg.setData(data); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); Intent intent = new Intent(this,MessengerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); }}
02-27 16:10:09.835 23614-23614/? D/com.app.song.ipc1: receive msg from Client:hello, this is client
上面的例子是客户端发信息给服务端,下面的例子是服务端会信息给客户端:
还是用上面的例子,对例子进行修改:
服务端:
public class MessengerService extends Service { private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_CLIENT: L.d("receive msg from Client:" + msg.getData().getString("msg")); Messenger client = msg.replyTo; Message replyMessage = Message.obtain(null,MyConstants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString("reply","嗯,你的消息我已经收到,稍后回复你。"); replyMessage.setData(bundle); try { client.send(replyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } public MessengerService() { } private final Messenger messenger= new Messenger(new MessengerHandler()); @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); }}
客户端:
public class MessengerActivity extends AppCompatActivity { private Messenger mService; private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_SERVICE: L.d("receive msg from Service:" + msg.getData().getString("msg")); break; default: super.handleMessage(msg); } } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = new Messenger(service); Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString("msg","hello, this is client"); msg.setData(data); msg.replyTo = mGetReplyMessenger; try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); Intent intent = new Intent(this,MessengerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); }}上面代码运行后的log是
02-27 17:49:40.832 22171-22171/? D/com.app.song.ipc1: receive msg from Client:hello, this is client
02-27 17:49:40.843 22075-22075/? D/com.app.song.ipc1: receive msg from Service:嗯,你的消息我已经收到,稍后回复你。
3.使用AIDL
Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个一个处理,如果有大量的并发请求,那么用Messenger就不太合适了。同时Messenger的主要功能是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层调用。
AIDL分为服务端和客户端两个方面
(1)服务端
服务端首先创建一个Service用来监听客户端的链接请求,然后创建一个AIDL文件,将暴露给客户端接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
(2)客户端
客户端所要做的事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返货的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
(3)AIDL接口的创建
首先是AIDL接口的创建。如下所示,我们创建一个后缀为AIDL的文件,在里面声明可一个接口和两个接口方法:
// IBookManager.aidlpackage com.app.song.ipc1;// Declare any non-default types here with import statementsimport com.app.song.ipc1.Book;interface IBookManager { List<Book> getBookList(); void addBook(in Book book);}
AIDL支持的类型:
*基本数据类型(int、long、chat、boolean、double等);
*String和CharSequence;
*List:只支持ArrayList,里面每个元素都必须能后被AIDL支持;
*Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
*Parcelable:所有实现了Paecelable接口的对象;
*AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;
以上6种数据类型就是AIDL支持的所有类型,其中自定义的Paecelable对象和AIDL对象必须显示import进来,不管它们是否和当前的AIDL在位于同一个包内。比如IBookManager.aidl,里面用到了Book这个类,这个类实现了Parcelable接口并且和IBookManager.aidl位于同一个包中,但是遵守AIDL的规范,我们需要现实的import进来:import com.app.song.ipc1.Book。
另外一个需要注意的地方是,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。在IBookManager.aidl中,我们用到了Book这个类,所以,我们必须要创建Book.aidl 如下:
// BookAIDL.aidlpackage com.app.song.ipc1;// Declare any non-default types here with import statements parcelable Book;
为了方便AIDL的开发,AIDL相关的文件最好放在一个包,当客户端是另外一个程序的时候方便复制。AIDL的包结构在服务端和客户端要保持一致,否则运行会错误,应为反序列化会失败。
(4)远程服务端Service的实现
上面已经定义了AIDL接口,接下来我们就需要实现这个接口了,我们先创建一个service,成为BookManagerService,代码如下:
public class BookManagerService extends Service { private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private Binder mBinder = new IBookManager.Stub(){ @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } }; public BookManagerService() { } @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1,"Android")); mBookList.add(new Book(2,"Ios")); } @Override public IBinder onBind(Intent intent) { return mBinder; }}
上面代码中的getBookList和addBookList这两个AIDL方法实现才用了CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写。AIDL方法是在服务端的Binder线程池中执行的,因为当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步
前面我们提到,AIDL中能够使用的List只有ArrayList,但是我们这里却使用CopyOnWriteArrayList(它不是继承自ArrayList),它能正常工作是因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范访问数据并最终形成一个新的ArrayList传递给客户端。所以,我们在服务端才用CopyOnWriteArrayList是完全可以的。和此类类似的还有ConcurrentHashMap。
BookManagerService 在设置为其他线程中:
<service android:name=".BookManagerService" android:process=":remote"></service>
(4)客户端的实现
客户端的实现很简单,首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个街陋去调用服务端的远程方法了
public class BookManagerActivity extends AppCompatActivity { private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { List<Book> list = bookManager.getBookList(); L.d("query book list, list type:" + list.getClass().getCanonicalName()); L.d("query book list:"+list.toString()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); Intent intent = new Intent(this,BookManagerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); }}运行之后打印的Log是:
02-28 11:07:12.864 28850-28850/? D/com.app.song.ipc1: query book list, list type:java.util.ArrayList
02-28 11:07:12.864 28850-28850/? D/com.app.song.ipc1: query book list:[com.app.song.ipc1.Book@dc495d4, com.app.song.ipc1.Book@3852937d]
从log中可以看出,虽然我们在服务端返回的是CopyOnWriteArrayList类型,但是客户端收到的仍然是ArrayList类型,这证实了上面的分析
接下来再调用另一个接口addBook,我们在客户端给服务端添加一本书,然后再获取一次,看看程序是否正常工作。
在客户端代码中改动如下:
private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { List<Book> list = bookManager.getBookList(); L.d("query book list, list type:" + list.getClass().getCanonicalName()); L.d("query book list:"+list.toString()); Book newBook = new Book(3,"Android 开发艺术探索"); bookManager.addBook(newBook); L.d("add book" + newBook); List<Book> newList = bookManager.getBookList(); L.d("query book list:" + newList.toString()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } };
02-28 13:14:28.771 5851-5851/? D/ipctest: query book list, list type:java.util.ArrayList
02-28 13:14:28.771 5851-5851/? D/ipctest: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios]]
02-28 13:14:28.772 5851-5851/? D/ipctest: add book[bookId:3, bookName:Android 开发艺术探索]
02-28 13:14:28.773 5851-5851/? D/ipctest: query book list:[bookId:3, bookName:Android 开发艺术探索]
02-28 13:17:02.130 6910-6910/? D/ipctest: query book list, list type:java.util.ArrayList
02-28 13:17:02.130 6910-6910/? D/ipctest: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios]]
02-28 13:17:02.131 6910-6910/? D/ipctest: add book[bookId:3, bookName:Android 开发艺术探索]
02-28 13:17:02.131 6910-6910/? D/ipctest: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:Android 开发艺术探索]]
下面添加新书监听功能:
首先,需要提供一个AIDL接口,每个用户都需要实现这个接口并向服务端申请新书提醒功能,当然每个用户可以随时取消这种功能。之所以选择AIDL接口而不是普通接口,是因为AIDL中无法使用普通接口,这里我们创建一个IOnNewBookArrivedLiseter.aidl文件,我们希望的情况是:当服务端有新书到的时候,就会通知每个已经申请提醒功能的用户。从程序上就是调用所有IOnNewBookArrivedListener对象中的onNewBookArrived方法,并把新书的对象传递给客户,实例如下:
// IOnNewBookArrivedListener.aidlpackage com.app.song.ipc1;// Declare any non-default types here with import statementsimport com.app.song.ipc1.Book;interface IOnNewBookArrivedListener { void onNewBookArrived(in Book newBook);}除了要新加一个AIDL接口,还需要在原有的接口中添加两个新方法,代码如下:
// IBookManager.aidlpackage com.app.song.ipc1;// Declare any non-default types here with import statementsimport com.app.song.ipc1.Book;import com.app.song.ipc1.IOnNewBookArrivedListener;interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void inregisterListener(IOnNewBookArrivedListener listener); }
接着,服务端中Service的实现也要稍微修改一下,主要是Service中IBookManager.Stub的实现,因为我们添加了两个新的方法在IBookManager中,所以在IBookManager.Stub中也要实现这两种方法,同时,在BookMangerService中还开启了一个线程,每隔5s向书库中添加一本新书并通知所有感兴趣的用户
public class BookManagerService extends Service { private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub(){ @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { if(!mListenerList.contains(listener)) { mListenerList.add(listener); } else { L.d("already exists."); } L.d("registerListener, size" + mListenerList.size()); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { if(mListenerList.contains(listener)) { mListenerList.remove(listener); L.d("unregister listener successd"); } else { L.d("not found, can not unregister."); } L.d("unregisterListener,current size:" + mListenerList.size()); } }; public BookManagerService() { } @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1,"Android")); mBookList.add(new Book(2,"Ios")); new Thread(new ServiceWorker()).start(); } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return mBinder; } private class ServiceWorker implements Runnable { @Override public void run() { while (!mIsServiceDestoryed.get()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId,"new book#" + bookId); try { onNewBookArrived(newBook); } catch (Exception e) { e.printStackTrace(); } } } } private void onNewBookArrived(Book newBook) throws RemoteException { mBookList.add(newBook); L.d("onNewBookArrived, notify listeners:"+mListenerList.size()); for(int i = 0; i < mListenerList.size();i++) { IOnNewBookArrivedListener listener = mListenerList.get(i); L.d("onNewBookArried, notify listener:" + listener); listener.onNewBookArrived(newBook); } }}
最后,需要修改一下客户端的代码,主要两个方面:首先客户端要注册IOnNewBookArrivedListener到远程服务端,这样当有新书时服务端才能通知当前客户端,同时我们还要在Activity退出时解除这个注册;另一方面,当有新书时,服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法,但是这个方法是在客户端的Binder线程池中执行的,因此,为了便于进行UI操作,我们需要有一个Handler可以将其切换到客户端的主线程中去执行
public class BookManagerActivity extends AppCompatActivity { private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: L.d("receive new book :" + msg.obj); break; } super.handleMessage(msg); } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { mRemoteBookManager = bookManager; List<Book> list = bookManager.getBookList(); L.d("query book list, list type:" + list.getClass().getCanonicalName()); L.d("query book list:"+list.toString()); Book newBook = new Book(3,"Android 开发艺术探索"); bookManager.addBook(newBook); L.d("add book" + newBook); List<Book> newList = bookManager.getBookList(); L.d("query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mRemoteBookManager = null; } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); Intent intent = new Intent(this,BookManagerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } @Override protected void onDestroy() { if(mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { L.d("unregister listener:" + mOnNewBookArrivedListener); mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); }}
实现功能后打印的日志
02-28 14:32:56.151 31021-31021/? D/ipctest: receive new book :[bookId:14, bookName:new book#14]
02-28 14:32:56.652 31236-31262/? D/ipctest: onNewBookArrived, notify listeners:1
02-28 14:32:56.653 31236-31262/? D/ipctest: onNewBookArried, notify listener:com.app.song.ipc1.IOnNewBookArrivedListener$Stub$Proxy@70d70c
02-28 14:32:56.657 31021-31021/? D/ipctest: receive new book :[bookId:15, bookName:new book#15]
02-28 14:32:57.157 31236-31262/? D/ipctest: onNewBookArrived, notify listeners:1
02-28 14:32:57.158 31236-31262/? D/ipctest: onNewBookArried, notify listener:com.app.song.ipc1.IOnNewBookArrivedListener$Stub$Proxy@70d70c
02-28 14:32:57.162 31021-31021/? D/ipctest: receive new book :[bookId:16, bookName:new book#16]
上面运行退出时有下面一行日志:
02-28 14:33:01.297 31236-31255/? D/ipctest: not found, can not unregister.
02-28 14:33:01.297 31236-31255/? D/ipctest: unregisterListener,current size:1
从上面的Log可以看出,程序没有像我们所预想的那样执行。在解注册的过程中,服务端竟然没法找到之前注册的那个listener,出现这种情况是因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然在组成和解注册过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后会重新产生两个全新的对象。别忘了对象是不能跨进程直接传递的,对象的跨进程传输本质都是反序列化的过程,这就是为什么AIDL中的自定义对象必须要实现Parcelable接口的原因。
用RemoteCallBackList可以实现注册功能
RemoteCakkBackList是专门提供的用于删除跨进程listener的接口。RemoteCallBackList是一个泛型,支持管理任何AIDL接口,这点从它的声明就可以看出,应为所有AIDL接口都继承自IInterface接口
class RemoteCallbackList<E extends IInterface>
它的工作原理很简单,在它的内部有一个map结构专门用来保存所有的AIDL回调,这个map的key是IBinder类型,value是CallBack类型
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
其中Callback中封装了真正的远程listener。当客户端注册listener的时候,它会把这个listener的信息存入mCallback中,其中key和value分别通过下面的方式获得:
public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { if (mKilled) { return false; } IBinder binder = callback.asBinder(); try { Callback cb = new Callback(callback, cookie); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; } } }
虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是他们底层的Binder对象是同一个,利用这个特性可以实现上面我们无法实现的功能。当客户端注册的时候,我们只要遍历服务端所有的listener,找到那个和注册listener具有相同Binder对象的服务端listener并把它删除即可,这就是RemoteCallbackList为我们做的事情。同时RemoteCallbackList还有一个很有用的功能,那就是当客户端终止后,它能够自动移除客户端所有注册的listener。另外,remoteCallbackList内部自动实现可线程同步的功能,所以我们使用它来注册和解除注册时,不需要做额外的线程同步工作
下面是修改后的代码:
public class BookManagerService extends Service { private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();// private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub(){ @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener);// if(!mListenerList.contains(listener))// {// mListenerList.add(listener);// }// else// {// L.d("already exists.");// }// L.d("registerListener, size" + mListenerList.size()); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.unregister(listener);//// if(mListenerList.contains(listener))// {// mListenerList.remove(listener);// L.d("unregister listener successd");// }// else// {// L.d("not found, can not unregister.");// }//// L.d("unregisterListener,current size:" + mListenerList.size()); } }; public BookManagerService() { } @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1,"Android")); mBookList.add(new Book(2,"Ios")); new Thread(new ServiceWorker()).start(); } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return mBinder; } private class ServiceWorker implements Runnable { @Override public void run() { while (!mIsServiceDestoryed.get()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId,"new book#" + bookId); try { onNewBookArrived(newBook); } catch (Exception e) { e.printStackTrace(); } } } } private void onNewBookArrived(Book newBook) throws RemoteException { mBookList.add(newBook); final int N = mListenerList.beginBroadcast(); for(int i = 0; i < N ; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if(l != null) { l.onNewBookArrived(newBook); } } mListenerList.finishBroadcast();// L.d("onNewBookArrived, notify listeners:"+mListenerList.size());// for(int i = 0; i < mListenerList.size();i++)// {// IOnNewBookArrivedListener listener = mListenerList.get(i);// L.d("onNewBookArried, notify listener:" + listener);// listener.onNewBookArrived(newBook);// } }}
运行结果:
02-28 15:50:55.833 27123-27123/? D/ipctest: receive new book :[bookId:8, bookName:new book#8]
02-28 15:50:56.338 27123-27123/? D/ipctest: receive new book :[bookId:9, bookName:new book#9]
02-28 15:50:56.840 27123-27123/? D/ipctest: receive new book :[bookId:10, bookName:new book#10]
02-28 15:50:57.268 27123-27123/? D/ipctest: unregister listener:com.app.song.ipc1.BookManagerActivity$3@3852937d
使用RemoteCallbackList,有一点需要注意,我们无法像操作List一样去操作,尽管它的名字中也带个List,但是并不是一个List。遍历RemoteCallbackList,必须要按照下面的方式进行,其中beginBroadcast和finishBroadcast必须要配对使用,哪怕我们仅仅是想要获取RemoteCallbackList中的元素个数,
final int N = mListenerList.beginBroadcast(); for(int i = 0; i < N ; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if(l != null) { l.onNewBookArrived(newBook); } } mListenerList.finishBroadcast();
需要注意的是,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程池会被挂起,这个时候如果服务端是UI线程的话,就会导致客户端ANR。因此,我们需要明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。由于客户端的onServiceConnected和onServiceDisconnected方法都是运行在UI线程中,所以也不能在他们里面调用服务端的耗时方法。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中来线程去进行异步操作,除非你知道你在干嘛。
同理,远程服务端需要调用客户端的listener中的方法时,被调用风方法也运行在Binder线程池中,只不过是客户端的线程池。所以,我们同样不可以在服务端中调用客户端的耗时方法。
为了程序的健壮性,我们需要监听Binder的死亡,有两种方法:1.给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到binderDied方法回调,在binderDied方法中我们可以重新连接远程服务。2.在onServiceDisconnected中重新链接远程服务。 两者的区别:onServiceDisconnected在UI线程,binderDied在客户端Binder线程中被调用。
AIDL中的权限验证功能
第一种方法,我们可以在onBind中进行验证,验证不通过就直接返回null,这样验证失败的客户端直接就无法绑定服务,至于验证方式可以有很多种,比如使用permission验证,使用这种验证方式,我们要先在AndroidMenifest中声明所需权限 例如:
<permission android:name="com.app.song.ipc1.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/>定义权先后,就可以在BookManagerService的onBind方法中做权限认证
public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.app.song.ipc1.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "onbind check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; }
如果权限认证不通过onBind直接返回null,最终结果是这个应用无法绑定我们的服务,这就达到了权限认证的效果,这种方法同样适用于Messenger
如果内部的应用想绑定到我们的服务中,只需要在它的AndroidMenifest文件中才用如下方式使用permission即可
<uses-permission android:name="com.app.song.ipc1.permission.ACCESS_BOOK_SERVICE" />
可以在服务端的onTransact方法中进行权限认证,如果失败直接返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护服务端的效果,至于具体的验证方式有很多,可以才用permission验证,具体实现方式和第一种方式一样,还可以通过Uid和Pid验证,通过getCallingUid和getCallingPid可以拿来客户端所属的Uid和Pid,通过这两个参数我们可以做一些验证工作,比如验证包名。下面的代码中对permission和报名同时验证:
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.app.song.ipc1.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } Log.d(TAG, "onTransact: " + packageName); if (!packageName.startsWith("com.app.song")) { return false; } return super.onTransact(code, data, reply, flags); }
- IPC 机制(二)
- IPC机制(二)
- IPC机制(二)
- 二、IPC机制(IPC介绍)
- 二、IPC机制续(IPC方式)
- 二、IPC机制续(IPC方式)
- 【Android】IPC机制(二)
- android IPC机制(二)
- Android IPC机制(二)
- 4.IPC 机制(二) IPC基础概念介绍
- IPC机制--开发艺术探索(二)
- (二十二) IPC机制通讯
- 读书笔记--IPC机制(二)
- IPC机制<二>AIDL
- android IPC机制讲解(二)
- Android的IPC机制二
- Android中IPC机制(二)
- IPC实现机制(二)---命名管道(FIFO)
- 相对布局(RelativeLayout)
- iOS绘图教程
- scala学习笔记,第一章
- HDU2000ASCII码排序(C,Java两个版本)
- 1048. 数字加密
- IPC 机制(二)
- 无法解析的外部符号 __imp__glewinit
- leetcode:Populating Next Right Pointers in Each Node II
- php substr函数的误用------如何截取中间串
- 影响速度优化和ANR日志分析
- 乐观锁和悲观锁
- POJ 3252 Round Numbers(数位dp && 记忆化搜索)
- 接上二维数组
- 【CSS3】设置动画播放方向