Android IPC简介
IPC为进程间通讯,两个进程之间进行数据交换的过程。
IPC不是Android所独有的,任何一个操作系统都有对应的IPC机制。Windows上通过剪切板、管道、油槽等进行进程间通讯。Linux上通过命名空间、共享内容、信号量等进行进程间通讯。Android中没有完全继承于Linux,有特色的进程间通讯方式是Binder,还支持Socket。
Android中的多进程模式
- 同一个应用,通过给四大组件在AndroidMenifest指定android:process属性,就可以开启多进程模式。
- 非常规的方式,通过JNI在Native层fork一个新的进程。
- 查看进程信息:DDMS;adb shell ps;adb shell ps | grep [包名]。
- 进程名以":“开头的属于当前应用的私有进程,其他应用的组件不可以和他跑在同一个进程里面。而进程名不以”:"开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
- 两个应用可以通过ShareUID跑在同一个进程并且签名相同,他们可以共享data目录、组件信息、共享内存数据。
- 多进程通讯的问题:
-
静态成员和单例模式完全失效。
-
线程同步机制完全失效。
-
SharedPreferences的可靠性下降
-
Application会多次创建
问题1、2原因是因为进程不同,已经不是同一块内存了;
问题3是因为SharedPreferences不支持两个进程同事进行读写操作,有一定机率导致数据丢失;
问题4是当一个组件跑在一个新的进程中,系统会为他创建新的进程同时分配独立的虚拟机,其实就是启动一个应用的过程,因此相当于系统又把这个应用重新启动了一遍,Application也重新创建。
多进程中,不同的进程组件拥有独立的虚拟机,Application,内存。
实现跨进程通讯有很多方式,比如:通过Intent,共享文件、SharedPreferences、基于Binder的Messenger和AIDL、Socket等。
IPC基础概念介绍
Serializable接口
Serializable是Java所提供的一个序列化接口,空接口。
serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID要和当前类的serialVersionUID相同才能正常的序列化。
-
静态成员变量属于类不属于对象,所以不会参加序列化过程;
-
其次用transient关键字标明的成员变量也不参加序列化过程。
重写如下两个方法可以重写系统默认的序列化和反序列化过程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
Parcelable接口
Android中特有的序列化方式,效率相对Serializable更高,占用内存相对也更少,但使用起来稍微麻烦点。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;//返回当前对象的内容描述,含有文件描述符返回1,否则0
}
public void writeToParcel(Parcel out, int flags) {//将当前对象写入序列号结构中
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
@Override
public String toString() {
return String.format(
"User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
}
}
序列化功能由writeToParcel方法来完成,最终通过Parcel中的一系列write方法完成的。反序列化功能由CREATOR来完成,其内部标明了如何创建序列号对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。内容描述功能由describeContents方法来完成,几乎所有情况都返回0,只有当前对象存在文件描述符时,才返回1。
Serializable是Java中的序列化接口,简单但开销大,序列化和反序列化需要大量的IO操作。
Parceable是Android中的序列化方式,使用起来麻烦,但是效率高。
Binder
Binder简介
直观来说,Binder是Android中一个类,实现了IBinder接口;
从IPC的角度来说,Binder是Android的一种跨进程的通讯方式;Binder还可以理解为一种虚拟的物理设备,设备驱动是/dev/binder;
从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager、等等)和相应ManagerService的桥梁;
从Android应用层来说,Binder是客户端与服务端通讯的媒介。在Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中普通的Service的Binder不涉及进程间通讯;而Messenger的底层其实就是AIDL。
AIDL:
Book.aidl:
package com.zza.stardust.app.ui.androidart;
parcelable Book;
IBookManager.aidl
package com.zza.stardust.app.ui.androidart;
import com.zza.stardust.app.ui.androidart.Book;
// /build/generated/aidl_source_output_dir目录下的com.zza.stardust.app.ui.androidart包中
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
生成的Java类:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /app/src/main/aidl/com/zza/stardust/app/ui/androidart/IBookManager.aidl
*/
package com.zza.stardust.app.ui.androidart;
//gen目录下的com.zza.stardust.app.ui.androidart.aidl包中
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.zza.stardust.app.ui.androidart.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.zza.stardust.app.ui.androidart.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.zza.stardust.app.ui.androidart.IBookManager interface,
* generating a proxy if needed.
*/
public static com.zza.stardust.app.ui.androidart.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.zza.stardust.app.ui.androidart.IBookManager))) {
return ((com.zza.stardust.app.ui.androidart.IBookManager) iin);
}
return new com.zza.stardust.app.ui.androidart.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<com.zza.stardust.app.ui.androidart.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.zza.stardust.app.ui.androidart.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.zza.stardust.app.ui.androidart.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.zza.stardust.app.ui.androidart.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.zza.stardust.app.ui.androidart.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.zza.stardust.app.ui.androidart.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.zza.stardust.app.ui.androidart.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.zza.stardust.app.ui.androidart.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.zza.stardust.app.ui.androidart.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.zza.stardust.app.ui.androidart.Book book) throws android.os.RemoteException;
}
系统会根据AIDL文件生成同名的.java类;首先声明与AIDL文件相对应的几个接口方法,还申明每个接口方法相对应的int id来做为标识,这几个id在transact过程中标识客户端请求的到底是什么方法。接着会声明一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端处于同一个进程的时候,方法调用不会走transact过程,处于不同进程时,方法调用会走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。所以核心实现在于它的内部类Stub和Stub的内部代理类Proxy,下面分析其中的方法:
DESCRIPTOR:Binder的唯一标识,一般用当前Binder的类名表示。
asInterface(android.os.IBinder obj):将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象;如果客户端和服务端位于相同进程,那么此方法返回的就是服务端Stub对象本身,否则返回系统封装后的Stub.proxy对象。
asBinder:用于返回当前的Binder对象
onTransact:运行在服务端的Binder线程池中,当客户端发起跨进程通讯时,远程请求会通过系统底层封装交由此方法处理。
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
- 服务端通过code确定客户端请求的目标方法是什么
- 接着从data取出目标方法所需要的参数,然后执行目标方法
- 执行后向reply写入返回值
- 如果返回false,服务端请求会失败,可以做权限验证
Proxy#getBookList和Proxy#addBook:
- 运行在客户端,首先该方法所需要的输入型对象Parcel _data对象,输出对象Parcel _reply对象和返回值对象List
- 然后把该方法的参数信息写入Parcel _data对象。
- 接着调研transact方法发起RPC,同时当前线程挂起
- 然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply取出RPC的返回结果,最后返回 _reply中的数据
Binder的两个重要方法linkToDeath和unlinkToDeath。通过linkToDeath可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,然后就可以重新发起连接请求。声明一个DeathRecipient对象,DeathRecipient是一个接口,其内部只有一个方法binderDied,实现这个方法后就可以在Binder死亡的时候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:接下来重新绑定远程Service
}
}
在客户端绑定远程服务成功后,给Binder设置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
Android中的IPC方式
使用Bundle
由于Binder实现了Parcelable接口,所以可以方便的在不同进程中传输;Activity、Service和Receiver都支持在Intent中传递Bundle数据。
使用文件共享
两个进程通过读/写一个文件来交换数据
适合对数据同步要求性不高的场景,并要避免并发写这种场景或者处理好线程同步问题。
SharedPreferences是个特例,虽然也是文件的一种,但系统在内存中有一份SharedPreferences文件的缓存,因此在多线程模式下,系统的读/写就变得不可靠,高并发读写SharedPreferences有一定机率会丢失数据,因此不建议在多进程通信中使用SharedPreferences。
使用Messenger
可以在不同的进程之间传递Message对象。Messenger是轻量级的IPC方案,底层实现是AIDL,对AIDL进行了封装。Messenger 服务端是以串行的方式来处理客户端的请求的,不存在并发执行的情形。
服务端:
- 创建一个Service来处理客户端的连接请求
- 创建一个Handler并通过它来创建一个Messager对象
- 在Service的onBind中返回这个Messager对象底层的Binder即可
public class MessengerService extends Service {
public MessengerService() {
}
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
ToastUtil.show("receive msg from Client:" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, 1);
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();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
}
客户端
- 绑定这个服务端的Server
- 用服务端返回的IBinder对象创建一个Messager对象
- 若需要回应客户端,需要创建一个Handler并创建一个新的Messager,并通过Messa的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端
public class MessengerActivity extends Activity {
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 1:
LogUtil.i("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);
LogUtil.d("bind service");
Message msg = Message.obtain(null, 1);
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 onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
findViewById(R.id.bt_start_service).setOnClickListener(v -> {
Intent intent = new Intent();
intent.setAction("com.zza.MessengerService.launch");
intent.setPackage(this.getPackageName());//需要添加包名
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
});
}
}
使用AIDL
Messenger不适合处理大并发请求。Messenger主要作用传递消息,跨进程调用服务端方法,无法做到。
服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
客户端首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转化成AIDL接口所属的类型,调用相对应的AIDL中的方法。
AIDL支持的数据类型:
-
基本数据类型;
-
String、CharSequence;
-
List: 只支持ArrayList,里面的元素必须都能被AIDL所支持;
-
Map: 只支持HashMap,里面的元素(key和value)必须都能被AIDL所支持;
-
Parcelable: 所有实现了Parcelable接口的对象;
-
AIDL: 所有AIDL接口本身也可以在AIDL文件中使用。
自定义的Parcelable对象和AIDL对象必须显示的import进来(即使在同一个包)。
如果ALDL文件中用到了自定义的Parcelable类型,必须新建一个和它同名ALDL文件,并在其中声明它为Parcelable类型。
AIDL中除了基本数据类型,其他类型参数必须标上方向:in、out或inout。
AIDL接口中只支持方法,不支持声明静态常量。
为了方便AIDL开发,建议把所有和AIDL相关的类和文件都放在同一个包中,好处在于,当客户端是另一个应用的时候,我们可以直接把整个包复制到客户端工程中去。
AIDL的包结构在服务端和客户端要保持一致,否则会运行出错。
客户端的listener和服务端的listener不是同一个对象,RemoteCallbackList是系统专门提供用于删除跨进程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因为所有AIDL接口都继承自android.os.IInterface接口。
需注意AIDL客户端发起RPC过程的时候,客户端的线程会挂起,如果是UI线程发起的RPC过程,如果服务端处理事件过长,就会导致ANR。
AIDL:
package com.zza.stardust.app.ui.androidart.aidl;
parcelable Book;
package com.zza.stardust.app.ui.androidart.aidl;
import com.zza.stardust.app.ui.androidart.aidl.Book;
import com.zza.stardust.app.ui.androidart.aidl.IOnNewBookArrivedListener;
// /build/generated/aidl_source_output_dir目录下的com.zza.stardust.app.ui.androidart包中
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
package com.zza.stardust.app.ui.androidart.aidl;
import com.zza.stardust.app.ui.androidart.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
服务端:
public class BookManagerService extends Service {
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
// private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList =
// new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
//测试
//SystemClock.sleep(5000);
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.zza.permission.ACCESS_BOOK_SERVICE");
LogUtil.d("check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
LogUtil.d("onTransact: " + packageName);
if (!packageName.startsWith("com.zza")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.register(listener);
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
LogUtil.d("registerListener, current size:" + N);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
boolean success = mListenerList.unregister(listener);
if (success) {
LogUtil.d("unregister success.");
} else {
LogUtil.d("not found, can not unregister.");
}
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
LogUtil.d("unregisterListener, current size:" + N);
}
;
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.zza.permission.ACCESS_BOOK_SERVICE");
LogUtil.d("onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
// do background processing here.....
while (!mIsServiceDestoryed.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();
}
}
}
}
}
客户端:
public class BookManagerActivity extends MActivity {
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
LogUtil.d("receive new book :" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
LogUtil.d("binder died. tname:" + Thread.currentThread().getName());
if (mRemoteBookManager == null)
return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:这里重新绑定远程Service
}
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
List<Book> list = bookManager.getBookList();
LogUtil.d("query book list, list type:"
+ list.getClass().getCanonicalName());
LogUtil.d("query book list:" + list.toString());
Book newBook = new Book(3, "Android进阶");
bookManager.addBook(newBook);
LogUtil.d("add book:" + newBook);
List<Book> newList = bookManager.getBookList();
LogUtil.d("query book list:" + newList.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
mRemoteBookManager = null;
LogUtil.d("onServiceDisconnected. tname:" + Thread.currentThread().getName());
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)
.sendToTarget();
}
};
@Override
protected void onInit(Bundle savedInstanceState) {
super.onInit(savedInstanceState);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
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();
runOnUiThread(() -> ToastUtil.show("size:" + newList.size()));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()) {
try {
LogUtil.d("unregister listener:" + mOnNewBookArrivedListener);
mRemoteBookManager
.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
@Override
protected BasePresenter createPresenter() {
return null;
}
@Override
protected int provideContentViewId() {
return R.layout.activity_book_manager;
}
}
使用ContentProvider
ContentProvider是Android专门用于不同应用之间进行数据共享的方式,适合跨进程通信,底层采用Binder实现。
ContentProvider主要以表格的形式来组织数据,可以包含多个表;ContentProvider支持普通文件,甚至可以采用内存中得一个对象来进行数据存储。
通过ContentProvider的notifyChange方法来通知外界当前ContentProvider中的数据已经发生改变。
query、update、insert、delete四大方法是存在多线程并发访问的,内部需要做好线程同步。
使用Socket
Socket也被称为“套接字”,是网络通讯中得概念,分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层中得TCP和UDP协议。
Service:
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.");
// 关闭流
out.close();
in.close();
client.close();
}
}
Client:
public class TCPClientActivity extends MActivity 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 onInit(Bundle savedInstanceState) {
super.onInit(savedInstanceState);
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
protected BasePresenter createPresenter() {
return null;
}
@Override
protected int provideContentViewId() {
return R.layout.activity_tcpclient;
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
//不能主线程 android.os.NetworkOnMainThreadException
//不应该这要做,这里为方便不做修改
new Thread(() -> mPrintWriter.println(msg)).start();
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...");
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Binder连接池
当项目越来越庞大后,需要使用到的AIDL接口文件也越来越多,但我们不能有多少个AIDL就添加多少个Service,Service是四大组件之一,是一种系统资源,太多的Service会让我们的App看起来很重量级;我们应该把所有AIDL放在一个Service中去管理。
这时候不同业务模块之间是不能有耦合的,所有实现细节需要单独来开,然后向服务端提供一个queryBInder接口,这个接口根据业务模块的特征来返回Binder对象给它们,不同的业务模块拿到所需的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了;由此可见Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行。
当新业务模块加入新的AIDL,那么在它实现自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,给自己添加一个新的binderCode并返回相对应的Binder对象即可,不需要添加新的Service。建议在AIDL开发过程中引入BinderPool机制。
选用合适的IPC方式
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输 Bundle 支持的数据类型 | 四大组件的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无开发访问情形,交换简单的数据实时性不高的场景。 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信。 | 使用复杂,需要处理好线程同步 | 一对多通信,且有 RPC 需求。 |
Message | 功能一般,支持一对多串行通信,支持实时通信。 | 不能很好地处理高并发情形,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型 | 低并发的一对多即时通信,无RPC 需求,或者无需要返回结果的 RPC 请求。 |
ContentProvider | 在数据源访问方面数据强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作。 | 可以理解为受约束的 AIDL,主要提供数据的 CRUD 操作。 | 一对多的进程间数据共享。 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信。 | 实现细节稍微有点繁琐,不支持直接的 RPC. | 网络数据传输。 |