Android中的IPC方式
1.使用Bundle
我们知道,四大组件中 三大组件(activity,service,Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输,基于这一点,当我们在一个进程中启动了另一个进程的Activity,asevice,receiver,我们可以用Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
2.使用文件共享
//在MainActivity中修改
private void persistToFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1, "hello world", false);
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(
new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
Log.d(TAG, "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectOutputStream);
}
}
}).start();
}
//在SecondActivity中修改
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
if (cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(
new FileInputStream(cachedFile));
user = (User) objectInputStream.readObject();
Log.d(TAG, "recover user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectInputStream);
}
}
}
}).start();
}
3.使用Messenger
Messenger可以翻译为信使,顾名思义,通过它可以在不同进程传递Message对象,它是一种轻量级的IPC方案,底层是通过AIDL实现的
(以串行方式处理客户端发来的信息,不适合处理并发请求)
实现一个Messenger有如下几个步骤,分为服务端和客户端
1. 服务端进程
首先要创建一个service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可
2.客户端进程
客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回 的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务器发送消息了,发送消息的对象为Message对象,如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messager,并把这个Messenger对象通过message的replyTo参数传递给服务端,服务daunt通过这个replyTo参数就可以回应客户端了
首先看服务端代码:
public class MessagerService extends Service {
private static final String TAG = MessagerService.class.getSimpleName();
private static class MessageHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
}
super.handleMessage(msg);
}
}
private final Messenger mMessenger = new Messenger(new MessageHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
然后在清单文件中注册:
<service
android:name=".messenger.MessengerService"
android:process=":remote" >
</service>
客户端的代码,实现比较简单
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = MessengerActivity.class.getSimpleName();
private Messenger messenger;
public static final int MSG_FROM_CLIENT = 0;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger = new Messenger(service);
Message message = Message.obtain(null,MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg","xxxx");
message.setData(bundle);
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this,MessagerService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
}
服务端回复客户端
public class MessagerService extends Service {
private static final String TAG = MessagerService.class.getSimpleName();
private static class MessageHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_FROM_CLIENT:
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("reply","666");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msg);
}
}
private final Messenger mMessenger = new Messenger(new MessageHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = MessengerActivity.class.getSimpleName();
private Messenger messenger;
public static final int MSG_FROM_CLIENT = 0;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger = new Messenger(service);
Message message = Message.obtain(null,MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg","xxxx");
message.setData(bundle);
//注意这一句
message.replyTo = REPLYmessenger;
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this,MessagerService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
//定义接受回传的地方
private static Messenger REPLYmessenger = new Messenger(new MessageHandle());
private static class MessageHandle extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//接受
}
}
}
4.使用AIDL
1.服务端
首先创建一个service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
2.客户端
首先绑定service,绑定成功后,将服务端返回的Binder对象转换成AIDL接口所属的类似,然后就可以调用AIDL中的方法了。
3.AIDL接口的创建
创建出现错误参考
在AIDL中,并不是所有数据类型都支持,支持的数据类型如下:
- 基本数据类型(int ,long,char,boolean,double,float)
- String 和CharSequence
- List:只支持ArrayList,里面每个元素都必须都能够被支持
- Map:只支持HashMap,里面每个元素都必须都能够被支持
- Parcelable:所有实现了Parcelable 接口的对象(需要import进去)
- AIDL:所有AIDL接口本身也可以在AIDL文件中使用(需要import进去)
**注意:**1.如果AIDL文件中用到一个自定义的Parcelable对象,那么必须创建一个和它同名的AIDL文件,并声明它为Parcelable类型,列如:
2.AIDL接口中只支持方法,不支持声明静态变量,这一点区别于传统的接口
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
}
package com.ryg.chapter_2.aidl;
parcelable Book;
4.远程服务端Service的实现
public class BookManagerService extends Service {
private static final String TAG = BookManagerService.class.getSimpleName();
private CopyOnWriteArrayList<Book> list = new CopyOnWriteArrayList<>();
private Binder binder = new IBookManager.Stub(){
@Override
public List<Book> getBookList() throws RemoteException {
return list;
}
@Override
public void addBook(Book book) throws RemoteException {
list.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
list.add(new Book(1,"ANDROID"));
list.add(new Book(2,"IOS"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
这里使用CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写,AIDL是在服务端的Binder线程池中执行的,因此当多个客户端进行连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们在这里直接使用CopyOnWriteArrayList来实现自动的线程同步。
//清单文件中注册
<service android:name=".BookManagerService"
android:process=":remote"/>
5.客户端的实现
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = BookManagerActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this,BookManagerService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> bookList = bookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
unbindService(connection);
super.onDestroy();
}
}
这里要实现一个新的功能就是有新书到的时候,会提醒对每一本新书感兴趣的用户,这是一种典型的观察者模式,在开发中经常用到,我们这里进行一个模拟。
首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以取消这种提醒。之所以选择AIDL接口而不是普通的接口,是因为AIDL中无法使用普通接口,这里我们新创建一个IOnNewBookArrivedListener.aidl文件,我们期望的情况是:当服务器有新书到来的时候,我们会通知每一个已经申请提醒功能的用户。从程序上讲就是调用所有IOnNewBookArrivedListener对象中的onNewBookArrived方法,并把新书通过参数推荐个客户端。
package com.aspsine.mobi.chapter2;
// Declare any non-default types here with import statements
import com.aspsine.mobi.chapter2.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
除了要新加一个AIDL接口,还需要在原有的接口中添加两个方法,即订阅这个功能和取消订阅的监听事件
package com.aspsine.mobi.chapter2;
// Declare any non-default types here with import statements
import com.aspsine.mobi.chapter2.Book;
import com.aspisne.mobi.chapter2.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
接着,服务端中Service的实现也要稍微修改一下,主要是Service中IBookManager.Stub()的实现,因为我们在IBookManager中增加了两个方法,所以在IBookManager.Stub()中也要实现这两个方法。同时,在BookManagerService中还要开启一个线程,每个5秒向书库增加一本书然后提醒用户。
public class BookManagerService extends Service {
private static final String TAG = BookManagerService.class.getSimpleName();
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean();
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new
CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<Book> list = new CopyOnWriteArrayList<>();
//这里别调用的方法运行在Binder线程池中,如果是耗时的,则避免ui线程去访问这个方法
private Binder binder = new IBookManager.Stub(){
@Override
public List<Book> getBookList() throws RemoteException {
return list;
}
@Override
public void addBook(Book book) throws RemoteException {
list.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(!mListenerList.contains(listener)){
mListenerList.add(listener);
}else {
Log.d(TAG,"listener exists");
}
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(mListenerList.contains(listener)){
mListenerList.remove(listener);
}else {
Log.d(TAG,"listener not register");
}
}
};
@Override
public void onCreate() {
super.onCreate();
list.add(new Book(1,"ANDROID"));
list.add(new Book(2,"IOS"));
new Thread(new ServiceWorker()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
//模拟延迟操作
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
int bookId = list.size() +1;
Book book = new Book(bookId,"new book" +bookId);
onNewBookArrived(book);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
//给每个listener都执行onNewBookArrived方法
private void onNewBookArrived(Book book) throws RemoteException{
list.add(book);
for (int i = 0; i < mListenerList.size(); i++) {
IOnNewBookArrivedListener listener = mListenerList.get(i);
listener.onNewBookArrived(book);
}
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
}
修改客户端
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = BookManagerActivity.class.getSimpleName();
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this,BookManagerService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
List<Book> bookList = bookManager.getBookList();
Book book = new Book(3,"ANDROID JINJIE");
bookManager.addBook(book);
//注册新书监听
bookManager.registerListener(iOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
}
};
@Override
protected void onDestroy() {
//取消注册
if(mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()){
try {
mRemoteBookManager.unregisterListener(iOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(connection);
super.onDestroy();
}
//切换到主线程
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
//更新ui
break;
}
}
};
private IOnNewBookArrivedListener iOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub(){
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();
}
};
}
利用RemoteCallbackList 进行跨进程的注册和解注册(p85)
RemoteCallbackList 是系统专门提供的用于删除跨进程的listener的接口,RemoteCallbackList 是一个泛型,支持任意的AIDL接口,这点从它的声明可以看出来,因为所以的AIDL都是继承自IInterface
public class RemoteCallbackList < E extends IInterface>
它的工作原理很简单,在它的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder,value是Callback类型
ArrayMap<IBinder,Callback> mCallbacks = new ArrayList<IBinder,Callback>();
其中Callback中封装了真正的远程listener,当客户端注册listener时,它会把这个listener的信息存入mCallbacks,同时RemoteCallbackList还有一个很重要的功能,那就是在客户端进程终止后,能够自动移除客户端注册的listener,另外,RemoteCallbackList内部自动实现了线程同步的功能,所有用他进行注册和解注册,不需要做额外的线程同步工作。
这里使用RemoteCallbackList很简单,只要把CopyOnWriteArrayList替换成RemoteCallbackList就好了,然后修改registerListener,unregisterListener俩个接口
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new
RemoteCallbackList<>();
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
//给每个listener都执行onNewBookArrived方法
private void onNewBookArrived(Book book) throws RemoteException{
list.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listenr = mListenerList.getBroadcastItem(i);
if(listenr != null){
listenr.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
注意使用RemoteCallbackList时,我们无法向list那样操作他,因为它并不是一个list,遍历RemoteCallbackList需要按上面的方法去做,其中
beginBroadcast()和finishBroadcast()必须配对使用;
总结注意:
我们知道客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞在那里,如果这个客户端线程是UI线程的话,就会导致客户端ANR,因此,如果我们知道某个远程方法是耗时的,那么久避免在客户端UI线程中去访问远程方法。由于客户端的onServiceConnected,onServiceDisconnected方法都是运行在UI线程里面的,所以也不可以再它们里面直接调用服务器的耗时方法,。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务daunt本身是可以执行耗时操作的,,这个时候千万不要在服务端去开启线程进行异步操作。
那么避免这个ANR很简单,我们把方法的调用放在非UI线程中就可以了。
public void onButton1Click(View view) {
Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
if (mRemoteBookManager != null) {
try {
List<Book> newList = mRemoteBookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}).start();
}
同理,当远程服务端需要调用客户端的listener中的方法时,被调用的方法也运行在Binder的线程池中只不过是在客户端的线程池中,所以,我们同样不可以再服务端中调用客户端中耗时的方法,比如针对BookManagerService的onNewBookArrived方法,它的内部调用了
IOnNewBookArrivedListener的onNewBookArrived 方法,如果这个方法在客户端中是比较耗时的,请保证BookManagerService的onNewBookArrived方法运行在非UI线程中,否则服务端无响应
private void onNewBookArrived(Book book) throws RemoteException{
list.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listenr = mListenerList.getBroadcastItem(i);
if(listenr != null){
listenr.onNewBookArrived(book);
}
}
另外,由于客户端的IOnNewBookArrivedListener的onNewBookArrived 方法运行在客户端的Binder线程池中,所有不能再它里面进行访问UI的相关操作,如果要访问,要切换到主线程,用handler来实现了。
同时为了程序的健壮性,Binder如果意外死亡,这是我们需要重新连接服务,这里与两个方法,一个是给Binder设置DeathRecipient监听,当Binder死亡时会在binderDied中回调,我们就可以在这个方法中重新连接服务。另一个方法就是在onServiceDisconnected中重新连接服务。两者的区别在于onServiceDisconnected是在客户端的UI线程中被回调,而
binderDied是在客户端的Binder线程池中被回调的。这就是说binderDied不能访问我们的ui.
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
if (mRemoteBookManager == null)
return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:这里重新绑定远程Service
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
};
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
//重启服务
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
最后在AIDL中如何使用权限验证功能。默认情况下,我们的远程服务任何人都可以进行连接,但这应该不是我们要看到的,所以必须给服务加入权限验证功能,权限验证失败则无法调用服务中的方法。常用又两种方法
第一种:我们可以在onBind中进行验证,验证不通过返回为null,这样验证失败 的客户端就无法绑定服务了。至于验证的方式有多种,比如首页permission验证。使用这种验证,我们首先要在清单文件中声明所需的权限
//关于permission 的定义方式再找
<permission android:name="com.aspsine.mobi.chapter2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
然后在onBind中验证
@Nullable
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.aspsine.mobi.chapter2.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED){
return null;
}
return binder;
}
如果我们内部的应用想绑定我们的服务只需定义如下就可以
<uses-permission android:name="com.aspsine.mobi.chapter2.permission.ACCESS_BOOK_SERVICE"/>
第二种方法:
我们可以在服务端的onTranact方法中进行权限认证,如果认证失败则直接返回false,这样服务端就不会终止执行AIDL中的方法,从而达到保护服务端的效果。至于验证方式有多中,可以使用第一种方法的验证方式,还可以通过Uid和Pid来验证,通过getCallingUid和getCallingPid可以拿到客户端所属应用的Uid和Pid,通过这两个参数我们可以进行一些验证工作,比如验证包名,下面方法即验证了permission有验证了Uid和Pid。
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//验证permission
int check = checkCallingOrSelfPermission("com.aspsine.mobi.chapter2.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED){
return false;
}
//验证包名是不是以com.aspsine开头
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if(packages != null && packages.length > 0){
packageName = packages[0];
}
if(!packageName.startsWith("com.aspsine")){
return false;
}
return super.onTransact(code,data,reply,flags);
}