您的位置:首页 > 移动开发 > Android开发

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

源代码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: