Estrutura Android —— Aprendendo sobre comunicação entre processos, começando com o uso do Binder

Prefácio

Binder é uma ferramenta de comunicação entre processos muito importante no Android, que fornece uma série de serviços para o mundo exterior por meio Binderdo Android . ServiceManagerO Learning Binder nos dará frameworkum bom começo no aprendizado.

Android usa vários processos

AndroidIniciar um processo é muito simples: basta especificar o atributo android:process AndoridMenifestpara os quatro componentes principais Activity( Service, Receiver, ) em .ContentProvider

Existe também uma forma não convencional de iniciar um processo, que é fock um novo processo na camada nativa através do jni.

No início, quando precisávamos usar multiprocessos e adicionar processatributos com entusiasmo aos componentes necessários, pensando que funcionaria normalmente, descobrimos que alguns problemas também ocorriam.

  1. Ao obter dados estáticos, você não obterá a mesma cópia.
  2. sharePreferenceA confiabilidade do compartilhamento de dados diminui.
  3. O aplicativo será criado várias vezes.
  4. O mecanismo de sincronização de threads falha completamente.

A principal razão para esses problemas é que diferentes processos são executados em diferentes máquinas virtuais e possuem espaços de memória independentes, portanto, modificações em valores estáticos por diferentes processos afetarão apenas seus próprios processos. Portanto, enquanto os dados forem compartilhados através da memória, eles falharão no multiprocesso dos quatro componentes principais, sendo este também o principal impacto do multiprocesso. Para resolver esses problemas, é necessário utilizar o método de comunicação multiprocesso fornecido pelo Android. Existem muitos métodos, podemos usar Intent, compartilhamento de arquivos, SharePreference, AIDL, Socket e Messager baseado em Binder para alcançá-lo. O Android usa o kernel Linux, mas o método de comunicação entre processos não é completamente herdado do Linux. Binder é a solução de comunicação entre processos mais exclusiva do sistema Android. Digo isso porque no Android, através do exclusivo Binder, podemos facilmente conseguir comunicação entre processos.

Índice

Aqui falamos principalmente sobre três aspectos

  1. Interface relacionada à serialização Serializable
  2. Parcelável relacionado à serialização.
  3. Fichário para comunicação entre processos

Quando queremos usar Intent e Binder para transmitir dados, precisamos usar Parcelable ou Serializable. Ou quando queremos persistir dados ou transmiti-los pela rede, também precisamos usar Serializable para completar a persistência do objeto.

Serializável

Serializable é uma interface de serialização fornecida por Java. É muito simples de usar. Você só precisa deixar a classe que precisa de serialização implementar a interface. Princípio de implementação de serialização :

  1. Executar ObjectOutputStram#writeObjectserialização
  2. Internamente ObjectStreamClass, serão criadas instâncias, serialVersionUIDvalores, writeObjectmétodos e readObjectmétodos mantidos, etc.
  3. Determine e execute writeReplacemétodos customizados para obter novos objetos a serem serializados
  4. Determine se é Serializableuma instância e execute writeOrdinaryObjecto método para escrever a serialização

Além de simplesmente implementar a interface Serializable, existem alguns campos e métodos opcionais que podem ser customizados, conforme mostrado no código a seguir:

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

Esses métodos são ObjectStreamClassmantidos e processados ​​para implementar a manutenção de versão e a personalização de serialização. O campo mais importante é serialVersionUIDo campo que marca o número da versão da classe de entidade.Ao desserializar, é avaliado comparando o número da versão se a estrutura não mudou significativamente e se o processo de desserialização foi concluído. **Fornecer este campo tornará a desserialização mais confiável e controlável. **Geralmente, este campo não precisa ser fornecido durante a transmissão de dados em tempo real. O sistema irá gerar automaticamente o valor hash deste tipo e atribuí-lo a serialVersionUID. Mas em algumas operações de persistência, fornecer este campo é uma tarefa mais importante. Porque se não for fornecido, uma vez alterados os atributos, hasho resultado da classe também será alterado, o que fará com que os dados anteriores falhem diretamente na desserialização com sucesso e gerem uma exceção. O impacto na experiência é severo. Portanto, em cenários onde são necessários dados persistentes, **serialVersionUID** os campos precisam ser fornecidos. O código de serialização e desserialização de Serializable é muito simples. Aqui está um exemplo simples.

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)

O código acima completa Serializabletodo o processo de serialização do método. É muito simples, basta usar ObjectOutputStreame ObjectInputStream.

Parcelável

Depois de apresentar o Serializable, vamos dar uma olhada no Parcelable. Parcelable também é uma interface, desde que essa interface seja implementada, a serialização pode ser implementada e passada por meio de intenção e fichário. Dê uma olhada em um uso clássico:

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

Você pode ver que existem quatro métodos personalizados, que são explicados a seguir:

  1. writeToParcel implementa a função de serialização e grava no pacote
  2. descriptionContents fornece uma descrição do conteúdo, quase sempre retorna 0 e só retorna 1 quando existe um descritor de arquivo
  3. createFromParcel implementa a função de desserialização e cria objetos originais a partir de objetos serializados.
  4. newArray fornece um contêiner de array

ParcelaleAmbos e Serializablepodem conseguir serialização, como escolher? Podemos escolher com base na diferença entre as duas opções. SerializableÉ simples de usar, mas a sobrecarga é relativamente alta, exigindo um grande número de operações de E/S durante a serialização e desserialização. O uso do Parcelableé um pouco mais complicado, mas tem melhor desempenho e é o método de serialização recomendado pelo Android. Então na hora de transferir memória você pode usar Parcelableserialização, mas quando se trata de persistência e transmissão de rede Parcelabletambém pode ser implementada, mas o uso será mais complicado, por isso é recomendado utilizá-lo nestes dois casos Serializable. O acima é a diferença entre os dois esquemas de serialização.

Encadernador

Binder é um método de comunicação proprietário do Android. A camada inferior do Binder é suportada pelo driver do kernel. O arquivo do driver do dispositivo é /dev/binder. Através deste driver, o Android tem um conjunto completo de arquitetura C/S na camada nativa e também encapsula uma arquitetura C/S na camada Java. A camada é implementada de acordo. Intuitivamente, Binder é uma classe do Android que herda a interface IBinder. O Binder pode realizar comunicação entre processos ou comunicação de processos locais. Quando escrevemos um serviço local LocalService que não precisa cruzar processos, podemos obter diretamente a classe Binder para comunicação. Com base no fichário, o Android implementa vários ManagerServices. Como o sistema Android possui vários componentes de hardware do sistema que precisam ser expostos a outros processos e gerenciados centralmente, depois que o Android implementa a solução de gerenciamento, ele expõe os serviços de interface correspondentes, como pms, ams e wms, por meio do fichário . No desenvolvimento Android, a aplicação mais direta do Binder pelos desenvolvedores é usar AIDL. O processo de uso relacionado segue aproximadamente as seguintes etapas:

  1. Crie o arquivo aidl e declare o método
  2. Herde a classe Stub gerada (subclasse abstrata do Binder) e implemente métodos de interface relacionados para operações do lado do servidor.
  3. Crie um serviço rodando em outro processo e retorne a instância do Binder em seu método onBind
  4. Utilize este Serviço ServiceConnection#onServiceConnectedpara obter a instância do Binder que define a interface através do parâmetro IBinder obtido no callback. Tal como IHelloManager.Stub.asInterface(service).
  5. Faça chamadas de métodos remotos por meio de instâncias do Binder.

AIDL (linguagem de definição de interface Android)

Vejamos primeiro a introdução da documentação oficial do desenvolvedor do Google. Podemos usar AIDL para definir uma interface de programação reconhecida tanto pelo cliente quanto pelo serviço, para que os dois possam se comunicar entre si por meio de comunicação entre processos (IPC). No Android, escrever código para comunicação entre processos é mais complicado. O Android usará AIDL para nos ajudar a lidar com esses problemas. Vamos começar com um exemplo típico de AIDL para explorar.

Definir interface AIDL

Vamos definir um arquivo de interface 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;

O objeto User é usado, portanto também está definido acima User.aidl. Este objeto implementa a interface Parcelable. Encontramos generated/aidl_source_output_dira classe java gerada correspondente à observação: 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;
}

Você pode ver que a interface é gerada IHelloManagere implementada IInterface. Você pode ver que três classes de implementação desta interface são geradas por padrão. Default, Stube Stub.Proxy. StubÉ uma Binderclasse e uma instância é um objeto de servidor. Stub.ProxyÉ Proxyuma classe proxy do lado do servidor. Ao executar um método, o método transact do lado do servidor é chamado para realizar a conversão interativa de dados entre processos. Essas duas classes de implementação são as classes IHelloManagerprincipais. Dê uma olhada no código da classe 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;
    }
}

O seguinte apresenta Stubo significado dos membros da classe:

  • DESCRITOR

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

Converta o objeto Binder no lado do servidor no objeto de interface AIDL correspondente. Ao queryLocalInterfacedistinguir os processos, se ambas as extremidades estiverem no mesmo processo, o objeto retornado é o objeto Stub. Se estiverem em processos diferentes, seu objeto Proxy é retornado.

  • comoBinder

Retorna a instância atual do Binder

  • onTransact

Este método executa operações de serialização e desserialização nos dados transmitidos. O método completo é public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags). Neste método, o código é usado para localizar o método solicitado pelo cliente, em seguida, os parâmetros exigidos pelo método são retirados dos dados e, em seguida, o método de destino é executado. Se o método de destino tiver um valor de retorno, o resultado é escrito no método reply. Se este método retornar falso, a solicitação do cliente falhará. Podemos colocar algumas restrições de chamada neste método para evitar que alguns processos indesejados chamem este método.

  • Proxy#getFriendeProxy#setFriend

Esses dois métodos de proxy primeiro processam os parâmetros recebidos, gravam-nos Parcele depois chamam para mRemote.transactiniciar uma solicitação RPC (chamada de procedimento remoto). Ao mesmo tempo, o thread atual é suspenso até que o processo RPC retorne e, em seguida, o thread atual continua a ser executado e o replyresultado retornado é recuperado. Retornar dados após a desserialização.

bindService

Por meio da análise acima do AIDL e de seu código gerado, sabemos que o AIDL é apenas uma maneira de gerarmos rapidamente o código do modelo de comunicação do Binder. Quando quisermos utilizar este Binder para IPC em componentes relacionados, precisamos obter Bindera instância através do serviço de vinculação. A seguir está bindero código relevante para aquisição de serviço vinculativo:

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 do código acima, podemos bindServiceobter a instância aidldefinida Binderpor meio de . Através desta instância do Binder, você pode fazer chamadas de método diretamente para o processo remoto. Qual é o processo específico de vinculação de serviços? Agora observe todo o caminho da chamada

  1. Iniciar serviço de vinculação:mBase.bindService
  2. Localize o método de ligação específico: Após verificar Activity#attacho método, ActivityThread#performLaunchActivitymétodo e createBaseContextForActivitymétodo, sabemos mBaseque é ContextImpluma instância.

mBase.bindServiceContextImpl#bindServiceCommonMétodo chamado

  1. Obtenha ActivityManagero objeto proxy Binder: No ActivityManager.``*getService*``()método, ServiceManager.getService(Context.ACTIVITY_SERVICE)obtenha a instância IBinder de(BinderProxy)
  2. Os serviços de vinculação são executados por meio do método de serviço de vinculação ActivityManagerchamado .ActivityManagerService

Depois de consultar o código-fonte e pesquisar na Internet, descobri que os princípios de aquisição Bindere comunicação envolvem a implementação do código-fonte AOSP . Não vou estudá-lo por enquanto, estudarei o mecanismo de comunicação do binder no AOSP mais tarde. Da mesma forma, primeiro estude o artigo de análise do mecanismo Binder do chefe Skytoby para ter uma ideia geral. O diagrama estrutural do mecanismo Binder emprestado do autor é o seguinte:binderServiceManagerBindernative C/S

Vamos ver a seguir como implementar o fichário manualmente.

Classe de implementação do Binder manuscrito

Através da análise acima, temos uma compreensão geral Binderdo mecanismo de funcionamento do . Portanto, tentamos não usar aidl e usar binder para comunicação de processos. A implementação básica requer apenas a escrita de três classes

  1. Definir classe de interface
interface IActivityManager : IInterface {
    fun startActivity(code: Int): String?
}
  1. A classe abstrata Binder do lado do servidor completa a desserialização de dados transmitidos remotamente e a execução de tarefas no lado do servidor em 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. Classe proxy do cliente (conclui o trabalho de serialização e desserialização dos dados, entregue especificamente ao objeto proxy para conclusão)
    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
        }
    }

Conclua o trabalho relacionado ao lado do servidor e do lado do cliente por meio InterfaceTokende tags. Em uso específico, implemente o ActivityManager e conclua as tarefas do lado do servidor. A implementação personalizada é a seguinte:

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

Depois de concluir a escrita, bindServicevocê pode vincular o serviço para obter o Binder para comunicação entre processos através do mesmo método. binder?.startActivity(1)Você pode obter o resultado da string correspondente chamando o exemplo .

fim

Aprendi aproximadamente como usar o Binder e também escrevi à mão uma ActivityManagerServiceimplementação simulada do Binder. Você tem uma compreensão geral de como realizar a comunicação entre processos por meio do Binder. Da mesma forma, existem muitos exemplos de uso do Binder para fornecer serviços externos no sistema Android. O Android ServiceManagerfornece PackageManagerService WindowsManagerServiceserviços como through. Se compreendermos a implementação destas camadas de estrutura, será muito útil para o nosso desenvolvimento. Compreender o uso do Binder é um ponto de partida e depois avançaremos para níveis avançados.

Se você ainda não domina o Framework e deseja entendê-lo completamente no menor tempo possível, consulte "Pontos de conhecimento básicos do Android Framework" , que inclui: Init, Zygote, SystemServer, Binder, Handler, AMS, PMS, Launcher. .. ...etc. para registrar pontos de conhecimento.

"Manual de resumo dos pontos de conhecimento básicos da estrutura" :https://qr18.cn/AQpN4J

Parte do princípio de implementação do mecanismo do manipulador:
1. Análise teórica macroscópica e análise do código-fonte da mensagem
2. Análise do código-fonte do MessageQueue
3. Análise do código-fonte do Looper
4. Análise do código-fonte do manipulador
5. Resumo

Princípio do Binder:
1. Pontos de conhecimento que devem ser entendidos antes de aprender o Binder
2. Mecanismo de Binder no ServiceManager
3. Processo de registro de serviço do sistema
4. Processo de inicialização do ServiceManager
5. Processo de aquisição de serviço do sistema
6. Inicialização do Java Binder
7. Processo de registro Java de serviços do sistema no fichário

Zigoto:

  1. O processo de inicialização do sistema Android e o processo de inicialização do Zygote
  2. Processo de inicialização do processo de inscrição

Análise de código-fonte AMS:

  1. Gerenciamento do ciclo de vida da atividade
  2. processo de execução onActivityResult
  3. Explicação detalhada do gerenciamento da pilha de atividades no AMS

Código fonte detalhado do PMS:

1. Processo de inicialização e processo de execução do PMS
2. Análise do código-fonte de instalação e desinstalação do APK
3. Arquitetura correspondente do filtro de intenção no PMS

WMS:
1. O nascimento do WMS
2. Membros importantes do WMS e o processo de adição da janela
3. O processo de exclusão da janela

"Manual de aprendizagem da estrutura Android":https://qr18.cn/AQpN4J

  1. Processo de inicialização
  2. Inicie o processo Zygote na inicialização
  3. Inicie o processo SystemServer na inicialização
  4. Driver de fichário
  5. Processo de inicialização do AMS
  6. Processo de inicialização do PMS
  7. Processo de inicialização do iniciador
  8. Quatro componentes principais do Android
  9. Processo de distribuição de eventos de entrada de serviço do sistema Android
  10. Renderização subjacente do Android - análise do código-fonte do mecanismo de atualização de tela
  11. Análise de código-fonte Android na prática

Acho que você gosta

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