This article is from: 103Style blog

“Android Development art Exploration” learning record

Base on AndroidStudio 3.5.1 track of


directory

  • preface
  • AIDL interface created
  • Data formats supported by AIDL
  • Server-side implementation
    • Create BookManagerService. Java
    • Handling concurrency
  • Client-side implementation
    • Create BookManagerActivity. Java
    • Run the program to view the logs
  • AIDL adds and dismisses callbacks
    • Add a callback for new data on the server
    • Failed to unwind the callback? The RemoteCallbackList?
  • AIDL adds permission validation
    • Permission to verify
    • The package name validation
  • summary

preface

Binder uses AIDL to explain how Binder works. Binder uses Bundles, file sharing, and Messenger to communicate between processes.

From our previous introduction to Messenger, we know that Messenger handles messages in a serial manner, so when there are a large number of concurrent requests, Messenger may not be appropriate. While Messenger is primarily used to deliver messages, many times we may need to call methods of other processes across processes, which Messenger cannot do.

This is where AIDL’s turn comes in. Messenger is also based on AIDL, is the system of AIDL encapsulation, easy to call the upper layer.

We introduced the concept of Binder through AIDL in introducing the working mechanism of Binder, and you should have a certain understanding of Binder.

Here we first introduce AIDL to carry out inter-process communication process, including AIDL interface creation, server, client.


AIDL interface created

Tips: For ease of development, it is recommended to put aiDL-related classes and files in a single directory so that the entire package can be copied when the client and server are different applications. Note: The AIDL package structure of the client and server must be the same, otherwise an error will be reported.

Create IBookManager. Aidl:

/ / IBookManager. Aidl: package aidl. import aidl.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book);
}
Copy the code

Next, let’s take a look at the data formats supported by AIDL.


Data formats supported by AIDL

Most of the data formats supported by AIDL, but not all data types, can be used with the following types:

  • Basic data types (int, long, char, Boolean, double, etc.)
  • String and CharSequence
  • List: Can only be an ArrayList, and its elements must be in a format that is supported by AIDL.
  • Map: Can only be a HashMap, and the elements must be in a format supported by AIDL.
  • AIDL: All AIDL interfaces are also available in AIDL. Import is required.
  • Parcelable: All objects that implement the interface.You need to import, the object still needs to be createdThe name of the class. The aidlFile and then add the following to the above exampleBookFor example:
    //Book.aidl
    package aidl;
    parcelable Book;
    Copy the code

In addition to the basic type, other types must be marked with direction when used as parameters: in, out, inout.

In: indicates input parameters. Out: indicates output parameters. Inout: indicates input and output parameters

And you can’t use Inout across the board, because the underlying performance is expensive, so use it on demand. For example, void addBook(in Book Book) in the above example;


Server-side implementation

First we create a Service on the server to handle the client’s connection request, and then we expose the interface to the client by implementing the declaration in AIDL in the Service.

Create BookManagerService. Java:

//BookManagerService.java
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";
    private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            returnbookList; } @Override public void addBook(Book book) throws RemoteException { bookList.add(book); }}; @Override public voidonCreate() {
        super.onCreate();
        bookList.add(new Book(1, "Exploring Android Art development"));
        bookList.add(new Book(2, "Java Concurrent Programming Guide"));
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        returnmBinder; }}Copy the code

The above example focuses on creating BInder classes that implement the methods declared in AIDL and return them in the Service’s onBind.

However, as mentioned earlier, it is executed in a Binder thread pool on the server side, so multiple threads can access it at the same time. So we’re going to handle thread synchronization in the AIDL method, because CopyOnWriteArrayList supports concurrent reads and writes, so we’re going to use CopyOnWriteArrayList directly for automatic thread synchronization.

List only supports ArrayList, and CopyOnWriteArrayList is not a subclass of ArrayList. So why does AIDL support ArrayList? This is because AIDL supports an abstract List, and List is an interface, so even though the server returns CopyOnWriteArrayList, But Binder accesses the data according to List specifications and eventually forms a new ArrayList to pass to the client.

Then declare the process in androidmanifest.xml :remote

<? xml version="1.0" encoding="utf-8"? > <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.ipc">
    <application>
        ...
        <service
            android:name="test.BookManagerService"
            android:process=":remote" />
    </application>
</manifest>
Copy the code

Client-side implementation

The client first binds the Service to the server, converts the Binder object returned by the server to the type of the AIDL interface, and then invokes AIDL methods.

Create BookManagerActivity. Java:

//BookManagerActivity.java
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = iBookManager.getBookList();
                Log.e(TAG, "query book list, type = " + list.getClass().getCanonicalName());
                Log.e(TAG, list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @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);
    }
    @Override
    protected void onDestroy() { unbindService(connection); super.onDestroy(); }}Copy the code

Run the program and see the following log information:

BookManagerActivity: query book list, type = java.util.ArrayList
BookManagerActivity: [Book{bookId=1, bookName='Android Art Development Exploration '}, Book{bookId=2, bookName='Java Concurrent Programming Guide '}]
Copy the code

AIDL adds and dismisses callbacks

In the code above, we implement the ability to notify the client when a new book is added to the server.

Let’s go ahead and masturbate. Since normal interfaces are not available in AIDL, we need to create an AIDL interface, iBookAddListener.aidl.

//IBookAddListener.aidl
package aidl;
import aidl.Book;
interface IBookAddListener{
    void onBookArrived(in Book newBook);
}
Copy the code

Then add the methods for adding and removing interfaces in the previous iBookManager.aidl.

//IBookManager.aidl
package aidl;
import aidl.Book;
import aidl.IBookAddListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IBookAddListener listener);
    void unregisterListener(IBookAddListener listener);
}
Copy the code

Then modify mBinder in the above server code BookManagerService to implement the two new methods and create a Worker to periodically add data to the server’s bookList.

//BookManagerService.java
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService"; Private AtomicBoolean destroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
    private CopyOnWriteArrayList<IBookAddListener> listeners = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {... @Override public void registerListener(IBookAddListener listener) throws RemoteException {if(! listeners.contains(listener)) { listeners.add(listener); }else {
                Log.e(TAG, "lister is already exist");
            }
            Log.e(TAG, "registerListener: listeners.size = "  + listeners.size());
        }
        @Override
        public void unregisterListener(IBookAddListener listener) throws RemoteException {
            if (listeners.contains(listener)) {
                listeners.remove(listener);
            } else {
                Log.e(TAG, "lister not found, can not unregister");
            }
            Log.e(TAG, "unregisterListener: listeners.size = "+ listeners.size()); }}; @Override public voidonCreate() {
        super.onCreate();
        bookList.add(new Book(1, "Exploring Android Art development"));
        bookList.add(new Book(2, "Java Concurrent Programming Guide"));
        new Thread(new Worker()).start();
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    @Override
    public void onDestroy() {
        destroyed.set(true);
        super.onDestroy();
    }
    private void onBookAdd(Book book) throws RemoteException {
        bookList.add(book);
        Log.e(TAG, "onBookAdd: notify listeners.size = " + listeners.size());
        for (IBookAddListener listener : listeners) {
            listener.onBookArrived(book);
        }
    }
    private class Worker implements Runnable {
        @Override
        public void run() {
            while(! destroyed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = bookList.size() + 1; Book book = new Book(bookId,"new book#" + bookId);
                try {
                    onBookAdd(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code

Then modify the BookManagerActivity on the client to add a listener on the server.

//BookManagerActivity.java
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";
    private static final int BOOK_ADD_MSG = 0x001;
    private IBookManager remoteBookManager;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BOOK_ADD_MSG:
                    Log.e(TAG, "A new book add:" + msg.obj);
                    break; default: super.handleMessage(msg); }}}; Private IBookAddListener bookAddListener = new IBookAddListener.Stub() {@override public void onBookArrived(Book newBook) throws RemoteException {// Run the Binder thread pool on the reclient, Cannot perform access to UI handler.obtainMessage(BOOK_ADD_MSG, newBook).sendtotarget (); }}; private ServiceConnection connection = newServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service);
            try {
                remoteBookManager = iBookManager;
                List<Book> list = iBookManager.getBookList();
                Log.e(TAG, "query book list, type = " + list.getClass().getCanonicalName());
                Log.e(TAG, list.toString());
                Book book = new Book(3, "Android Software Security Guide"); remoteBookManager.addBook(book); remoteBookManager.registerListener(bookAddListener); } catch (RemoteException e) {e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { remoteBookManager = null; Log.e(TAG,"binder died "); }}; @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); // Bind service} @override protected voidonDestroy() { unregisterListener(); // Unregister unbindService(connection); // Unbind super.ondestroy (); } private voidunregisterListener() {
        if(remoteBookManager ! = null && remoteBookManager.asBinder().isBinderAlive()) { try { remoteBookManager.unregisterListener(bookAddListener); } catch (RemoteException e) { e.printStackTrace(); }}}}Copy the code

Then run the program and print the following log.

BookManagerActivity: query book list,type = java.util.ArrayList
BookManagerActivity: [Book{bookId=1, bookName='Android Art Development Exploration '}, Book{bookId=2, bookName='Java Concurrent Programming Guide '}] BookManagerActivity: a new book add: book {bookId=4, bookName='new book#4'} BookManagerActivity: a new book add: book {bookId=5, bookName='new book#5'} BookManagerActivity: a new book add: book {bookId=6, bookName='new book#6'Size = 2 BookManagerService: onBookAdd: notify listeners.size = 1 BookManagerService: onBookAdd: notify listeners.size = 1 BookManagerService: onBookAdd: Notice. Size = 1 → BookManagerService: Lister not found, can not unregister BookManagerService: unregisterListener: listeners.size = 1 BookManagerService: onBookAdd: notify listeners.size = 1Copy the code

As we can see from the log, there is indeed a new piece of data being monitored every 5s. However, we found a problem: when canceling the registration, lister was prompted not found, can not unregister. Deregistration failed. Why?

This is because of the Binder mechanism, which converts objects passed by the client and generates a new object. Since objects cannot be transferred directly across processes, object transfer is essentially a deserialization process, which is why objects in AIDL must implement the Parcelabe interface.

So how do we unregister? Use the system-provided RemoteCallbackList, which provides a callback interface for removing cross-processes. As you can see from its generics, it supports managing arbitrary AIDL interfaces. public class RemoteCallbackList

{}

Let’s modify our previous BookManagerService:

/ / BookManagerService. Java only posted to modify the public class BookManagerService extends the Service {... private RemoteCallbackList<IBookAddListener> listeners = new RemoteCallbackList<>(); private Binder mBinder = new IBookManager.Stub() {
        ...
        @Override
        public void registerListener(IBookAddListener listener) throws RemoteException {
            listeners.register(listener);
            final int N = listeners.beginBroadcast();
            Log.e(TAG, "registerListener: size = " + N);
            listeners.finishBroadcast();
        }
        @Override
        public void unregisterListener(IBookAddListener listener) throws RemoteException {
            listeners.unregister(listener);
            final int N = listeners.beginBroadcast();
            Log.e(TAG, "unregisterListener: size = "+ N); listeners.finishBroadcast(); }}; private void onBookAdd(Book book) throws RemoteException { bookList.add(book); final int N = listeners.beginBroadcast();for (int i = 0; i < N; i++) {
            IBookAddListener listener = listeners.getBroadcastItem(i);
            if (listener != null) {
                listener.onBookArrived(book);
            }
        }
        listeners.finishBroadcast();
    }
}
Copy the code

Run the program, from the log we can see that the unregistration was successful.

BookManagerActivity: query book list,type = java.util.ArrayList
BookManagerActivity: [Book{bookId=1, bookName='Android Art Development Exploration '}, Book{bookId=2, bookName='Java Concurrent Programming Guide '}] BookManagerActivity: a new book add: book {bookId=4, bookName='new book#4'E/BookManagerService: unregisterListener: size = 0Copy the code

With RemoteCallbackList, it’s important to note that although the name has a List in it, we can’t operate on it like a List. To iterate over its data or get its size, we must pair beginBroadcast with finishBroadcast, referring to the method of registering and unregistering the callback in the code above.

At this point, the basic ways to use AIDL are covered, but there are a few more points that need to be emphasized:

  • Binder thread pools run on the server side when the client invokes the remote service’s methods,The client is suspended until the method completes execution, if the method is time-consuming, if the client inThe UI threadA direct call to theANR. So when we know how long a method takes, we can’t call it directly from the UI thread, we need to use child threads to handle it, such as the two methods of ServiceConnection in the client BookManagerActivity exampleonServiceConnectedonServiceDisconnectedIt’s all running in the UI thread.
  • The other is the callback in the customer side, in the example BookManagerActivitybookAddListenerThe Binder thread pool runs on the client side, so the user cannot directly access the UI content. If the user needs to access the UI, it needs to switch the thread through the Handler.

In addition, for robustness, we also protected the Binder from accidental death, often due to server processes stopping unexpectedly and requiring reconnection to the service. There are two ways:

  • DeathRecipient listener for Binder. When Binder dies, we receive a callback for binderDied, which we have described in Binder’s work mechanism here.
  • Reconnect the remote service in onServiceDisconnected.

AIDL adds permission validation

By default, our remote service allows anyone to connect, but this is not what we want, so we will add permission authentication in AIDL. The following two methods are used: 1. ObBinder returns NULL if the authentication fails. In this case, clients that fail to authenticate cannot be bound to services. There are several ways to verify this, such as permission authentication. To use this authentication, we need to declare the required permissions in androidmanifest.xml, as shown in the following example:

// AndroidManifest.xml <? xml version="1.0" encoding="utf-8"? > <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.okhttptesst"<permission android:name="com.aidl.test.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
    ...
</manifest>

//BookManagerService.java
public class BookManagerService extends Service {
    ...
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.aidl.test.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        returnmBinder; }... }Copy the code

Then we can declare permissions within the application that we want to bind to the service.

// AndroidManifest.xml <? xml version="1.0" encoding="utf-8"? > <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.okhttptesst"> // Registration permission <uses-permission android:name="com.aidl.test.permission.ACCESS_BOOK_SERVICE"/>
    ...
</manifest>
Copy the code

2. Verify in onTransact on the server if the onTransact fails, return false, and the server terminates the AIDL method to protect the server. There are also many specific verification methods. Permission validation in the first validation can be used, and the implementation is the same. The Uid and Pid can also be used for verification. You can use getCallingUid and getCallingPid to obtain the Uid and Pid of the application to which the client belongs. You can use these two parameters to verify the package name. As an example, we override the onTransact method of mBinder in BookManagerService to add permissions and package name validation:

//BookManagerService.java
public class BookManagerService extends Service {
    ...
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.aidl.test.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "PERMISSION_DENIED");
                return false;
            }
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if(packages ! = null && packages.length > 0) { packageName = packages[0]; }if(! packageName.startsWith("com.aidl")) {
                Log.e(TAG, "packageName is illeagl = " + packageName);
                return false;
            }
            returnsuper.onTransact(code, data, reply, flags); }... }; . }Copy the code

Start the program and view the log:

BookManagerService: PERMISSION_DENIED
Copy the code

After permissions are declared, run:

BookManagerService: packageName is illeagl = com.example.aidltest
BookManagerService: packageName is illeagl = com.example.aidltest
BookManagerService: packageName is illeagl = com.example.aidltest
Copy the code

In addition to the above two validation methods, we can also specify the Android: Permission attribute and so on to the Service.


summary

Let’s review the content of this article again:

  • This paper introduces the basic usage of AIDL and the data formats supported by AIDL.
  • Adding and removing callbacks to AIDL via RemoteCallbackList, traversing data or getting size must be paired with beginBroadcast and finishBroadcast.
  • It also introduces permission verification and package name verification for AIDL.

In the next section we introduce IPC via ContentProvider.

If you think this article is good, please give it a thumbs up.

The above


Scan the qr code below, follow my public account Android1024, click attention, don’t get lost.