解密 Android IPC 机制_使用篇
我正在参加「掘金·启航计划」
IPC各类手段
基于Binder的手段
Intent-Bundle
使用 Intent 是可以在启动四大组件的时候携带数据传递过去的,即使是不同进程间的数据,因为 Intent 的底层是使用了 Binder 机制来传递数据的。而在传递数据的时候,需要考虑的一点是就是数据需要时可序列化的。,此时我们可以使用 Bundle 类型来装载数据,Bundle 是支持序列化的一类常用数据。
大家都知道,通过intent是能够传递数据的,但是当传递的数据量过大是,会抛出TransactionTooLargeException的异常,这就证明我们用intent传递数据时,只能是一些很小的数据,以防应用崩溃。
安卓在TransactionTooLargeException异常的文档中,就已经进行了详细的说明。简单来说,是因为当使用Intent来传递数据时,用到了Binder机制,数据就存放在了Binder的事务缓冲区里面,而事务缓冲区是有大小限制的,在没有修改过ROM的手机里,一般都是1M的大小。但是这并不意味着小于1M的数据就是安全的,因为这个事务缓冲区,是当前整个进程共享,可能会同时有多个地方在传递数据。这也就是说我们用intent传递数据时,就一定要考虑数据的大小
如何解决
开发中如果activity之间要传递一个Bitmap图片、文件等内容,虽然Bitmap对象已经序列化,可以通过Bundle传递,但是Bitmap对象通常较大,很容易引发TransactionTooLargeException异常。所以我们可以只传递该图片的id、网址、文件路径等,然后在接收的activity通过这些参数去还原该图片或者文件,而不需要把Bitmap对象或文件给整体传过去。
Messenger
Messenger 是一种基于 AIDL 封装的轻量型 IPC 方式,其直译为信使,负责的就是在进程间传递 Message 信息。Messenger 作为一个单次传递的信使,一次只能出力一个请求,传递单份信件,无需做线程同步的处理。
由于 Messenger 基于 AIDL 封装的,所以它的实现思路也是基于 C/S 结构通信。我们可以在客户端进程中绑定服务端的 Service,利用服务端返回的 IBinder 来创建一个 Messenger 对象,然后再使用这个对象来进行通信。具体编写如下所示:
首先我们来看服务端,我们在 onBinder 中返回了当前创建的 Messenger 的底层 Binder 实例。而其中的 Messenger 则是由一个自定义的 Handler 来创建的,这个 Handler 中先是在接收到匹配的信息后,对其内容进行打印;然后 msg.replyTo 是客户端传过来的回复信使,使用其在服务端进行实例声明,接着使用回复信使发送 relpyMessage。
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.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
接者我们再看客户端,首先使用 ServiceConnection 来编写与服务端的连接逻辑,这部分逻辑中,使用了服务端返回的 IBinder 创建一个信使 mService,mService 发送的 Message 中,记得要设置 replyTo 参数,要跟着发送给服务端的。这个 replyTo 参数是一个新的信使,其也使用了一个新的 Handler 来处理服务端返回的信息,其作用是用于服务端传递返回的消息。
public class MessengerActivity extends Activity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Log.d(TAG, "bind service");
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello, this is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent("com.ryg.MessengerService.launch");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
由上述的代码实践中,我们可以看到在发送消息和返回消息这两次的消息传递中,我们使用了两个 Messenger 信使,这也说明了这一种 IPC 方式不是高效的,无法并发处理消息。而且在 Message 中,我们最常用到的数据类型就是 Bundle 了,由于它是已支持序列化的,所以可以进程间通信,这一个字段支持大量的数据类型,让我们可以进行传递的类型也更加丰富。还有一点需要注意的是,Message 的 object 字段,只有系统实现了 Parcelable 接口的数据类型才可以利用这个字段进行进程间通信。
AIDL
那么,作为 Messenger 的底层实现,AIDL 和 Messenger 又有什么不同呢?他们的不同点其实在于使用和功能方面
Messenger:不用编写 AIDL 文件,支持直接创建实例调用通信;串行处理消息;只支持传递消息
AIDL:需要编写 AIDL 文件生成支持 Binder 的代码;可以处理大量的并发请求;支持调用服务端的方法,不止于传递信息
前面已经介绍过 AIDL 的基本结构组成和生成方法了,这里再补充一些编写 AIDL 文件的规则
-
支持的数据类型
- 基本数据类型,int、short等
- String 和 CharSequence
- List:只支持 ArrayList ,且每个元素都需要被 AIDL 支持
- Map:只支持 HashMap ,且每个元素都需要被 AIDL 支持
- Parcelable:所有实现了 Parcelabel 接口的方法
- AIDL:所有 AIDL 接口文件本身

-
import规则:所有自定义的 Parcelable 对象和 AIDL 对象都需要显式 import 进来
-
自定义 Parcelable 对象规则:如果使用到自定义的 Parcelable 对象,那么需要为每一个这样的对象新建一个与它同名的 AIDL 文件
-
参数标志:除了基本类型,其他类型的参数必须标上
in、out或inout。分别表示:输入型参数、输出型参数、输入输出型参数。不要乱用,因为各自开销不同,乱用影响性能 -
接口支持:AIDL 中可以定义接口,但是这种接口只支持方法,不支持声明静态常量
-
文件位置:为了让方便迁移 AIDL 文件,建议把所有的 AIDL 文件放在同一个包中。因为如果需要将客户端和服务端的实现分到不同工程包中,或者要在不同 APP 中做 AIDL 通信,需要在客户端和服务端保持 AIDL 的包结构一致。所以如果放在同一个包中就方便迁移了。保持结构一致的原因是为了让反序列化可以正常执行。当然,新版的 AS 中,新建 AIDL 文件就会自动帮你归纳到 aidl 包中了。
下面我们看一个利用 AIDL 来进行进程间通信的例子,它一样使用的是 C/S 结构
这里要实现的功能是客户端进程从服务端中获取书籍的列表,以及对其添加新的书籍;并且需要实现一个观察者模式,在客户端订阅书籍之后,服务端定时检查书籍并向客户端发布最新的书籍内容。其中,AIDL 文件可以基于上文中提到的进行修改,首先,需要新建一个观察者模式中需要用到的监听接口。其次,在 IBookManager.aidl 中添加绑定监听和解绑监听的接口。
// IOnNewBookArrivedListener.aidl
package com.qxy.potatos.module.test.aidl;
import com.qxy.potatos.module.test.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
// IBookManager.aidl
package com.qxy.potatos.module.test.aidl;
import com.qxy.potatos.module.test.aidl.Book;
import com.qxy.potatos.module.test.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
由 AIDL 文件生成 Binder 模板代码后,接下来就可以编写服务端的代码了。服务端的主要逻辑是实例化 IBookManager.Stub() 返回给连接它的客户端,并且设置了一个 5 秒向客户端启用一次回调的子线程服务。其中需要注意的是,CopyOnWriteArrayList 是 自适应支持并发的 ArrayList,所以无需担心其中的并发问题。而另一个的 RemoteCallbackList ,是用于专门用于提供来绑定和解绑跨进程 Listener 接口的类,由于跨进程通信使用到的序列化和反序列化出来的示例并非同一示例,所以 RemoteCallbackList 中以 IBinder 为 key 存储监听对象,就可以解决多次传递 Listener 不是同一 Listener 的问题了。
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对
CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListeners = new RemoteCallbackList<>();
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
public BookManagerService() {
}
/**
* <p>根据 AIDL 生成的模板代码,建立一个 Binder 实例</p>
*
* <p>里面实现了包括权限检查,以及四个接口</p>
*/
private Binder mBinder = new IBookManager.Stub() {
/**
* <pre>
* 该方法是服务端用于处理客户端请求的方法,所以可以在这里进行权限的鉴别。
* </pre>
*
* @param code 客户端请求的目标方法
* @param data 包含目标方法所需的参数
* @param reply 目标方法的返回值
* @param flags 标志
* @return 如果不允许通信,则返回 false,否则返回 true
* @throws RemoteException 异常抛出
*/
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.qxy.potatos.permission.ACCESS_BOOK_SERVICE");
LogUtil.d(TAG, "onTransact check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
@Override public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
/**
* <p>
* 直接调用 RemoteCallbackList 的监听方法,该类专门为多进程监听而生
* 调用 RemoteCallbackList 的 beginBroadcast() 计数后,记得调用 finishBroadcast() 停止计数
* </p>
*
* @param listener 传入的监听实例
* @throws RemoteException 异常抛出
*/
@Override public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListeners.register(listener);
final int N = mListeners.beginBroadcast();
LogUtil.i(TAG,"current size :" + N);
mListeners.finishBroadcast();
}
@Override public void unRegisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListeners.unregister(listener);
final int N = mListeners.beginBroadcast();
LogUtil.i(TAG,"current size :" + N);
mListeners.finishBroadcast();
}
};
/**
* @param intent 传递的 Intent 类
* @return 返回值是当下的 Binder 实例,供客户端调用
*/
@Override public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.qxy.potatos.permission.ACCESS_BOOK_SERVICE");
LogUtil.d(TAG, "onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
/**
* Called by the system when the service is first created. Do not call this method directly.
*/
@Override public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(1,"IOS"));
new Thread(new ServiceWorker()).start();
}
/**
* Called by the system to notify a Service that it is no longer used and is being removed. The
* service should clean up any resources it holds (threads, registered
* receivers, etc) at this point. Upon return, there will be no more calls
* in to this Service object and it is effectively dead. Do not call this method directly.
*/
@Override public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
/**
* 在有新书之后,遍历所有以及进行注册的监听者,然后调用其中的回调方法,把书籍实例传递过去
* @param book 需要通知到客户端的书籍实例
* @throws RemoteException 抛出异常
*/
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListeners.getBroadcastItem(i);
if (l != null){
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListeners.finishBroadcast();
}
/**
* 每隔5秒,启动一次处理回调的方法
*/
private class ServiceWorker implements Runnable{
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override public void run() {
while (!mIsServiceDestroyed.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();
}
}
}
}
}
设计好服务端后,我们来看一下客户端.客户端在 ServiceConnection 中获取到 IBookManager 的实例,接着就可以调用服务端中的代码了。而 onNewBookArrived 回调接收到的信息,则由 Handler 转换到 UI 线程中处理。还需要注意的一点是,客户端中使用 DeathRecipient 来设置死亡代理,可以在服务端死亡断联后,重新对其发起连接。
public class CoupletsActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
/**
* <p>使用了弱引用和静态类,防止内存泄漏</p>
*
* <p>由于客户端接收到的消息后,一般是要使用 UI 线程将内容改变显示出来。但是监听接口是运行在客户端线程池中的,不是 UI
* 线程,所以需要使用到 Handler 再将此消息传递到 UI 线程中使用</p>
*/
private static class SafeHandler extends Handler{
private WeakReference<CoupletsActivity> ref;
public SafeHandler(@NonNull Looper looper,CoupletsActivity activity){
super(looper);
this.ref = new WeakReference<>(activity);
}
/**
* Subclasses must implement this to receive messages.
*
* @param msg
*/
@Override public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
LogUtil.d(TAG,"receive new book :" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 当服务端意外死亡后,进行断连和重连
*/
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override public void binderDied() {
if (mRemoteBookManager == null){
return;
}
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mRemoteBookManager = null;
//重新绑定
Intent intent = new Intent(CoupletsActivity.this, BookManagerService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
};
/**
* <p>连接 service 的方法,运行在主线程中,是连接调用服务端的第一使用位置</p>
*
* <p>在连接服务的方法中,可以接收到服务端返回的 Binder 实例,然后以此实例化用于通信的类,以及调用其中的接口方法</p>
*
* <p>注意,这里还进行了死亡代理的设置</p>
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient,0);
List<Book> list = bookManager.getBookList();
LogUtil.i(TAG,"list type: "+list.getClass().getCanonicalName());
LogUtil.i(TAG,"list: "+list.get(1).bookName);
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
}
};
/**
* 使用 Handler,将调用转移到 UI 线程中进行
*/
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub(){
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*
* @param newBook
*/
@Override public void onNewBookArrived(Book newBook) throws RemoteException {
new SafeHandler(getMainLooper(),CoupletsActivity.this)
.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook.bookName).sendToTarget();
}
};
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_couplets);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
@Override protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()){
LogUtil.i(TAG,"unregister listener: " + mOnNewBookArrivedListener);
try {
mRemoteBookManager.unRegisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(serviceConnection);
super.onDestroy();
}
}
AIDL 调用服务端的方法时候,特别要注意线程耗时问题,因为服务端的执行跨进程方法的时候,客户端的方法也会挂起等待。所以这时客户端对该方法的调用不能运行在 UI 线程中,否则很容易出现 ANR。而客户端连接服务端的 onServiceConnected 和 onServiceDisconnected 正是运行在 UI 线程中的,所以如果服务端的方法是耗时的,记得手动切换客户端的调用到子线程中。
同理,服务端要调用客户端的监听方法 onNewBookArrived 的时候,也要注意 onNewBookArrived 是否为耗时方法,因为服务端调用客户端的时候也会挂起。我们需要保证服务端的调用运行在子线程中,否则容易出现无法响应的情况,因为 Service 默认运行在主线程中的。
ContentProvider
ContentProvider 作为 Android 的四大组件之一,其功能是用于 Android 的不同程序间进行数据共享。它的底层实现也是 Binder ,但是由于 Android 的封装,我们对 ContentProvider 的使用就会简单方便很多。Android 的不同程序就是不同进程,所以 ContentProvider 就是用于进程间通信的官方手段。
由于 ContentProvider 是 Android 的基础内容,所以这里不做过多的基础使用介绍,需要复习的同学可以查看《第一行代码》,下面只做一些重点解释和需要注意的点。
ContentProvider 提供给开发者几个分别需要重写的抽象方法,其中最主要的分别是增删查改四个方法,这几个方法在涉及的结构上是很像数据库的四个操作方法。但是它所能支持的数据操作不限于数据库,图片、文本等都可以通过它提供给外界进行增删查改。下面的代码是一个内容提供器的实现,主要功能是提供给其他进程操作该进程的两个数据库,相信大家都能看懂下面的代码。
下面我们对一些重要的点做说明
- 需要重写的6个方法中,只有 onCreate() 是运行在主线程中的,其他的5个方法都是运行在 Binder 线程池的。
- 在进行了数据更新的地方使用
mContext.getContentResolver().notifyChange(uri, null);通知注册了观察者的客户端。而在客户端可以使用registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer) unregisterContentObserver(ContentObserver observer)注册和解除其注册。 - 增删查改方法是并发执行的,所以要做好其同步操作。但是由于调用的 SQLiteDatebase 是自支持线程同步的,所以使用单个 SQLiteDatebase 操作的时候无需考虑同步操作,但是同时使用多个 SQLiteDatebase 操作的时候就会导致数据操作出错。
- 如果共享的数据底层是 List 这种,则需要考虑多线程带来的线程不安全问题,并发操作时候容易出现脏数据。那么可以使用
Collection集合、vector类或者是CopyOnWrite类来进行数据同步操作。
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.ryg.chapter_2.book.provider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://"
+ AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://"
+ AUTHORITY + "/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(
UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
private Context mContext;
private SQLiteDatabase mDb;
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate, current thread:"
+ Thread.currentThread().getName());
mContext = getContext();
initProviderData();
return true;
}
private void initProviderData() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from " + DbOpenHelper.USER_TALBE_NAME);
mDb.execSQL("insert into book values(3,'Android');");
mDb.execSQL("insert into book values(4,'Ios');");
mDb.execSQL("insert into book values(5,'Html5');");
mDb.execSQL("insert into user values(1,'jake',1);");
mDb.execSQL("insert into user values(2,'jasmine',0);");
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public String getType(Uri uri) {
Log.d(TAG, "getType");
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.d(TAG, "insert");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, "delete");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.d(TAG, "update");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return row;
}
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TALBE_NAME;
break;
default:break;
}
return tableName;
}
}
Binder之外的手段
共享文件
使用文件共享来进行进程通信的方式为:在A进程中写入数据到文件中,然后在B进程中读取该文件的数据,这就完成了两个进程间的通信。而若是需要传递对象的话,可以将对象序列化存入文件当中,然后再由其他进程来反序列化读取该数据出来。
文件共享来传递数据对文件格式是没有要求的,可以是 xml,也可以是文本文件,只要协商好了同一的格式就可以了。由于 Android 的底层是 Linux,所以是不会在读写文件的时候加锁的,所以 Android 是有利于并发读写的。当然,这种方式只适合在对数据同步要求不高的进程之间使用。
虽说文件共享方式对于文件格式是没有要求的,但是却并不建议使用 SharedPreferences 来进行进程间通信。为什么呢?SP 的底层不是 xml 文件么?事实上,不建议的原因并不是因为文件格式问题,而是由于 SP 的读取缓存策略问题。SP 在读取文件的时候,不同进程会在内存中复制一份数据以便于快速读写,所以当使用多进程访问该文件的时候,就会很容易出现并发性导致的数据丢失问题,所以不建议使用 SP 做进程间通信。
Socket
Socket 是网络套接字,分为流式套接字和用户数据报套接字,分别对应 TCP 和 UDP 协议。TCP 提供稳定的双向通信,三次握手使得数据可以稳定传输;而 UDP 则是注重于效率,不能保证数据能够正确传输。
使用 Socket 借助 TCP 或者 UDP 在 Android 中开辟端口来提供数据和读取数据,一样也可以实现进程间的通信。
下面例子是实现一个客户端向服务端发送信息,然后服务端接收到后就随机回复一条信息的功能。下面给出服务端的实现例子
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefinedMessages = new String[] {
"你好啊,哈哈",
"请问你叫什么名字呀?",
"今天北京天气不错啊,shy",
"你知道吗?我可是可以和多个人同时聊天的哦",
"给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
private class TcpServer implements Runnable {
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
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("欢迎来到聊天室!");
while (!mIsServiceDestoryed) {
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.");
// 关闭流
MyUtils.close(out);
MyUtils.close(in);
client.close();
}
}
然后我们来看一下客户端的实现例子
public class TCPClientActivity extends Activity implements 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 mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(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(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
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 v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
@SuppressLint("SimpleDateFormat")
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...");
}
}
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...");
MyUtils.close(mPrintWriter);
MyUtils.close(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
由于 Socket 的跨进程通信是使用了设备的端口来连接通信的,所以它也可以实现跨设备的通信。而 Socket 的进程间通信方式开销太大了,在性能上不利于用于设备内部的进程通信,但是多用于与设备外部的进程通信。
不同方式优缺点

使用 Binder 连接池
前面我们在使用 AIDL 来进行通信的时候,是一个 service 实现一个AIDL接口的功能,那么当一个进程中的 AIDL 很多的时候,应该怎么办呢?我们总不能使用很多个 service 来进行服务,这样子 App 的开销就太大了。所以这里使用 Binder 连接池来作一个服务化架构,Binder 连接池对服务端线程的所有的 AIDL 进行统一管理和统一分配,在服务端提供一个 queryBinder 接口返回相应 Binder 对象给服务端。这可以实现各功能的解耦、方便客户端调用以及易于功能的更新迭代。

具体的实现来自于任玉刚老师的《Android开发艺术探索》,可以在此查看源码。其中有几点做如下记录
CountDownLatch可以阻塞线程,使得线程之间的执行同步化。保证线程池中方法的正确执行顺序BinderPool运行在客户端进程中,其内部静态类BinderPoolImpl运行在服务端进程中,这点可以从各自调用以及静态内部类特性看出。- 客户端访问 Binder 连接池获取对应 IBinder 的时候需要在子线程中执行,因为前面做了线程同步化工作,其有可能耗时,服务端调用 Binder 是在 Binder 线程池的,也可能是耗时的。
- 可以在 Application 中初始化 BinderPool,以减少初次加载耗时,优化体验
- 即使 BinderPool 中有死亡连接机制,断联后仍需在客户端获取最新的 IBinder
参考
序列化和反序列化有什么作用?java序列化和反序列化的作用小官学长的博客-CSDN博客
关于Java中Serializable的一些问题_viclee108的博客-CSDN博客
教妹学Java(九):一文搞懂Java中的基本数据类型沉默王二的博客-CSDN博客java基本数据类型
Intent、Bundle传递数据的那些秘密_请叫我鲜鲜哥的博客-CSDN博客
转载自:https://juejin.cn/post/7233396218104692773