内容来自《Android开发艺术探索》一书。
简单使用AIDL来进行IPC
- AIDL是方便我们实现 Binder,来进行进程间通信的。我们知道系统怎么根据AIDL来生成代码的,完全可以不用AIDL来直接写一个Binder。
- AIDL只是一种快速实现 Binder 的工具而已。
- 通过AIDL定义需要暴露给客户端(或者说另一个进程)的接口,客户端通过暴露的接口进行取数据,写数据。
简单使用:
-
先定义一个进程间需要传递的数据,假如两个进程间需要传递 “书”,我们定义一个Book类,并让它支持序列化。
Book.class 的目录结构:
package com.lying.ms_ipc_binder.aidl;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName){
this.bookId = bookId;
this.bookName = bookName;
}
/**
* @return 当前对象的内容描述。含有文件描述符返回 1,否则返回 0.几乎所有情况都是返回0.
*/
@Override
public int describeContents() {
return 0;
}
/**
* 序列化
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
/**
* 反序列化
*/
public static final Creator CREATOR = new Creator() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel parcel){
bookId = parcel.readInt();
bookName = parcel.readString();
}
@NonNull
@Override
public String toString() {
return "bookId = " + bookId + ", bookName = " + bookName;
}
}
-
在想要的包名路劲下创建 aidl 文件
创建:
创建后的目录结构:
创建后会在和java同级目录下生成 aidl,AIDL文件的包名和创建时想要的相同。
创建的AIDL代码:
IBookManager.aidl (为什么要 import Book类?因为:自定义的Parcelable对象和AIDL对象都需要显示的 import。)
// IBookManager.aidl package com.lying.ms_ipc_binder.aidl; import com.lying.ms_ipc_binder.aidl.Book; import com.lying.ms_ipc_binder.aidl.IOnNewBookArrivedListener; // Declare any non-default types here with import statements interface IBookManager { List
getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); } Book.aidl(为什么需要创建Book.aidl?因为:如果AIDL文件中用到了自定义的Parcelable对象,那必须新建一个和它同名的AIDL文件。这里IBookManager.aidl 文件中用到了Book,而Book是我们自定义的一个Parcelable对象。)
// Book.aidl package com.lying.ms_ipc_binder.aidl; // Declare any non-default types here with import statements parcelable Book;
IOnNewBookArrivedListener.aidl
// IOnNewBookArrivedListener.aidl package com.lying.ms_ipc_binder.aidl; // Declare any non-default types here with import statements import com.lying.ms_ipc_binder.aidl.Book; interface IOnNewBookArrivedListener { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void onNewBookArrived(in Book newBook); }
-
编译下项目,会在 app/build/generated/aidl_source_output_dir 中生成对应的java文件。
生成的java文件:
查看IBookManager.java 文件:
- 其中Stub类是核心,这个类就是个Binder。
- 下面是通过AIDL定义的接口,需要服务端实现。
其中Stub的结构分析:
- 其中 DESCRIPTOR 表示Binder的唯一标识
- asInterface ,客户端就是通过这个静态方法拿到 IBookManager 的,拿到后就可以调用 IBookManager 中服务端暴露的接口了。
- asBinder 返回当前的Binder对象
- onTransact 这个方法运行在Binder线程池中。客户端发起请求(比如:通过asInterface拿到IBookManager对象后,调用其中的getBookList方法),这个方法就会处理请求并返回结果(比如:返回List
数据) - Proxy 这个实现了IBookManager,客户端调用 IBookManager中的方法后,拿到参数,调用onTransact方法,得到结果。
-
服务端和客户端代码编写
-
服务端和客户端属于同一个应用情况下
服务端和客户端结构图:
BookManagerService.java 服务端代码如下:通过继承Stub,实现了暴露接口的具体实现,为客户端提供Binder对象。
package com.lying.ms_ipc_binder.service; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import androidx.annotation.Nullable; import com.lying.ms_ipc_binder.aidl.Book; import com.lying.ms_ipc_binder.aidl.IBookManager; import com.lying.ms_ipc_binder.aidl.IOnNewBookArrivedListener; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; public class BookManagerService extends Service { private static final String TAG = "BookManagerService"; private CopyOnWriteArrayList
mBookList = new CopyOnWriteArrayList<>(); private RemoteCallbackList mListenerList = new RemoteCallbackList<>(); private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false); //Binder 对象,实现了Stub。给客户端用的。客户端可以根据这个对象调用asInterface方法拿到IBookManager对象。 private Binder mBinder = new IBookManager.Stub() { @Override public List getBookList() throws RemoteException { // SystemClock.sleep(5000);//模拟服务端可能的耗时动作,客户端不能在主线程调用服务端的方法,否则会ANR。 return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); Log.i(TAG, "registerListener: mListenerList.size() = " + mListenerList.getRegisteredCallbackCount()); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.unregister(listener); Log.i(TAG, "unregisterListener: mListenerList.size() = " + mListenerList.getRegisteredCallbackCount()); } }; @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } //有新书时,调用此方法通知所有监听的人 private void arrivedNewBook(Book book) throws RemoteException{ int n = mListenerList.beginBroadcast(); for(int i = 0; i < n; i++){ IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i); if(listener != null){ try{ listener.onNewBookArrived(book); }catch (RemoteException e){ e.printStackTrace(); } } } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable{ @Override public void run() { //没被销毁情况下,5S通知一次 while (!mIsServiceDestroyed.get()){ try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "newBook_" + bookId); mBookList.add(newBook); try { arrivedNewBook(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } @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() { super.onDestroy(); mIsServiceDestroyed.set(true); } } 然后在 AndroidMainfest 文件中为BookManagerService指明到一个新的进程。
BookmanagerActivity.java 客户端代码:客户端运行在默认进程中,通过连接服务,利用Binder进行通信。
package com.lying.ms_ipc_binder.client; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.TextView; import com.lying.ms_ipc_binder.R; import com.lying.ms_ipc_binder.aidl.Book; import com.lying.ms_ipc_binder.aidl.IBookManager; import com.lying.ms_ipc_binder.aidl.IOnNewBookArrivedListener; import com.lying.ms_ipc_binder.service.BookManagerService; import java.lang.ref.WeakReference; import java.util.List; public class BookManagerActivity extends AppCompatActivity { private static final String TAG = "BookManagerActivity"; private static final int MESSAGE_NEW_BOOK = 111; private TextView textView; private IBookManager bokManager; private MyHandler myHandler; public void bt(View view) { try { List
books = bokManager.getBookList(); textView.setText(textView.getText() + books.toString()); } catch (RemoteException e) { e.printStackTrace(); } } private static class MyHandler extends Handler{ WeakReference weakReference ; public MyHandler(BookManagerActivity bookManagerActivity){ weakReference = new WeakReference<>(bookManagerActivity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); if(msg.what == MESSAGE_NEW_BOOK){ Log.i(TAG, "handleMessage: 接收到新书提醒 book = " + msg.obj); weakReference.get().textView.setText(msg.obj.toString()); } } }; private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { myHandler.obtainMessage(MESSAGE_NEW_BOOK, newBook).sendToTarget(); } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bokManager = IBookManager.Stub.asInterface(service); try { List list = bokManager.getBookList(); Log.i(TAG, "onServiceConnected: query book list: " + list.toString()); Book newBook = new Book(3, "Android 开发艺术探索"); bokManager.addBook(newBook); Log.i(TAG, "onServiceConnected: 添加了一本新书"); List newList = bokManager.getBookList(); Log.i(TAG, "onServiceConnected: query book list, list type : " + newList.getClass().getCanonicalName()); Log.i(TAG, "onServiceConnected: query book list: " + newList.toString()); bokManager.registerListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { bokManager = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); textView = findViewById(R.id.tv_1); myHandler = new MyHandler(this); //启动服务 Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); Log.i(TAG, "onCreate: 启动服务"); } @Override protected void onDestroy() { super.onDestroy(); if(bokManager != null && bokManager.asBinder().isBinderAlive()){ try { bokManager.unregisterListener(listener); Log.i(TAG, "onDestroy: 移除了监听"); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); Log.i(TAG, "onDestroy: 销毁服务"); } } 这样就完成了客户端和服务器两个进程间的通信。
-
服务端和客户端是两个应用
1、首先服务端 BookManagerService.java代码不动。
ActivityManifest 文件中要修改 Service 的描述,修改为:将exproted设置为true,保证客户端应用能访问的到;增加action,使得客户端应用能够隐示启动服务端。
2、需要将服务端定义的传输的数据类 Book.java 和 定义的AIDL复制过来。因为服务端和客户端通信是用序列化,所以Book.java序列化类的定义要相同,复制过来;还需要Binder来通信,需要使用服务端AIDL暴露的接口,因为本来就是给客户端用的,所以定义要相同,复制过来。
然后在MainActivity中启动服务,进行进程间通信。和同一个应用中的客户端代码几乎一样,只是启动服务的地方不同。因为不是一个应用,不能拿到服务的class对象了,需要隐式启动。
MainActivity.java 客户端代码如下:package com.lying.ms_ipc_binder; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.widget.TextView; import com.lying.ms_ipc_binder.aidl.Book; import com.lying.ms_ipc_binder.aidl.IBookManager; import com.lying.ms_ipc_binder.aidl.IOnNewBookArrivedListener; import java.lang.ref.WeakReference; import java.util.List; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private IBookManager bokManager; private MyHandler myHandler; private static final int MESSAGE_NEW_BOOK = 111; TextView textView; private static class MyHandler extends Handler { WeakReference
weakReference ; public MyHandler(MainActivity bookManagerActivity){ weakReference = new WeakReference<>(bookManagerActivity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); if(msg.what == MESSAGE_NEW_BOOK){ Log.i(TAG, "handleMessage: 接收到新书提醒 book = " + msg.obj); weakReference.get().textView.setText(msg.obj.toString()); } } }; private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { myHandler.obtainMessage(MESSAGE_NEW_BOOK, newBook).sendToTarget(); } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bokManager = IBookManager.Stub.asInterface(service); try { List list = bokManager.getBookList(); Log.i(TAG, "onServiceConnected: query book list: " + list.toString()); Book newBook = new Book(3, "Android 开发艺术探索"); bokManager.addBook(newBook); Log.i(TAG, "onServiceConnected: 添加了一本新书"); List newList = bokManager.getBookList(); Log.i(TAG, "onServiceConnected: query book list, list type : " + newList.getClass().getCanonicalName()); Log.i(TAG, "onServiceConnected: query book list: " + newList.toString()); bokManager.registerListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { bokManager = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.tv_client); myHandler = new MyHandler(this); Intent intent = new Intent(); intent.setAction("com.lying.ms_ipc_binder.service.BookManagerService"); intent.setPackage("com.lying.ms_ipc_binder");//设置服务端的package bindService(intent, mConnection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); if(bokManager != null && bokManager.asBinder().isBinderAlive()){ try { bokManager.unregisterListener(listener); Log.i(TAG, "onDestroy: 移除了监听"); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); Log.i(TAG, "onDestroy: 销毁服务"); } } 这样就完成了客户端和服务器两个进程间的通信。
-
-
注意
- 由于客户端的 onServiceConnected 和 onServiceDisconnected 是运行在UI线程的,调用服务端方法时,如果服务端方法很耗时,会照成ANR。调用方法需要放入子线程。
- 同样,服务端回调客户端的方法 listener.onNewBookArrived(book); 也要注意耗时问题。因为客户端可能在这个方法中做了耗时操作,这个方法是在客户端的Binder线程池中运行的。所以服务端回调客户端方法也要在子线程中。
- listener.onNewBookArrived(book); 在客户端的Binder线程池中,所以客户端不能在该方法中刷新主界面。
- Binder可能意外死亡。可以给Binder设置DeathRecipient监听,当Binder死亡,会收到 binderDied 回调。另一种方法是在 onServiceDisconnect 中重连。两种方法的区别:binderDied在客户端的Binder线程池中回调,onServiceDisconnect在主线程中回调。
- 默认情况下,任何人都可以连接我们提供的服务。所以我们需要做好权限验证。