Androidのコンポーネント化されたアーキテクチャ

第一言語

時は流れ、もう2021年3月で、元気いっぱいです。新年も頑張ってください。

前書き

プロジェクト開発では、共通コードがcommon_moduleに抽出され、いくつかの個別の関数がlib_moduleにカプセル化され、モジュールがビジネスに応じて分割され、チームメンバーが独自のモジュールを開発します。
ただし、プロジェクトの反復により、いくつかのビジネスモジュールを追加した後、ますます多くの機能が相互呼び出しの状況が増加し、ビジネスモジュール間の結合が非常に深刻になり、コードの保守が困難になり、スケーラビリティが不十分です。コンポーネント化が生まれます。
コンポーネントベース:ビジネスおよび基本機能のマルチモジュール分割。
コンポーネント:アダプテーション、支払い、ルーティングコンポーネントなどの単一の機能コンポーネントを個別に抽出して、SDKを形成できます。
モジュール:ライブブロードキャスト、ホームページモジュールなどの独立したビジネスモジュール。モジュールには、さまざまなコンポーネントが含まれている場合があります。
基本的なコンポーネント化されたアーキテクチャ

特徴

  1. ホイールの再作成を避け、開発とメンテナンスのコストを節約します。
  2. 開発効率を向上させるために、コンポーネントとモジュールを通じて人員を合理的に配置します。
  3. さまざまなプロジェクトが1つのコンポーネントまたはモジュールを共有して、技術ソリューションの統合を保証します。
  4. 将来的には、プラグインは基礎となるモデルの共通セット用に準備される予定です。

コンポーネントプログラミング

コンポーネント化されたアプリケーション

汎用モジュールにアプリケーションがあり、メインモジュールにカスタムアプリケーションがない場合は、当然、汎用モジュールのアプリケーションを参照します。
汎用モジュールに2つのカスタムアプリケーションがある場合、コンパイルエラーが発生し、競合を解決する必要があります。tools:replace="android:name"アプリのコンパイルでは、最終的に1つのアプリケーションしか宣言できないため、このソリューションを使用できます

コンポーネント間の通信

コンポーネント内のモジュールは互いに独立しており、依存関係はなく、依存関係なしに情報を送信することはできません。このとき、基本レイヤー(CommonModule)に依存する必要があります。コンポーネントレイヤーのモジュールはすべて、モジュール間の情報交換の基礎となるCommonModuleに依存します。
Androidでのアクティビティ、フラグメント、およびサービスの情報送信はより複雑であり、ブロードキャストの形式でメッセージ送信を実現するには時間がかかり、安全ではないため、イベントバスメカニズムが発生します。これは、パブリッシュ/サブスクライブモデルの実装です。これは、さまざまなコンポーネントが相互依存することなく相互に通信できるようにする一元化されたイベント処理メカニズムであり、分離の目的を達成します。

サードパーティのバスフレームワーク
  • EventBus
    EventBusは、Android側で最適化されたパブリッシュ/サブスクライブメッセージバスであり、アプリケーション内のコンポーネント間、およびコンポーネントとバックグラウンドスレッド間の通信を簡素化します。具体的な使用法については、私のブログ「Android EventBusのEventBus」を参照してください
  • RxBus
    RxBusはRxJava応答プログラミング由来の成分間の通信モードである現在では、プロジェクト開発のネットワーク要求はRetofit + RxJavaフレームワークを使用して実装されている特定の使用方法については、私のブログを参照してください。。。アンドロイドRxJavaの使用;レトロフィット
比較

スレッドスケジューリングに関しては、RxJavaのスレッドスケジューリングの方が優れており、さまざまな演算子、チェーンタイプを使用してコードを記述する方が、Eventbusよりも優れていますが、リフレクションメカニズムを使用しないため、運用効率はEventBusよりも低くなります。

総括する

実際のプロジェクト開発では、通信イベントをCommonModuleに配置する必要があり、CommonModuleもバスフレームワークに依存する必要があります。ただし、異なるモジュールを追加または削除する場合は、メッセージモデルを追加または削除する必要があります。これにより、イベントバスのアーキテクチャ全体が非常に肥大化し、複雑になり、コンポーネント化の原則に違反します。解決策は、イベントバスモジュールを抽出することです。CommonModuleはこのモジュールに依存し、メッセージモデルはイベントバスモジュールにあります。

コンポーネント間をジャンプする

コンポーネント化では、2つの機能モジュールは直接依存していませんが、CommonModuleを介して間接的に依存しています。通常、アクティビティは別のアクティビティにジャンプし、startActivityを使用してインテントを送信しますが、他のモジュールのアクティビティを参照することはできません。ジャンプは暗黙のアクションによって実現できます。モジュールを取り外すときは、ジャンプも削除する必要があることに注意してください。そうしないと、クラッシュが発生します。

ARouterルートジャンプ

暗黙のアクションはジャンプするための最良の方法ではありません。現時点ではARouterが表示されます。
ARouterは、Androidアプリのコンポーネント化を支援するためにAlibabaのAndroid技術チームによって開発されたオープンソースのルーティングフレームワークであり、モジュール間のルーティング、通信、およびデカップリングをサポートします。
Githubアドレス:https//github.com/alibaba/ARouter


  • CommonModuleで最初に依存関係を追加するを使用します。
    implementation 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'

次に、annotationProcessorはjavaCompileOptions構成を使用して現在のモジュールの名前を取得し、それを各モジュールのbuild.gradleのdefaultConfigプロパティに追加します。

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

各モジュールの依存関係属性は、ARouter aptによって参照される必要があります。そうしないと、インデックスファイルをaptで生成できず、ジャンプを成功させることができません。

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

アプリケーションで初期化:

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

例としてアクティビティジャンプを取り上げます。最初に、ジャンプアクティビティに注釈ルートを追加する必要があります。パスはパスです。

@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";

アクティビティにジャンプする必要がある場合は、以下を使用します。ビルドパラメータはジャンプアクティビティパスです。

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

具体的な使用法については、中国語の公式ドキュメントを参照してください:https//github.com/alibaba/ARouter/blob/master/README_CN.md

コンポーネント化されたストレージ

5つのネイティブAndroidストレージ方法があり、これらもコンポーネント化において完全に普遍的です。
コンポーネント化で人気のあるデータベースは、JetpackスイートのRoomです。データベースの作成、追加、削除、変更、チェックなどの操作をアノテーション形式で完了します。シンプルで効率的に使用できます。
コンポーネント化された設計では、デカップリングが考慮され、データベース層がモジュールに分離されます。データベースに対する操作はすべてこのモジュール内にあり、CommonModuleに依存します。

コンポーネント化された権利管理

各モジュールのAndroidManifest.xmlで、各モジュールの権限アプリケーションを確認できます。これは、最終的にルートAndroidManifest.xmlファイルにマージされます。
コンポーネント開発では、CommonModuleに通常レベルの権限を設定し、各モジュールに個別に危険レベルの権限を適用します。これの利点は、モジュールを追加または削除するときに危険レベルの権限を削除することです。最大のデカップリング。

動的許可フレームワーク

RxPermissionは、RxJavaに基づくAndroid動的権限アプリケーションフレームワークです。
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();
                    }
                });
    }

RxPermissionとRxJavaの組み合わせは、非常に合理化され、シンプルで実用的です。

コンポーネント化されたリソースの競合
AndroidMainfestの競合

アプリケーションのapp:name属性はAndroidMainfestで参照され、tools:replace = "android:name"は、競合が発生した場合にアプリケーションが置き換え可能であることを宣言するために使用されます。

パケットの競合

パッケージの競合が発生した場合は、gradledependenciesコマンドを使用して依存関係ディレクトリツリーを表示します。*でマークされた依存関係は、依存関係が無視されることを示します。この依存関係にも依存する他のトップレベルの依存関係があるため、excludeを使用して依存関係を除外できます。次に例を示します。

 androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2', {
    
    
        exclude group: 'com.android.support', module: 'support-annotations'
    }
リソース名の競合

マルチモジュール開発では、複数のモジュール内のすべてのリソースの名前が異なるという保証はありません。同じリソース名を選択するためのルールは、コンパイル後のモジュールが以前にコンパイルされたモジュールのリソースフィールドのコンテンツを上書きすることです。 。同じことがリソース参照エラーを引き起こします。問題。2つの解決策があります。1つ
目は、競合が発生したときにリソースの名前を変更することです。
2番目:resourcePrefixフィールドを使用したgradleの命名プロンプトメカニズム:

android {
    
    
	resourcePrefix "组件名_"
}

すべてのリソース名には、指定した文字列をプレフィックスとして付ける必要があります。そうしないと、エラーが報告されますが、resourcePrefixは画像リソースを制限できません。画像リソースの場合、リソース名を手動で変更する必要があります。

コンポーネント化された難読化

Android Studioは、Javaバイトコードファイルを圧縮、最適化、難読化するためのツールである難読化にProGuardを使用しています。これにより、不要なクラスやコメントを削除し、バイトコードファイルを最大限に最適化できます。
難読化により、プロジェクトの不要なリソースが削除され、apkインストールパッケージのサイズが効果的に削減されます。
混乱はリバースエンジニアリングの難しさを増し、それをより安全にします。
混乱には、縮小、最適化、難読化、事前検証の4つの操作があります。

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

各モジュールが作成されると、proguard-rules.proのカスタム難読化ファイルが付属し、各モジュールに独自の難読化ルールを設定できます。
コンポーネント化では、各モジュールが独自の難読化を使用すると、難読化が繰り返され、リソースファイルをクエリできないという問題が発生します。apkの生成時に、難読化が1つだけ必要であることを確認する必要があります。
解決策:修正されたサードパーティライブラリの難読化をCommonModuleに配置し、各モジュールの一意の参照ライブラリの難読化を独自のproguard-rules.proに配置し、最後にAndroidの基本属性をアプリのproguard-rules.pro難読化ステートメントに配置します。 4つの主要なコンポーネントとグローバルな難読化構成として。混乱とデカップリングの作業は最大限に完了することができます。

コンポーネント化されたマルチチャネル

プロジェクト開発でユーザー端末や管理端末を生成する必要がある場合や、一部のバージョンで支払いや共有などが不要な場合は、これらのモジュールを組み込む必要がなく、同時に取引量とパッケージ容量も必要ありません。減らすことができます。
複数のアプリをエクスポートする必要がある場合、メンテナンスと開発のコストが増加します。開発コストを削減し、結合を理解するには、複数のチャネルを使用する必要があります。例えば:

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

productFlavorsを介して複数のチャネルを設定し、manifestPlaceholdersは異なるチャネルの異なる属性を設定します。これらの属性はAndroidMainfestで宣言された場合にのみ使用できます。xxxImplementationを設定すると、異なるチャネルで参照する必要のあるモジュールを構成できます。Android Studioの左側のサイドバーにビルドバリアントがあり、さまざまなアクティブビルドバリアントを選択できます。
新しいクラスまたはファイルを導入する必要があるさまざまなチャネルについて、プロジェクトディレクトリの下にさまざまなチャネルフォルダを作成し、それらにファイルを配置して、それぞれに使用できます。
マルチチャネル

Gradleの最適化

Gradleは基本的に、Groovyのドメイン固有言語(DSL)に基づいてプロジェクト設定を宣言する自動ビルドツールです。AndroidStudioはプロジェクトをビルドするときに、gradleによって作成されたプラグインを使用してプロジェクト構成を読み込み、ファイルをコンパイルします。
コンポーネント化では、各モジュールにbuild.gradleファイルがあり、各モジュールのbuild.gradleファイルにいくつかの必須属性があります。同じAndroidプロジェクトでは、参照がある場合、compileSdkVersionなどの異なるモジュールでこれらの属性が一貫している必要があります。一貫性がない場合、属性はマージされてプロジェクトに導入されないため、リソースが重複し、コンパイルの効率が低下します。
統一された基本的なGradle構成が必要であり、version.gradleファイルを作成し、いくつかの変数を記述して、プロジェクトのbuild.gradleのビルドスクリプトに追加する必要があります。

apply from :"versions.gradle"

静的変数を参照してプロパティを参照する方法と同様に、プロジェクトで使用されるウェアハウスをversion.gradleで統一された方法で構成することもできます。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

次に、モジュールのbuild.gradleでこのように使用する必要があります。

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

このように、プロジェクトがAndroidツールライブラリの複数の異なるバージョンを参照しないようにパラメーター変数の構成が統合され、apk容量の増加を回避するために構成が統合されます。

デバッグの最適化

コンポーネント化は、単一のモジュールをアプリとして開始し、それをデバッグとテストに使用して、個々のモジュールを個別にデバッグできるようにすることをサポートします。
変更する必要があります:

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

srcにデバッグフォルダを作成します。デバッグフォルダは、デバッグに必要なAndroidMainfest.xmlファイル、Javaファイル、resファイルなどを配置するために使用され、デフォルトの起動アクティビティを設定する必要があります。
isModule変数を統合開発モードとコンポーネント開発モードのスイッチとして設定できます。これは、モジュールのbuild.gradleで次のように判断できます。

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

同時に、統合開発モードでは、デバッグフォルダー内のすべてのファイルを除外する必要があります。

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

元のアプリのbuild.gradleは、個別にデバッグされたモジュールの依存関係を削除する必要があります。

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

総括する

Androidプロジェクトでのコンポーネント化の実践により、再利用性が向上し、結合が減少します。この記事では、主にプロジェクトでのコンポーネント化の一般的な使用シナリオを要約し、より関連するシナリオをプロジェクト開発で要約します。

おすすめ

転載: blog.csdn.net/yang_study_first/article/details/111658088