Marco de Android: aprendizaje sobre la comunicación entre procesos, a partir del uso de Binder

Prefacio

Binder es una herramienta de comunicación entre procesos muy importante en Android, que proporciona una serie de servicios al mundo exterior a través de BinderAndroid . ServiceManagerLearning Binder nos dará frameworkun buen comienzo en el aprendizaje.

Android utiliza múltiples procesos

AndroidIniciar un proceso es muy simple: simplemente especifique el atributo android:process AndoridMenifestpara los cuatro componentes principales Activity( Service,, ) en .ReceiverContentProvider

También existe una forma poco convencional de iniciar un proceso, que consiste en crear un nuevo proceso en la capa nativa a través de jni.

Al principio, cuando necesitábamos usar multiproceso y agregamos processatributos a los componentes requeridos con entusiasmo, pensando que se ejecutaría normalmente, descubrimos que también ocurrieron algunos problemas.

  1. Al obtener datos estáticos, no obtendrá la misma copia.
  2. sharePreferenceLa confiabilidad del intercambio de datos disminuye.
  3. La aplicación se creará varias veces.
  4. El mecanismo de sincronización de subprocesos falla por completo.

La razón principal de estos problemas es que diferentes procesos se ejecutan en diferentes máquinas virtuales y tienen espacios de memoria independientes, por lo que las modificaciones de valores estáticos por diferentes procesos solo afectarán sus propios procesos. Por lo tanto, siempre que los datos se compartan a través de la memoria, el multiproceso de los cuatro componentes principales fallará, lo que también constituye el principal impacto del multiproceso. Para solucionar estos problemas, es necesario utilizar el método de comunicación multiproceso proporcionado por Android. Hay muchos métodos, podemos usar Intent, compartir archivos, SharePreference, AIDL, Socket y Binder-based Messager para lograrlo. Android usa el kernel de Linux, pero el método de comunicación entre procesos no se hereda completamente de Linux. Binder es la solución de comunicación entre procesos más exclusiva del sistema Android. Digo esto porque en Android, a través del exclusivo Binder, podemos lograr fácilmente la comunicación entre procesos.

Tabla de contenido

Aquí hablamos principalmente de tres aspectos

  1. Interfaz relacionada con la serialización Serializable
  2. Parcelable relacionado con la serialización.
  3. Carpeta para la comunicación entre procesos.

Cuando queremos usar Intent y Binder para transmitir datos, necesitamos usar Parcelable o Serializable. O cuando queremos conservar datos o transmitirlos a través de la red, también necesitamos usar Serializable para completar la persistencia del objeto.

Serializable

Serializable es una interfaz de serialización proporcionada por Java, es muy simple de usar, solo necesita dejar que la clase que necesita serialización implemente la interfaz. Principio de implementación de serialización :

  1. Ejecutar ObjectOutputStram#writeObjectserialización
  2. Internamente ObjectStreamClassse crearán instancias, se mantendrán serialVersionUIDvalores, writeObjectmétodos y readObjectmétodos, etc.
  3. Determinar y ejecutar writeReplacemétodos personalizados para obtener nuevos objetos para serializar.
  4. Determinar si es Serializableuna instancia y ejecutar writeOrdinaryObjectel método para escribir serialización.

Además de simplemente implementar la interfaz serializable, existen algunos campos y métodos opcionales que se pueden personalizar, como se muestra en el siguiente código:

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() {}
    }
}

Estos métodos se ObjectStreamClassmantienen y procesan para implementar el mantenimiento de la versión y la personalización de la serialización. El campo más importante es serialVersionUIDel campo, que marca el número de versión de la clase de entidad. Al deserializar, se juzga comparando el número de versión si la estructura no ha cambiado significativamente y se completa el proceso de deserialización. **Proporcionar este campo hará que la deserialización sea más confiable y controlable. ** Generalmente, no es necesario proporcionar este campo durante la transmisión de datos en tiempo real. El sistema generará automáticamente el valor hash de este tipo y lo asignará a serialVersionUID. Pero en algunas operaciones de persistencia, proporcionar este campo es una tarea más importante. Porque si no se proporciona, una vez que se cambian los atributos, hashel resultado de la clase también cambiará, lo que provocará directamente que los datos anteriores no se puedan deserializar con éxito y se arroje una excepción. El impacto en la experiencia es severo. Por lo tanto, en escenarios donde se requieren datos persistentes, **serialVersionUID** se deben proporcionar campos. El código de serialización y deserialización de Serializable es muy simple, aquí hay un ejemplo simple.

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)

El código anterior completa Serializabletodo el proceso de serialización del método. Es muy simple, solo usa ObjectOutputStreamy ObjectInputStream.

Parcelable

Después de presentar Serializable, echemos un vistazo a Parcelable. Parcelable también es una interfaz. Siempre que esta interfaz esté implementada, la serialización se puede implementar y pasar a través de intent y binder. Eche un vistazo a un uso clásico:

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)
        }
    }
}

Puede ver que existen cuatro métodos personalizados, que se explican a continuación:

  1. writeToParcel implementa la función de serialización y escribe en el paquete
  2. describeContents proporciona una descripción del contenido, casi siempre devuelve 0 y solo devuelve 1 cuando existe un descriptor de archivo.
  3. createFromParcel implementa la función de deserialización y crea objetos originales a partir de objetos serializados.
  4. newArray proporciona un contenedor de matrices

ParcelaleAmbos y Serializablepueden lograr la serialización, ¿cómo elegir? Podemos elegir en función de la diferencia entre las dos opciones. SerializableEs fácil de usar, pero la sobrecarga es relativamente alta y requiere una gran cantidad de operaciones de E/S durante la serialización y deserialización. El uso de Parcelablees un poco más complicado, pero tiene mejor rendimiento y es el método de serialización recomendado por Android. Entonces, al transferir memoria, puedes usar Parcelablela serialización, pero cuando se trata de persistencia y transmisión de red, Parcelabletambién se puede implementar, pero el uso será más complicado, por lo que se recomienda usarlo en estos dos casos Serializable. Lo anterior es la diferencia entre los dos esquemas de serialización.

Aglutinante

Binder es un método de comunicación exclusivo de Android. La capa inferior de Binder es compatible con el controlador del kernel. El archivo del controlador del dispositivo es /dev/binder. A través de este controlador, Android tiene un conjunto completo de arquitectura C/S en la capa nativa y También encapsula una arquitectura C/S en la capa Java, que se implementa en consecuencia. Intuitivamente, Binder es una clase en Android que hereda la interfaz IBinder. Binder puede realizar comunicación entre procesos o comunicación de proceso local. Cuando escribimos un servicio local LocalService que no necesita procesos cruzados, podemos obtener directamente la clase Binder para comunicarnos. Basado en Binder, Android implementa múltiples ManagerServices. Debido a que el sistema Android tiene varios componentes de hardware del sistema que deben exponerse a otros procesos y deben administrarse de manera centralizada, después de que Android implementa la solución de administración, expone los servicios de interfaz correspondientes, como pms, ams y wms, a través de Binder. . En el desarrollo de Android, la aplicación más directa para Binder por parte de los desarrolladores es usar AIDL. El proceso de uso relacionado tiene aproximadamente los siguientes pasos:

  1. Crear archivo Aidl y declarar método.
  2. Herede la clase Stub generada (subclase abstracta de Binder) e implemente métodos de interfaz relacionados para operaciones del lado del servidor.
  3. Cree un servicio que se ejecute en otro proceso y devuelva la instancia de Binder en su método onBind
  4. Utilice este Servicio ServiceConnection#onServiceConnectedpara obtener la instancia de Binder que define la interfaz a través del parámetro IBinder obtenido en el callback. Como por ejemplo IHelloManager.Stub.asInterface(service).
  5. Realice llamadas a métodos remotos a través de instancias de Binder.

AIDL (lenguaje de definición de interfaz de Android)

Primero veamos la introducción de la documentación oficial para desarrolladores de Google: podemos usar AIDL para definir una interfaz de programación reconocida tanto por el cliente como por el servicio, de modo que los dos puedan comunicarse entre sí mediante la comunicación entre procesos (IPC). En Android, escribir código para la comunicación entre procesos es más engorroso y Android utilizará AIDL para ayudarnos a solucionar este tipo de problemas. Comencemos con un ejemplo típico de AIDL para explorar.

Definir la interfaz AIDL

Definamos un archivo de interfaz Aidl.

//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;

Se utiliza el objeto Usuario, por lo que también se define arriba.Este User.aidlobjeto implementa la interfaz Parcelable. Encontramos generated/aidl_source_output_dirla clase java generada correspondiente a la observación: 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;
}

Puede ver que la interfaz está generada IHelloManagere implementada IInterface. Puede ver que de forma predeterminada se generan tres clases de implementación de esta interfaz. Default, Stuby Stub.Proxy. StubEs una Binderclase y una instancia es un objeto de servidor. Stub.ProxyEs Proxyuna clase de proxy del lado del servidor. Al ejecutar un método, se llama al método de transacción del lado del servidor para realizar la conversión interactiva de datos entre procesos. Estas dos clases de implementación son las clases IHelloManagerprincipales. Eche un vistazo al código de la clase Stub:

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;
    }
}

A continuación se presenta Stubel significado de miembros de la clase:

  • DESCRIPTOR

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

Convierta el objeto Binder en el lado del servidor en el objeto de interfaz AIDL correspondiente. Al queryLocalInterfacedistinguir los procesos, si ambos extremos están en el mismo proceso, el objeto devuelto es el objeto Stub, si están en procesos diferentes, se devuelve su objeto Proxy.

  • como carpeta

Devuelve la instancia actual de Binder

  • enTransact

Este método realiza operaciones de serialización y deserialización en los datos transmitidos. El método completo es public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags). En este método, el código se utiliza para ubicar el método solicitado por el cliente, luego los parámetros requeridos por el método se extraen de los datos y luego se ejecuta el método de destino. Si el método de destino tiene un valor de retorno, el resultado está escrito en el método reply. Si este método devuelve falso, la solicitud del cliente fallará. Podemos imponer algunas restricciones de llamadas a este método para evitar que algunos procesos no deseados llamen a este método.

  • Proxy#getFriendyProxy#setFriend

Estos dos métodos de proxy primero procesan los parámetros entrantes, los escriben Parcely luego llaman para mRemote.transactiniciar una solicitud RPC (llamada a procedimiento remoto). Al mismo tiempo, el subproceso actual se suspende hasta que regrese el proceso RPC, luego el subproceso actual continúa ejecutándose y replyse recupera el resultado devuelto. Devuelve datos después de la deserialización.

enlazarServicio

A través del análisis anterior de AIDL y su código generado, sabemos que AIDL es solo una forma de generar rápidamente el código de la plantilla de comunicación de Binder. Cuando queremos utilizar este Binder para IPC en componentes relacionados, necesitamos obtener Binderla instancia a través del servicio de enlace. El siguiente es binderel código relevante para la adquisición de servicios vinculantes:

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)
}

A partir del código anterior, podemos bindServiceobtener la instancia aidldefinida Bindera través de . A través de esta instancia de Binder, puede realizar llamadas a métodos directamente al proceso remoto. ¿Cuál es el proceso específico de vinculación de servicios? Ahora mira toda la ruta de la llamada.

  1. Iniciar servicio vinculante:mBase.bindService
  2. Localice el método de enlace específico: después de verificar Activity#attachel método, ActivityThread#performLaunchActivityel método y createBaseContextForActivityel método, sabemos mBaseque es ContextImpluna instancia.

mBase.bindServiceContextImpl#bindServiceCommonMétodo llamado

  1. Obtenga ActivityManagerel objeto proxy de Binder: en ActivityManager.``*getService*``()el método, ServiceManager.getService(Context.ACTIVITY_SERVICE)obtenga la instancia de IBinder de(BinderProxy)
  2. Los servicios de enlace se realizan a través del método de servicio de enlace ActivityManagerllamado .ActivityManagerService

Después de consultar el código fuente y buscar en Internet, descubrí que los principios de adquisición Bindery comunicación implican la implementación del código fuente de AOSP . No lo estudiaré por ahora, estudiaré el mecanismo de comunicación de carpeta en AOSP más adelante. En consecuencia, primero estudie el artículo de análisis del mecanismo Binder del jefe de Skytoby para tener una idea general. El diagrama de estructura del mecanismo Binder tomado del autor es el siguiente:binderServiceManagerBindernative C/S

Veamos a continuación cómo implementar la carpeta a mano.

Clase de implementación de Binder manuscrita

A través del análisis anterior, tenemos una comprensión general Binderdel mecanismo de funcionamiento de. Por eso tratamos de no usar Aidl y usar Binder para la comunicación de procesos. La implementación básica solo requiere escribir tres clases.

  1. Definir clase de interfaz
interface IActivityManager : IInterface {
    fun startActivity(code: Int): String?
}
  1. La clase abstracta Binder del lado del servidor completa la deserialización de datos transmitidos de forma remota y la ejecución de tareas del lado del servidor en 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. Clase de proxy de cliente (completa el trabajo de serialización y deserialización de datos, específicamente entregado al objeto proxy para su finalización)
    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 el trabajo relacionado con el lado del servidor y el lado del cliente a través de InterfaceTokenetiquetas. En uso específico, implemente ActivityManager y complete las tareas del lado del servidor. La implementación personalizada es la siguiente:

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

Después de completar la redacción, bindServicepuede vincular el servicio para obtener Binder para la comunicación entre procesos mediante el mismo método. binder?.startActivity(1)Puede obtener el resultado de la cadena correspondiente llamando en el ejemplo .

fin

Aprendí aproximadamente cómo usar Binder y también escribí a mano una ActivityManagerServiceimplementación simulada de Binder. Tiene un conocimiento general de cómo realizar la comunicación entre procesos a través de Binder. De manera similar, hay muchos ejemplos del uso de Binder para proporcionar servicios externos en el sistema Android, y Android ServiceManagerproporciona PackageManagerService WindowsManagerServiceservicios como a través de. Si comprendemos la implementación de estas capas del marco, será de gran ayuda para nuestro desarrollo. Comprender el uso de Binder es un punto de partida, y luego avanzaremos hacia niveles avanzados.

Si aún no domina Framework y desea comprenderlo a fondo en el menor tiempo posible, puede consultar "Puntos de conocimiento básicos de Android Framework" , que incluye: Init, Zygote, SystemServer, Binder, Handler, AMS, PMS, Launcher. .. ...etc.para registrar puntos de conocimiento.

"Manual de resumen de los puntos de conocimiento básicos del marco" :https://qr18.cn/AQpN4J

Parte del principio de implementación del mecanismo del controlador:
1. Análisis teórico macroscópico y análisis del código fuente del mensaje
2. Análisis del código fuente de MessageQueue
3. Análisis del código fuente de Looper
4. Análisis del código fuente del controlador
5. Resumen

Principio de Binder:
1. Puntos de conocimiento que deben comprenderse antes de aprender Binder
2. Mecanismo de Binder en ServiceManager
3. Proceso de registro de servicios del sistema
4. Proceso de inicio de ServiceManager
5. Proceso de adquisición de servicios del sistema
6. Inicialización de Java Binder
7. Proceso de registro de Java de los servicios del sistema en carpeta

Cigoto:

  1. El proceso de inicio del sistema Android y el proceso de inicio de Zygote
  2. Proceso de inicio del proceso de solicitud.

Análisis del código fuente de AMS:

  1. Gestión del ciclo de vida de la actividad.
  2. Proceso de ejecución de onActivityResult
  3. Explicación detallada de la gestión de la pila de actividades en AMS

Código fuente de PMS en profundidad:

1. Proceso de inicio y proceso de ejecución de PMS
2. Análisis del código fuente de instalación y desinstalación de APK
3. Arquitectura coincidente del filtro de intención en PMS

WMS:
1. El nacimiento de WMS
2. Miembros importantes de WMS y el proceso de agregar Window
3. El proceso de eliminar Window

"Manual de aprendizaje del marco de Android":https://qr18.cn/AQpN4J

  1. Proceso de inicio de arranque
  2. Inicie el proceso Zygote al arrancar
  3. Inicie el proceso SystemServer al arrancar
  4. conductor de carpeta
  5. Proceso de inicio de AMS
  6. Proceso de inicio del PMS
  7. Proceso de inicio del lanzador
  8. Android cuatro componentes principales
  9. Servicio del sistema Android: proceso de distribución de eventos de entrada
  10. Representación subyacente de Android: análisis del código fuente del mecanismo de actualización de pantalla
  11. Análisis del código fuente de Android en la práctica

Supongo que te gusta

Origin blog.csdn.net/maniuT/article/details/132723466
Recomendado
Clasificación