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

跨进程通信使用总结(二)_Android中的IPC方式

2019-03-05 22:08 405 查看

第一种方式,使用Bundle
Android中的4大组件都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输

第二种方式,使用文件共享:
读写在同一个地方的文件(例如读取外存储卡上的data.txt文件,前提是要有读写文件的权限)

第三种方式.使用Messenger(信使)
通过信使Messenger可以在不同的进程中传递Message(消息)对象,在消息对象中放入我们要传递的数据,就可以轻松实现进程中传递数据了,信使是一种轻量级的IPC方案,它的底层是AIDL,信使是一次处理一个请求,因此服务端不用考虑线程同步的问题,因为服务端不存在线程并发的情况
信使的2种构造函数如下:

//一般用在服务端
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
//一般用在客户端
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}

我们先看一下它的大体逻辑:

服务端进程:
首先,我们要在服务端创建一个Service来处理客户端的请求,同时创建一个Handler并通过它来创建一个Mesenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可

客户端进程:
首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息,发送的消息类型是:Message对象.

都是在Handler里面从msg上去获取数据的
.
服务端具体实现代码如下:

public class MessengerService extends Service{

private class MessengerHandler  extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case Constants.MSG_FROM_CLIENT:
//从客户端msg里面取到的消息内容
String msgContent=   msg.getData().getString("client");
LogUtils.d("服务端msgContent"+msgContent);
//从客户端msg里面取到的信使
Messenger msger=  msg.replyTo;
//创建一个新的消息对象,一会儿给客户端传递消息
Message tempMsg=Message.obtain(null, Constants.MSG_FROM_SERVICE);
//给消息里面放数据
Bundle bundle=new Bundle();
bundle.putString("serviceReply","你好,客户端,你发送的消息我已经收到了");
tempMsg.setData(bundle);
try {
//信使携带消息去客户端
msger.send(tempMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
Messenger  messenger=new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}

}

客户端的具体实现完整代码如下:

public class MessengerClient extends AppCompatActivity {

private class MessengerHandler  extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case Constants.MSG_FROM_SERVICE:
//从服务端msg里面取到的消息内容
String msgContent= msg.getData().getString("serviceReply");
LogUtils.d("msgContent"+msgContent);
break;
default:
super.handleMessage(msg);
break;
}
}
}

Messenger  replyMsger = new Messenger(new MessengerHandler());

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent a=new Intent(this,MessengerService.class);
bindService(a,mServiceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection=new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//执行在主线程
//用服务端返回的service,创建Messenger信使,一会儿给服务端发送信息
Messenger  msger=new Messenger(service);
//创建一个新的消息对象,作为数据的载体
Message clientMsg=Message.obtain(null, Constants.MSG_FROM_CLIENT);
//给消息里面放数据
Bundle bundle=new Bundle();
bundle.putString("client","你好,服务端,这是我客户端发送的消息");
clientMsg.setData(bundle);

//注意这步:把信使放到msg里面,方便服务端从msg里面取信使
clientMsg.replyTo=replyMsger;

try {
//信使携带消息去服务端
msger.send(clientMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
//执行在主线程
}

@Override
public void onBindingDied(ComponentName name) {
//执行在主线程 :重新连接
}
};

@Override
protected void onDestroy() {
unbindService(mServiceConnection);
super.onDestroy();
}
}

第四种方式,使用AIDL
1,AIDL通信的流程:

第一步:服务端:
服务端首先要创建一个service来监听客户端的连接请求,然后创建一个AIDL文件,把暴露给客户端调用的接口在这个AIDL文件中声明,最后在service中实现这个接口

第2步:客户端,
首先绑定服务端的service,绑定成功后,把服务端返回的Binder对象自己转换成AIDL接口所属的类型,接口就可以调用AIDL中的方法了,注意链接断开的时候增加Binder死亡监听

第3步:AIDL接口的创建:创建文件名为IBookManager2 .aidl的文件

// IBookManager2.aidl
package com.hlj.demo.aidl;

// Declare any non-default types here with import statements
//Book1 是自定义的parcelable对象,必须要显示的import进来
import com.hlj.demo.aidl.Book1;
import android.os.Parcel;
import android.os.Parcelable;

import com.hlj.demo.aidl.IOnNewBookArrivedListener;

interface IBookManager2 {
/**
* 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);
List<Book1> getBookList();
//in:表示输入类型
//Book1 是自定义的parcelable对象,必须要显示的import进来
void addBook(in Book1 book1);

void registerListerner(IOnNewBookArrivedListener lister);
void unregisterListerner(IOnNewBookArrivedListener lister);

}

说明;
1,AIDL支持的数据类型为:基本数据类型,Arraylist,HashMap,Parcelable,AIDl
2,自定义的parcelable对象和AIDl的对象,必须要显示的import进来,不管它们是否和当前的AIDL文件处于同一个包内
3,除了基本数据类型之外,其他类型的参数必须要标上方向,in,out,或者inout
4,AIDL的包结构在服务端和客户端要一致,因为客户端要反序列化服务端中和AIDl相关的所有类,如果类的完整路径不一样的话,否则会运行报错,所以建议把所有和AIDL相关的类和文件全部放入同一个包中

第4步和第5步就是服务端和客户端的具体实现,后面会写一个Demo具体看demo
代码较多 24000 ,就不具体贴出来啦 在这里我提几个重要的注意点:

1.集合用CopyOnWriteArrayList,理由是:它支持并发读写,因为AIDl方法是在Binder线程池中执行的,会存在多个线程同事访问的情况,所以我们要在AIDl方法中处理线程同步,而CopyOnWriteArrayList可自动进行线程同步

CopyOnWriteArrayList<Book1> mBookList=new CopyOnWriteArrayList<Book1>();

2,Binder线程池中执行的程序不能更新UI,是子线程

3,有类似观察者模式或者回调情况的集合要用RemoteCallbackList,RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口,它内部也实现了线程同步

RemoteCallbackList<IOnNewBookArrivedListener> mListenerList=
new RemoteCallbackList<IOnNewBookArrivedListener>();

用法:

public void onNewBookArrived(Book1  book1) throws RemoteException {
mBookList.add(book1);
//注意beginBroadcast和finishBroadcast要配对使用
final int N=  mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener lister= mListenerList.getBroadcastItem(i);
if (lister!=null) {
lister.onNewBookArrived(book1);
}
}
mListenerList.finishBroadcast();
};

注意beginBroadcast和finishBroadcast要配对使用

4远程服务不是任何客户端相连就可以连上的,所以我们要做一些验证才可以:这里做的是权限加包名验证

<!-- 表示服务端自定义了一个权限 -->
<permission
android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />

<!-- 表示所有连接这个服务的客户端都要有这个权限,否则不会连接成功 -->
<service
android:name=".service.BookManagerService"
android:permission="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"
android:process=":BookManagerService" />
<!-- 表示客户端可以使用这个权限,已经有了这个权限 -->
<uses-permission android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE" />
@Override
public IBinder onBind(Intent intent) {
//先验证权限在验证包名,只要有一个没有通过就返回null
String pkgName="";
int permission= ActivityCompat.checkSelfPermission(this,"com.hlj.demo.permiss_ACCESS_BOOK_SERVICE");
if (permission == PackageManager.PERMISSION_DENIED) return null;
String[] pkgNames=getPackageManager().getPackagesForUid(Binder.getCallingUid());
if (pkgNames==null||pkgNames.length<=0) return null;
pkgName=pkgNames[0];
if (!pkgName.startsWith("com.hlj."))return null;
return mBinder;
}

5,Binder是有可能意外死亡的,那当服务断开了的时候,我们要重新连接服务
第一种方式:
给Binder设置DeathRecipient监听,当Binder死亡的时候,我们会受到binderDied方法的回调,可在此方法中重新连接(在非UI线程中执行)
第二种方式:
在onServiceDisconnected()方法中重新连接(在UI线程中执行)

第五种方式,使用ContentProvider
第六种方式,使用Socket(此2中方式在项目中使用较少,略过)

一点优化:
当要提供的AIDl服务过多时,可以使用Binder连接池

结尾:
终于写完了,老衲甚是欣慰啊^- ^

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