This chapter focuses on summarizing:
Using AIDL
If there are a lot of concurrent requests, using Messenger is not suitable, and if you need to call the server-side methods across processes, Messenger can not do it. At this point we can use AIDL.
The process is as follows:
1. The server needs to create a service to listen to client requests, then create an AIDL file, declare the interface exposed to the client in the AIDL file, and finally implement the AIDL interface in Service.
2. The client first binds the Service of the server. After successful binding, the Binder object returned by the server is converted to the type of AIDL interface, and then the method in AIDL can be invoked.
Matters needing attention:
1. Data types supported by AIDL:
i. Basic data types, String, CharSequence
ii. List: Only support ArrayList, where each element must be supported by AIDL
iii. Map: Only HashMap is supported, and every element in it must be supported by AIDL
iv. Parcelable
v. All AIDL interfaces themselves can also be used in AIDL files
2. Customized arcelable objects and AIDL objects must be import ed explicitly, regardless of whether they are in the same package as the current AIDL file.
3. If a custom arcelable object is used in an AIDL file, a new AIDL file with the same name must be created and declared as a Parcelable type.
package com.ryg.chapter_2.aidl;
parcelable Book
4. The parameters in the AIDL interface must indicate the direction in/out except for the basic type. The AIDL interface file only supports methods and does not support declaring static constants. It is recommended that all classes and files related to AIDL be placed in the same package for easy management.
void addBook(in Book book);
5. The AIDL method is executed in the Binder thread pool on the server side, so when multiple clients connect at the same time, the collection of managed data is directly synchronized by CopyOnWriteArrayList. Similarly, there is Concurrent HashMap.
6. Because the listener on the client side and the listener on the server side are not the same object, RecmoteCallbackList is a special interface for deleting cross-process listeners, and supports the management of arbitrary AIDL interfaces, because all AIDL interfaces inherit from IInterface interfaces.
public class RemoteCallbackList
It stores all AIDL callbacks internally through a Map interface, which has an IBinder key and a Callback value. When the client is unregistered, it traverses all listeners on the server side, finds the server listener with the same Binder object as the client listener, and deletes it.
7. The thread will be suspended when the client RPC is running. Because the called method runs in the server-side Binder thread pool, it may be time-consuming and cannot call the server-side method in the main thread.
Use ContentProvider
1. ContentProvider is one of the four components, and its underlying implementation is Binder, like Messenger. Content Provider is naturally used for inter-process communication. It only needs to implement a custom or pre-set Content Provider through the query, update, insert and delete methods of Content Resolver.
2. To create ContentProvider, we only need to inherit ContentProvider to implement six Abstract methods: onCreate, query, update, insert and getType. Apart from onCreate, which is called back by the system and run on the main thread, the other five methods are called by the outside world and run in the Binder thread pool.
Using Sock
Socket can realize the communication between two processes in computer network, of course, it can also realize the communication between processes locally. Service s listen to local ports, and clients connect to specified ports. When the connection is successful, they can send messages to the server or receive messages sent by the server when they get the Socket object.
Binder connection pool
AIDL is one of the most commonly used IPC methods, and it is the first choice when dealing with IPC in daily development. The process of AIDL mentioned earlier is that the client gets the Stub object inherited from AIDL in Service's onBind method, and then the client can RPC through the Stub object.
If the project is huge, many business modules need to use AIDL for IPC. As the number of AIDL increases, we can not unlimited increase Service. We need to put all AIDL in the same Service to manage.
The process is:
1. There is only one Service on the server side. We should put all AIDL in one Service to manage different business modules.
There is no coupling between them.
2. The server provides a queryBinder interface that returns the response based on the characteristics of the business module.
Binder object to client
3. Different business modules get the required Binder objects and can perform RPC.
Choosing the Appropriate IPC Mode
1 Use AIDL
In the last section, we introduced the method of using Messenger to communicate between processes. We can find that Messenger is a serial way to process messages sent by clients. If a large number of messages are sent to the server at the same time, the server can only process one by one. If there are a large number of concurrent requests, then Messenger is not suitable. At the same time, Messenger's role is mainly to deliver messages. In many cases, we may need to call methods on the server side across processes, which can not be done with Messenger, but we can use AIDL to implement cross-process method calls. AIDL is also the underlying implementation of Messenger, so Messenger is essentially AIDL, but the system encapsulated for us to facilitate the call of the upper layer. In the previous section, we introduced the concept of Binder, and you have a certain understanding of Binder. On the basis of Binder, we can understand AIDL more easily. This paper first introduces the process of using AIDL to communicate between processes, which is divided into two aspects: server and client.
1. server side
The server first creates a service to listen to the client's connection requests, then creates an AIDL file, declares the interface exposed to the client in this AIDL file, and finally implements the AIDL interface in Service.
2. client
The client needs to do something a little simpler. First, it needs to bind the Service of the server. After the binding is successful, the Binder object returned by the server is converted to the type of AIDL interface, and then the method in AIDL can be invoked.
3. Creation of AIDL interface
First look at the creation of the AIDL interface. As shown below, we created a file suffixed with AIDL, in which we declared an interface and two interface methods.
package com.nextvpu.myapplication;
import com.nextvpu.myapplication.Book;
import com.nextvpu.myapplication.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
Not all data types are available in AIDL files, so what data types do AIDL files support? As shown below.
- Basic data types (int, long, char, boolean, double, etc.);
- String and CharSequence;
- List: Only support ArrayList, where each element must be able to be supported by AIDL;
- Map: Only HashMap is supported. Every element in it must be supported by AIDL, including key and value.
- Parcelable: All objects that implement the Parcelable interface;
- AIDL: All AIDL interfaces themselves can also be used in AIDL files.
Another thing to note is that if you use a custom Parcelable object in an AIDL file, you must create a new AIDL file with the same name and declare it as a Parcelable type. In the IBookManager.aidl above, we used the Book class, so we had to create Book.aidl, and then add the following:
package com.nextvpu.myapplication;
parcelable Book;
We need to note that every class in AIDL that implements the Parcelable interface needs to create the corresponding AIDL file and declare that class as Parcelable in the way described above. In addition, besides the basic data types, other types of parameters in AIDL must be marked with directions: in, out or inout, in for input parameters, out for output parameters, inout for input and output parameters, as for their specific differences, this is not to say. We need to specify parameter types according to actual needs. We can't use out or inout all the time, because there is overhead in the underlying implementation. Finally, AIDL interfaces only support methods and do not support declaring static constants, which is different from traditional interfaces.
In order to facilitate the development of AIDL, it is recommended that all classes and files related to AIDL be placed in the same package. The advantage of this is that when the client is another application, we can copy the whole package directly to the client project. For this example, the package com.ryg.chapter_2.aidl and the files in the package should be copied intact to the client. If AIDL-related files are in different packages, then you need to copy these packages one by one into the client project, which is more cumbersome and error-prone. It should be noted that the package structure of AIDL should be consistent between the server and the client, otherwise the operation will be wrong. This is because the client needs to deserialize all classes related to the AIDL interface in the server. If the complete path of the class is different, it will not be able to successfully deserialize and the program will not run properly. In order to facilitate demonstration, all the examples in this chapter are carried out in the same project, but the reader should understand that the multi-process nature of one project and two projects is the same. In the case of two projects, except for the need to copy the package related to the AIDL interface to the client, the reader can experiment on it by himself.
4. The remote Service actually tells us how to define the AIDL interface. Next we need to implement this interface. Let's first create a Service called BookManagerService, which is coded as follows:
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private Binder mBinder = 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,"Android"));
mBookList.add(new Book(2,"Ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
The above is a typical implementation of a server-side Service. First, the information of two books is initialized in onCreate, then a Binder object is created and returned in onBind. This object inherits from IBookManager.Stub and implements its internal AIDL method. This process has been described in the section of Binder, so I will not talk about it here. Here we mainly look at the implementation of getBookList and addBook, which are two AIDL methods. The implementation process is relatively simple. Note that CopyOnWriteArrayList is adopted here. This CopyOnWriteArrayList supports concurrent reading/writing. As we mentioned earlier, the AIDL method is executed in the server-side Binder thread pool, so when multiple clients connect at the same time.
There will be multiple threads accessing at the same time, so we need to deal with thread synchronization in AIDL method, and we use CopyOnWriteArrayList directly for automatic thread synchronization.
As we mentioned earlier, only ArrayList can be used in AIDL, but we use CopyOnWriteArrayList here (note that it is not inherited from ArrayList). Why does it work properly? This is because AIDL supports Abstract lists, while List is only an interface, so although the server returns CopyOnWriteArrayList, it accesses data in Binder according to the List specification and eventually forms a new ArrayList to pass to the client. So it's entirely possible for us to adopt CopyOnWriteArrayList on the server side. Similarly, Concurrent HashMap allows readers to experience this transformation. Then we need to register the service in XML, as shown below. Note that the BookManager Service runs in a separate process and is not in the same process as the client's Activity, thus constituting a scenario for inter-process communication.
5. Implementation of Client
The implementation of the client is relatively simple. First, the remote service should be bound. After successful binding, the Binder object returned by the server can be converted into an AIDL interface. Then the remote method of the server can be invoked through this interface. The code is as follows.
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,IBinder
service) {
IBookManager bookManager = IBookManager.Stub.asInterface
(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG,"query book list,list type:" + list.getClass().
getCanonicalName());
Log.i(TAG,"query book list:" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this,BookManagerService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
Once the binding is successful, the getBookList method is invoked through the bookManager, and the obtained book information is printed out. It is important to note that the server-side approach may take a long time to complete. At this time, the following code will lead to ANR. This should be noted. This situation will be introduced later. The reason why I wrote this first is to let the reader better understand the implementation steps of AIDL.
It can be found that although we return the CopyOnWriteArrayList type on the server side, the client still receives the ArrayList type, which confirms the analysis we made earlier. The second line of log indicates that the client has successfully obtained the book list information of the server. This is a complete process of IPC using AIDL. I believe that readers should have a whole understanding of AIDL, but it is not over yet. The complexity of AIDL is much more than these. Here we continue to introduce some common difficulties in AIDL. We then call another interface, addBook. We add a book to the server on the client side and get it again to see if the program works properly. Or the code above. After the service connection, the client makes the following changes in onService Connected:
public void onServiceConnected(ComponentName className,IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG,"query book list:" + list.toString());
Book newBook = new Book(3,"Android Exploration of Developing Art");
bookManager.addBook(newBook);
Log.i(TAG,"add book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.i(TAG,"query book list:" + newList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
Now let's consider a situation, suppose there's a demand: the user doesn't want to look up the list of books from time to time. He's too tired, so he goes to the library and asks, "Can you tell me the information of the book when there's a new book?" You should understand that this is a typical observer model. Every interested user observes a new book. When the new book arrives, the library notifies every interested user. This model is used in practical development. Let's simulate this situation. First of all, we need to provide an AIDL interface. Every user needs to implement this interface and apply to the library for the reminder function of new books. Of course, users can cancel this reminder at any time. The reason for choosing AIDL interface instead of ordinary interface is that ordinary interface can not be used in AIDL. Here we create a
The IOnNewBookArrivedListener.aidl file, what we expect is that when a new book arrives at the server, it will inform every user who has applied for a reminder function. Programmatically, it calls the onNewBookArrived method in all IOnNewBookArrivedListener objects and passes the object of the new book to the client through parameters, as shown below.
package com.nextvpu.myapplication;
import com.nextvpu.myapplication.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
In addition to adding a new AIDL interface, two new methods need to be added to the original interface. The code is as follows.
package com.nextvpu.myapplication;
import com.nextvpu.myapplication.Book;
import com.nextvpu.myapplication.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
Next, the implementation of Service on the server side needs to be modified slightly, mainly in Service.
IBookManager.Stub implementation, because we have added two new methods in IBookManager, so in IBookManager.Stub also need to implement these two methods. At the same time, a thread is opened in the BookManager Service, adding a new book to the library every five seconds and notifying all interested users. The whole code is shown below.
package com.nextvpu.myapplication;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
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);
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check = checkCallingOrSelfPermission("com.nextvpu.myapplication.permission.ACCESS_BOOK_SERVICE");
Log.e("xyz"," 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];
}
Log.e("xyz","onTransact: " + packageName);
if (!packageName.startsWith("com.nextvpu")){
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();
Log.e("xyz","registerListener, current size:" + N);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
boolean success = mListenerList.unregister(listener);
if (success){
Log.e("xyz"," unregister success. ");
}else{
Log.e("xyz", "not found, can not unregister.");
}
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.e("xyz","registerListener, current size:" + N);
}
};
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.nextvpu.myapplication.permission.ACCESS_BOOK_SERVICE");
Log.e("xyz", "onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"Ios"));
new Thread(new ServiceWorker()).start();
}
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();
}
}
}
}
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();
}
}
Finally, we need to modify the client code, there are two main aspects: first, the client needs to register IOnNewBook ArrivedListener to the remote server, so that the server can notify the current client when there is a new book, and at the same time we need to cancel the registration when Activity exits; on the other hand, when there is a new book, the server will call back the client's IOnNewBook ArrivedListe. The onNewBook Arrived method in the NER object is executed in the client's Binder thread pool. Therefore, in order to facilitate UI operations, we need a Handler to switch it to the client's main thread to execute. This principle has been analyzed in Binder, so let's not say much here. The client code is modified as follows:
package com.nextvpu.myapplication;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import java.util.List;
public class BookManagerActivity extends Activity {
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:
Log.e("xyz", "receive new book :" + msg.obj);
break;
}
super.handleMessage(msg);
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e("xyz", "binder died. tname:" + Thread.currentThread().getName());
if (mRemoteBookManager == null)
return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mRemoteBookManager = null;
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient,0);
List<Book> list = bookManager.getBookList();
Log.e("xyz", "query book list, list type:"
+ list.getClass().getCanonicalName());
Log.e("xyz", "query book list:" + list.toString());
Book newBook = new Book(3,"Android Advanced");
bookManager.addBook(newBook);
Log.i("xyz", "add book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.e("xyz", "query book list:" + newList.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
Log.e("xyz", "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 onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
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();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
protected void onDestroy() {
if (mRemoteBookManager!=null && mRemoteBookManager.asBinder().isBinderAlive()){
Log.e("xyz", "unregister listener:" + mOnNewBookArrivedListener);
try {
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
}
If you think that the introduction of AIDL is over, you are wrong. As I said before, AIDL is far more than that. There are still some difficulties that we haven't touched on yet, and we will continue to introduce them to readers. As can be seen from the above code, when BookManager Activity is closed, we will cancel the listener registered to the server in onDestroy, which is equivalent to that we do not want to receive new book reminders from the library anytime, so we can cancel this reminder service at any time. Press the back key to exit BookManager Activity. Here is the printed log.
09-06 10:34:05.344 15264-15264/? E/xyz: onbind check=0
09-06 10:34:10.397 15264-15264/com.nextvpu.myapplication E/xyz: query book list, list type:java.util.concurrent.CopyOnWriteArrayList
09-06 10:34:10.399 15264-15264/com.nextvpu.myapplication E/xyz: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:new book#3]]
09-06 10:34:10.399 15264-15264/com.nextvpu.myapplication I/xyz: add book:[bookId:3, bookName:Android Advanced]
09-06 10:34:15.400 15264-15264/com.nextvpu.myapplication E/xyz: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:new book#3], [bookId:3, bookName:Android Advanced], [bookId:5, bookName:new book#5]]
If you think that the introduction of AIDL is over, you are wrong. As I said before, AIDL is far more than that. There are still some difficulties that we haven't touched on yet, and we will continue to introduce them to readers.
As can be seen from the above code, when BookManager Activity is closed, we will cancel the listener registered to the server in onDestroy, which is equivalent to that we do not want to receive new book reminders from the library anytime, so we can cancel this reminder service at any time. Press the back key to exit BookManager Activity. Here is the printed log.
I/BookManagerActivity(5642): unregister listener:com.ryg.chapter_2.aidl.BookManagerActivity$3@405284c8D/BMS(5650): not found,can not unregister.D/BMS(5650): unregisterListener,current size
From the log above, we can see that the program did not perform as we expected. In the process of understanding the registration, the server could not find the listener we registered before. When we registered and reconciled on the client side, we clearly transmitted the same listener. Ultimately, the server failed to register because it could not find the listener to be deactivated. This is certainly not the result we want, but think carefully, it seems that this way can not complete the de-registration. In fact, it is inevitable that this method of de-registration is often used in the daily development process, but it can not work in multi-process, because Binder will re-transform the object passed by the client and generate a new object. Although we use the same client object in the process of registration and reconciliation, when we pass it to the server through Binder, it will do so.
Generate two entirely new objects. Don't forget that objects can't be transmitted directly across processes. Object's cross-process transmission is essentially a deserialized process, which is why all custom objects in AIDL must implement Parcelable interface. So what can we do to realize the function of unregistration? The answer is to use the RemoteCallbackList, which seems abstract, but it doesn't matter. See the next detailed analysis.
RemoteCallbackList is a system-specific interface for deleting cross-process listener s. RemoteCallbackList is a generic type that supports the management of arbitrary AIDL interfaces, as can be seen from its declaration, because all AIDL interfaces are inherited from IInterface interfaces. Are readers still impressed?
public class RemoteCallbackList<E extends IInterfac>
Its working principle is very simple. There is a Map structure inside it which is used to save all AIDL callbacks. The key of this Map is IBinder type, and the value is Callback type, as shown below.
ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
The real remote listener is encapsulated in Callback. When a client registers a listener, it stores the listener's information in mCallbacks, where key and value are obtained by:
ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
The real remote listener is encapsulated in Callback. When a client registers a listener, it stores the listener's information in mCallbacks, where key and value are obtained by:
IBinder key= listener.asBinder()Callback alue neCallback(listener,cooki
At this point, the reader should understand that although the same object of the multi-time cross-process transmission client will generate different objects on the server side, these newly generated objects have one thing in common, that is, their underlying Binder objects are the same, using this feature, we can achieve the above functions that we can not achieve. When the client unregisters, we just need to go through all listeners on the server, find out the service listener with the same Binder object and delete it. That's what RemoteCallbackList does for us. At the same time, RemoteCallbackList also has a useful function, that is, when the client process terminates, it can automatically remove the listener registered by the client. In addition, RemoteCallbackList implements thread synchronization automatically, so we don't need to do additional thread synchronization when we use it to register and unregister. Thus, RemoteCallbackList is indeed a valuable class. Here's how to use it to complete de-registration.
RemoteCallbackList is easy to use. We need to make some changes to the BookManagerService. First, we need to create a RemoteCallbackList object to replace the previous CopyOnWriteArrayList, as shown below.
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList newRemoteCallbackList<IOnNewBookArrivedListener>(
Then modify the implementation of the registerListener and unregisterListener interfaces, as shown below.
@Override
public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.unregister(listener);
};
What about? Is it easy to use? Then we need to modify the onNewBook Arrived method. When there is a new book, we need to notify all registered listener s, as shown below.
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()
}
The modification of BookManagerService has been completed. In order to verify the function of the program, we need to add some logs and print out the number of all listeners after registration and reconciliation. If the program works properly, then the total number of listeners after registration is 1, and the total number after unregistration should be 0. Let's run the program again to see if this is the case. From the following log, it's clear that using RemoteCallbackList can do cross-process de-registration.
I/BookManagerActivity(8419): register listener:com.ryg.chapter_2.aidl.
BookManagerActivity$3@40537610
D/BMS(8427): registerListener,current size:1
I/BookManagerActivity(8419): unregister listener:com.ryg.chapter_2.aidl.
BookManagerActivity$3@40537610
D/BMS(8427): unregister success.
D/BMS(8427): unregisterListener,current size:0
With RemoteCallbackList, it's important to note that we can't manipulate it like a List. Although it has a List in its name, it's not a List. To traverse the RemoteCallbackList, we must follow the following way: its begin Broadcast and begin Broadcast must be used in pairs, even if we just want to get the number of elements in the RemoteCallbackList, which is a must.
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
//TODO handle l
}
}
mListenerList.finishBroadcast();
At this point, the basic usage of AIDL has been introduced, but there are several points that need to be explained again. We know that when a client calls a method of a remote service, the called method runs in the Binder thread pool on the server side, and the client thread is suspended. If the server-side method takes a long time to execute, it will cause the client thread to block here for a long time. If the client thread is a UI thread, it will cause the client ANR, of course. Not what we want to see. Therefore, if we clearly know that a remote method is time-consuming, we should avoid accessing the remote method in the client's UI thread. Since the onService Connected and onService Disconnected methods of the client run in UI threads, the time-consuming methods of the server can not be invoked directly in them, which should be paid special attention to. In addition, since the server-side method itself runs in the server-side Binder thread pool, the server-side method itself can perform a lot of time-consuming operations. At this time, remember not to open threads in the server-side method to perform asynchronous tasks, unless you know exactly what you are doing, it is not recommended to do so. Now let's modify the getBookList method on the server side a little, assuming that this method is time-consuming, then the server can do this:
@Override
public List<Book> getBookList() throws RemoteException {
SystemClock.sleep(5000);
return mBookList;
}
Then a button is placed in the client and the getBookList method of the server is called when it is clicked. It can be predicted that the client will ANR after several consecutive clicks.
Avoiding the ANR mentioned above is very simple. We just need to put the call in a non-UI thread, as shown below.
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();
}
Similarly, when the remote server needs to call the method in the listener of the client, the called method runs in the Binder thread pool, which is just the thread pool of the client. So we can't call the client's time-consuming methods in the server. For example, the onNewBook Arrived method for BookManagerService is shown below. In it
The onNewBookArrived method in the client's IOnNewBookArrived Listener is called internally. If the onNewBookArrived method in the client is time-consuming, make sure that onNewBookArrived in the BookManager Service runs in non-UI threads, otherwise the server will not be able to respond.
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);
}
}
For the robustness of the program, we need to do one more thing. Binder may accidentally die, often because
The server process stopped unexpectedly, and then we need to reconnect the service. There are two ways, the first way is to give
Binder sets DeathRecipient listening. When Binder dies, we will receive a callback from the binderDied method. In the binderDied method, we can reconnect the remote service. The specific method has been introduced in the section of Binder, which is not described in detail here. Another approach is to reconnect remote services in onService Disconnected. These two methods
We can choose any one to use, but the difference is that onService Disconnected is called back in the client's UI thread, while binderDied is called back in the client's Binder thread pool. That is to say, we can't access UI in the binderDied method, which is the difference between them. To verify the difference between the two, we first kill the server-side process through DDMS, and then print out the name of the current thread in the two methods, as follows:
D/BookManagerActivity(13652): onServiceDisconnected. tname:main
D/BookManagerActivity(13652): binder died. tname:Binder Thread #2
From the log s and Figures 2-8 above, we can see that onService Disconnected runs in the main thread, i.e. UI thread, while binderDied runs in the "Binder Thread#2" thread, which is obviously a thread in the Binder thread pool.
So far, we have a systematic understanding of AIDL, but there is still one last step: how to use the privilege validation function in AIDL. By default, our remote service can be connected by anyone, but this should not be what we would like to see, so we must add permission validation function to the service. If the permission validation fails, the method in the service can not be invoked. Authorization is done in AIDL. Two common methods are introduced here.
In the first way, we can validate in onBind and return null directly if the validation does not pass.
Failed clients can't bind services directly, and there are many ways to verify, such as using permission authentication.
Using this authentication method, we first declare the required permissions in Android Menifest, such as:
<permission
android:name="com.nextvpu.myapplication.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
As for the definition of permission, the reader is asked to check the relevant information. This section does not expand in detail. After all, the main content of this section is to introduce AIDL. After defining permissions, you can do permission validation in the onBind method of BookManagerService, as shown below.
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.nextvpu.myapplication.permission.ACCESS_BOOK_SERVICE");
Log.e("xyz", "onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
When an application binds our service, it verifies the permission of the application. If it does not use this permission, the onBind method will return null directly. The end result is that the application can not be bound to our service, which achieves the effect of permission validation. This method is also applicable to Messenger. Readers can expand themselves. If our own internal application wants to be bound to our service, we just need to use permission in its Android Menifest file as follows.
<permission
android:name="com.nextvpu.myapplication.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
In the second way, we can validate the privileges in the onTransact method on the server side. If the validation fails, we can return false directly, so that the server will not terminate the execution of the method in AIDL to achieve the effect of protecting the server side. As for the specific verification methods, there are many, you can use permission verification, the specific implementation method is the same as the first method. Uid and Pid can also be used to verify, and the Uid and Pid of the client application can be obtained by getCallingUid and getCallingPid. Through these two parameters, we can do some verification work, such as verifying the package name. In the following code, both permission and package name are validated. If an application wants to call a method in a service remotely, it first uses our custom limit "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE".
Next, the package name must start with "com.ryg", otherwise the method invoking the server will fail.
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check = checkCallingOrSelfPermission("com.nextvpu.myapplication.permission.ACCESS_BOOK_SERVICE");
Log.e("xyz"," 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];
}
Log.e("xyz","onTransact: " + packageName);
if (!packageName.startsWith("com.nextvpu")){
return false;
}
return super.onTransact(code, data, reply, flags);
}
Two common methods of privilege validation in AIDL are introduced above, but there are certainly other ways to do privilege validation, such as specifying android:permission attributes for Service, which are not introduced here. So far, this section is all over. Readers should have a deep understanding of the process of using AIDL.