天天看點

【Android】元件化元件化

元件化

1 Gradle

1.1 列印資訊

Gradle建構工具,Groovy腳本語言基本Java做了拓展,Gradle = JDK + Groovy

建立工程,在app的build.gradle中,添加:

println("hello gradle")
或
println "hello gradle"
           

同步時,在Build中可以看到列印出來的資訊:

> Configure project :app
hello gradle

> Configure project :library
hello gradle

> Task :prepareKotlinBuildScriptModel UP-TO-DATE

BUILD SUCCESSFUL in 525ms
           

1.2 多module環境的管理

建立Android module:在項目上右鍵建立Android module,出現新的module叫做”library“,和app平級。

Gradle可以用來:

  • 所有module的版本号和依賴的統一管理
  • 生産環境和開發環境的分離

具體做法:

①在項目上右鍵,建立file,取名config.gradle:

// 添加多個自定義的屬性,可以通過ext代碼塊
ext{
    username = "simon"
}
           

②在根目錄下的build.gradle頭部加入自定義config.gradle,相當于layout布局中加入include

③在app和library的build.gradle中加入:

println "${username}"
或者
println "${rootProject.ext.username}"
           

④點選同步即可列印:

> Configure project :app
hello gradle
simon

> Configure project :library
hello gradle
simon

> Task :prepareKotlinBuildScriptModel UP-TO-DATE

BUILD SUCCESSFUL in 435ms
           

groovy是弱類型語言,展現在可以自由修改資料類型,并且列印:

rootProject.ext.username = 163  
println "${rootProject.ext.username}"
           

真正的使用在項目中:

①擴充config.gradle的内容:

// 添加多個自定義的屬性,可以通過ext代碼塊
ext{
    username = "simon"

    // 生産/開發環境
    isRelease = true

    // 建立Map存儲,對象名,key都可以自定義,groovy糖果文法,靈活
    androidId = [
            compileSdkVersion : 30,
            buildToolsVersion : "30.0.3",
            minSdkVersion : 16,
            targetSdkVersion : 30,
            versionCode : 1,
            versionName : "1.0"
    ]

    appId = [
            applicationId : "com.example.componentizationstudy",
            library : "com.example.library",
    ]

    // 生産/開發環境 URL
    utl = [
            "debug" : "https://11.22.33.44/debug",
            "release" : "https://11.22.33.44/release"
    ]

    supportLibrary = "28.0.0"
    // 第三方庫
    dependencies = [
            "appcompat":"androidx.appcompat:appcompat:1.1.0",
            "material":"com.google.android.material:material:1.1.0",
            					"constraintlayout":"androidx.constraintlayout:constraintlayout:1.1.3",
            "junit":"androidx.test.ext:junit:1.1.1",
            "espresso":"androidx.test.espresso:espresso-core:3.2.0"
    ]
}
           

②在app和library的build.gradle中,定義變量:

// 指派與引用
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
           

android部分:

android {
    compileSdkVersion androidId.compileSdkVersion
    buildToolsVersion androidId.buildToolsVersion

    defaultConfig {
        applicationId appId.applicationId
        minSdkVersion androidId.minSdkVersion
        targetSdkVersion androidId.targetSdkVersion
        versionCode androidId.versionCode
        versionName androidId.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
           

dependencies部分:

dependencies {
    // 依賴library庫
    implementation project(":library")
    testImplementation 'junit:junit:4.+'
    implementation support.appcompat
    implementation support.material
    implementation support.constraintlayout
    androidTestImplementation support.junit
    androidTestImplementation support.espresso

    或者:
    implementation project(":library")
    testImplementation 'junit:junit:4.+'
    support.each {k, v->implementation v}  // 最簡潔的方法,一句話搞定
}
           

依賴的兩種寫法:

// 标準寫法
implementation group:"androidx.appcompat", name:"appcompat", version:"1.1.0"

// 簡寫
implementation 'androidx.appcompat:appcompat:1.1.0'
           

1.3 在BuildConfig.java檔案中添加一個變量

BuildConfig.java是一個配置檔案,會打包到apk中,檔案在工程中的位置是:

module下的build-->generated-->source-->buildConfig-->debug-->com.example.componentizationstudy-->BuildConfig.java
           

在app的build.gradle中:

主要是這句話:
buildConfigField("String", "url", "\"${url.debug}\"");

加上之後是:
android{
	buildTypes {
        debug{
            buildConfigField("String", "url", "\"${url.debug}\"");
        }
        release {
            minifyEnabled false
            buildConfigField("String", "url", "\"${url.release}\"");
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
           

2 元件化項目詳細部署

2.1 元件化的意義

1-面試

2-開發需求-不互相依賴,可以互相互動,任意組合,高度解耦

3-團隊效率:分子產品打包、測試,統一版本管理

2.2 Phone Module 和 Android Library的差別、切換

Phone Module:元件化
(1)是個子產品,可以獨立運作
(2)apply plugin:'com.android.application'
(3)有applicationId


Android Library: 內建化
(1)是個安卓庫,不能獨立運作
(2)apply plugin:'com.android.library'
(3)無applicationId
           

2.3 元件化和內建化:

元件化,多個子產品作為Phone module可以獨立運作,一個項目可以打包出多個apk

內建化,多個子產品作為Android Library不可以獨立運作,一個項目打包出一個apk

兩者可以通過gradle切換

①擴充config.gradle的内容:

// 添加多個自定義的屬性,可以通過ext代碼塊
ext{
    username = "simon"

    // 生産/開發環境
    // false:元件化模式,子子產品可以獨立運作
    // true:內建化模式,子子產品不能獨立運作
    isRelease = true

    // 建立Map存儲,對象名,key都可以自定義,groovy糖果文法,靈活
    androidId = [
            compileSdkVersion : 30,
            buildToolsVersion : "30.0.3",
            minSdkVersion : 16,
            targetSdkVersion : 30,
            versionCode : 1,
            versionName : "1.0"
    ]

    appId = [
            app : "com.example.componentizationstudy",
            library : "com.example.library",
            order : "com.example.order",
            personal : "com.example.personal",
    ]

    // 生産/開發環境 URL
    url = [
            "debug" : "https://11.22.33.44/debug",
            "release" : "https://11.22.33.44/release"
    ]

    // 第三方庫
    dependencies = [
            "appcompat":"androidx.appcompat:appcompat:1.1.0",
            "material":"com.google.android.material:material:1.1.0",
            "constraintlayout":"androidx.constraintlayout:constraintlayout:1.1.3",
            "junit":"androidx.test.ext:junit:1.1.1",
            "espresso":"androidx.test.espresso:espresso-core:3.2.0"
    ]
}
           

② order子產品build.gradle

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

// 指派與引用
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies

android {
    compileSdkVersion androidId.compileSdkVersion
    buildToolsVersion androidId.buildToolsVersion

    defaultConfig {
        if(!isRelease){ // 元件化模式,可獨立運作,有applicationId
            applicationId appId.order
        }
        minSdkVersion androidId.minSdkVersion
        targetSdkVersion androidId.targetSdkVersion
        versionCode androidId.versionCode
        versionName androidId.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    // 循環引入第三方庫
    support.each{k, v->implementation v}

    // 公共基礎庫
    implementation project(":common")
}
           

③ personal子產品的build.gradle

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

// 指派與引用
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        if(!isRelease){ // 元件化模式,可獨立運作,有applicationId
            applicationId appId.personal
        }
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"

        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    // 循環引入第三方庫
    support.each{k, v->implementation v}

    // 公共基礎庫
    implementation project(":common")
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}
           

④ 全局的build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

// 根目錄下的build.gradle頭部加入自定義config.gradle,相當于layout布局中加入include
apply from:"config.gradle"

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.2"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
           

⑤ 元件化與內建化的切換,通過Gradle動态切換

isRelease = true  點同步後,切成內建化模式

isRelease = false 點同步後,切成元件化模式
           

2.4 動态隔離,不把一些代碼加載到apk中

① order和personal的build.gradle

android{
    sourceSets{
        main{
            if(!isRelease){
                // 元件化模式,需要單獨運作時
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            }else{
                // 內建化模式,整個項目打包apk時
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java{
                    // release時,debug目錄下的檔案不需要合并到主工程
                    exclude '**/debug/**'
                }
            }
        }
    }
}
           

② 以order為例

在order子產品的src的main上右鍵,建立debug檔案夾,複制order的AndroidManifest.xml到這個檔案夾下一份;

在java下的包上右鍵,建立package,取名debug,把測試代碼都放在這個debug檔案夾下。

③ 将config.gradle中的isRelease标簽改成true,打包成apk,發現apk中沒有debug中的代碼

2.5 Phone module引用Android Module

在common中寫的activity,可以在order中和personal中被繼承。

3 Module和Module之間的互動

3.1 Module之間的互動方式

  • EventBus。難以實作:EventBean非常多(一對一),因為一對多就會混亂不堪、難以維護
  • 反射。可以實作,但維護成本高,且易出現高版本@hide限制
  • 隐式意圖。維護成本一般,但較為麻煩,需要維護Manifest中的action
  • BroadCastReceiver。需要動态注冊(7.0後),需求方發送廣播
  • 類加載。需要準确的全類目路徑,維護成本高,易出錯

建議的實作方案:

3.2 方案1:類加載

從app的MainActivity.java跳轉到order和personal兩個子產品:

public void jumpOrder(View view){
    Intent intent = new Intent(this, Order_MainActivity.class);
    intent.putExtra("name", "simon");
    startActivity(intent);
}

public void jumpPersonal(View view){
    Intent intent = new Intent(this, Personal_MainActivity.class);
    intent.putExtra("name", "simon");
    startActivity(intent);
}
           

從order的Order_MainActivity跳到app和personal兩個子產品:

public void jumpApp(View view){
    // 類加載方式互動
    try {
        Class targetClass = Class.forName("com.example.componentizationstudy.MainActivity");
        Intent intent = new Intent(this, targetClass);
        intent.putExtra("name", "simon");
        startActivity(intent);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

public void jumpPersonal(View view){
    // 類加載方式互動
    try {
        Class targetClass = Class.forName("com.example.personal.Personal_MainActivity");
        Intent intent = new Intent(this, targetClass);
        intent.putExtra("name", "simon");
        startActivity(intent);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}
           

從personal的Personal_MainActivity跳到app和order兩個子產品:

public void jumpApp(View view){
    // 類加載方式互動
    try {
        Class targetClass = Class.forName("com.example.componentizationstudy.MainActivity");
        Intent intent = new Intent(this, targetClass);
        intent.putExtra("name", "simon");
        startActivity(intent);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

public void jumpOrder(View view){
    // 類加載方式互動
    try {
        Class targetClass = Class.forName("com.example.order.Order_MainActivity");
        Intent intent = new Intent(this, targetClass);
        intent.putExtra("name", "simon");
        startActivity(intent);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}
           

3.3 方案2:全局Map記錄資訊

在common中,建立PathBean.java

package com.example.common;

public class PathBean {
    private String path;
    private Class clazz;

    public PathBean() {
    }

    public PathBean(String path, Class clazz) {
        this.path = path;
        this.clazz = clazz;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }
}
           

在common中,建立RecordPathManager.java

/**
 * 全局路徑記錄器(根據子子產品分組)
 */
public class RecordPathManager {
    // key : "order"組
    // value:order子子產品下,對應所有的Activity路徑資訊
    private static Map<String, List<PathBean>> groupMap = new HashMap<>();

    public static void joinGroup(String groupName, String pathName, Class<?> clazz){
        List<PathBean> list = groupMap.get(groupName);
        if (list == null) {
            list = new ArrayList<>();
            list.add(new PathBean(pathName, clazz));
            groupMap.put(groupName, list);
        } else{
            for (PathBean pathBean : list) {
                if(!pathName.equalsIgnoreCase(pathBean.getPath())){
                    list.add(new PathBean(pathName, clazz));
                    groupMap.put(groupName, list);
                }
            }
        }
    }

    /**
     * 根據組名和路徑名擷取類對象,達到跳轉目的
     * @param groupName 組名
     * @param pathName 路徑名
     * @return 跳轉目标的class類對象
     */
    public static Class<?> getTargetClass(String groupName, String pathName){
        List<PathBean> list = groupMap.get(groupName);
        if (list == null) {
            return null;
        }
        for (PathBean pathBean : list) {
            if(pathName.equalsIgnoreCase(pathBean.getPath())){
                return pathBean.getClazz();
            }
        }
        return null;
    }
}
           

在app中建立AppApplication.java:

public class AppApplication extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        RecordPathManager.joinGroup("app", "MainActivity", MainActivity.class);
        RecordPathManager.joinGroup("order", "Order_MainActivity", Order_MainActivity.class);
        RecordPathManager.joinGroup("personal", "Personal_MainActivity", Personal_MainActivity.class);
    }
}
           

并将其添加到AndroidMenifest.xml中:

android:name=".base.AppApplication"
           

在app中,修改跳轉方式:

public void jumpApp(View view){
    // 類加載方式互動
    //        try {
    //            Class targetClass = Class.forName("com.example.componentizationstudy.MainActivity");
    //            Intent intent = new Intent(this, targetClass);
    //            intent.putExtra("name", "simon");
    //            startActivity(intent);
    //        } catch (ClassNotFoundException e) {
    //            e.printStackTrace();
    //        }

    // 全局Map加載
    Class<?> targetClass = RecordPathManager.getTargetClass("app", "MainActivity");
    if (targetClass == null) {
        Log.e("djtest", "擷取targetClass為空");
    }
    Intent intent = new Intent(this, targetClass);
    intent.putExtra("name", "simon");
    startActivity(intent);
}

public void jumpOrder(View view){
    //        Intent intent = new Intent(this, Order_MainActivity.class);
    //        intent.putExtra("name", "simon");
    //        startActivity(intent);

    // 全局Map加載
    Class<?> targetClass = RecordPathManager.getTargetClass("order", "Order_MainActivity");
    if (targetClass == null) {
        Log.e("djtest", "擷取targetClass為空");
    }
    Intent intent = new Intent(this, targetClass);
    intent.putExtra("name", "simon");
    startActivity(intent);
}

public void jumpPersonal(View view){
    //        Intent intent = new Intent(this, Personal_MainActivity.class);
    //        intent.putExtra("name", "simon");
    //        startActivity(intent);

    // 全局Map加載
    Class<?> targetClass = RecordPathManager.getTargetClass("personal", "Personal_MainActivity");
    if (targetClass == null) {
        Log.e("djtest", "擷取targetClass為空");
    }
    Intent intent = new Intent(this, targetClass);
    intent.putExtra("name", "simon");
    startActivity(intent);
}
           

4 元件化APT

注解處理器,Annotation Processing Tool

通俗了解:根據規則幫我們生成代碼,生成類檔案

結構體語言:element組成的結構體

<html>
	<body>
		<div>...</div>
	<body>
<html>
           

java也是一種結構體語言:

package com.example.xxx; // PackageElement 包元素/節點

public class Main{ // TypeElement 類元素/節點

	private int x; // VariableElement 屬性元素/節點
	
	private Main(){ // ExecuteableElement 方法元素/節點
	}
	
	private void print(String msg){
	
	}
}
           

需要掌握的API

getEnclosedElements()		傳回該元素直接包含的子元素
getEnclosingElement()		傳回包含該element的父element,與上一個方法相反
getKind()					傳回element的類型,判斷是哪種element
getModifiers()				擷取修飾關鍵字,如 public static final
getSimpleName()				擷取名字,不帶包名
getQualifiedName()			擷取全名,如果是類的話,包含完整的包名路徑
getParameters()				擷取方法的參數元素,每個元素是一個VariableElement
getReturnType()				擷取方法元素的傳回值
getConstantValue()			如果屬性變量被final修飾,則可以使用該方法擷取它的值
           

4.1 手寫ARouter注解

① 建立工程,在MainActivity上加注解,此時不會有用,因為還沒寫注解

@ARouter(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
           

②建立Java Library,取名annotation,在其中建立ARouter,類型為Annotation

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Activity使用的布局檔案注解
 *
 * @Target(ElementType.TYPE) 接口、類、枚舉、注解
 * @Target(ElementType.FIELD) 屬性、枚舉的常量
 * @Target(ElementType.METHOD) 方法
 * @Target(ElementType.PARAMETER) 方法參數
 * @Target(ElementType.CONSTRUCTOR) 構造函數
 * @Target(ElementType.LOCAL_VARIABLE) 局部變量
 * @Target(ElementType.ANNOTATION_TYPE) 該注解使用在另一個注解上
 * @Target(ElementType.PACKAGE) 包
 * @Retention(RetentionPolicy.RUNTIME) 該注解會在class位元組碼檔案中存在,jvm加載時可以通過反射擷取到該注解的内容
 *
 *
 * 生命周期 SOURCE < CLASS < RUNTIME
 * 1.一般如果需要在運作時去動态擷取注解資訊,用RUNTIME注解
 * 2.要在編譯時進行一些預處理操作,如ButterKnife,用CLASS注解,注解會在class檔案中存在,但是在運作時會被丢棄
 * 3.要做一些檢查性的工作,如@Override,用SOURCE源碼注解,注解僅存在源碼級别,在編譯的時候丢棄該注解
 *
 */
@Target(ElementType.TYPE)  // 作用在類上
@Retention(RetentionPolicy.CLASS)  // 沒有執行run之前,在編譯期的預編譯操作
public @interface ARouter {
    // 詳細路由路徑(必填),如"/app/MainActivity"
    String path();
    // 從path中截取出來,規範開發者的編碼
    String group() default "";
}
           

③ 在app的build.gradle中添加依賴

④ 在根目錄下的build.gradle中添加阿裡雲鏡像,加快下載下傳速度:

repositories {
        // 超級實用:阿裡雲鏡像更新
        maven{
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
        google()
        jcenter()
    }
           

⑤ 建立Java Library,取名compiler,建立ARouterProcessor類用作ARouter注解的處理器,在注解處理器中讀取注解内容,生成對應的類檔案。

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.example.annotation.ARouter") // 注解處理器支援的注解類型,需要把ARouter放進來,才能在process中處理, 必須是全類名
@SupportedSourceVersion(SourceVersion.RELEASE_7) // 你需要用到什麼jdk的版本來進行編譯,生成這個class,必填!
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {

    private Elements elementUtils; // 操作Element工具類
    private Types typesUtils; // type類資訊工具類
    private Messager messager; // 用來輸出警告、錯誤等日志
    private Filer filer; // 檔案生成器
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        // 初始化工作
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typesUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        String content = processingEnvironment.getOptions().get("content");
        // 有坑,不能像Android中Log.e的寫法
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

    // 通過注解替代
//    @Override
//    public Set<String> getSupportedAnnotationTypes() {
//        // 注解處理器支援的注解類型,需要把ARouter放進來,才能在process中處理
//        return super.getSupportedAnnotationTypes();
//    }
//
//    @Override
//    public SourceVersion getSupportedSourceVersion() {
//        // 你需要用到什麼jdk的版本來進行編譯,生成這個class,必填!
//        return super.getSupportedSourceVersion();
//    }
//
//    @Override
//    public Set<String> getSupportedOptions() {
//        // 接收外面傳來的參數
//        return super.getSupportedOptions();
//    }

    /**
     * 相當于main函數,開始處理注解
     * 注解處理器的核心方法,處理具體的注解,生成java檔案
     *
     * @param set 使用了支援處理注解的節點集合(類,上面寫了注解)
     * @param roundEnvironment 目前或是之前的運作環境,可以通過該對象查找找到的注解
     * @return true 表示後續處理器不再處理(已經處理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if(set.isEmpty()){
            return false;
        }
        // 擷取項目中所有使用了ARouter注解的節點
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        // 周遊所有的類節點
        for (Element element : elements) {
            // 類節點之上,就是包節點
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            // 擷取簡單類名
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "備注接的類有:"+className);
            // 最終想要生成的類檔案,如“MainActivity$$ARouter”
            String finalClassName = className + "$$ARouter";

            try {
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName); // 建立源檔案
                Writer writer = sourceFile.openWriter();
                // 設定包名
                writer.write("package "+packageName+";\n");
                writer.write("public class "+finalClassName+" {\n");
                writer.write("public static Class<?> findTargetClass(String path) {\n");

                // 擷取類之上@ARouter注解的path值
                ARouter aRouter = element.getAnnotation(ARouter.class);

                writer.write("if (path.equalsIgnoreCase(\""+aRouter.path()+"\")) {\n");
                writer.write("return "+className+".class;\n}\n");
                writer.write("return null;\n");
                writer.write("}\n}");

                // 非常重要
                writer.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}
           

compiler子產品的build.gradle:

添加:
// 注冊注解,并對其生成META-INF的配置資訊
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

// 引入annotation,讓注解處理器,處理注解
implementation project(':annotation')

// java控制台輸出中文亂碼
tasks.withType(JavaCompiler){
    options.encoding = "UTF-8"
}



添加後全文如下:
import javax.tools.JavaCompiler

plugins {
    id 'java-library'
}

dependencies{
    implementation fileTree(dir:'libs', include:['*.jar'])

    // 注冊注解,并對其生成META-INF的配置資訊
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

    // 引入annotation,讓注解處理器,處理注解
    implementation project(':annotation')
}

// java控制台輸出中文亂碼
tasks.withType(JavaCompiler){
    options.encoding = "UTF-8"
}

// Jdk編譯的版本是1.7
java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}
           

app子產品的build.gradle:

// 第一:在Gradle檔案中配置選項參數值(用于APT傳參接收)
defaultConfig {
    // 切記:必須寫在defaultConfig節點下
        javaCompileOptions{
            annotationProcessorOptions{
            arguments = [content : 'hello apt']
        }
    }
}


// 第二:添加注解處理器
annotationProcessor project(':compiler')
           

⑥ 在app下,建立OrderActivity和PersonalActivity,在兩者頭上加ARouter注解

@ARouter(path = "/app/OrderActivity")
public class OrderActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_order);
        Log.i("djtest", "OrderActivity");
    }
    public void jump(View view){
        Class<?> targetClass = PersonalActivity$$ARouter.findTargetClass("/app/PersonalActivity");
        startActivity(new Intent(this, targetClass));
    }
}
           
@ARouter(path = "/app/PersonalActivity")
public class PersonalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_personal);
        Log.i("djtest", "PersonalActivity");
    }

    public void jump(View view){
        Class<?> targetClass = MainActivity$$ARouter.findTargetClass("/app/MainActivity");
        startActivity(new Intent(this, targetClass));
    }
}
           

滑鼠點選app,Build菜單中Make Module “ComponentAPT.app”,

在app/build/generated/ap_generated_sources/debug/out/com.example.componentapt/下生成:

(1)MainActivity$$ARouter.java

package com.example.componentapt;

public class MainActivity$$ARouter {
    public static Class<?> findTargetClass(String path) {
        if (path.equalsIgnoreCase("/app/MainActivity")) {
            return MainActivity.class;
        }
        return null;
    }
}
           

(2)OrderActivity$$ARouter.java

package com.example.componentapt;

public class OrderActivity$$ARouter {
    public static Class<?> findTargetClass(String path) {
        if (path.equalsIgnoreCase("/app/OrderActivity")) {
            return OrderActivity.class;
        }
        return null;
    }
}
           

(3)PersonalActivity$$ARouter.java

package com.example.componentapt;

public class PersonalActivity$$ARouter {
    public static Class<?> findTargetClass(String path) {
        if (path.equalsIgnoreCase("/app/PersonalActivity")) {
            return PersonalActivity.class;
        }
        return null;
    }
}
           

同理,在app/build/intermediates/javac/debug/classes/com/example/componentapt/下生成:

(1)MainActivity$$ARouter.class

package com.example.componentapt;

public class MainActivity$$ARouter {
    public MainActivity$$ARouter() {
    }

    public static Class<?> findTargetClass(String path) {
        return path.equalsIgnoreCase("/app/MainActivity") ? MainActivity.class : null;
    }
}
           

(2)OrderActivity$$ARouter.class

package com.example.componentapt;

public class OrderActivity$$ARouter {
    public OrderActivity$$ARouter() {
    }

    public static Class<?> findTargetClass(String path) {
        return path.equalsIgnoreCase("/app/OrderActivity") ? OrderActivity.class : null;
    }
}
           

(3)PersonalActivity$$ARouter.class

package com.example.componentapt;

public class PersonalActivity$$ARouter {
    public PersonalActivity$$ARouter() {
    }

    public static Class<?> findTargetClass(String path) {
        return path.equalsIgnoreCase("/app/PersonalActivity") ? PersonalActivity.class : null;
    }
}
           

4.2 元件化APT進階用法JavaPoet

APT + JavaPoet = 超級利刃

JavaPoet的8個常用類

MethodSpec		代表一個構造函數或者方法聲明
TypeSpec		代表一個類,接口,或者常量
FieldSpec		代表一個成員變量,一個字段聲明
JavaFile		包含一個頂級類的Java檔案
ParameterSpec	用來建立參數
AnnotationSpec	用來建立注解
ClassName		用來包裝一個類
TypeName		類型,如在添加傳回值類型是使用TypeName.VOID
           

JavaPoet字元串格式化規則:

$L	字面量,	"int value=$L",10
$S	字元串,	$S," hello"
$T	類、接口,   $T,MainActivity
$N	變量,		user.$N,name
           

① 建立工程,建立兩個java Library,分别為annotation和compiler。

② 在annotation中,建立ARouter注解檔案:

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE) // 作用于類
@Retention(RetentionPolicy.CLASS) // 編譯時進行一些預處理工作,存在于class檔案中,運作時删除
public @interface ARouter {
    String path();
}
           

③ 在compiler中,建立注ARouterProcessor解處理器檔案:

package com.example.compiler;

import com.example.annotation.ARouter;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class) // 注冊注解處理器
@SupportedAnnotationTypes("com.example.annotation.ARouter") // 對哪種注解進行處理
@SupportedSourceVersion(SourceVersion.RELEASE_7) // 編譯器類型
@SupportedOptions("content") // 額外的參數
public class ARouterProcessor extends AbstractProcessor {

    private Elements eLementsUtils;
    private Types typesUtils;
    private Messager messager;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        eLementsUtils = processingEnvironment.getElementUtils();
        typesUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        String content = processingEnvironment.getOptions().get("content");
        messager.printMessage(Diagnostic.Kind.NOTE, content); // 列印獲得的參數
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 如果沒有任何一處使用了@ARouter注解,那就不做任何處理
        if (set.isEmpty()) {
            return false;
        }

        // 擷取所有被@ARouter注解的類節點
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        // 周遊這些節點,每個節點生成一個類檔案
        for (Element element : elements) {
            // 類節點的上一個節點:包節點
            String packageName = eLementsUtils.getPackageOf(element).getQualifiedName().toString();
            // 擷取簡單類名
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被@ARouter注解的類有:"+className);
            // 最終生成的類檔案名
            String finalClassName = className + "$$ARouter";

            ARouter aRouter = element.getAnnotation(ARouter.class);

            // 方法:public static Class<?> findTargetClass(String path){}
            MethodSpec methodSpec = MethodSpec.methodBuilder("findTargetClass") // 方法名
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(Class.class) // 傳回值類型是Class
                    .addParameter(String.class, "path") // 參數類型和參數名
                    // return path.equalsIgnoreCase("app/MainActivity") ? MainActivity.class : null;
                    .addStatement("return path.equalsIgnoreCase($S) ? $T.class : null",
                            aRouter.path(),
                            ClassName.get((TypeElement) element))
                    .build();

            // 類:public class XActivity$$ARouter {}
            TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName) // 類名
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(methodSpec) // 将方法添加到類中
                    .build();

            // 包:package com.example.componetaptjavapoet;
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec) // 包名,類名
                    .build();

            // 寫到檔案中去
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}
           

寫注解生成類檔案之前先将想要的類檔案寫出來:

在app下建立XActivity$$ARouter.java,之後就仿照這個類檔案,在注解處理器中用代碼生成:

package com.example.componetaptjavapoet;

public class XActivity$$ARouter {
    public static Class<?> findTargetClass(String path){
        return path.equalsIgnoreCase("app/MainActivity") ? MainActivity.class : null;
    }
}
           

compiler的build.gradle:

plugins {
    id 'java-library'
}

dependencies{
    implementation fileTree(dir:'libs', include:['*.jar'])

    // 注冊注解,并對其生成META-INF的配置資訊
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

    // 幫助我們通過類調用的形式來生成JAVA代碼
    implementation 'com.squareup:javapoet:1.9.0'

    // 依賴注解
    implementation project(':annotation')
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}
           

檢查注解是否注冊成功:

在compiler/build/classes/java/main/META-INF/services/下,打開檔案,看到下列内容,說明注冊成功。

com.example.compiler.ARouterProcessor
           

④ app中應用@ARouter注解

app的build.gradle中添加依賴

implementation project(':annotation')
annotationProcessor project(':compiler')
           

app中的MainActivity、OrderActivity和PersonalActivity:

MainActivity.java:

@ARouter(path="/app/MainActivity")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i("djtest", "MainActivity...");
    }

    public void jump(View view){
        Class targetClass = OrderActivity$$ARouter.findTargetClass("/app/OrderActivity");
        startActivity(new Intent(this, targetClass));
    }
}
           

OrderActivity.java:

@ARouter(path="/app/OrderActivity")
public class OrderActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_order);
        Log.i("djtest", "OrderActivity...");
    }

    public void jump(View view){
        Class targetClass = PersonalActivity$$ARouter.findTargetClass("/app/PersonalActivity");
        startActivity(new Intent(this, targetClass));
    }
}
           

PersonalActivity.java:

@ARouter(path="/app/PersonalActivity")
public class PersonalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_personal);
        Log.i("djtest", "PersonalActivity...");
    }

    public void jump(View view){
        Class targetClass = MainActivity$$ARouter.findTargetClass("/app/MainActivity");
        startActivity(new Intent(this, targetClass));
    }
}
           

5 元件化路由架構設計

元件化項目部署:

  • 配置arouter_api/build.gradle
  • 配置common/build.gradle
  • 完成模拟APT生成的類代碼
  • app子子產品中,模拟跳轉代碼
【Android】元件化元件化
  1. 為什麼需要組名?

    隻加載相關類,節省記憶體

  2. 生成這些檔案有什麼用?

    根據path,擷取類對象

    根據組名,找到對應的list,根據從組名截取的path,擷取類對象。