Arquitetura com componentes Android

Primeiro idioma

O tempo está passando, já é março de 2021 e estou cheio de ânimo. Continue trabalhando duro no novo ano.

Introdução

No desenvolvimento do projeto, o código comum é extraído em common_module, algumas funções individuais são encapsuladas em lib_module e os módulos são divididos de acordo com o negócio e os membros da equipe desenvolvem seus próprios módulos.
Porém, com a iteração do projeto, mais e mais funções, após adicionar alguns módulos de negócios, a situação de chamadas mútuas aumentará, e o acoplamento entre os módulos de negócios será muito sério, resultando em código difícil de manter e o a escalabilidade é pobre. Nasce a componentização.
Base de componentes: divisão multi-módulo de negócios e funções básicas.
Componentes: Um único componente funcional, como adaptação, pagamento, componentes de roteamento, etc., pode ser extraído separadamente para formar um SDK.
Módulo: Módulo de negócios independente, como transmissão ao vivo, módulo de página inicial, etc. O módulo pode conter muitos componentes diferentes.
Arquitetura básica de componentes

Recursos

  1. Evite recriar rodas, economizando custos de desenvolvimento e manutenção.
  2. Organize a mão de obra razoavelmente por meio de componentes e módulos para melhorar a eficiência do desenvolvimento.
  3. Diferentes projetos compartilham um componente ou módulo para garantir a unidade das soluções técnicas.
  4. No futuro, o plug-in será preparado para um conjunto comum de modelos subjacentes.

Programação de componentes

Aplicativo Componentizado

Se o módulo de função tem um aplicativo, e o módulo principal não tem um aplicativo personalizado, ele faz referência naturalmente ao aplicativo do módulo de função.
Se o módulo de função tiver dois aplicativos personalizados, ocorrerão erros de compilação e os conflitos precisarão ser resolvidos. Você pode usar a tools:replace="android:name"solução, porque a compilação do aplicativo permitirá que apenas um aplicativo seja declarado no final.

Comunicação entre componentes

Os módulos do componente são independentes uns dos outros, não há dependência e nenhuma informação pode ser transmitida sem dependência. Neste momento, você precisa contar com a camada básica (CommonModule), todos os módulos da camada de componentes dependem do CommonModule, que é a base para a troca de informações entre os módulos.
A transmissão de informações de atividades, fragmentos e serviços no Android é mais complicada e demorada e insegura para realizar a transmissão de mensagens na forma de difusão, resultando em um mecanismo de barramento de eventos. É uma implementação do modelo publicar-assinar. É um mecanismo de processamento de eventos centralizado que permite que diferentes componentes se comuniquem entre si sem interdependência, alcançando um propósito de desacoplamento.

Estrutura de ônibus de terceiros
  • EventBus
    EventBus é um barramento de mensagem de publicação / assinatura otimizado no lado Android, o que simplifica a comunicação entre componentes no aplicativo e entre componentes e threads de fundo. Para uso específico, consulte meu blog: EventBus do Android Event Bus .
  • RxBus
    RxBus é um modo de comunicação entre componentes derivados da programação responsiva RxJava. Atualmente, as solicitações de rede de desenvolvimento de projeto são implementadas usando a estrutura Retofit + RxJava. Para métodos de uso específicos, consulte meu blog: Uso de Android RxJava ; Retrofit .
Comparado

Em termos de escalonamento de encadeamento, o escalonamento de encadeamento do RxJava é mais excelente e é melhor do que o Eventbus para escrever código através de uma variedade de operadores, tipo de cadeia, mas como não usa o mecanismo de reflexão, a eficiência operacional é menor do que EventBus.

Resumindo

No desenvolvimento real do projeto, os eventos de comunicação devem ser colocados em CommonModule, e CommonModule também precisa contar com a estrutura de barramento. No entanto, quando diferentes módulos são adicionados ou excluídos, os modelos de mensagem precisam ser adicionados ou excluídos, o que torna toda a arquitetura do barramento de eventos muito inchada e complicada, o que viola o princípio da componentização. A solução é extrair um módulo de barramento de evento, CommonModule depende deste módulo, o modelo de mensagem está no módulo de barramento de evento.

Salte entre os componentes

Na componentização, os dois módulos funcionais não são diretamente dependentes, mas são indiretamente dependentes por meio do CommonModule. Geralmente, uma atividade pula para outra atividade e startActivity é usado para enviar um intent, mas a atividade de outros módulos não pode ser referenciada. O salto pode ser realizado por meio da ação implícita. Deve-se observar que ao remover o módulo, o salto também deve ser removido, caso contrário ocorrerá um crash.

Rota de salto roteador

Ação implícita não é a melhor maneira de pular, um roteador aparece neste momento.
ARouter é uma estrutura de roteamento de código aberto desenvolvida pela equipe técnica do Android do Alibaba para ajudar a componentizar aplicativos Android e oferece suporte a roteamento, comunicação e desacoplamento entre módulos.
Endereço do Github: https://github.com/alibaba/ARouter

  • Use a
    primeira adição de dependência em CommonModule:
    implementation 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'

Em seguida, o annotationProcessor usará a configuração javaCompileOptions para obter o nome do módulo atual e adicioná-lo à propriedade defaultConfig do build.gradle de cada módulo:

android {
    
    
    defaultConfig {
    
    
        ...
        javaCompileOptions {
    
    
            annotationProcessorOptions {
    
    
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

O atributo dependencies de cada módulo precisa ser referenciado pelo ARouter apt, caso contrário, o arquivo de índice não pode ser gerado no apt, e o salto não pode ser bem sucedido.

dependencies {
    
    
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
}

Inicializar no aplicativo:

if (isDebug()) {
    
               
    ARouter.openLog();   
    ARouter.openDebug();   
}
ARouter.init(mApplication); 

Veja a atividade de salto como um exemplo. Primeiro, você precisa adicionar a anotação Rota à atividade de salto, e o caminho é o caminho

@Route(path = RouterPath.LOGIN_PAGE)
public class LoginActivity extends BaseActivity<ActivityLoginBinding> {
    
    }
//路由跳转尽量统一管理,可以module路径命名。
  String SURROUNDING_PAGE = "/surrounding/ui";
  String TRAVEL_PAGE = "/travel/ui";
  String CITY_SERVICE_PAGE = "/city_service/ui";

Quando você precisa pular para a atividade, use o seguinte, o parâmetro de construção é o caminho da atividade de pulo.

ARouter.getInstance().build(RouterPath.LOGIN_PAGE).navigation();

Para uso específico, consulte a documentação oficial chinesa: https://github.com/alibaba/ARouter/blob/master/README_CN.md

Armazenamento de componentes

Existem cinco métodos de armazenamento Android nativos, que também são completamente universais na componentização.
O banco de dados popular em componentização é o Room no conjunto Jetpack. Ele completa operações como criação, adição, exclusão, modificação e verificação do banco de dados na forma de anotações. Simples e eficiente de usar.
No projeto componentizado, o desacoplamento é levado em consideração, e a camada do banco de dados é separada em um módulo.As operações no banco de dados estão todas neste módulo e dependem do CommonModule.

Gerenciamento de direitos de componentes

No AndroidManifest.xml de cada módulo, podemos ver o aplicativo de permissão de cada módulo, que eventualmente será mesclado no arquivo raiz AndroidManifest.xml.
No desenvolvimento do componente, colocamos as permissões de nível normal no CommonModule e aplicamos as permissões de nível perigoso em cada módulo separadamente. A vantagem disso é remover as permissões de nível perigoso ao adicionar ou remover um módulo. Desacoplamento máximo.

Estrutura de permissão dinâmica

RxPermission é uma estrutura de aplicativo de permissão dinâmica Android baseada em RxJava.
Endereço do Github: https://github.com/tjianssbruyelle/RxPermissions .

    public void initPermissions(String[] permissions, PermissionResult permissionResult) {
    
    
        if (rxPermissions == null) {
    
    
            rxPermissions = new RxPermissions(this);
        }
        rxPermissions.requestEachCombined(permissions)
                .subscribe(permission -> {
    
    
                    if (permission.granted) {
    
    
                        permissionResult.onSuccess();
                    } else if (permission.shouldShowRequestPermissionRationale) {
    
    
                        permissionResult.onFailure();
                    } else {
    
    
                        permissionResult.onFailureWithNeverAsk();
                    }
                });
    }

A combinação de RxPermission e RxJava é muito simplificada, simples e prática.

Conflito de recursos com componentes
Conflito do AndroidMainfest

O atributo app: name do Application é referenciado em AndroidMainfest e tools: replace = "android: name" é usado para declarar que o Application é substituível em caso de conflito.

Conflito de pacotes

Quando ocorre um conflito de pacote, use o comando gradle dependencies para ver a árvore do diretório de dependências.As dependências marcadas com * indicam que as dependências são ignoradas. Como há outras dependências de nível superior que também dependem dessa dependência, você pode usar exclude para excluir a dependência, por exemplo:

 androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2', {
    
    
        exclude group: 'com.android.support', module: 'support-annotations'
    }
Conflito de nome de recurso

No desenvolvimento de vários módulos, não há garantia de que os nomes de todos os recursos em vários módulos sejam diferentes. A regra para selecionar o mesmo nome de recurso é que o módulo pós-compilado sobrescreverá o conteúdo no campo de recurso do módulo compilado anteriormente O mesmo causará erros de referência de recursos. Existem duas soluções: a
primeira: renomear recursos quando ocorrerem conflitos.
O segundo: mecanismo de prompt de nomenclatura do gradle, usando o campo resourcePrefix:

android {
    
    
	resourcePrefix "组件名_"
}

Todos os nomes de recursos devem ser prefixados com a string especificada, caso contrário, um erro será relatado, mas resourcePrefix não pode limitar os recursos de imagem. Para recursos de imagem, você precisa modificar manualmente o nome do recurso.

Ofuscação de componentes

O Android Studio usa ProGuard para ofuscação, que é uma ferramenta para compactar, otimizar e ofuscar arquivos de bytecode Java. Ele pode excluir classes e comentários inúteis e otimizar arquivos de bytecode ao máximo.
A ofuscação excluirá recursos inúteis do projeto e reduzirá efetivamente o tamanho do pacote de instalação do apk.
A confusão aumenta a dificuldade da engenharia reversa e a torna mais segura.
A confusão tem quatro operações: Encolhimento, Otimização, Ofuscação e Pré-verificação.

  buildTypes {
    
    
        release {
    
    
        //是否打卡混淆
            minifyEnabled false
            //是否打开资源压缩
            shrinkResources true
            设置proguard的规则路径
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

Depois que cada módulo é criado, ele vem com um arquivo de ofuscação customizado de proguard-rules.pro, e cada módulo pode ter suas próprias regras de ofuscação.
Na componentização, se cada módulo usar sua própria ofuscação, ocorrerá uma ofuscação repetida, causando o problema de não ser possível consultar os arquivos de recursos. Precisamos garantir que apenas uma ofuscação seja necessária quando o apk for gerado.
Solução: coloque a ofuscação de biblioteca de terceiros fixa no CommonModule e coloque a ofuscação de biblioteca de referência exclusiva de cada módulo em seu próprio proguard-rules.pro e, finalmente, coloque os atributos básicos do Android na instrução de ofuscação proguard-rules.pro do aplicativo, como como quatro componentes principais e configuração de ofuscação global. O trabalho de confusão e dissociação pode ser concluído ao máximo.

Multicanal Componentizado

Quando o terminal de usuário e o terminal de gerenciamento precisam ser gerados no desenvolvimento do projeto, ou algumas versões não exigem pagamento, compartilhamento, etc., não precisamos incorporar esses módulos e, ao mesmo tempo, o volume de negócios e a capacidade do pacote pode ser reduzido.
Quando precisamos exportar vários apps, os custos de manutenção e desenvolvimento aumentam, como reduzir os custos de desenvolvimento e entender o acoplamento, precisamos usar vários canais. Por exemplo:

productFlavors {
    
    
        phone {
    
    
            applicationId "com.zdww.enjoyluoyang"
            manifestPlaceholders = [name:"用户端",icon:"@mipmap/logo"]
        }
        terminal {
    
    
            applicationId "com.zdww.enjoyluoyang_terminal"
            versionCode 2
            versionName "1.1.0"
            manifestPlaceholders = [name:"管理端",icon:"@mipmap/logo"]
        }
    }
     phoneImplementation project(path: ":module_my")

Configuramos vários canais por meio de productFlavors e manifestPlaceholders definimos diferentes atributos de diferentes canais. Esses atributos só podem ser usados ​​quando declarados em AndroidMainfest. A configuração xxxImplementation pode configurar módulos que precisam ser referenciados por canais diferentes. Você pode encontrar as variantes de compilação na barra lateral esquerda do Android Studio para escolher uma variante de compilação ativa diferente.
Para diferentes canais que precisam introduzir novas classes ou arquivos, você pode criar diferentes pastas de canais no diretório do projeto, colocar os arquivos neles e usá-los para cada um.
multicanal

Otimização do Gradle

O Gradle é essencialmente uma ferramenta de compilação automatizada que declara as configurações do projeto com base na linguagem de domínio específico (DSL) do Groovy. Quando o Android Studio cria o projeto, ele usa o plug-in escrito pelo gradle para carregar a configuração do projeto e compilar os arquivos.
Na componentização, cada módulo tem um arquivo build.gradle, e o arquivo build.gradle de cada módulo tem alguns atributos obrigatórios. O mesmo projeto Android requer que esses atributos sejam consistentes em módulos diferentes, como compileSdkVersion, etc., se as referências são inconsistentes. Os atributos não serão mesclados e introduzidos no projeto, o que causará a duplicação de recursos e reduzirá a eficiência da compilação.
Deve haver uma configuração Gradle unificada e básica, criar um arquivo version.gradle, escrever algumas variáveis ​​e adicioná-las ao script de compilação do build.gradle do projeto

apply from :"versions.gradle"

Semelhante à maneira de referenciar variáveis ​​estáticas para se referir a propriedades, você também pode configurar o warehouse usado pelo projeto em version.gradle de maneira unificada. Basta adicioná-lo em project.gradle.

ext.deps = [:]

def versions = [:]
versions.gradle = "4.0.1"
versions.appcompat = "1.2.0"
versions.constraintlayout = "2.0.4"
versions.junit = "4.12"
versions.ext_junit = "1.1.2"
versions.espresso_core = "3.3.0"
versions.multidex = "1.0.3"
def build_versions = [:]

build_versions.compileSdk = 29
build_versions.minSdk = 19
build_versions.targetSdk = 29
build_versions.versionCode = 11
build_versions.versionName = "1.4.5"
build_versions.application_id = "com.example.yhj"
build_versions.gradle = "com.android.tools.build:gradle:$versions.gradle"
ext.build_versions = build_versions

def view = [:]
view.constraintlayout = "androidx.constraintlayout:constraintlayout:$versions.constraintlayout"
view.recyclerview = "androidx.recyclerview:recyclerview:$versions.recyclerview"
view.glide = "com.github.bumptech.glide:glide:$versions.glide"
view.glide_compiler = "com.github.bumptech.glide:compiler:$versions.glide_compiler"
view.circleimageview = "de.hdodenhof:circleimageview:$versions.circleimageview"
view.gif_drawable = "pl.droidsonroids.gif:android-gif-drawable:$versions.gif_drawable"
view.material = "com.google.android.material:material:$versions.material"
deps.view = view

def addRepos(RepositoryHandler handler) {
    
    
    handler.google()
    handler.jcenter()
    handler.flatDir {
    
     dirs project(':lib_common').file('libs') }
    handler.maven {
    
     url "https://jitpack.io" }
}

ext.addRepos = this.&addRepos

Então, só precisamos usá-lo assim no build.gradle do módulo.

android {
    
    
    compileSdkVersion build_versions.compileSdk

    defaultConfig {
    
    
        minSdkVersion build_versions.minSdk
        targetSdkVersion build_versions.targetSdk
        versionCode build_versions.versionCode
        versionName build_versions.versionName
        
	api deps.android.appcompat
    api deps.view.constraintlayout
    //glide
    api deps.view.glide
    annotationProcessor deps.view.glide_compiler

Dessa forma, a configuração da variável de parâmetro é unificada para que o projeto não faça referência a várias versões diferentes da biblioteca de ferramentas do Android, e a configuração é unificada para evitar o aumento da capacidade do apk.

Otimização de depuração

A componentização oferece suporte para iniciar um único módulo como um aplicativo e, em seguida, usá-lo para depuração e teste, garantindo que os módulos individuais possam ser depurados separadamente.
Precisa mudar:

apply plugin: 'com.android.library'——>apply plugin: 'com.android.application'

Crie uma pasta de depuração em src. A pasta de depuração é usada para colocar os arquivos AndroidMainfest.xml, arquivos java, arquivos res, etc. necessários para depuração, e a atividade de inicialização padrão precisa ser definida.
Podemos definir uma variável isModule como a mudança de desenvolvimento integrado e modo de desenvolvimento de componente, que pode ser julgada no build.gradle do módulo da seguinte maneira:

if (isModule.toBoolean()) {
    
    
    apply plugin: 'com.android.application'
} else {
    
    
    apply plugin: 'com.android.library'
}

Ao mesmo tempo, todos os arquivos na pasta de depuração precisam ser excluídos no modo de desenvolvimento integrado.

sourceSets {
    
    
        main {
    
    
            if (isModule.toBoolean()) {
    
    
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
    
    
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //集成开发模式下排除debug文件夹中的所有文件
                java {
    
    
                        exclude 'debug/**'
                }
            }
        }
    }

O build.gradle do aplicativo original precisa remover as dependências do módulo que foram depuradas separadamente.

dependencies {
    
    
    if (!isModule.toBoolean()) {
    
    
       implementation project(path: ':module_my')
    }
}

Resumindo

A prática de componentização no projeto Android pode melhorar a reutilização e reduzir o acoplamento. Este artigo resume principalmente os cenários de uso comum de componentização no projeto, e mais cenários relacionados serão resumidos no desenvolvimento do projeto.

Acho que você gosta

Origin blog.csdn.net/yang_study_first/article/details/111658088
Recomendado
Clasificación