Recently, I was asked about cross-process communication for many times in the interview. For the first time, I thought the question was about the use of AIDL, so I simply said: I know about it, but I have never used it in the project. Later in the interview, this question was mentioned too often, so I went back to the “Android Development Art Exploration” again, this time with questions to read the efficiency is really high, so there is a summary of this article

IPC Concept introduction

IPC stands for inter-process Communication, or inter-process Communication. Communication is just like writing letters, sending emails, making phone calls and sending wechat messages. There are also several code implementation methods as follows:

  • Bundle
  • File sharing
  • Message
  • AIDL
  • ContentProvider
  • Socket

Since there are six ways to achieve this, what should a patient like me with dysparexia do? Refer to the table below to choose your own business scenario

The name of the advantages disadvantages Applicable scenario
Bundle Simple and easy to use Only data types supported by the Bundle can be transferred Interprocess communication between the four components
File sharing Simple and easy to use Does not support concurrency, cannot instant communication Scenarios with no concurrent access, simple data and no real-time requirements
AIDL Supports one-to-many concurrent communication and real-time communication It is complicated to use and needs to handle thread synchronization itself One-to-many communication, and RPC requirements
Message Supports one-to-many serial communication and real-time communication Does not support high concurrency, does not support RPC, and can only transmit data types supported by the Bundle Low concurrency one-to-many instant communication, no RPC requirement or no RPC requirement to return results
ContentProvider Good at data resource access, support one to many concurrent data sharing, extensible Constrained AIDL, which mainly provides CRDU operations for data sources One-to-many interprocess data sharing
Socket Transmit byte stream through network, support one – to – many concurrent real-time communication Implementation details are cumbersome and direct RPC is not supported Network data exchange

*RPC stands for Remote Procedure Call

With the six implementations outlined above, I move on to the topic of AIDL in detail.

The use of an AIDL

AIDL stands for Android Interface Definition Language, which enables cross-process communication between a Service and multiple application components. This enables multiple applications to share the same Service. Its use can be simply summarized as the server and client. Similar to Socket, the server serves all clients and supports one-to-many services. But how does the server serve the client? Just as a guest in a hotel rings a bell to ask the attendant to clean up after checking in, the server needs to create its own response system, the AIDL interface. However, this AIDL interface is different from ordinary interfaces in that it supports only six data types internally:

  1. Basic data type (via net friendLiu SiqiRemind,shortNot supported)
  2. StringandCharSequence
  3. List interface (Automatically converts the List interface toArrayList), and every element of the collection must be able to be supported by AIDL
  4. Map interface (The Map interface is automatically converted toHashMap), and each element’s key and value must be supported by AIDL
  5. ParcelableThe implementation of the class
  6. AIDL interface itself

Creation of the AIDL interface

Create the process without maps, directly on the code:

// JobsInterface.aidl package com.vincent.keeplive; // Declare any non-default types here with import statements // Declare any non-default types here with import statements // use the import statement to Declare any non-default type interface JobsInterface {/** * Demonstrates some basic types that you can use as parameters * andreturn values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
Copy the code

The basicTypes method is an example, which means the basic data types that are supported. We can simply remove it and then add two methods that we need to test:

// JobsInterface.aidl package com.vincent.keeplive; / / Declare any non - default types here with the import statements / / using the import statements in this declaration any non-default type import com. Vincent. Keeplive. Offer;  interface JobsInterface { List<Offer> queryOffers(); void addOffer(in Offer offer);
}

// Offer.aidl
package com.vincent.keeplive;

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

parcelable Offer;
Copy the code

Note for creating AIDL

  • Use the import statement to declare any non-default types here, that is, custom objects that need to be displayed are imported using import
  • If a custom is used in an AIDL fileparcelableObject, you must create a new AIDL file with the same name as the example above. Then add the code shown in the following image to your Module’s build.gradle

  • In addition to basic type data, parameters of other types must be oriented: in, out, inout. In denotes input; Out indicates output; Inout represents input/output parameters. Use them as needed, because out and inout are expensive to implement at the bottom.
  • The AIDL interface only supports methods, not static variables, and does not support normal interfaces
  • All of AIDL’s related classes need to ensure that all files have exactly the same path when used by another application, because cross-process serialization and deserialization are involved. It would be an error if process A’s A was serialized to process B and no responding object was found under the same file path.

Server-side implementation

First on the code again notes:

/** * class RemoteService:Service() { private val TAG = this.javaClass.simpleName private val mList = mutableListOf<Offer>() private val mBinder = object  :JobsInterface.Stub(){
        override fun queryOffers(): MutableList<Offer> {
            return mList;
        }

        override fun addOffer(offer: Offer) {
            mList.add(offer)
        }
    }

    override fun onCreate() {
        super.onCreate()
        mList.add(Offer(5000,Zhaopin.com))
    }

    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }
}
Copy the code

Precautions for creating a server:

  1. Because methods in AIDL are on the server sideBinderThread pool execution. Synchronization of methods needs to be considered when the server is multitime
  2. When the server parameters are implementedListInterface (orMapInterface),BinderWill be in accordance with theList(orMap) to access data and formArrayList(orHashMap) to the client. The point is that the server doesn’t have to think about what it is, rightListMap).

Client-side implementation

We don’t produce code, we’re just carriers of code. Here, I’ll just copy the code from the book:

class MainActivity : AppCompatActivity() { private val TAG = this.javaClass.simpleName private val mConnection = object :ServiceConnection{ override fun onServiceDisconnected(name: ComponentName?) { } override fun onServiceConnected(name: ComponentName? , service: IBinder?) { val manager = JobsInterface.Stub.asInterface(service) val list = manager.queryOffers() Log.e(TAG,"The list type:${list.javaClass.canonicalName}")
            Log.e(TAG,"queryOffers:${list.toString()}")
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bindService(Intent(this, RemoteService::class.java),mConnection,Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        unbindService(mConnection)
        super.onDestroy()
    }
}
Copy the code

Precautions for creating a client:

  1. The method of calling the client can be understood as the method of calling the server, that is, the worker thread needs to be started for time-consuming operations
  2. The data type returned by the server, as mentioned above, can only beArrayList(HashMap) type

Log:

list type: Java.util.ArrayList queryOffers:[[Salary :5000, Company: Zhaopin]]Copy the code

This is a complete IPC communication, but such communication can only be one-way. Just like the APP can only access the server, and the server can not access the APP, but now the server has push, how can our server instant communication? Let’s look at instant communication through observers.

The realization of instant communication

Principles of instant Communication

The principle is to declare an AIDL interface, and then add and remove the declared AIDL interface through registration and deregistration in the AIDL interface implemented by the server. It then iterates through all registered interfaces to initiate communication when the server needs to send a message to the client.

Speaking of the code is more boring, next through the code actual combat to see the specific process!

The practice of instant messaging

1. Declare the AIDL interface

package com.vincent.keeplive.aidl; import com.vincent.keeplive.aidl.Offer; / / offer observation interface interface IOnNewOfferArrivedInterface {void onNewOfferArrived (in Offer offer);
}
Copy the code

2. Modify the AIDL interface on the server

// JobsInterface.aidl package com.vincent.keeplive.aidl; // Declare any non-default types here with import statements // Declare any non-default types here with import statements using import statements com.vincent.keeplive.aidl.Offer; import com.vincent.keeplive.aidl.IOnNewOfferArrivedInterface; interface JobsInterface { List<Offer> queryOffers(); void addOffer(in Offer offer);
    void registerListener(IOnNewOfferArrivedInterface listener);
    void unregisterListener(IOnNewOfferArrivedInterface listener);
}
Copy the code

3. Use interfaces on the server to achieve instant communication

<p>@author <p> * <p>@date 2019/4/140014 <p> * <p>@update 2019/4/140014 <p> 1<p> * */ class RemoteService :Service() {private val TAG = this. JavaClass. SimpleName / / containers offer private val mList = mutableListOf < offer > special container () / / an aidl interface private val mListenerList = RemoteCallbackList<IOnNewOfferArrivedInterface>() private val mBinder = object : JobsInterface.Stub(){
        override fun registerListener(listener: IOnNewOfferArrivedInterface?) {
            mListenerList.register(listener)
        }

        override fun unregisterListener(listener: IOnNewOfferArrivedInterface?) {
            mListenerList.unregister(listener)
        }

        override fun queryOffers(): MutableList<Offer> {
            returnmList; {} override fun addOffer (offer: offer) mList. Add (offer) / / to the client communication val size = mListenerList. BeginBroadcast ()for (i in 0 until size ){
                val listener = mListenerList.getBroadcastItem(i)
                listener.onNewOfferArrived(offer)
            }
            mListenerList.finishBroadcast()
        }
    }


    override fun onCreate() {
        super.onCreate()
        mList.add(Offer(5000, Zhaopin.com))

    }

    override fun onBind(intent: Intent): IBinder {
        Handler().postDelayed({
            mBinder.addOffer(Offer(4500,"51job"))
        },1000)
        return mBinder
    }
}
Copy the code

4. The client receives real-time information from the server

/** * client */ class MainActivity:AppCompatActivity() { private val TAG = this.javaClass.simpleName var manager:JobsInterface? = null private val mConnection = object :ServiceConnection{ override fun onServiceDisconnected(name: ComponentName?) { } override fun onServiceConnected(name: ComponentName? , service: IBinder?) { manager = JobsInterface.Stub.asInterface(service) val list = manager? .queryOffers() Log.e(TAG,"The list type:${list? .javaClass? .canonicalName}")
            Log.e(TAG,"queryOffers:${list.toString()}") manager? .registerListener(mArrivedListener) // service? .linktoDeath ({// // Binder connection death callback here needs to reset manager //},0)}} private val mArrivedListener = object: IOnNewOfferArrivedInterface.Stub(){
        override fun onNewOfferArrived(offer: Offer?) {
            Log.e(TAG,"ThreadId:${Thread.currentThread().id}    offer:${offer}")
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bindService(Intent(this, RemoteService::class.java),mConnection,Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy() { manager? .let {if(it.asBinder().isBinderAlive){ it.unregisterListener(mArrivedListener) } } unbindService(mConnection) super.onDestroy() }}Copy the code

RemoteCallbackList

Public class RemoteCallbackList

RemoteCallbackList

ArrayMap

mCallbacks = new ArrayMap

(); The Binder is the client’s underlying information transfer Binder as the key, and the AIDL interface as the value
,>
,>

RemoteCallbackList cannot operate data like List. You need to follow the example to obtain the number of elements or register or unregister an interface. BeginBroadcast and finishBroadcast must be used together. Otherwise, an exception will occur beginBroadcast() called while already in a broadcast or finishBroadcast() called outside of a broadcast.

RemoteCallbackList triggers ibInder.linktoDeath in the register method and ibInder.unlinktoDeath in the unregister method.

Notes for instant messaging

  1. The client invokes the server-side method, and the invoked method runs on the server-sideBinderThread pool, while client threads are suspended. The server method runs onBinderIn a thread pool, time-consuming tasks can be performed. It is not recommended to start a worker thread for asynchronous tasks. Similarly, when a server calls a method on the client, the server hangs, and the called method runs on the clientBinderThread pools also need to be aware of thread switching for time-consuming tasks
  2. There are two ways to call a disconnected programServiceConnection.onServiceDisconnected(), the method runs on the client UI thread; The other one isBinder.DeathRecipient.binderDied(), the method runs on the client sideBinderThread pool, no access to UI

Log:

QueryOffers :[[Salary :5000, company: zhaopin.com]] ThreadId:1262 Offer :[Salary :4500, Company :51job]Copy the code

AIDL permission authentication

By default, our remote access is available to anyone, which is not what we want, so we need to add permission validation. Permission validation can be performed on the server side in the onBind () method or in the onTransact () method, either by custom or by package name.

Example:

private val mBinder = object : JobsInterface.Stub() {... override fun onTransact(code: Int, data: Parcel, reply: Parcel? , FLAGS: Int): Boolean {// Validate permission returnsfalseRights not verify by val check = checkCallingOrSelfPermission ("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE")
            if(check == PackageManager.PERMISSION_DENIED){
                return false
            }
            val packages = packageManager.getPackagesForUid(Binder.getCallingUid())
            if(packages ! = null && packages.size>0){if(! packages[0].endsWith("keeplive")) {return false}}return super.onTransact(code, data, reply, flags)
        }
    }
    
// AndroidManifest
<uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
<service android:name=".RemoteService"
                 android:enabled="true"
                 android:exported="true"
                 android:process=":jing"
                 android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
Copy the code

Precautions for customizing permissions

  • Components with custom permissions need to be enabled implicitlyIntent().setClassName("com.vincent.keeplive","com.vincent.keeplive.RemoteService")
  • The procedure for customizing permissions is as follows:
  1. Define permissions:<permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" android:protectionLevel="normal"/>(The verified party directly skips this step)
  2. Use this permission in a project:<uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" />
  3. Add permissions for components that need to be verified:<service android:name=".RemoteService" android:enabled="true" android:exported="true" android:process=":jing" android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
  4. If the defined permissions are dangerous permissions, systems 6.0 or higher need to apply for them dynamically:if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(arrayOf("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"),1000) }

Reference:

AIDL created using Android Studio found no workaround for custom classes when compiling

Custom permissions

The source code