Android Framework——Learning about inter-process communication, starting from the use of Binder

Preface

Binder is a very important inter-process communication tool in Android. It provides a series of services to the outside world through BinderAndroid . ServiceManagerLearning Binder will give us frameworka good start in learning.

Android uses multiple processes

AndroidStarting a process is very simple. Just specify the android:process attribute AndoridMenifestfor the four major components Activity( Service, Receiver, ) in .ContentProvider

There is also an unconventional way to start a process, which is to fock a new process in the native layer through jni.

At the beginning, when we needed to use multi-process and enthusiastically added processattributes to the required components, thinking that it would run normally, we found that some problems also occurred.

  1. When obtaining static data, you will not get the same copy.
  2. sharePreferenceThe reliability of data sharing decreases.
  3. Application will be created multiple times.
  4. The thread synchronization mechanism completely fails.

The main reason for these problems is that different processes run in different virtual machines and have independent memory spaces. Therefore, modifications to static values ​​by different processes will only affect their own processes. Therefore, as long as the data is shared through memory, it will fail in the multi-process of the four major components. This is also the main impact of multi-process. In order to solve these problems, it is necessary to use the multi-process communication method provided by Android. There are many methods, we can use Intent, file sharing, SharePreference, AIDL, Socket and Binder-based Messager to achieve it. Android uses the Linux kernel, but the inter-process communication method is not completely inherited from Linux. Binder is the most unique inter-process communication solution in the Android system. I say this because in Android, through the unique Binder, we can easily achieve inter-process communication.

Table of contents

Here we mainly talk about three aspects

  1. Serialization related interface Serializable
  2. Serialization related Parcelable.
  3. Binder for inter-process communication

When we want to use Intent and Binder to transmit data, we need to use Parcelable or Serializable. Or when we want to persist data or transmit it over the network, we also need to use Serializable to complete object persistence.

Serializable

Serializable is a serialization interface provided by Java. It is very simple to use. You only need to let the class that needs serialization implement the interface. Serialization implementation principle :

  1. Execute ObjectOutputStram#writeObjectserialization
  2. Internally ObjectStreamClass, instances will be created, serialVersionUIDvalues, writeObjectmethods and readObjectmethods maintained, etc.
  3. Determine and execute custom writeReplacemethods to obtain new objects to be serialized
  4. Determine whether it is Serializablean instance and execute writeOrdinaryObjectthe method to write serialization

In addition to simply implementing the Serializable interface, there are some optional fields and methods that can be customized, as shown in the following code:

class User(val name: String = "你好阿") : Serializable {
    private fun writeReplace(): Any? = null
    private fun readResolve(): Any? = null
    companion object {
        private val serialVersionUID = 1L
        private fun writeObject(ops: ObjectOutputStream) {}
        private fun readObject(ips: ObjectInputStream) {}
        private fun readObjectNoData() {}
    }
}

These methods are ObjectStreamClassmaintained and processed in to implement version maintenance and serialization customization. The more important field is serialVersionUIDthe field, which marks the version number of the entity class. When deserializing, it is judged by comparing the version number whether the structure has not changed significantly, and the deserialization process is completed. **Providing this field will make deserialization more reliable and controllable. **Generally, this field does not need to be provided during real-time data transmission. The system will automatically generate the hash value of this type and assign it to serialVersionUID. But in some persistence operations, providing this field is a more important task. Because if it is not provided, once the attributes are changed, hashthe result of the class will also change, which will directly cause the previous data to fail to be deserialized successfully and throw an exception. The impact on the experience is severe. Therefore, in scenarios where persistent data is required, **serialVersionUID** fields need to be provided. The serialization and deserialization code of Serializable is very simple. Here is a simple example.

val file = File("aaaa")
file.createNewFile()
///序列化过程
ObjectOutputStream(FileOutputStream(file))
    .use {
        it.writeObject(User("张三"))
    }
///反序列化
val user: User? =
    ObjectInputStream(FileInputStream(file)).use {
        it.readObject() as User?
    }
println("序列化结果")
println(user?.name)

The above code completes Serializablethe entire process of method serialization. It's very simple, just use ObjectOutputStreamand ObjectInputStream.

Parcelable

After introducing Serializable, let’s take a look at Parcelable. Parcelable is also an interface. As long as this interface is implemented, serialization can be implemented and passed through intent and binder. Take a look at a classic usage:

class User(val name: String? = "小王") : Parcelable {
    constructor(parcel: Parcel) : this(parcel.readString()) {
    }
    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
    }
    override fun describeContents(): Int {
        return 0
    }
    companion object CREATOR : Parcelable.Creator<User> {
        override fun createFromParcel(parcel: Parcel): User {
            return User(parcel)
        }
        override fun newArray(size: Int): Array<User?> {
            return arrayOfNulls(size)
        }
    }
}

You can see that there are four custom methods, which are explained as follows:

  1. writeToParcel implements the serialization function and writes to parcel
  2. describeContents provides content description, almost always returns 0, and only returns 1 when a file descriptor exists
  3. createFromParcel implements the deserialization function and creates original objects from serialized objects.
  4. newArray provides an array container

ParcelaleBoth and Serializablecan achieve serialization, how to choose? We can choose based on the difference between the two options. SerializableIt is simple to use, but the overhead is relatively high, requiring a large number of I/O operations during serialization and deserialization. The use of Parcelableis a little more complicated, but it has better performance and is the serialization method recommended by Android. So when transferring memory, you can use Parcelableserialization, but when it comes to persistence and network transmission, Parcelableit can also be implemented, but the use will be more complicated, so it is recommended to use it in these two cases Serializable. The above is the difference between the two serialization schemes.

Binder

Binder is a communication method proprietary to Android. The bottom layer of Binder is supported by the kernel driver. The device driver file is /dev/binder. Through this driver, Android has a complete set of C/S architecture in the native layer and also encapsulates a C/S architecture in the Java layer. The layer is implemented accordingly. Intuitively, Binder is a class in Android that inherits the IBinder interface. Binder can perform cross-process communication or local process communication. When we write a local service LocalService that does not need to cross processes, we can directly obtain the Binder class for communication. Based on binder, Android implements multiple ManagerServices. Because the Android system has various system component hardware that needs to be exposed to other processes and needs to be managed centrally, after Android implements the management solution, it then exposes the corresponding interface services, such as pms, ams, and wms, through binder. In Android development, the most direct application for Binder by developers is to use AIDL. The related usage process roughly has the following steps:

  1. Create aidl file and declare method
  2. Inherit the generated Stub class (abstract subclass of Binder) and implement related interface methods for server-side operations.
  3. Create a service running in another process and return the Binder instance in its onBind method
  4. Use this Service ServiceConnection#onServiceConnectedto obtain the Binder instance that defines the interface through the parameter IBinder obtained in the callback. Such as IHelloManager.Stub.asInterface(service).
  5. Make remote method calls through Binder instances.

AIDL (Android Interface Definition Language)

Let's first look at the introduction of Google's official developer documentation. We can use AIDL to define a programming interface recognized by both the client and the service, so that the two can communicate with each other using inter-process communication (IPC). In Android, writing code for inter-process communication is more cumbersome. Android will use AIDL to help us deal with such problems. Let's start with a typical AIDL example to explore.

Define AIDL interface

Let's define an aidl interface file

//IHelloManager.aidl
package top.guuguo.wanandroid.tv;
import top.guuguo.wanandroid.tv.User;
interface IHelloManager {
    User getFriend();
    void setFriend(in User friend);
}
//User.aidl
package top.guuguo.wanandroid.tv;
parcelable User;

The User object is used, so it is also defined above User.aidl. This object implements the Parcelable interface. We find generated/aidl_source_output_dirthe java class generated corresponding to the observation: IHelloManager.java.

public interface IHelloManager extends android.os.IInterface {
    /**
     * Default implementation for IHelloManager.
     */
    public static class Default implements top.guuguo.wanandroid.tv.IHelloManager {
        /***/
    }
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements top.guuguo.wanandroid.tv.IHelloManager {
        /***/
        private static class Proxy implements top.guuguo.wanandroid.tv.IHelloManager {
            /***/
        }
    }
    public top.guuguo.wanandroid.tv.User getFriend() throws android.os.RemoteException;
    public void setFriend(top.guuguo.wanandroid.tv.User friend) throws android.os.RemoteException;
}

You can see that the interface is generated IHelloManagerand implemented IInterface. You can see that three implementation classes of this interface are generated by default. Default, Stuband Stub.Proxy. StubIt is a Binderclass and an instance is a server object. Stub.ProxyIt is Proxya server-side proxy class. When executing a method, the server-side transact method is called to perform inter-process data interactive conversion. These two implementation classes are the IHelloManagercore classes. Take a look at the code of the Stub class:

public static abstract class Stub extends android.os.Binder implements top.guuguo.wanandroid.tv.IHelloManager {
    private static final java.lang.String DESCRIPTOR = "top.guuguo.wanandroid.tv.IHelloManager";
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }
    public static top.guuguo.wanandroid.tv.IHelloManager asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof top.guuguo.wanandroid.tv.IHelloManager))) {
            return ((top.guuguo.wanandroid.tv.IHelloManager) iin);
        }
        return new top.guuguo.wanandroid.tv.IHelloManager.Stub.Proxy(obj);
    }
    @Override
    public android.os.IBinder asBinder() {}
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(descriptor);
                return true;
            }
            case TRANSACTION_getFriend: {
                data.enforceInterface(descriptor);
                top.guuguo.wanandroid.tv.User _result = this.getFriend();
                reply.writeNoException();
                if ((_result != null)) {
                    reply.writeInt(1);
                    _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
            case TRANSACTION_setFriend: {
                data.enforceInterface(descriptor);
                top.guuguo.wanandroid.tv.User _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = top.guuguo.wanandroid.tv.User.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.setFriend(_arg0);
                reply.writeNoException();
                return true;
            }
            default: {
                return super.onTransact(code, data, reply, flags);
            }
        }
    }
    static final int TRANSACTION_getFriend = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_setFriend = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(top.guuguo.wanandroid.tv.IHelloManager impl) {
        if (Stub.Proxy.sDefaultImpl != null) {
            throw new IllegalStateException("setDefaultImpl() called twice");
        }
        if (impl != null) {
            Stub.Proxy.sDefaultImpl = impl;
            return true;
        }
        return false;
    }
    public static top.guuguo.wanandroid.tv.IHelloManager getDefaultImpl() {
        return Stub.Proxy.sDefaultImpl;
    }
}

The following introduces Stubthe meaning of members in the class:

  • DESCRIPTOR

Binder`的唯一标识,一般是当前Binder的类名。本例是`"top.guuguo.wanandroid.tv.IHelloManager"
  • asInterface(android.os.IBinder obj)

Convert the Binder object on the server side into the corresponding AIDL interface object. By queryLocalInterfacedistinguishing the processes, if both ends are in the same process, the returned object is the Stub object. If they are in different processes, their Proxy object is returned.

  • asBinder

Returns the current Binder instance

  • onTransact

This method performs serialization and deserialization operations on the transmitted data. The complete method is public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags). In this method, the code is used to locate the method requested by the client, then the parameters required by the method are taken out from the data, and then the target method is executed. If the target method has a return value, the result is written to the method reply. If this method returns false, the client's request will fail. We can put some calling restrictions on this method to avoid some unwanted processes calling this method.

  • Proxy#getFriendandProxy#setFriend

These two proxy methods first process the incoming parameters, write them to Parcel, and then call to mRemote.transactinitiate an RPC (remote procedure call) request. At the same time, the current thread is suspended until the RPC process returns, and then the current thread continues to execute and the replyreturned result is retrieved. Return data after deserialization.

bindService

Through the above analysis of AIDL and its generated code, we know that AIDL is just a way for us to quickly generate Binder communication template code. When we want to use this Binder for IPC in related components, we need to obtain Binderthe instance through the binding service. The following is binderthe relevant code for binding service acquisition:

val connection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        "onServiceConnected".toast()
        binder = IHelloManager.Stub.asInterface(service)
    }
    override fun onServiceDisconnected(name: ComponentName?) {
        "onServiceDisconnected".toast()
    }
}
override fun onStart() {
    super.onStart()
    val intent = Intent(this, HelloService::class.java)
    intent.action = ":startHello"
    bindService(intent, connection, BIND_AUTO_CREATE)
}
override fun onStop() {
    super.onStop()
    unbindService(connection)
}

From the above code, we can bindServiceget the aidldefined Binderinstance through . Through this Binder instance, you can directly make method calls to the remote process. What is the specific process of binding services? Now look at the entire call path

  1. Initiate binding service:mBase.bindService
  2. Locate the specific binding method: After checking Activity#attachthe method, ActivityThread#performLaunchActivitymethod and createBaseContextForActivitymethod, we know mBasethat it is ContextImplan instance.

mBase.bindServiceContextImpl#bindServiceCommonMethod called

  1. Get ActivityManagerthe Binder proxy object: In ActivityManager.``*getService*``()the method, ServiceManager.getService(Context.ACTIVITY_SERVICE)get the IBinder instance from(BinderProxy)
  2. Binding services are performed through the binding service method ActivityManagercalled .ActivityManagerService

After consulting the source code and searching the Internet, I found that the acquisition Binderand bindercommunication principles involve the implementation of AOSP source code . ServiceManagerI will not study it for now. I will study the binder communication mechanism in AOSP later. Correspondingly, first study the Binder mechanism analysis article by Skytoby boss to get a general idea. The structure diagram of the Binder mechanism borrowed from the author is as follows:Bindernative C/S

Let’s see next, and how to implement the binder by hand.

Handwritten Binder implementation class

Through the above analysis, we have a general understanding Binderof the working mechanism of . So we try not to use aidl and use binder for process communication. The basic implementation only requires writing three classes

  1. Define interface class
interface IActivityManager : IInterface {
    fun startActivity(code: Int): String?
}
  1. The server-side Binder abstract class completes the deserialization of remotely transmitted data and the server-side execution of tasks in onTransact.
abstract class ActivityManager : Binder(), IActivityManager {
    companion object {
        val DESCRIPTOR = "top.guuguo.aidltest.IActivityManager"
        val CODE_START_ACTIVITY = FIRST_CALL_TRANSACTION + 0
        fun asInterface(obj: IBinder?): IActivityManager? {
            if (obj == null) return null
            return (obj.queryLocalInterface(DESCRIPTOR)
                ?: Proxy(obj)) as IActivityManager
        }
    }
    override fun asBinder(): IBinder {
        return this
    }
    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
        when (code) {
            INTERFACE_TRANSACTION -> {
                reply?.writeString(DESCRIPTOR);
                return true;
            }
            CODE_START_ACTIVITY -> {
                data.enforceInterface(DESCRIPTOR)
                reply?.writeNoException()
                reply?.writeString(startActivity(data.readInt()))
                return true
            }
        }
        return super.onTransact(code, data, reply, flags)
    }
}
  1. Client proxy class (completes the serialization and deserialization work of data, specifically handed over to the proxy object for completion)
    class Proxy(val remote: IBinder) : ActivityManager() {
        override fun startActivity(code: Int): String? {
            val params = Parcel.obtain()
            val reply = Parcel.obtain()
            params.writeInterfaceToken(DESCRIPTOR)
            params.writeInt(code)
            remote.transact(CODE_START_ACTIVITY, params, reply, 0)
            reply.readException()
            val str = reply.readString()
            params.recycle()
            reply.recycle()
            return str
        }
        override fun getInterfaceDescriptor(): String? {
           return DESCRIPTOR
        }
        override fun asBinder(): IBinder {
            return remote
        }
    }

Complete server-side and client-side related work through InterfaceTokentags. In specific use, implement ActivityManager and complete the server-side tasks. The custom implementation is as follows:

inner class HelloManagerImpl : ActivityManager() {
    override fun startActivity(code: Int): String? {
        return "progress:" + getProcessName(baseContext)
    }
}

After completing the writing, bindServiceyou can bind the service to obtain Binder for inter-process communication through the same method. binder?.startActivity(1)You can get the corresponding string result by calling in the example .

end

I roughly learned how to use Binder, and also hand-written a simulated ActivityManagerServiceBinder implementation. You have a general understanding of how to perform inter-process communication through Binder. Similarly, there are many examples of using Binder to provide external services in the Android system. Android ServiceManagerprovides PackageManagerService WindowsManagerServiceservices such as through. If we understand the implementation of these framework layers, it will be very helpful for our development. Understanding the use of Binder is a starting point, and then we will move towards advanced levels.

If you haven't mastered Framework yet and want to understand it thoroughly in the shortest time, you can refer to "Android Framework Core Knowledge Points" , which includes: Init, Zygote, SystemServer, Binder, Handler, AMS, PMS, Launcher... ...etc. to record knowledge points.

"Framework Core Knowledge Points Summary Manual" :https://qr18.cn/AQpN4J

Handler mechanism implementation principle part:
1. Macroscopic theoretical analysis and Message source code analysis
2. Source code analysis of MessageQueue
3. Source code analysis of Looper
4. Source code analysis of handler
5. Summary

Binder principle:
1. Knowledge points that must be understood before learning Binder
2. Binder mechanism in ServiceManager
3. System service registration process
4. ServiceManager startup process
5. System service acquisition process
6. Java Binder initialization
7. Java Registration process of system services in Binder

Zygote :

  1. The startup process of Android system and the startup process of Zygote
  2. Application process startup process

AMS source code analysis:

  1. Activity life cycle management
  2. onActivityResult execution process
  3. Detailed explanation of Activity stack management in AMS

In-depth PMS source code:

1. PMS startup process and execution process
2. APK installation and uninstallation source code analysis
3. Matching architecture of intent-filter in PMS

WMS:
1. The birth of WMS
2. Important members of WMS and the process of adding Window
3. The process of deleting Window

"Android Framework Learning Manual":https://qr18.cn/AQpN4J

  1. Boot Init process
  2. Start the Zygote process on boot
  3. Start the SystemServer process on boot
  4. Binder driver
  5. AMS startup process
  6. PMS startup process
  7. Launcher startup process
  8. Android four major components
  9. Android system service-Input event distribution process
  10. Android underlying rendering - screen refresh mechanism source code analysis
  11. Android source code analysis in practice

Guess you like

Origin blog.csdn.net/maniuT/article/details/132723466