1.Android中的多进程模式:
在android中最有特色的进程通讯方式就是Binder了,通过Binder可以轻松的实现进程间通讯,除了Binder,还有Socket,通过Socket也可以实现任意两个进程之间的通讯,当然同一个设备的两个进程通过Socket也是可以通讯的。通过四大组件的addroid:process属性,就可以开启多线程模式,但是却会有很多其他问题,下面会说到。
1.1开启多进程模式
在Android 中使用多进程只有一个方法,那就是在AndroidMenifest中指定android:process属性,除此之外没有任何其他方法,也就是说我们无法给一个线程或一个实体类指定其运行时的进程。其实还有一个非常规的方法,那就是通过JNI在native层去fork一个新的进程。但是不用作考虑这种特殊情况。
下面演示如何使用:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity android:name=".SecondActivity" android:process=":processB"/>
<activity android:name=".ThirdActivity" android:process="jian.com.ipctest.remote"/>
</application>
上面的SecondActivity和ThirdActivity指定了process属性。并且他们属性不同,这意味着当前应用又增加了两个新进程。
我们尝试从MainActivity调到SecondActivity中,然后到ThirdActivity。
在命令行中输入:shell ps |grep 你的包名可得:
可以看到我们成功的启用了多个进程。
在SecondActivity和ThirdActivity中的android:process属性一个有带包名一个没带。”:”的含义是在当前的进程名称加上当前的包名,这个简单的写法。它是属于当前进程的私有进程。其他应用不可以和他跑在同一个进程中,而进程名不“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和他跑在同一个进程中。
1.2多进程模式运行机制
1.2.1多进程中的问题
新建一个UserManager。
public class UserManager {
public static int mUserId = 1;
}
然后在上面的MainActivity中将其赋值为2,并打印,然后跳到SecondActivity中再次打印,正常情况下两个都应该是2才对。我们看结果:
并没有像我们在以前在用一个进程中使用的那样。所以多进程并不是在android:process中设置属性那么简单。
那么为什么会出现这种情况呢?原因是SecondActivity运行在了一个独立的进程中。android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机。不同的虚拟机在内存中有不同的地址空间,这样的话,不同的虚拟机访问同一个类的对象的时候就会产生多份副本。也就是说。这两个进程都存在一个UserManger类,并且互补干扰,在同一个进程中修改mUserId只会影响当前进程,对其他进程没有任何的影响的。这就是为什么SecondActivity中的mUserId依然还是1的原因。
使用多进程的问题:
- 静态成员和单例模式完全失效。
- 线程同步机制完全失效
- SharePreference的可靠性降低
- Application会多次创建
2Andorid中IPC方式
2.1Bundle
Activity ,Service,Receiver 都是支持Intent 中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不o同广东进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity,Service或Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
能被传数据类型必须要是基本类型或实现了Parcelable接口的对象,实现了Serializable接口的对象。
下面作为测试的例子。因为方便直接在一个model中使用。设置不同的进程可以在AndroidManifest.xml中使用andorid :process=”“标签
如:
<activity android:name=".MainActivity" android:process=":processA">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity android:name=".ActivityB" android:process=":processB"/>
MainActivity在进程A中,ActivityB在进程B中
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,ActivityB.class);
Bundle bundle = new Bundle();
bundle.putInt("id",10);
intent.putExtra(ActivityB.BUNDLE_TAG,bundle);
startActivity(intent);
}
});
}
}
ActivityB.java
public class ActivityB extends AppCompatActivity {
public static final String BUNDLE_TAG = "BUNDLE";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activityb);
Intent intent =getIntent();
Bundle bundle = intent.getBundleExtra(BUNDLE_TAG);
int id = bundle.getInt("id");
Toast.makeText(ActivityB.this,"id="+id,Toast.LENGTH_LONG).show();
}
}
运行结果
可以看到是可以成功的传输过去,和在同一进程没啥分别。
2.2使用文件共享
在Andorid中,使用并发读/写文件可以没有限制的进行,甚至两个线程同时对同一个文件进行写操作都是可以的,尽管这可能出现问题。通过文件交换数据很好用。除了可以交换一些文本信息。还可以序列化一个对象文件系统中的同事从另一个进程中恢复这个对象,下面就展示这个使用方法。
我们新建一个User类:
public class User implements Serializable
{
private int id;
private String info;
private boolean isLogin;
public User(int id, String info, boolean isLogin) {
this.id = id;
this.info = info;
this.isLogin = isLogin;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", info='" + info + '\'' +
", isLogin=" + isLogin +
'}';
}
}
在Activity中执行以下方法
private void persistToFile(){
PATH =getFilesDir().toString();
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1,"hello",false);
File dir = new File(PATH,"User");
if(!dir.exists()){
dir.mkdirs();
}
File cacheFile = new File(PATH,CACHPATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cacheFile));
objectOutputStream.writeObject(user);
Log.d("MainActivity---User","persist user:"+user);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
在SecondActivity中执行以下方法:
private void recoverFromFile(){
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cacheFile = new File(getFilesDir().toString(),CACHPATH);
if(cacheFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(cacheFile));
user = (User) objectInputStream.readObject();
Log.d("SecondActivity---User", "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
,然后查看运行的结果:
我们可以看到上面成功的恢复了之前保存的User对象中的内容,说是内容是因为得到的对象只是在内容上和序列化前的是一样的,但本质上是两个不同的对象。
tip:文件共享的方式适合用于数据同步要求不高的进程之间进行通讯。并且要妥善处理并发读/写的问题。
关于SharedPreferences是个特例,众所周知,SharedPreferences是Android中提供的轻量级的存储方案,底层用XML文件来存储键值对。本质来说sharePreferences也属于文件的一种,但是由于系统对他的读/写有一定的缓存策略,即使在内存中有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠了。当面对文件并发的读/写会有很大的可能丢失数据,因此不建议在多进程通讯中使用SharePreferences。
2.3使用Messenger
Messenger是一个轻量级的IPC方案,它的底层实现是AIDL我们来看一下Messenger的构造方法就知道了.明显的可以看到有AIDL的痕迹在这里。
public Messenger(Handler target){
mTarget =target.getIMessenger();
}
public Messenger(IBinder target){
mTarget = IMessenger.Stub.asInterface(target);
}
由于它每一次处理一个请求,因此在服务端不需要考虑多线程的同步的问题。因为服务端不存在并发执行的情形。实现一个Messenger的步骤分为服务端和客服端。
- 服务端进程
首先,我们需要在服务器端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中班会这个Messenger对象即可。 - 客户端进程
客户端进程首先要先绑定服务端的Service,绑定成功后用服务端返回的IBind对象创建一个Messenger,通过这个Messenger就可以向服务器发送消息了,发送消息类型为Messenger对象。如果需要服务端能回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个messenger对象通过replyTo参数传递给服务端,服务端通过这个参数就可以回应客服端。下面的例子说明了这个。
创建MessengerService.java
用MessengerHandler来处理客户端发送的消息,并返回一个消息给客户端。Messenger的作用是将客户端发送的消息传递给MessengerHandler处理
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 1:
Log.i(TAG,"接收到消息从客户端:"+msg.getData().getString("msg"));
//回复消息给客户端
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,2);
Bundle bundle = new Bundle();
bundle.putString("reply","我收到了你的消息了,稍后给你信息");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mMessenger.getBinder();
}
}
然后注册service,让它独立运行在一个进程中。
<service
android:name=".service.MessengerService"
android:process=":remote" />
客户端的实现
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mService =new Messenger(iBinder);
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","hello jian,I am client");
msg.setData(data);
//这个将需要接收服务端回复的Messenger通过Message的replyTo参数传递给服务端
msg.replyTo = mGetReplyMessager;
try{
//通过Messenger发送消息
mService.send(msg);
}catch (RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
private Messenger mGetReplyMessager = new Messenger(new MessengerHandler());
// 处理服务端返回的消息
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 2:
Log.i(TAG,"接收服务端的消息:"+msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//绑定Service
Intent intent = new Intent(MessengerActivity.this, MessengerService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
});
}
@Override
protected void onDestroy() {
unbindService(connection);
super.onDestroy();
}
}
预期的结果是点击按钮,服务端接收到消息,然后客户端接收到服务端返回的消息
上面可以看出,在Messenger中进行数据传递必须将数据放入Message中,而Message都实现了Parcelable接口,因此可以跨进程传输。这里用到Bundle,bundle可以支持大量的数据类型。有一个很关键的是,如果需要接收服务端返回的参数,那么就需要设置客户端Message的replyTo参数,也就是一个Handler。(可以理解错传递了一个对象国服务端,然后服务端调用这个对象里面的方法)
Messenger工作原理
3使用AIDL
Messenger 是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理。如果有大量的并发请求,那么用Messenger就不合适了。同事Messenger主要的作用是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情况用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,所以Messenger本质上也是AIDL。
AIDL实现跨进程通信也分为客户端和服务端。
- 服务端服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客服端的接口文件在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可
- 客户端
客户端所要做的事情就稍微简单一些,首先要绑定服务器的Service,绑定成功后,将服务端返回的Binder对象转化成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。 - AIDL接口的创建
首先看AIDL接口的创建,如下。我们创建一个AIDL文件,在里面声明接口和两个方法。
3.1在AIDL中支持的数据类型:
- 基本数据类型
- String和CharSequence
- List:只支持ArrayList,里面每个元素都必须能被AIDL支持
- MAP:只支持HashMap,里面每个元素都必须能被AIDL支持,包括Key和value
- Parcelable: 所有实现了这个接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL中使用
值得注意的是如果AIDL用到了自定义的Parcelable独享,那么必须新建一个和它同名的AIDL文件,并在其中声明他为Parcelable类型。
看个简单的例子。新建Book.java ,Book.aidl,IBookManager.aidl
Book.java
public class Book implements Parcelable{
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
}
Book.aidl
package jian.com.ipctest.aidl;
// Declare any non-default types here with import statements
parcelable Book;
IBookManager.aidl
package jian.com.ipctest.aidl;
// Declare any non-default types here with import statements
import jian.com.ipctest.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
其中Book.java是一个图书信息的类,它实现了Parcelable接口。Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是我们定义的一个接口,里面有个getBookListhe和addBook方法。尽管Book和IBookManager位于相同包中,但是在IBookManager.aidl中仍然要导入Book类。
创建Service。
public class BookManagerSerVice extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private Binder binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"andoird"));
mBookList.add(new Book(2,"三体"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
上面是一个服务端Service的典型实现,首先在onCreate中初始化添加了两本图书的信息,然后创建了一个Binder对象并在onBind中返回它,这个对象继承自IBookManager.Stub 并实现了他的内部方法。
这里采用了CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写。它可以让线程自动同步。
注册Service,和Activity放在不同的进程中
<service android:name=".aidl.BookManagerSerVice"
android:process=":remote">
</service>
客户端实现:首先绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = iBookManager.getBookList();
Log.i(TAG, "列表类型" + list.getClass().getCanonicalName());
Log.i(TAG, "查询列表" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookManagerActivity.this, BookManagerSerVice.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
});
}
@Override
protected void onDestroy() {
unbindService(connection);
super.onDestroy();
}
}
绑定成功后,会通过bookManager去调用getBookList方法,然后打印出所获取的图书信息。
运行结果
工作原理:
刚刚只是调用了getBookList接口获取,下面调用addBook方法从客户端发送数据给服务端。
在onServiceConnected中做如下修改
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = iBookManager.getBookList();
Log.i(TAG, "列表类型" + list.getClass().getCanonicalName());
Log.i(TAG, "查询列表" + list.toString());
Book book = new Book(3,"简的博客");
iBookManager.addBook(book);
Log.i(TAG,"add book"+book);
List<Book> newList = iBookManager.getBookList();
Log.i(TAG, "查询列表" + newList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
然后在次运行,得到结果:
现在就是一个比较基础的AIDL使用
那么,如果用户不想要每次都去查询图书,而是图书有更新的时候让用户知道呢?也即是相当于使用一个观察者模式。这里的Service就作为一个被监听者,所有的用户都是监听者,当有信息更新的时候,用户也就是监听者会监听到信息的变化,并做出对应的处理。
步骤:首先我们需要提供一个AIDL接口,每个用户都需要实现这个接口并向图书馆申请新书的题型功能。当然用户可以取消这种题型。之所以选中AIDL而不是普通的接口因为AIDL中无法使用普通接口。这里我们新建一个IOnNewBookArrivedListener.aidl文件
package jian.com.ipctest.aidl;
// Declare any non-default types here with import statements
import jian.com.ipctest.aidl.Book;
interface IOnNewBookArrivedListener {
void OnNewBookArrived(in Book book);
}
IBookManager.aidl也要更改:添加一个注册接口方法一个移除接口方法。
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void regiesterListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
BookManagerSerVice.java修改如下:
public class BookManagerSerVice extends Service {
private static final String TAG = "BMS";
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private Binder binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void regiesterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (!mListenerList.contains(listener)) {
mListenerList.add(listener);
} else {
Log.i(TAG, "already exists");
}
Log.d(TAG, "register :size" + mListenerList.size());
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (mListenerList.contains(listener)) {
mListenerList.remove(listener);
Log.i(TAG, "remove succeed");
} else {
Log.i(TAG, "not found ,can not unregister");
}
Log.d(TAG, "unregister :size" + mListenerList.size());
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "andoird"));
mBookList.add(new Book(2, "三体"));
new Thread(new ServiceWorker()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
Log.d(TAG, "onNewBookArrived,notify listeners:" + mListenerList.size());
for (int i = 0; i < mListenerList.size(); i++) {
IOnNewBookArrivedListener listener = mListenerList.get(i);
Log.d(TAG, "onNewBookArrived,notify listener" + listener);
listener.OnNewBookArrived(book);
}
}
private class ServiceWorker implements Runnable {
//每隔五秒模拟添加一本书
@Override
public void run() {
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 AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_ARRIVED = 1;
private IBookManager mRemoteBookManager;
//处理新书信息
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_ARRIVED:
Log.d(TAG, "receive new book" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = iBookManager;
List<Book> list = iBookManager.getBookList();
Log.i(TAG, "列表类型" + list.getClass().getCanonicalName());
Log.i(TAG, "查询列表" + list.toString());
Book book = new Book(3, "简的博客");
iBookManager.addBook(book);
Log.i(TAG, "add book" + book);
List<Book> newList = iBookManager.getBookList();
Log.i(TAG, "查询列表" + newList.toString());
iBookManager.regiesterListener(iOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mRemoteBookManager=null;
Log.e(TAG,"binder died");
}
};
private IOnNewBookArrivedListener iOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub(){
@Override
public void OnNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_ARRIVED,book)
.sendToTarget();
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookManagerActivity.this, BookManagerSerVice.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
});
}
@Override
protected void onDestroy() {
if(mRemoteBookManager!=null&&mRemoteBookManager.asBinder().isBinderAlive()){
try {
mRemoteBookManager.unregisterListener(iOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(connection);
super.onDestroy();
}
}
运行结果:
个人图解,(个人的理解):
我们可以看到当我们Activity执行OnDestory的时候,会注销掉绑定到服务端的Listener,这就相当于我们不想再接收图书馆的新书题型了。所以我们按下back退出BookActiivty。下面是按下后的打印
并没有像我们预期的那样停止掉,我们解注册的时候,服务器无法找到我们之前注册的那个listener,但是我们传递的明明是同一个listener!其实是因为Binder会把客户端传递过来的对象重新转化生成一个新的对象,虽然我在注册和解注册使用的同一个对象,但是通过BInder传递到服务器后,却会产出两个全新的变量。别忘了对象是不可以跨进程直接传输的,对象的跨进程传输的本质上都是反序列化的过程,这就是为什么AIDL中自顶一个对象都要实现Parcelable接口的原因,那么到底我们该怎么做才能实现解注册呢。Android给我们提供了RemoteCallbackList。
RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口,RemoteCallbackList是一个泛型
,支持管理任意的AIDL接口,这点从它的声明就可以看出来,因为所有的AIDL接口都继承自IInterface接口。
public class RemoteCallbackList<E extends IInterface>
它的工作原理很简单,在它的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是CallBack类型,如下
ArrayMap<IBinder,CallBack>mCallBack = new ArrayMap<>();
其中CallBack中封装了正真的远程Listener,当客户端注册listener的时候,他会把这个listener信息存入mCallBack中,其中key和value分别通过下面的方法获取:
IBinder key = listener.asBinder();
Callback value = new Callback(listener,cookie)
其实很容易理解,虽说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是底层用Binder对象都是同一个。这样我们就可以实现我们的注册和解注册了,当客户端注册时,我们只要历遍服务端所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把他删掉即可,这也就是RemoteCallbackList为我们做的事情,同事RemoteCallbackList还有一个很有用的功能,那就是当客户端进程终止后,它能够自动删除客户端所注册的listener。另外,RemoteCallbackList内部自动做了线程同步的功能,所以我们使用它来注册和解注册的时候,不需要做额外的线程同步工作,所以RemoteCallbackList是非常的实用的。
完成上面没完成的解注册功能。
对BookManagerService做一些修改,首先用RemoteCallbackList对象替换之前的CopyOnWriteArrayList
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
然后修改registerListener和unregisterListener这两个接口的实现
@Override
public void regiesterListener(IOnNewBookArrivedListener listener) throws RemoteException {
//因为内部实现用的是ArrayMap所以不用判断是否存在
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
然后修改onNewBookArrived方法。
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
Log.d(TAG, "onNewBookArrived,notify listeners:" +N );
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if(l!=null){
try {
l.OnNewBookArrived(book);
}catch (Exception e){
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
很显然,使用RemoteCallbackList成功的完成了跨进程的解注册功能。
但是使用RemoteCallbackList,有一个点需要注意的是,我们无法像操作List一样去操作它,尽管她的名字中也带有List,但是它并不是一个List,遍历RemoteCallbackList,必须按下下面的方法进行,其中beginBroadcast和finishBroadcast必须配对使用,哪怕只是想获取元素的个数。
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if(l!=null){
// TODO handle l
}
}
未完待续
纪录于andorid开发艺术探索。