Android AIDL开发Binder应用中注意事项
2017-04-09 16:21
567 查看
1 前言
在我们的应用开发中,我们常常有跨进程通信的需求,如果使用AIDL方式,就是用Binder进行通信,往往会新建AIDL文件来定义好服务,服务端实现这些服务,而客户端会具体调用这些服务。关于怎么新建AIDL文件,以及如何实现客户端与服务端,这里就不做讨论了,网上由于大把的教程,这里主要讨论下需要开发中注意的事项。
需要注意的事项如下:
2 Binder通信注意事项
1 AIDL文件路径问题当我们在studio中新建AIDL文件时,由于Server与Client需要共享原始的Bean与AIDL文件,并且两端需要有共同的包名,因此,我们往往通过studio中的如下命令来新建AIDL文件:
这样我们往往在src/main/目录下会新建一个aidl包,如下:
这个包与java包同级,同时为了避免aidl包中的类我们在编译的时候找不到,应该在build.gradle中添加如下配置:
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] aidl.srcDirs = ['src/main/aidl'] } }
不管是客户端还是服务器,都需要以上的配置,这样就能保证进行AIDL部分在客户端与服务端都具有相同的包名,便于序列化与反序列化。
2 服务端代码需要考虑多线程访问的同步问题
一般来说,服务端需要处理多个客户端同时访问的情况,因此必须考虑多线程的问题,可以采用锁住数据源的方式同步,也可以采用一些类比如CopyOnWriteArrayList< T >来解决。
例如下面的例子,使用CopyOnWriteArrayList定义了mBookList,这样服务端端对mBookList的操作都是线程安全的,比如添加,更新,删除等:
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); ...... /** * 运行在Binder线程池中 */ private Binder mBinder = new IBookManager.Stub(){ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public List<Book> getBookList() throws RemoteException { Log.d(TAG,"thread :" + Thread.currentThread().getName()); // try { // Thread.sleep(10000); // } catch (InterruptedException e) { // e.printStackTrace(); // } return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } ...... }
3 Binder通信中的观察者模式
在Binder通信中,一般来说是客户端主动去调用服务端的服务,但是在某些情况下,需要服务端去通知客户端,比如,某些数据已经准备好,客户端可以开始调用xxx服务了,这个时候就需要用到观察者模式了,一般来说的是这样实现的。
客户端作为观察者,服务端作为被观察者,客户端注册某个监听到服务端,服务端当数据准备好或者条件满足,通过监听的回调通知客户端。
由于Binder通信是跨进程的,一般来说监听是需要跨进程传输,为了便于客户端的注册与反注册监听器,需要使用RemoteCallbackList< E >辅助类来实现监听的注册与反注册。
一个简单的例子如下:
服务端与客户端AIDL文件:
interface IBookManager { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); /* * 获取所有的book * */ List<Book> getBookList(); /* * 添加一本书 * */ void addBook(in Book book); void registerListener(in IOnNewBookArrivedListener listener); void unRegisterListener(in IOnNewBookArrivedListener listener); }
interface IOnNewBookArrivedListener { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); //新书到来 void onNewBookArrived(in Book book); }
BookManagerService实现:
/** * Created by qiyei2015 on 2017/4/7. * 1273482124@qq.com */ public class BookManagerService extends Service { private static final String TAG = "BMS"; //需要同步 private AtomicBoolean mIsServiceDestroy = new AtomicBoolean(false); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); //无法取消注册 //private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>(); //使用这个来取消注册 private RemoteCallbackList<IOnNewBookArrivedListener> mRemoteCallbackList = new RemoteCallbackList<>(); /** * 运行在Binder线程池中 */ private Binder mBinder = new IBookManager.Stub(){ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public List<Book> getBookList() throws RemoteException { Log.d(TAG,"getBookList thread :" + Thread.currentThread().getName()); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mRemoteCallbackList.register(listener); Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast()); mRemoteCallbackList.finishBroadcast(); } @Override public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException { mRemoteCallbackList.unregister(listener); Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast()); mRemoteCallbackList.finishBroadcast(); } }; @Nullable @Override public IBinder onBind(Intent intent) { Log.d(TAG,"onBind"); return mBinder; } @Override public void onCreate() { super.onCreate(); Log.d(TAG,"onCreate"); init(); new Thread(new ServiceWorker()).start(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG,"onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { Log.d(TAG,"onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { mIsServiceDestroy.set(true); Log.d(TAG,"onDestroy"); super.onDestroy(); } private void init(){ for (int i = 0;i < 10;i++){ Book book = new Book(i+1,"book_" + (i+1)); mBookList.add(book); } } public class ServiceWorker implements Runnable{ @Override public void run() { while (!mIsServiceDestroy.get()){ try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book book = new Book(bookId,"book_" + bookId); try { notifyClientNewBook(book); } catch (Exception e) { e.printStackTrace(); } } } } /** * 通知客户端新书到来 * @param book * @throws Exception */ private void notifyClientNewBook(Book book) throws Exception{ mBookList.add(book); int size = mRemoteCallbackList.beginBroadcast(); Log.d(TAG,"notifyClientNewBook: "+ book.toString()); for (int i = 0;i <size;i++){ IOnNewBookArrivedListener listener = mRemoteCallbackList.getBroadcastItem(i); Log.d(TAG,"notify listener: "+ listener.toString()); listener.onNewBookArrived(book); } mRemoteCallbackList.finishBroadcast(); } /** * 检查BookServiceDe调用权限 * @return */ private boolean checkBookServicePermission(){ int check = checkCallingOrSelfPermission("com.qiyei.ipc.book.ACCESS_BOOK_SERVICE"); return check == PackageManager.PERMISSION_DENIED; } }
客户端实现:
public class MainActivity extends AppCompatActivity { private static final String TAG = "Client"; private static final int MESSAGE_NEW_BOOK = 1; private Context mContext; private TextView mTextView; private Button mButton; private Button mClear; private Button mButton2; private IBookManager mBookManager; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.what == MESSAGE_NEW_BOOK){ Book book = (Book) msg.obj; mTextView.setText(book.toString()); } } }; /** * Binder死亡代理 ,Binder死亡时会回调该方法 */ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if (mBookManager == null){ return; } mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0); mBookManager = null; //重新连接服务 bindBookService(); } }; /** * 运行在主线程中 */ private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG,"onServiceConnected thread :" + Thread.currentThread().getName()); mBookManager = IBookManager.Stub.asInterface(service); try { mBookManager.asBinder().linkToDeath(mDeathRecipient,0); mBookManager.registerListener(mListener); } catch (RemoteException e) { e.printStackTrace(); } try { mTextView.setText(mBookManager.getBookList().toString()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { //bindBookService(); } }; /** * Service调用,运行在Client端的Binder线程中 */ private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub(){ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public void onNewBookArrived(Book book) throws RemoteException { Log.d(TAG,"onNewBookArrived thread :" + Thread.currentThread().getName()); Message message = Message.obtain(); message.what = MESSAGE_NEW_BOOK; message.obj = book; mHandler.sendMessage(message); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; initView(); } private void initView(){ mTextView = (TextView) findViewById(R.id.tv1); mClear = (Button) findViewById(R.id.btn0); mClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mTextView.setText(""); } }); mButton = (Button) findViewById(R.id.btn1); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { bindBookService(); } }); mButton2 = (Button) findViewById(R.id.btn2); mButton2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { unbindService(mServiceConnection); startActivity(new Intent(mContext,TestActivity.class)); } }); } @Override protected void onDestroy() { super.onDestroy(); if (mBookManager != null && mBookManager.asBinder().isBinderAlive()){ try { mBookManager.unRegisterListener(mListener); Log.d(TAG,"unRegister listener "); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mServiceConnection); } /** * 绑定BookService */ private void bindBookService(){ Intent intent = new Intent(); //这里需要指定Action 与packageName intent.setAction("android.intent.action.BOOK_MANAGER_SERVICE"); intent.setPackage("com.qiyei.ipcserver"); bindService(intent,mServiceConnection, BIND_AUTO_CREATE); } }
客户端直接在MainActivty去连接服务,并且在绑定成功后的onServiceConnected(ComponentName name, IBinder service)去注册IOnNewBookArrivedListener ,客户端自己实现该监听器,并在Activity的onDestroy中去取消注册与解绑服务。
4 Binder通信中的线程问题
对于服务端来说,客户端调用服务端的服务,具体的服务方法运行在服务端的Binder线程池中,并没有运行在服务端的主线程中。并且调用会阻塞。
在上面的服务BookManagerService中:
/** * 运行在Binder线程池中 */ private Binder mBinder = new IBookManager.Stub(){ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public List<Book> getBookList() throws RemoteException { Log.d(TAG,"getBookList thread :" + Thread.currentThread().getName()); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mRemoteCallbackList.register(listener); Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast()); mRemoteCallbackList.finishBroadcast(); } @Override public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException { mRemoteCallbackList.unregister(listener); Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast()); mRemoteCallbackList.finishBroadcast(); } };
当运行时,可以看到如下输出:
可以看到,确实运行在服务端的Binder线程中
对于客户端来说, 绑定服务成功后的回调ServiceConnection是运行在主线程(UI线程中的),同时由于调用服务端的服务可能会阻塞,因此尽量避免在主线程中去调用服务端的服务。另外,服务端回调客户端的监听Listener中的方法却是运行在客户端的Binder线程池中,因此不能直接更新UI,我们运行客户端的,可以看到如下:
5 客户端Binder意外死亡时重连的问题
一般来说,我们需要考虑到服务端程序的意外终止,这个时候Binder就会意外死亡,这个时候我们就需要重新连接服务。一般来说有两种方法,一种在ServiceConnection的onServiceDisconnected中进行重新连接服务,但是需要区分正常的断开服务,另外一种是给Binder设置DeathRecipient监听,当Binder意外死亡时会回调监听中的binderDied()方法
/** * Binder死亡代理 ,Binder死亡时会回调该方法 */ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if (mBookManager == null){ return; } mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0); mBookManager = null; //重新连接服务 bindBookService(); } }; /** * 运行在主线程中 */ private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG,"onServiceConnected thread :" + Thread.currentThread().getName()); mBookManager = IBookManager.Stub.asInterface(service); try { mBookManager.asBinder().linkToDeath(mDeathRecipient,0); mBookManager.registerListener(mListener); } catch (RemoteException e) { e.printStackTrace(); } try { mTextView.setText(mBookManager.getBookList().toString()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { //bindBookService(); } };
这样当Binder意外死亡时,会回调binderDied,我们在里面去重连服务即可。
6 Binder通信的权限问题
我们在Binder通信中不可避免的会用到权限的问题,有时服务端对服务的访问有限制,比如需要指定的客户端才能访问。这个时候我们就可以采取以下两种办法来实现:
(1) public IBinder onBind(Intent intent) 中验证权限,有权限才返回Binder,没有就返回null
@Nullable @Override public IBinder onBind(Intent intent) { Log.d(TAG,"onBind"); if (checkBookServicePermission()){ return mBinder; }else { return null; } } /** * 检查BookServiceDe调用权限 * @return */ private boolean checkBookServicePermission(){ int check = checkCallingOrSelfPermission("com.qiyei.ipc.book.ACCESS_BOOK_SERVICE"); return check == PackageManager.PERMISSION_DENIED; }
其中com.qiyei.ipc.book.ACCESS_BOOK_SERVICE定义在服务端的mainfest文件中:
<!--定义BookService 的权限--> <permission android:name="com.qiyei.ipc.book.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/> ...... <service android:name=".service.BinderPoolService" android:exported="true" android:permission="com.qiyei.ipc.book.ACCESS_BOOK_SERVICE"> <intent-filter> <action android:name="android.intent.action.BINDER_POOL_SERVICE"/> </intent-filter> </service>
这样只有客户端有这个权限时才能访问这个服务。
(2)在服务端的Binder对象中onTransact()根据Uid与pid来验证客户端的权限
比如,我们在实现返回的Binder对象中时,重写onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException方法,通过getCallingUid和getCallingPid方法来得到客户端的包名,根据具体的包名来决定是否给予访问的权限,注意,这一条也可以在IBinder onBind(Intent intent)中使用。
/** * 运行在Binder线程池中 */ private Binder mBinder = new IBookManager.Stub(){ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public List<Book> getBookList() throws RemoteException { Log.d(TAG,"getBookList thread :" + Thread.currentThread().getName()); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mRemoteCallbackList.register(listener); Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast()); mRemoteCallbackList.finishBroadcast(); } @Override public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException { mRemoteCallbackList.unregister(listener); Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast()); mRemoteCallbackList.finishBroadcast(); } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0){ packageName = packages[0]; } Log.d(TAG,"packageName:" + packageName); if (!packageName.startsWith("com.qiyei.ipc")){ return false; } return super.onTransact(code, data, reply, flags); } };
上面就是判断包名是否以”com.qiyei.ipc”开头,符合才给予权限访问。
本篇的介绍就到此结束,后续会继续补充,相关代码下载见我的github
源代码下载
相关文章推荐
- Android BLE应用开发的注意事项
- Android应用开发中,需要注意的一些事项
- 关于Android应用开发的一些安全注意事项
- Android应用开发的一些安全注意事项
- 关于Android应用开发的一些安全注意事项
- 关于Android应用开发的一些安全注意事项
- 关于Android应用开发的一些安全注意事项
- 关于Android应用开发的一些安全注意事项
- android 系统应用 开发 注意事项(该版本采用方法效率较高)
- Android BLE应用开发的注意事项
- Android开发应用框架的搭建步骤及注意事项
- 利用 Intellij IDEA 10 开发多工程Grails应用的关键步骤及注意事项(随笔)
- IIS做android应用下载服务器注意事项
- Android生存指南:一些开发注意事项
- Android 平台上蓝牙开发的关于 UUID 设置的注意事项
- Android NDK C++开发注意事项总结
- jsp开发技术应用的29个注意事项
- 低内存(256MB)应用开发说明及注意事项 推荐
- Silverlight开发企业级应用之部署问题注意事项(18)
- 分享开发Android应用需注意的两个要点