IPC机制之四:IPC方式(AIDL)
来源:互联网 发布:网络电视怎么搜索节目 编辑:程序博客网 时间:2024/05/12 05:42
Messenger与AIDL
上一节讲解的Messenger来进行进程间通信的方法,可以发现,Messenger是以串行的方式来处理客户端的请求的,如果大量的消息同时发送给服务端,仍然也只能一个一个的处理。所以如果有大量的并发请求,那么用Messenger是不太合适了。同时,Messenger的作用是用来传递消息的,很多时候我们需要跨进程调用服务端的方法。这种情形Messenger是无法做到的。只能使用AIDL来进行。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL。只不过系统为我们做了封装方便调用而已。
AIDL进程间通信的流程,分为服务端和客户端:
1.服务端
首先需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
2.客户端
首先通过绑定服务端的Service,绑定成功后,将服务端返回的IBinder对象转成AIDL接口所属的类型。接着就可以调用AIDL中的方法了。
AIDL接口的创建
// IBookManager.aidlpackage com.thh.ipcdemo2aidl.aidl;// Declare any non-default types here with import statementsimport com.thh.ipcdemo2aidl.aidl.Book;import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unRegisterListener(IOnNewBookArrivedListener listener);}
AIDL支持的数据类型:
基本数据类型、String和CharSequence、List(只支持ArrayList,并且里面的每个元素都必须能被AIDL支持)、Map(只支持HashMap,里面的每个元素都必须被AIDL支持,包括KEY和Value)、Parcelable、AIDL(所有AIDL接口本身也可以在AIDL文件中使用)
注意:
自定义的Parcelable对象不是与AIDL文件是否在同一个包下,都需要显式的import进来。
如果AIDL文件中用到了自定义的Parcelable对象,必须创建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。如下:
package com.thh.ipcdemo2aidl.aidl;parcelable Book;
.
AIDL中除了基本数据类型以外,其他类型的参数必须标上方向:in、out或者inout。
如果client不需要传输数据给server,client只需要处理经过server处理过后的数据,
那么 client 和 server 都为 out
如果client只需要传输数据给server,而不需要处理返回的数据,
那么client和server都为 in
如果client需要传输数据给server,而且需要处理返回的数据,
则client和server都为 inout
AIDL中只支持方法,不支持静态常量。这点区别于传统接口。
示例代码:
package com.thh.ipcdemo2aidl;import android.annotation.TargetApi;import android.app.Service;import android.content.Intent;import android.content.pm.PackageManager;import android.os.Binder;import android.os.Build;import android.os.IBinder;import android.os.Parcel;import android.os.RemoteCallbackList;import android.os.RemoteException;import android.support.annotation.Nullable;import android.util.Log;import com.thh.ipcdemo2aidl.aidl.Book;import com.thh.ipcdemo2aidl.aidl.IBookManager;import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.atomic.AtomicBoolean;/** * Created by TangHui on 2015/10/21. */public class IService extends Service { private AtomicBoolean mIsServiceDestory = new AtomicBoolean(false); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>(); 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); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); Log.i("thhi", "[IService registerListener] registerListener size:"+mListenerList.getRegisteredCallbackCount()); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.unregister(listener); Log.i("thhi", "[IService unRegisterListener] current size:" + mListenerList.getRegisteredCallbackCount()); } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { // 第二种远程调用的验证方式 int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED){ Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED"); return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if (packages!=null && packages.length > 0){ packageName = packages[0]; } if (!packageName.startsWith("com.thh")){ return false; } return super.onTransact(code, data, reply, flags); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "IOS")); new Thread(new ServiceWorker()).start(); } @Nullable @Override public IBinder onBind(Intent intent) { // 第一种远程调用的验证方式// int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");// if (check == PackageManager.PERMISSION_DENIED){// Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED");// return null;// } return mBinder; } private class ServiceWorker implements Runnable { @Override public void run() { while (!mIsServiceDestory.get()){ try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new Book # " + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i); Log.i("thhi", "[IService onNewBookArrived] notify listener:"+listener); listener.onNewBookArrived(book); } mListenerList.finishBroadcast(); } @Override public void onDestroy() { super.onDestroy(); mIsServiceDestory.set(true); }}
CopyOnWriteArrayList支持并发读/写,其原理是这样的,开始大家共享同一个内容,但如果一个人要去修改,它就会Copy一份新的内容,当修改并添加完毕后,自动将引用指向这个新的内容。在源代码中可以发现,它的add方法是添加的有锁的,get方法没有锁。这种类型对象适合多读少写的操作,因为可能导致数据的不一致性。因为AIDL方法在服务端的Bindler线程池中执行,因此当多个客户端访问时,我们需要做线程同步的处理,所以这里就用CopyOnWriteArrayList。
另外还有一个ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
package com.thh.ipcdemo2aidl;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import com.thh.ipcdemo2aidl.aidl.Book;import com.thh.ipcdemo2aidl.aidl.IBookManager;import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;import java.util.List;public class MainActivity extends AppCompatActivity { private static final int MESSAGE_NEW_BOOK_ARRIVED = 0; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.i("thhi", "[MainActivity mHandler handlerMessage]: MESSAGE_NEW_BOOK_ARRIVED, msg:"+msg.obj); break; } super.handleMessage(msg); } }; private IBookManager iBookManager; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { iBookManager = IBookManager.Stub.asInterface(service); try { List<Book> bookList = iBookManager.getBookList(); Log.i("thhi", "bookList:" + bookList); iBookManager.addBook(new Book(3, "Window Phone")); Log.i("thhi", "addBook after:" + iBookManager.getBookList()); iBookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { iBookManager = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this, IService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book book) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget(); } }; @Override protected void onDestroy() { if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) { try { iBookManager.unRegisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mServiceConnection); super.onDestroy(); }}
知识点:
AIDL文件中无法使用的普通接口,只能是AIDL接口。
另外通过RemoteCallbackList来删除跨进程listener的接口。用于解决无法删除对应listener对象。因为是客户端传递给服务端的对象在服务端会生成一个不同的对象,但它们底层的Binder对象是同一个,利用这个特点,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删除掉。另外,当客户端进程终止后,它能够自动移除客户端所注册的listener。
客户端调用服务端方法时,尽量使用子线程。服务端AIDL方法调用客户端方法时,因为当前线程是Binder线程池的子线程,所以不得进行UI操作。在上面例子中,服务端需要调用客户端listener的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。
.
.
Binderl意外中断
往往是由于服务端进程意外停止了,这时我们需要重新连接服务。有两种方法:
给Binder设置DeathRecipient监听,当Binder中断后,我们会收到binderDeath方法的回调。
另一种方法在onServiceDisconnected中重连。
以上两种方法区别在于:onServiceDisconnected在UI线程中,而DeathRecipient binderDeath方法是在Binder线程池中被回调。
.
.
AIDL权限验证
在服务端Service的onBind方法进行验证,或在onTransact方法进行权限验证。
验证的方法有多种,比如设置Service的permission,或者通过getCallingUid或getCallingPid做验证。
服务端
<uses-permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" /><service android:name=".IService" android:enabled="true" android:exported="true" android:permission="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" android:process=":remote"> <intent-filter> <action android:name="com.thh.ipcdemo2aidl.action.BOOK_SERVICE" /> </intent-filter> </service><permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" android:protectionLevel="normal" />
@Override public IBinder onBind(Intent intent) { // 第一种远程调用的验证方式 int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED){ Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED"); return null; } return mBinder; }
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { // 第二种远程调用的验证方式,权限验证 int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED){ Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED"); return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if (packages!=null && packages.length > 0){ packageName = packages[0]; } if (!packageName.startsWith("com.thh")){ return false; } return super.onTransact(code, data, reply, flags); }
客户端
<uses-permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" />
Intent intent = new Intent(); intent.setAction("com.thh.ipcdemo2aidl.action.BOOK_SERVICE"); Intent explicitIntent = getExplicitIntent(this, intent); bindService(explicitIntent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBookManager = IBookManager.Stub.asInterface(service); try { Log.i("thhi", "[MainActivity onServiceConnected] bookList:" + mBookManager.getBookList()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }, BIND_AUTO_CREATE);
public static Intent getExplicitIntent(Context context, Intent implicitIntent) { // Retrieve all services that can match the given intent PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); // Make sure only one match was found if (resolveInfo == null || resolveInfo.size() != 1) { return null; } // Get component info and create ComponentName ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); // Create a new intent. Use the old one for extras and such reuse Intent explicitIntent = new Intent(implicitIntent); // Set the component to be explicit explicitIntent.setComponent(component); return explicitIntent; }
示例代码:https://github.com/huivs12/IPCDemo2AIDL
- IPC机制之四:IPC方式(AIDL)
- IPC机制(AIDL)
- IPC机制系列之三 Android中的IPC方式 (AIDL)
- IPC机制之AIDL、Messenger
- android IPC机制之 AIDL
- 【Android机制】IPC机制之AIDL
- Android AIDL IPC机制
- IPC机制---使用AIDL
- IPC机制<二>AIDL
- 二、IPC机制续(IPC方式)
- 二、IPC机制续(IPC方式)
- Android IPC机制之AIDL的使用
- IPC机制之AIDL传递Parcelable
- 读书笔记--IPC机制(四)
- Android IPC之AIDL
- Android IPC 之AIDL
- Android IPC之AIDL
- IPC之AIDL简析
- java.net.URISyntaxException: Illegal character in path at
- Ifree黄金版最常见问题
- 【巩固地基】系列之:C#基础读书笔记(杂)
- 例题4.10 离海最远的点 LA3890
- sql执行顺序
- IPC机制之四:IPC方式(AIDL)
- [sikulim] 几个testcase
- 一些重要的算法源码
- 表单标签--浏览器交互
- 文字过长,QLabel显示不全的问题,QLabel怎么自动分行显示
- Android Studio JNI javah遇到的问题
- java中checked和unchecked 异常处理的例子
- Android layout_weight体验(实现按比例显示)
- Determinant