Android process communication-how to use AIDL

Introduction to AIDL

AIDL is the Android interface definition language, which is a bit similar to the ordinary interface in our development. Since memory cannot be shared between different processes, in order to solve the problem of inter-process communication, inter-process communication can be realized through the AIDL interface language.

AIDL file supported data types

  • Basic data types (int, long, char, boolean, double)
  • String and CharSequence
  • List and Map collection
    • The elements in the collection must be data types supported by AIDL
    • The specific collection used by the server must be ArrayList and HashMap
  • Parcelable: an object that implements the Parcelable interface
  • AIDL's own interface can also be used in AIDl files.

AIDL steps

There are three main steps

  1. Create AIDL
    1. To create an instance class, you must implement the Parcelable interface for serialization and deserialization
    2. Create a new aidl folder, create aidl files with the same name as the instance class and other aidl interface files in it
    3. Rebuild the project to generate Binder's java interface file.
  2. Create a server (Service)
    1. Create Service, return Binder instance object in onBind method, and implement aidl interface method.
  3. Write customer service terminal (Activity or other)
    1. Use bindService to start the service, implement the ServiceConnection interface, and get the Binder object generated by aidl.
    2. Call the function method defined by aidl.

AIDL concrete realization

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, so that they can be used in other places and can be copied directly.
The following is the file directory of the specific example:
Write picture description here

The aidl file directory is at the same level as the java directory

1. Create entity classes that need to be transmitted between processes and implement the Parcelable interface

public class Book implements Parcelable{

    private String bookName;

    public Book(String bookName) {
        this.bookName = bookName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.bookName);
    }

    protected Book(Parcel in) {
        this.bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                '}';
    }
}

The Parcelable interface is implemented so that instance classes can communicate between processes.
In fact, inter-process communication is a process of serialization and deserialization. For serialization, please refer to this article Android Process Communication-Serialization Serialzable and Parcelable

2. Create a new aidl folder, create aidl files with the same name as the instance class and other aidl interface files in it

a. Create a new aidl folder, the main aidl directory and java directory are at the same level, under the main directory.
Write picture description here
b. Create an aidl file with the same name as the entity class (Book): Book.aidl

// Book.aidl 
package com.hzw.progress.aidl;

//声明Book实体类已序列化,可用于进程间传输
parcelable Book;

The purpose of this is to let the aidl file know that the Book class implements the Parcelable interface, and the Book object can be used as a carrier for transferring data between processes.

Note: The Book entity class and the Book.aidl file must be in the same package structure directory, otherwise it will run incorrectly, because the client needs to deserialize the AIDL interface-related classes of the server. If the path of the class is different, the client Desequence will fail.

IBookManager.aidl interface

// IBookManager.aidl
package com.hzw.progress.aidl;

// Declare any non-default types here with import statements
//虽然在IBookManager.aidl和Book.aidl同一个包名下,但必须以import方式将Book.aial文件导入(手动导入)
import com.hzw.progress.aidl.Book;

import com.hzw.progress.aidl.OnNewBookAddListener;

//定义客户端与服务端间的回调接口
interface IBookManager {

    //得到全部数据(全部书籍)
    List<Book> getBookList();
    //添加数据的操作(添加书籍)
    void addBook(in Book book);
    //注册监听,用于监听新数据的变化。是典型的观察者模式的运用
    void registerListener(OnNewBookAddListener listener);
    //解注册
    void unregisterListener(OnNewBookAddListener listener);
}

OnNewBookAddListener.aidl interface file

// OnNewBookAddListener.aidl
package com.hzw.progress.aidl;

import com.hzw.progress.aidl.Book;


// Declare any non-default types here with import statements

//定义监听新数据变化的接口(添加新书籍)
interface OnNewBookAddListener {

    void onNewBookAdd(in Book book);

    void onAllBook();
}

The above points to note, there is an in in the addBook and onNewBookAdd method parameters, which means the input type parameter. In addition to basic data types in AIDL, other data types must be marked with direction, in, out or inout.

  • in means input parameter
  • out means output parameter
  • inout represents input and output parameters

c. After creating the aidl file, you need to rebuild the project to generate the Binder java interface file.

3. Create a server-side BookManagerService inherited from Service

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";
    private List<Book> mBookList=new ArrayList<>();
    private static List<OnNewBookAddListener> mAddListenerList=new ArrayList<>();

    public BookManagerService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化书籍
        mBookList.add(new Book("Android群英传"));
        mBookList.add(new Book("Android开发艺术探索"));

    }

    @Override
    public IBinder onBind(Intent intent) {
        //获取Service需要的权限,进行自我验证,防止其他进程访问该服务
       int check= checkCallingOrSelfPermission("com.hzw.progress.aidl.permission.LOCAL");
       //未授权该应用,则无法访问
       if (check== PackageManager.PERMISSION_DENIED){
           return null;
       }
       //添加新书的过程
        BookFactory factory = new BookFactory();
        factory.execute(5); //添加五本书
        return mBinder;
    }

    /*
    * 初次new IBookManager方式构建Binder对象,会报错找不到IBookManager对象
    * 需要Rebuild下项目
    * */
    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 registerListener(OnNewBookAddListener listener) throws RemoteException {
            if (!mAddListenerList.contains(listener)){
                mAddListenerList.add(listener);
            }
            Log.i(TAG, "registerListener: "+mAddListenerList.size());
        }

        @Override
        public void unregisterListener(OnNewBookAddListener listener) throws RemoteException {
            if (mAddListenerList.contains(listener)){
                mAddListenerList.remove(listener);
            }
        }
    };

    /**
     * 使用静态内部类,避免泄漏
     */
    private static   class BookFactory extends AsyncTask<Integer,Book,Book>{

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected Book doInBackground(Integer... integers) {
	        //每隔2秒添加新建一本新书,最多只能添加5本
            int i=0;
            Book mBook=null;
            while (i< integers[0]){
                mBook=new Book("新书"+(i+1));
                try {
                    Thread.sleep(2000);
                    publishProgress(mBook);
                    i++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

            return mBook;
        }

        @Override
        protected void onProgressUpdate(Book... values) {
            super.onProgressUpdate(values);
            //监听每次添加数据内容
            for (int i = 0; i <mAddListenerList.size() ; i++) {
                OnNewBookAddListener listener = mAddListenerList.get(i);
                try {
                    if (listener!=null){
                        listener.onNewBookAdd(values[0]);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        protected void onPostExecute(Book book) {
            super.onPostExecute(book);
            //得到全部的书籍
            for (int i = 0; i <mAddListenerList.size() ; i++) {
                OnNewBookAddListener listener = mAddListenerList.get(i);
                try {
                    if (listener!=null){
                        listener.onAllBook();
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

The server side is a typical implementation of Service. For specific use, please refer to this article for in-depth understanding of the Service example . The above points to note:

  1. In the onBind method, it is best to add the judgment of the access permission to prevent some malicious access. You can use Permission to verify and declare it in the manifest.
  2. When creating a Binder object, you cannot use the new Binder () method. You must use new IBookManager.Stub() to create a Binder object, which is a Binder object automatically generated by the AIDL interface file.

Finally, you need to register the Service in the Mainfest file, and at the same time customize and reference the access rights of the server.

 <!--自定义访问Service的权限-->
    <permission android:name="com.hzw.progress.aidl.permission.LOCAL"/>
    <!--声明本拥有该权限-->
    <uses-permission android:name="com.hzw.progress.aidl.permission.LOCAL"/>

 <service
            android:name=".aidl.BookManagerService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"
            android:permission="com.hzw.progress.aidl.permission.LOCAL">
        </service>

Note: BookManagerService is required in a separate process, and the attribute android:process=":remote" must be defined

3. Write the customer service terminal After
binding the remote service, obtain the proxy object Binder through ServiceConnection, and at the same time convert the Binder object into an AIDL interface, and then call the method of the server through the AIDL interface.

public class AIDLActivity extends AppCompatActivity {
    private static final String TAG = "AIDLActivity";
    private Intent mIntent;
    private IBookManager mIBookManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        mIntent = new Intent(this, BookManagerService.class);
    }

    public void onClick(View view) {

        switch (view.getId()){
            case R.id.bindService:
	            //绑定服务
                bindService(mIntent,mConnection,BIND_AUTO_CREATE);
                break;
            case R.id.unbindService:
	            //解绑服务
                unbindService(mConnection);
                break;
        }
    }

    private ServiceConnection mConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                //ClassCastException
//                IBookManager iBookManager=(IBookManager)service;
                mIBookManager = IBookManager.Stub.asInterface(service);
                mIBookManager.addBook(new Book("Android进阶之光"));
                List<Book> bookList = mIBookManager.getBookList();
//                Log.i(TAG, "全部书籍: "+bookList.toString());
                mIBookManager.registerListener(sListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
        try {
            mIBookManager.unregisterListener(sListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private  OnNewBookAddListener sListener=new OnNewBookAddListener.Stub() {
        @Override
        public void onNewBookAdd(Book book) throws RemoteException {
            mIBookManager.addBook(book);
            Log.i(TAG, "onNewBookAdd: "+book.toString());
        }

        @Override
        public void onAllBook() throws RemoteException {
            List<Book> bookList = mIBookManager.getBookList();
            Log.i(TAG, "onAllBook: "+bookList.toString());
        }
    };
    
}

The above is the basic implementation of the client, you need to pay attention to the following points:

  1. When getting the proxy object Binder, you cannot directly use the forced conversion method, otherwise it will report a type conversion exception. You must use IBookManager.Stub.asInterface() to get the Binder object generated by the AIDL interface file.
  2. When you need to get an AIDl interface instance object, you need the .Stub() method to get the instance.

The above three steps are simple examples of AIDL's realization of process communication, and the running results are as follows:
Write picture description here

07-14 17:59:47.974 6970-6988/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书1'}
07-14 17:59:49.974 6970-6989/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书2'}
07-14 17:59:51.974 6970-6988/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书3'}
07-14 17:59:53.975 6970-6989/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书4'}
07-14 17:59:55.976 6970-6988/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书5'}
07-14 17:59:55.977 6970-6989/com.hzw.progress I/AIDLActivity: onAllBook: [Book{bookName='Android群英传'},
 Book{bookName='Android开发艺术探索'}, Book{bookName='Android进阶之光'}, Book{bookName='新书1'}, 
 Book{bookName='新书2'}, Book{bookName='新书3'}, Book{bookName='新书4'}, Book{bookName='新书5'}]
 

to sum up

The above example basically realizes the communication between AIDL processes, and there are still many questions, such as:

1. What exactly does AIDL do in the process of realizing communication?
2. The server creates a Binder object or obtains an AIDL interface object, both of which are implemented by calling the Stub() method, so what are the specific operations of this method?
3. What is Binder?

Points to note when using AIDL

  1. The package structure of the AIDL interface file must be the same structure as the entity class, otherwise the deserialization will fail
  2. The AIDL interface file with the same name as the entity class must use parcelable to declare that the Book class has been serialized, and note that it is all lowercase
  3. When using another AIDL file in one AIDL file, you need to manually import it explicitly (full package name).

If any of the above points is accidentally mistaken, AIDL will not be able to achieve process communication.
Regarding the above questions, I plan to learn Android process communication in the next article
-AIDL use analysis and Binder talk

reference

  • Android development art exploration
  • https://blog.csdn.net/luoyanglizi/article/details/51980630
  • https://www.jianshu.com/p/b9b15252b3d6
  • https://blog.csdn.net/u011240877/article/details/72765136

Guess you like

Origin blog.csdn.net/hzw2017/article/details/81048650