Android的IPC机制(下)
文章目录
1.4 Android中的IPC方式
- Android中IPC的方式有很多:Intent中附加extras传递信息、共享文件方式共享数据、Binder方式跨进程通信、ContentProvider天生跨进程通信、网络通信Socket等。
1.4.1 使用Bundle
Activity、Service、Receiver支持在Intent中传递Bundle数据;Bundle实现了Parcelable接口,可以在不同进程间传输。通过Bundle传输的数据必须能够序列化,比如:基本类型、实现Parcelable接口的对象、实现Serializable接口的对象、Android支持的特殊对象。
Bundle使用示例:
//Bundle保存的数据,是以key-value(键值对)的形式存在的。 Bundle bundle = new Bundle(); bundle.putXXXXX(); Intent intent = new Intent(); intent.putExtra("name",bundle);
思路:修改跨进程程序。举例A进程执行计算,计算完成后启动B进程的一个组件传递计算结果给B进程,如果计算结果不支持放入Bundle,无法通过Intent传输,更换其他IPC方式会复杂很多。我们可以通过Intent启动进程B的一个Service组件,让Service在后台计算,计算完成后启动B进程的目标组件,由于Service和B进程同处一个进程,可直接获取计算结果。这样我们只用了很小的代价就避免了进程间的通信问题。
1.4.2 使用文件共享
通过两个进程读/写同一文件来交换数据。但存在一个问题:Android系统基于Linux,使得其并发读/写文件可以没有限制地进行,可能会出现问题。虽然可能存在问题但这仍是一种好用的IPC方式。
文件共享举例:
Activity A序列化Person对象到一个文件里;Activity B从该文件中读取对象并进行反序列化:
//Activity A(在onResume中执行) private void persistToFile(){ new Thread(new Runnable() { @Override public void run() { Person person = new Person("Tom",22,true,info); File dir = new File(FILE_PATH); if (!dir.exists()){ dir.mkdir(); } File cachedFile = new File(FILE_PATH); ObjectOutputStream objectOutputStream = null; try{ objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile)); objectOutputStream.writeObject(person); }catch (IOException e){ e.printStackTrace(); }finally { if (null!=objectOutputStream){ try { objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }).start(); }
//Activity B(在onResume中执行) private void recoverFromFile() { new Thread(new Runnable() { @Override public void run() { Person person = null; File cachedFile = new File(FILE_PATH); if (cachedFile.exists()) { ObjectInputStream objectInputStream = null; try { objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile)); person = (Person) objectInputStream.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { objectInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }).start(); }
文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
SharedPreference是Android提供的轻量级存储方案,底层实现上采用XML文件存储键值对;虽然SharedPreference也是文件读/写的形式,但是他的读写有一定的缓存策略,在内存中会有一份SharedPreference文件的缓存,这使得在多进程模式下,系统对它的读/写就变得不可靠,高并发的读/写访问会使SharedPreference有很大几率丢失数据。不建议在进程间通信中使用SharedPreference。
1.4.3 使用Messenger
Messenger是轻量级的IPC方案,底层实现是AIDL,可以在不同进程中传递Message对象。Messenger一次处理一个请求,因此在服务端不用考虑线程同步问题,服务端中不存在并发执行的情形。
服务端进程:
在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
服务端典型代码:
public class MessengerService extends Service { private static final String TAG = "MessengerService"; private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_CLIENT: Log.d(TAG, "receive msg from Client: " + msg.getData().get("msg")); break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
<application> <!--Manifest中注册服务--> <service android:name=".MessengerService" android:enabled="true" android:exported="true" android:process=":remote"/> </application>
客户端进程:
首先绑定服务端的Service,绑定成功后服务端返回的IBinder对象创建一个Messenger对象,通过Messenger就可以向服务端发送类型为Message类型的对象。
如果需要服务端能够回应客户端,客户端就需要像服务端一样,创建一个Handler并通过它创建一个Messenger对象,并把这个Messenger对象通过通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。
客户端典型代码:
public class MessengerActivity extends AppCompatActivity { private static final String TAG = "MessengerActivity"; 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","client"); msg.setData(data); try { mService.send(msg); }catch (RemoteException e){ e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent(this,MessengerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } }
Messenger中传递的数据需要放入Message中,Messenger和Message都实现了Parcelable接口。Message中可以使用的载体有what、arg1、arg2、Bundle、Object和replyTo。
在上述例子上修改,使得服务端收到客户端消息后返回一个消息,客户端根据这个消息在进行处理:
//服务端 public class MessengerService extends Service { private static final String TAG = "MessengerService"; private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_CLIENT: Log.d(TAG, "receive msg from Client: " + msg.getData().get("msg")); Messenger client = msg.replyTo; Message relplyMessage = Message.obtain(null,MyConstants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString("reply","I am Service !"); relplyMessage.setData(bundle); try { client.send(relplyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
//客户端 public class MessengerActivity extends AppCompatActivity { private static final String TAG = "MessengerActivity"; private Messenger mService; private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle()); 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", "I am client !"); msg.setData(data); //注意:这里添加了replyTo msg.replyTo = mGetReplyMessenger; try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; private static class MessengerHandle extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_SERVICE: Log.d(TAG, "receive msg from Service: " + msg.getData().get("reply")); break; default: super.handleMessage(msg); } } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent(this, MessengerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } }
- Messenger工作原理示意图:
1.4.4 使用AIDL
Messenger是以串行的方式处理客户端发来的信息,如果大量的信息同时发送到服务端,服务端仍然只能一个一个处理,如果有大量并发请求,Messenger不再合适。Messenger的主要作用是传递消息,当我们需要跨进程调用服务端的方法,Messenger无法做到,但我们可以使用AIDL实现跨进程方法调用。
服务端:
- 服务端要创建一个Service监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,之后在Service中实现这个AIDL接口即可。
客户端:
- 绑定服务端的Service,成功后将服务端返回的Binder对象转成AIDL接口所属的类型,之后调用AIDL中的方法即可。
AIDL接口的创建:
AIDL文件支持的数据类型:
- 1.基本数据类型。
- 2.String和CharSequence。
- 3.List:只支持ArrayList,且其中每个元素都能被AIDL支持。
- 4.Map:只支持HashMap,且其中每个元素都能被AIDL支持,包括key和value。
- 5.Parcelable:所有实现了Parcelable接口的对象。
- 6.AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
自定义Parcelable对象和AIDL对象要显式import进来。
自定义Parcelable对象要新建一个同名的AIDL文件,声明它为Parcelable类型。
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout。in表示输入型参数;out表示输出型参数;inout表示输入输出型参数。
AIDL接口中只支持方法,不支持声明静态常量。
推荐将AIDL相关的类和AIDL文件放入同一个包中,方便复制转移。AIDL的包结构要在服务端和客户端保持一致,否则反序列化的时候会出错。
RemoteCallbackList:
通过AIDL实现注册和解绑时传递listener时会出现无法解绑的问题。这是因为Binder会将客户端传递过来的对象重新转化并生成一个新的对象;当我们注册和解绑时,虽然从客户端传过去的是同一个对象,但是在服务端Binder将会产生两个新的对象。注意:对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程。
RemoteCallbackList是系统专门提供用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。
public class RemoteCallbackList<E extends IInterface>
RemoteCallbackList工作原理:在内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,calue是Callback类型:
Arraymap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
其中Callback中封装了真正的远程listener。当客户端注册listener的时候,他会把这个listener的信息存入mCallbacks中。
key和value分别通过以下方式获得:
IBinder key = listener.asBinder(); Callback value = new Callback(listener,cookie);
当客户端解绑时,只需遍历服务端所有的listener,找到与需要解绑的客户端listener具有相同Binder对象的服务器端的listener,将其删除即可。
使用RemoteCallbackList,当客户端进程终止后,可以自动移除客户端所注册的listener。
RemoteCallbackList内部自动实现了线程同步的功能,使用其注册和解绑时,不需要做额外的线程同步工作。
在AIDL中使用权限验证功能:
1.在onBind中进行验证,验证不通过返回null。
<!--定义权限--> <permission android:name="com.virtual.aidltest.ACCESS_PERSON_SERVICE" android:protectionLevel="normal" />
public IBinder onBind(Intent intent){ int check = checkCallingOrSelfPermission("com.virtual.aidltest.ACCESS_PERSON_SERVICE"); if(check==PackageManager.PERMISSION_DENIED){ return null; } }
<!--注册权限--> <uses-permission android:name="com.virtual.aidltest.ACCESS_PERSON_SERVICE"/>
2.在服务端的onTransact方法中验证,失败返回false。
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.virtual.aidltest.ACCESS_PERSON_SERVICE"); if (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.virtual")) { return false; } return super.onTransact(code, data, reply, flags); }
需要注意的一些补充:
客户端调用远程服务的方法,被调用的方法在服务端的Binder线程池中,同时客户端线程会被挂起。如果是耗时操作,且客户端线程是UI线程,就会导致客户端ANR。客户端onServiceConnected和onServiceDisconnected方法运行在UI线程中,注意不要在其中调用耗时方法。
服务端方法运行在Binder线程池中,服务端本身可执行大量耗时操作,不需要在服务端开线程去执行异步任务。
Binder死亡的处理方式:
1.给Binder设置DeathRecipient监听,Binder死亡时,收到binderDied方法回调。已在Binder介绍中提及。
2.onServiceDisconnected回调。
两种方法,区别在于:onServiceDisconnected在客户端的UI线程中被回调;binderDied在客户端的Binder进程池中回调(不能访问UI)。
补充一个完整的使用AIDL例子:点击这里
1.4.5 使用ContentProvider
ContentProvider的底层实现是Binder。
ContentProvider举例:
创建DbOpenHelper.java
/** * 实现一个数据库来管理信息 */ public class DbOpenHelper extends SQLiteOpenHelper { private static final String DB_NAME = "person_provider.db"; public static final String PERSON_TABLE_NAME = "person"; private static final int DB_VERSION=1; //人员表和年龄表 private String CREATE_PERSON_TABLE = "CREATE TABLE IF NOT EXISTS "+PERSON_TABLE_NAME+"(_id INTEGER PRIMARY KEY,"+"name TEXT,"+"sex INT)"; public DbOpenHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.execSQL(CREATE_PERSON_TABLE); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { } }
创建PersonProvider.java
public class PersonProvider extends ContentProvider { private static final String TAG = "PersonProvider"; public static final String AUTHORITY = "com.virtual.PersonProvider"; /** * 为了明确外界访问的是具体哪一张表,需要为表定义单独的Uri和Uri_Code,并使用UriMatcher将Uri与Uri_Code关联 * 当外界访问ContentProvider时,根据Uri得到Uri_Code;通过Uri_Code明确访问的表 */ public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person"); public static final int PERSON_URI_CODE = 0; private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { mUriMatcher.addURI(AUTHORITY, "person", PERSON_URI_CODE); } private Context mContext; private SQLiteDatabase mDb; private String getTableName(Uri uri) { String tableName = null; switch (mUriMatcher.match(uri)) { case PERSON_URI_CODE: tableName = DbOpenHelper.PERSON_TABLE_NAME; break; default: break; } return tableName; } /** * onCreate()在主线程运行,不要做耗时操作。 * query()、update()、insert()、delete()在Bidner线程。 */ @Override public boolean onCreate() { Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName()); mContext = getContext(); //演示代码,实际过程中不推荐在主线程进行耗时的数据库操作 initProviderData(); return true; } /** * query()、update()、insert()、delete()方法是存在多线程并发的,需要内部做好线程同步; * 示例使用SQLite且只有一个SQLiteDatabase的连接,可以正确应对多线程情况。 * 在SQLiteDatabase内部对数据库的操作是有同步处理的。 * 但多个SQLiteDatabase之间无法保证线程同步,因为SQLiteDatabase对象之间无法进行线程同步。 */ private void initProviderData() { mDb= new DbOpenHelper(mContext).getWritableDatabase(); mDb.execSQL("delete from "+DbOpenHelper.PERSON_TABLE_NAME); mDb.execSQL("insert into person values(1,'Tom',21); "); mDb.execSQL("insert into person values(2,'Jack',17); "); } @Override public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { Log.d(TAG, "query, current thread:" + Thread.currentThread().getName()); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } return mDb.query(table,strings,s,strings1,null,null,s1,null); } @Override public String getType(Uri uri) { Log.d(TAG, "getType"); return null; } @Override public Uri insert(Uri uri, ContentValues contentValues) { Log.d(TAG, "insert"); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } mDb.insert(table,null,contentValues); mContext.getContentResolver().notifyChange(uri,null); return uri; } @Override public int delete(Uri uri, String s, String[] strings) { Log.d(TAG, "delete"); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } int count = mDb.delete(table,s,strings); if (count>0){ mContext.getContentResolver().notifyChange(uri,null); } return count; } @Override public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { Log.d(TAG, "update"); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } int row = mDb.update(table,contentValues,s,strings); if(row>0){ mContext.getContentResolver().notifyChange(uri,null); } return row; } }
创建ProviderActivity.java
public class ProviderActivity extends AppCompatActivity { private static final String TAG = "ProviderActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_provider); Uri uri = Uri.parse("content://com.virtual.PersonProvider/person"); //添加 ContentValues values = new ContentValues(); values.put("_id",3); values.put("name","Jone"); values.put("sex",0); getContentResolver().insert(uri,values); //查看 Cursor personCursor=getContentResolver().query(uri,new String[]{"_id","name","sex"},null,null); while (personCursor.moveToNext()){ StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(personCursor.getInt(0)); stringBuilder.append(personCursor.getString(1)); stringBuilder.append(personCursor.getInt(2)); Log.d(TAG, "query person: "+stringBuilder.toString()); } personCursor.close(); } }
Manifest注册
<!--注册活动--> <activity android:name=".ProviderActivity"/> <!--注册ContentProvider--> <!--authorities是ContentProvider的唯一标识--> <!--permission是访问该ContentProvider需要声明的权限名--> <provider android:authorities="com.virtual.PersonProvider" android:name="com.virtual.taskcontentprovider.PersonProvider" android:permission="com.virtual.PROVIDER" android:process=":provider"/>
1.4.6 使用Socket
Socket(套接字)支持跨进程传输任意字节流。
Socket举例:
服务端:
public class TCPServerService extends Service { private boolean mIsServiceDestroyed = false; private String[] mDefinedMessages = new String[]{ "Hello", "hi", "Morning", "Well", "Cool" }; @Override public void onCreate() { new Thread(new TcpServer()).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { mIsServiceDestroyed = true; super.onDestroy(); } private class TcpServer implements Runnable{ @Override public void run() { ServerSocket serverSocket = null; try { //监听8688端口 serverSocket=new ServerSocket(8688); } catch (IOException e) { System.err.println("establish tcp server failed, port:8688"); e.printStackTrace(); return; } while (!mIsServiceDestroyed){ try { //接收客户端请求 final Socket client = serverSocket.accept(); System.out.println("accept"); new Thread(){ @Override public void run() { try { responseClient(client); } catch (IOException e) { e.printStackTrace(); } } }.start(); } catch (IOException e) { e.printStackTrace(); } } } private void responseClient(Socket client) throws IOException{ //用于接收客户端消息 BufferedReader in =new BufferedReader(new InputStreamReader(client.getInputStream())); //用于向客户端发送消息 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true); out.println("Welcome!"); while (!mIsServiceDestroyed){ String str = in.readLine(); System.out.println("msg from client:"+str); if (str == null){ //断开客户端 break; } int i =new Random().nextInt(mDefinedMessages.length); String msg= mDefinedMessages[i]; out.println(msg); System.out.println("send:"+msg); } System.out.println("client quit."); //关闭流 out.close(); in.close(); client.close(); } } }
客户端:
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener { private static final int MESSAGE_RECEIVE_NEW_MSG = 1; private static final int MESSAGE_SOCKET_CONNECTED = 2; private Button mSendButton; private TextView mMessageTextView; private EditText mMessageEditView; private PrintWriter mPrintWriter; private Socket mClientSocket; private Handler mHandler=new Handler(){ @Override public void handleMessage(@NonNull Message msg) { switch (msg.what){ case MESSAGE_RECEIVE_NEW_MSG:{ mMessageTextView.setText(mMessageTextView.getText()+(String)msg.obj); break; } case MESSAGE_SOCKET_CONNECTED:{ mSendButton.setEnabled(true); break; } default: break; } } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tcpclient); mMessageTextView=findViewById(R.id.tv_client); mSendButton=findViewById(R.id.bt_client); mSendButton.setOnClickListener(this); mMessageEditView=findViewById(R.id.et_client); Intent service = new Intent(this,TCPServerService.class); startService(service); new Thread(){ @Override public void run() { connectTCPServer(); } }.start(); } @Override protected void onDestroy() { if (mClientSocket !=null){ try { mClientSocket.shutdownInput(); mClientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); } @Override public void onClick(View view) { if (view == mSendButton){ final String msg = mMessageEditView.getText().toString(); if(!TextUtils.isEmpty(msg) && mPrintWriter!=null){ new Thread(new Runnable() { @Override public void run() { mPrintWriter.println(msg); } }).start(); mMessageEditView.setText(""); String time = formatDateTime(System.currentTimeMillis()); final String showMsg = "self "+time+":"+msg+"\n"; mMessageTextView.setText(mMessageTextView.getText()+showMsg); } } } private String formatDateTime(long time){ return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time)); } private void connectTCPServer(){ Socket socket=null; while (socket==null){ try { socket=new Socket("localhost",8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); System.out.println("connect server success"); } catch (IOException e) { SystemClock.sleep(1000); System.out.println("connect tcp server failed, retry ..."); e.printStackTrace(); } } try { //接收服务端的消息 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (!TCPClientActivity.this.isFinishing()){ String msg = br.readLine(); System.out.println("receive: "+msg); if (msg!=null){ String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "server "+time+":"+msg+"\n"; mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget(); } } //断开连接 System.out.println("quit..."); mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
权限声明:
<!--权限声明--> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
1.5 Binder连接池
AIDL是一种最常用的进程间通信方式,是日常开发中涉及进程间通信时的首选。
AIDL使用流程:
- 1.创建一个Service和一个AIDL接口。
- 2.创建一个类继承AIDL接口中的Stub类并实现Stub中的抽象方法。
- 3.在Service的onBind方法中返回这个类的对象。
- 4.客户端绑定服务端Servioce,建立连接。
随着AIDL数量的增加,不能无限制增加Service(Service本身消耗一定系统资源)。我们可以将所有的AIDL放在一个Service中管理。
每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的 Binder 对象;服务端只需要一个 Service即可,服务端提供一个queryBinder 接口,该接口能够根据业务模块的特征来返回相应的Binder对象。
Binder连接池示例:
需要的AIDL文件:
// ISecurityCenter.aidl package com.virtual.bindertest; interface ISecurityCenter { String encrypt(String content); }
// ICompute.aidl package com.virtual.bindertest; interface ICompute { int add(int a,int b); }
// IBinderPool.aidl package com.virtual.bindertest; interface IBinderPool { IBinder queryBinder(int binderCode); }
两个不同功能的AIDL的接口实现:
public class SecurityCenterImpl extends ISecurityCenter.Stub { private static final char SECRET_CODE = '^'; @Override public String encrypt(String content) throws RemoteException { char[] chars = content.toCharArray(); for (int i = 0; i < chars.length; i++) { chars[i] ^= SECRET_CODE; } return new String(chars); } }
public class ComputeImpl extends ICompute.Stub { @Override public int add(int a, int b) throws RemoteException { return a + b; } }
进程池:
/** * BinderPool是一个单例实现,在同一个进程中只会初始化一次。 * 提前初始化BinderPool,比如在Application中提前初始化,可以优化程序。 */ public class BinderPool { private static final String TAG = "BinderPool"; public static final int BINDER_NONE = -1; public static final int BINDER_COMPUTE = 0; public static final int BINDER_SECURITY_CENTER = 1; private Context mContext; private IBinderPool mBinderPool; private static volatile BinderPool sInstance; //countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次。 private CountDownLatch mConnectBinderPoolCountDownLatch; private BinderPool(Context context) { mContext = context.getApplicationContext(); connectBinderPoolService(); } public static BinderPool getInstance(Context context) { if (sInstance == null) { synchronized (BinderPool.class) { if (sInstance == null) { sInstance = new BinderPool(context); } } } return sInstance; } /** * BinderPool要在线程中执行: * CountDownLatch将bindService这一异步操作转换成了同步操作,即其可能为耗时操作。 * 耗时操作不能在主线程进行。 */ private synchronized void connectBinderPoolService() { mConnectBinderPoolCountDownLatch = new CountDownLatch(1); Intent service = new Intent(mContext, BinderPoolService.class); mContext.bindService(service, mBinderPoolConnect, Context.BIND_AUTO_CREATE); try { mConnectBinderPoolCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public IBinder queryBinder(int binderCode) { IBinder binder = null; try { if (mBinderPool != null) { binder = mBinderPool.queryBinder(binderCode); } } catch (RemoteException e) { e.printStackTrace(); } return binder; } private ServiceConnection mBinderPoolConnect = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinderPool = IBinderPool.Stub.asInterface(service); try { mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } mConnectBinderPoolCountDownLatch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { } }; private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died."); mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0); mBinderPool = null; connectBinderPoolService(); } }; public static class BinderPoolImpl extends IBinderPool.Stub { public BinderPoolImpl() { super(); } @Override public IBinder queryBinder(int binderCode) throws RemoteException { IBinder binder = null; switch (binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputeImpl(); break; } default: break; } return binder; } } }
进程池服务:
public class BinderPoolService extends Service { private static final String TAG = "BinderPoolService"; private Binder mBinderPool = new BinderPool.BinderPoolImpl(); @Override public void onCreate() { super.onCreate(); } @Nullable @Override public IBinder onBind(Intent intent) { Log.d(TAG, "onBind"); return mBinderPool; } @Override public void onDestroy() { super.onDestroy(); } }
MainActivity中测试:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; ISecurityCenter mSecurityCenter; ICompute mCompute; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { doWork(); } }).start(); } private void doWork() { BinderPool binderPool = BinderPool.getInstance(MainActivity.this); //AIDL 1 Log.d(TAG, "visit ISecurityCenter"); IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER); mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder); String msg = "hello world"; try { Log.d(TAG, "encrypt:" + mSecurityCenter.encrypt("Hello")); } catch (RemoteException e) { e.printStackTrace(); } //AIDL 2 Log.d(TAG, "visit ICompute"); IBinder computebinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE); mCompute = ComputeImpl.asInterface(computebinder); try { Log.d(TAG, "compute:" + "3+5=" + mCompute.add(3, 5)); } catch (RemoteException e) { e.printStackTrace(); } } }
1.6 选择合适的IPC方式
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,无法做到进程间即时通信 | 无并发访问情形,交换简单的数据且实时性不高的情况 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能强大,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点烦琐,不支持直接的RPC | 网络数据交换 |
1.7 其他
1.7.1 运行环境
- Compile Sdk Version 28
- Build Tools Version 28.0.3
- Source Compatibility 1.8
- Target Compativility 1.8
1.7.2 过程中可能产生的问题
产生错误 :… aidl.exe finished with non-zero exit value …
- 可能原因1:运行环境问题,Build Tools Version和Build Tools Version不匹配。
- 可能原因2:使用的自定义对象没有正确配置,需要手动import、in、out、inout。
1.7.3 参考资料
- Android开发艺术与探索