Preface
Binder is a very important inter-process communication tool in Android. It provides a series of services to the outside world through Binder
Android . ServiceManager
Learning Binder will give us framework
a good start in learning.
Android uses multiple processes
Android
Starting a process is very simple. Just specify the android:process attribute AndoridMenifest
for 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 process
attributes to the required components, thinking that it would run normally, we found that some problems also occurred.
- When obtaining static data, you will not get the same copy.
sharePreference
The reliability of data sharing decreases.- Application will be created multiple times.
- 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
- Serialization related interface Serializable
- Serialization related Parcelable.
- 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 :
- Execute
ObjectOutputStram#writeObject
serialization - Internally
ObjectStreamClass
, instances will be created,serialVersionUID
values,writeObject
methods andreadObject
methods maintained, etc. - Determine and execute custom
writeReplace
methods to obtain new objects to be serialized - Determine whether it is
Serializable
an instance and executewriteOrdinaryObject
the 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 ObjectStreamClass
maintained and processed in to implement version maintenance and serialization customization. The more important field is serialVersionUID
the 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, hash
the 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 Serializable
the entire process of method serialization. It's very simple, just use ObjectOutputStream
and 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:
- writeToParcel implements the serialization function and writes to parcel
- describeContents provides content description, almost always returns 0, and only returns 1 when a file descriptor exists
- createFromParcel implements the deserialization function and creates original objects from serialized objects.
- newArray provides an array container
Parcelale
Both and Serializable
can achieve serialization, how to choose? We can choose based on the difference between the two options. Serializable
It 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 Parcelable
is a little more complicated, but it has better performance and is the serialization method recommended by Android. So when transferring memory, you can use Parcelable
serialization, but when it comes to persistence and network transmission, Parcelable
it 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:
- Create aidl file and declare method
- Inherit the generated Stub class (abstract subclass of Binder) and implement related interface methods for server-side operations.
- Create a service running in another process and return the Binder instance in its onBind method
- Use this Service
ServiceConnection#onServiceConnected
to obtain the Binder instance that defines the interface through the parameter IBinder obtained in the callback. Such asIHelloManager.Stub.asInterface(service)
. - 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_dir
the 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 IHelloManager
and implemented IInterface
. You can see that three implementation classes of this interface are generated by default. Default
, Stub
and Stub.Proxy
. Stub
It is a Binder
class and an instance is a server object. Stub.Proxy
It is Proxy
a 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 IHelloManager
core 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 Stub
the meaning of members in the class:
Binder`的唯一标识,一般是当前Binder的类名。本例是`"top.guuguo.wanandroid.tv.IHelloManager"
Convert the Binder object on the server side into the corresponding AIDL interface object. By queryLocalInterface
distinguishing 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.
Returns the current Binder instance
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.
These two proxy methods first process the incoming parameters, write them to Parcel
, and then call to mRemote.transact
initiate 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 reply
returned 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 Binder
the instance through the binding service. The following is binder
the 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 bindService
get the aidl
defined Binder
instance 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
- Initiate binding service:
mBase.bindService
- Locate the specific binding method: After checking
Activity#attach
the method,ActivityThread#performLaunchActivity
method andcreateBaseContextForActivity
method, we knowmBase
that it isContextImpl
an instance.
mBase.bindService
ContextImpl#bindServiceCommon
Method called
- Get
ActivityManager
the Binder proxy object: InActivityManager.``*getService*``()
the method,ServiceManager.getService(Context.ACTIVITY_SERVICE)
get the IBinder instance from(BinderProxy)
- Binding services are performed through the binding service method
ActivityManager
called .ActivityManagerService
After consulting the source code and searching the Internet, I found that the acquisition Binder
and binder
communication principles involve the implementation of AOSP source code . ServiceManager
I 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:Binder
native 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 Binder
of 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
- Define interface class
interface IActivityManager : IInterface {
fun startActivity(code: Int): String?
}
- 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)
}
}
- 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 InterfaceToken
tags. 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, bindService
you 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 ActivityManagerService
Binder 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 ServiceManager
provides PackageManagerService
WindowsManagerService
services 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 :
- The startup process of Android system and the startup process of Zygote
- Application process startup process
AMS source code analysis:
- Activity life cycle management
- onActivityResult execution process
- 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
- Boot Init process
- Start the Zygote process on boot
- Start the SystemServer process on boot
- Binder driver
- AMS startup process
- PMS startup process
- Launcher startup process
- Android four major components
- Android system service-Input event distribution process
- Android underlying rendering - screen refresh mechanism source code analysis
- Android source code analysis in practice