IPC机制(四)——其他IPC方式
知道了序列化和Binder,下面通过其他跨进程的方式来分析。
使用Bundle
在平常的项目中Bundle主要在fragment、activity之间传递数据时使用。之前没有细想过Bundle,点到源码看后发现Bundle实现了Parcelable接口,说明传输的数据必须能够被序列化。
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
可以通过这样的方式传输:
Bundle bundle = new Bundle();
bundle.putString("name",lxy);
Intent intent = new Intent();
intent.putExtra("bundle",bundle);
Intent中的putExtra方法也是封装了Bundle实现的。
public @NonNull Intent putExtra(String name, String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value);
return this;
}
使用文件共享
两个进程通过读/写同一个文件来交换数据。
- Windows上:
一个文件如果被加了排斥锁将会导致其他线程无法访问,包括读和写。
- Linux上:
并发读/写文件可以没有限制的进行,甚至两个线程同时对一个文件进行读写操作都是可以的,尽管可能会出现问题。
通过文件可以交换一些文本信息之外,还可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象。
文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要处理好读/写问题
SharedPreference
SharedPreferences也是属于文件的一种,但是系统对它的读写有一定的缓存策略,即内存中会有一份SharedPreferences文件的缓存,并发读写时会有很大几率丢失数据,所以不推荐使用SharedPreferences进程间通信。
使用Messenger
Messenger充当信使的作用,通过它可以在不同的进程中传递Message对象。在Message中放入需要传递的数据,就实现了进程间数据的传递。
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。在Messenger的源码中可以看到这两个方法:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
从这两个构造方法中可以看到Messenger的底层是AIDL。
服务端进程
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
public static final int MSG_FROM_CLIENT = 1;
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_FROM_CLIENT:
Log.d(TAG,"Service收到客户端的消息:"+msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
服务端实现步骤:
- 创建一个Service来处理客户端的连接请求
- 创建一个Handler并通过它来创建一个Messenger对象
- 在Service的onBind中返回Messenger对象底层的Binder即可
mMessenger.getBinder方法实现:
public IBinder getBinder() {
return mTarget.asBinder();
}
客户端进程
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private ServiceConnection mServiceConnection;
private Messenger mMessenger;
private Button start;
private Button stop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = findViewById(R.id.start);
stop = findViewById(R.id.stop);
start.setOnClickListener(this);
stop.setOnClickListener(this);
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message msg = Message.obtain(null,MessengerService.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello this is client");
msg.setData(data);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MessengerService.class);
int id = v.getId();
switch (id){
case R.id.start:
Log.d(TAG,"进行绑定");
bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
break;
case R.id.stop:
unbindService(mServiceConnection);
Log.d(TAG,"解除绑定");
break;
}
}
}
客户端实现步骤:
- 首先要绑定服务端的Service——bindService()
- 绑定成功后用服务端返回的IBinder创建一个Messenger
- 通过Messenger向服务端发送消息,消息的类型类Message
如果让服务端在收到客户端的消息后恢复消息,就需要修改一些代码:
服务端修改代码
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_FROM_CLIENT:
Log.d(TAG,"Service收到客户端的消息:"+msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply","收到客户端的来信。。。");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
通过msg.reply获得Messenger对象。
查看源码:
/**
* Optional Messenger where replies to this message can be sent. The
* semantics of exactly how this is used are up to the sender and
* receiver.
*/
public Messenger replyTo;
replyTo本身就是Messenger,在收到消息且回复此消息的时候使用。且这个Messenger是客户端传递过来的。
客户端修改
private static Messenger mGetReplyMessage = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MessengerService.MSG_FROM_SERVICE:
Log.d(TAG,"收到服务端的来信:"+ msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
客户端添加了一个接受消息的Messenger和Handler
注意:
一定要在客户端发送消息的时候,把接受服务端回复的Messenger通过Message的replyTo参数传递给服务端。如果不添加就会出现空指针异常的错误。
如下所示:
mMessenger = new Messenger(service);
Message msg = Message.obtain(null,MessengerService.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello this is client");
msg.setData(data);
msg.replyTo = mGetReplyMessage;
Messenger工作原理图
使用ContentProvider
ContentProvider是android中专门用于不同进程间数据共享的方式。和Messenger一样,ContentProvider的底层实现同样也是Binder。
下面通过一个例子来看一下:
创建自定义ContentProvider
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
@Override
public boolean onCreate() {
Log.e(TAG,"onCreate,current thread:"+Thread.currentThread().getName());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e(TAG,"query,current thread:"+Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG,"getType");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e(TAG,"insert");
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG,"delete");
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG,"update");
return 0;
}
}
ContentProvider中有六个方法:
- onCreate()
代表ContentProvider的创建,一般用来完成一些初始化的操作
- query()
- insert()
- delete()
- update()
- getType()
用来返回一个uri请求所对应的MIME类型(媒体类型),如果不关注这个选项可以直接在这个方法中返回null或者/
此时的ContentProvider是可以工作的,但是无法向外界提供有效的数据。
注册BookProvider
<provider
android:authorities="com.lxy.contentprovider.provider.BookProvider"
android:name=".BookProvider"
android:process=":provider"
android:permission="com.lxy.PROVIDER">
</provider>
android:authorities:是ContentProvider的唯一标识,通过这个属性外部应用就可以访问BookProvider。
客户端
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Uri uri = Uri.parse("content://com.lxy.contentprovider.provider.BookProvider");
Log.e(TAG,"onCreate()");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
}
}
通过ContentResolver对象的query方法去查询BookProvider中的数据,"content://com.lxy.contentprovider.provider.BookProvider"唯一标识了BookProvider。
根据Binder原理,ContentProvider的六个方法都运行在它自己的进程中。根据结果发现BookProvider的query方法被调用了三次,且这三次都不在同一个进程中,它们运行在一个Binder线程中。而onCreate方法运行在main线程中,因此不能在onCreate方法中做耗时的操作。