博客主頁html
參考:java
Android組件化開發的配置,離不開gradle構建工具,它的出現讓工程有無限的可能。gradle核心是基於groovy腳本語言,groovy腳本基於java且擴展了java,因此gradle須要依賴JDK和Groovy庫。android
gradle語法:從gradle的日誌輸出開始講解組件化設計之旅git
// 第一種打印字符串的方式 println("hello, gradle!") // 第二種打印字符串的方式 println "hello2, gradle!"
從這兩種字符串輸出方式能夠看出,方法能夠不寫括號,一句話後能夠不寫分號,這是groovy的特性。github
能夠在Android工程中的build.gradle文件中使用println函數輸出日誌。而後經過 Build->Toggle view 查看build輸出的日誌編程
gradle能夠添加額外的自定義屬性,經過ext屬性實現。先新建一個config.gradle文件,並自定義isRelease屬性,用於動態切換:組件化模式/集成化模式segmentfault
ext { // false: 組件化模式(子模塊能夠獨立運行) // true :集成化模式(打包整個項目apk,子模塊不可獨立運行) isRelease = true }
那麼這個config文件怎麼使用呢?須要在項目的根build.gradle文件經過 apply from 方式引用config.gradle文件api
// build.gradle // 能夠經過apply from方式引用 apply from: 'config.gradle'
而後在app應用項目的build.gradle文件中使用自定義屬性網絡
// build.gradle // 使用自定義屬性,屬性須要寫在${}中 println "${rootProject.ext.isRelease}"
再新建一個購物shop庫模塊,項目結構以下圖:
查看app應用模塊和shop庫模塊中的build.gradle文件,發現上圖紅色框配置不少相似,那麼是否是可使用分模塊方式配置呢,使用gradle自定義屬性將共性的配置抽取出來,放在單獨的文件裏,供其餘build引用?答案是能夠的。閉包
接下來動手操做下,紅色框的配置都是跟Android版本有關的,能夠定義一個Map集合存相關屬性信息,配置以下:
// config.gradle ext { // 定義Map存取版本相關的信息,key名稱能夠任意取 versions = [ compileSdkVersion: 29, buildToolsVersion: "29.0.2", minSdkVersion : 21, targetSdkVersion : 29, versionCode : 1, versionName : "1.0" ] }
而後在app應用模塊和shop庫模塊中的build.gradle文件,訪問Map集合中定義的屬性,例如app應用模塊build.gradle中訪問Map中自定義的屬性
// 獲取Map def versions = rootProject.ext.versions android { // 直接經過map.key訪問值 compileSdkVersion versions.compileSdkVersion buildToolsVersion versions.buildToolsVersion defaultConfig { applicationId "com.example.modular.todo" minSdkVersion versions.minSdkVersion targetSdkVersion versions.targetSdkVersion versionCode versions.versionCode versionName versions.versionName } }
1. applicationId配置
在組件化模塊與集成化模塊作切換,組件化模塊爲了可以獨立運行,將庫模塊切換成應用模塊時須要設置applicationId
因此還須要配置不一樣的applicationId屬性
// config.gradle ext { // 組件化與集成化切換時,設置不一樣的applicationId appId = [ app : "com.example.modular.todo", shop: "com.example.modular.shop" ] }
在app應用模塊中的build.gradle文件中使用
def appId = rootProject.ext.appId android { defaultConfig { applicationId appId.app } }
2. 代碼中生產和正式環境配置切換
有的時候還須要在代碼中切換生產和正式環境配置,如:網絡請求的URL。Android爲咱們提供自定義BuildConfig功能。
// config.gradle ext { baseUrl = [ debug : "https://127.0.0.1/debug", // 測試版本URL release: "https://127.0.0.1/relase" // 正式版本URL ] }
在app應用模塊build.gradle文件中經過buildConfigField配置,在代碼中就能夠經過BuildConfig.baseUrl訪問到了。
// build.gradle def baseUrl = rootProject.ext.baseUrl android { buildTypes { debug { // void buildConfigField( // @NonNull String type, // @NonNull String name, // @NonNull String value) { } buildConfigField("String", "baseUrl", "\"${baseUrl.debug}\"") } release { buildConfigField("String", "baseUrl", "\"${baseUrl.release}\"") } } }
3. dependencies依賴配置
// config.gradle ext { appcompatVersion = "1.0.2" constraintlayoutVersion = "1.1.3" dependencies = [ appcompat : "androidx.appcompat:appcompat:${appcompatVersion}", constraintlayout: "androidx.constraintlayout:constraintlayout:${constraintlayoutVersion}", ] tests = [ "junit" : "junit:junit:4.12", "espresso" : "androidx.test.espresso:espresso-core:3.1.1", "androidJunit": "androidx.test.ext:junit:1.1.0" ] }
app模塊中build.gradle文件引用
// build.gradle def supports = rootProject.ext.dependencies def tests = rootProject.ext.tests dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // 標準寫法 // implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.2' // implementation supports.appcompat // implementation supports.constraintlayout // supports 依賴 supports.each { key, value -> implementation value } testImplementation tests.junit androidTestImplementation tests.espresso androidTestImplementation tests.androidJunit }
4. 簽名配置
簽名配置時須要注意:signingConfigs 必須寫在buildTypes以前
android { // 簽名配置(隱形坑:必須寫在buildTypes以前) signingConfigs { debug { // 天坑:填錯了,編譯不經過還找不到問題 storeFile file('/Users/xujinbing839/.android/debug.keystore') storePassword "android" keyAlias "androiddebugkey" keyPassword "android" } release { // 簽名證書文件 storeFile file('/Users/xujinbing839/work/mycode/todo/todo_modular/keystore/modular') storeType "modular" // 簽名證書的類型 storePassword "123456" // 簽名證書文件的密碼 keyAlias "modular" // 簽名證書中密鑰別名 keyPassword "123456" // 簽名證書中該密鑰的密碼 v2SigningEnabled true // 是否開啓V2打包 } } buildTypes { debug { // 對構建類型設置簽名信息 signingConfig signingConfigs.debug } release { // 對構建類型設置簽名信息 signingConfig signingConfigs.release } } }
其它配置
android { defaultConfig { // 開啓分包 multiDexEnabled true // 設置分包配置 // multiDexKeepFile file('multidex-config.txt') // 將svg圖片生成 指定維度的png圖片 // vectorDrawables.generatedDensities('xhdpi','xxhdpi') // 使用support-v7兼容(5.0版本以上) vectorDrawables.useSupportLibrary = true // 只保留指定和默認資源 resConfigs('zh-rCN') // 配置so庫CPU架構(真機:arm,模擬器:x86) // x86 x86_64 mips mips64 ndk { //abiFilters('armeabi', 'armeabi-v7a') // 爲了模擬器啓動 abiFilters('x86', 'x86_64') } } // AdbOptions 能夠對 adb 操做選項添加配置 adbOptions { // 配置操做超時時間,單位毫秒 timeOutInMs = 5 * 1000_0 // adb install 命令的選項配置 installOptions '-r', '-s' } // 對 dx 操做的配置,接受一個 DexOptions 類型的閉包,配置由 DexOptions 提供 dexOptions { // 配置執行 dx 命令是爲其分配的最大堆內存 javaMaxHeapSize "4g" // 配置是否預執行 dex Libraries 工程,開啓後會提升增量構建速度,不過會影響 clean 構建的速度,默認 true preDexLibraries = false // 配置是否開啓 jumbo 模式,代碼方法是超過 65535 須要強制開啓才能構建成功 jumboMode true // 配置 Gradle 運行 dx 命令時使用的線程數量 threadCount 8 // 配置multidex參數 additionalParameters = [ '--multi-dex', // 多dex分包 '--set-max-idx-number=50000', // 每一個包內方法數上限 // '--main-dex-list=' + '/multidex-config.txt', // 打包到主classes.dex的文件列表 '--minimal-main-dex' ] } // 執行 gradle lint 命令便可運行 lint 檢查,默認生成的報告在 outputs/lint-results.html 中 lintOptions { // 遇到 lint 檢查錯誤會終止構建,通常設置爲 false abortOnError false // 將警告看成錯誤來處理(老版本:warningAsErros) warningsAsErrors false // 檢查新 API check 'NewApi' } }
什麼是組件化開發?
組件化開發就是將一個app分紅多個模塊,每一個模塊都是一個組件(Module),開發的過程當中咱們可讓這些組件相互依賴或者單獨調試部分組件,可是最終發佈的時候是將這些組件合併統一成一個apk,這就是組件化開發。
組件化和插件化開發略有不一樣:
插件化開發時將整個app拆分紅不少模塊,這些模塊包括一個宿主和多個插件,每一個模塊都是一個apk(組件化的每一個模塊是個lib),最終打包的時候將宿主apk和插件apk分開打包。
爲何要組件化呢?
1. 開發需求
不相互依賴、能夠相互交互、任意組合、高度解耦
2. 團隊效率
Phone Module 和 Android Library區別:
Phone Module是一個能夠獨立運行,編譯成一個apk,且build.gradle文件中須要配置applicationId;而Android Library 不能獨立運行,不能單獨編譯成一個apk,且build.gradle文件中不須要配置applicationId。
Phone Module 和 Android Library切換:
下面以子模塊購物shop爲例:
若是購物shop模塊可以獨立編譯apk,就須要切換爲Phone Module(也就是組件化模式),經過isRelease動態切換:
其中isRelease變量是config.gradle中定義的屬性
// build.gradle if (isRelease) { // 若是是發佈版本時,各個模塊都不能獨立運行 apply plugin: 'com.android.library' } else { apply plugin: 'com.android.application' } android { defaultConfig { // 若是是集成化模式,不能有applicationId if (!isRelease) applicationId appId.shop } }
當子模塊購物shop從library模塊切換爲application模塊時,可能須要編寫測試代碼,如:啓動的入口。
使用sourceSets配置,將測試的代碼放入debug文件夾中,當切換到集成化模式時,打包成apk時移除全部的debug代碼(也就是debug代碼不會打包到apk中)。
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/**' } } } } }
組件化開發規範:
Module間怎麼交互(包括:跳轉、傳參等)?方式有不少:
經過Class的forName加載目標類,須要準確知道目標類的全類名路徑。
private void jump() { try { Class<?> targetClass = Class.forName("com.example.modular.shop.ShopActivity"); Intent intent = new Intent(this, targetClass); intent.putExtra("moduleName","app"); startActivity(intent); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
要跳轉,若是知道了目標類的Class對象,不就能夠跳轉了,接下來只須要解決目標類的Class對象查找就能夠了。
能夠定義一個PathBean用於封裝目標類的相關信息,如:目標類的全路徑名,目標類的Class對象
public class PathBean { public String path; // 跳轉目標全類名 public Class<?> targetClass; // 跳轉目標類的Class對象 public PathBean(String path, Class<?> targetClass) { this.path = path; this.targetClass = targetClass; } }
一個模塊中會有不少PathBean,能夠List存取PathBean,而又有不少模塊,可使用Map區分不一樣的模塊。
// key:模塊名,如shop模塊 value:該模塊下全部的Activity路徑信息 private static final Map<String, List<PathBean>> mGroupMap = new HashMap<>();
mGroupMap是一個Map,key是模塊名,如:shop模塊;value是該模塊下全部的Activity路徑信息.
將全部的路徑信息加入到mGroupMap中,使用時經過根據組名(模塊名)和路徑名獲取目標類對象。
/** * 將路徑信息加入到全局的Map中 */ public static void addGroup(String groupName, String path, Class<?> targetClass) { List<PathBean> pathBeans = mGroupMap.get(groupName); if (pathBeans == null) { pathBeans = new ArrayList<>(); pathBeans.add(new PathBean(path, targetClass)); mGroupMap.put(groupName, pathBeans); } else { pathBeans.add(new PathBean(path, targetClass)); } } /** * 根據組名和路徑名獲取目標類 */ public static Class<?> findTargetClass(String groupName,String path) { List<PathBean> pathBeans = mGroupMap.get(groupName); if (pathBeans != null) { for (PathBean pathBean : pathBeans) { if (!TextUtils.isEmpty(path) && path.equalsIgnoreCase(pathBean.path)) { return pathBean.targetClass; } } } return null; }
APT(Annotation Processing Tools)是一種處理註釋的工具,它對源代碼文件進行檢測找出其中的Annotaion,使用Annotation進行額外的處理。Annotation處理器在處理Annotation時能夠根據源文件中的Annotaion生成額外的源文件和其它的文件(文件具體內容由Annotaion處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一塊兒生成class文件。
通俗的理解:根據規則,幫助咱們生成代碼,生成類文件。
1. APT核心實現原理
編譯時Annotation解析的基本原理是,在某些代碼元素上(如類型、函數、字段等)添加註解,在編譯時javac編譯器會檢查AbstractProcessor的子類,而且調用該類型的process函數,而後將添加了註解的全部元素都傳遞到process函數中,使得開發人員能夠在編譯器進行相應的處理,例如:根據註解生成新的java類,這也就是ARouter、Butterknife、Dragger等開源庫的基本原理。
2. java源文件編程層Class文件
工具是經過javac工具,註解處理器是一個在javac中的,用來編譯時掃描和處理的註解工具。能夠認爲是特定的註解,註冊你本身的註解處理器。
3. 怎麼註冊註解處理器
MyProcessor到javac中。你必須提供一個.jar文件。就像其它.jar文件同樣,你打包你的註解處理器到此文件中。而且,在你的jar中,你須要打包一個特定的文件javax.annotation.processing.Processor到META-INF/services路徑下。
1. jar
2. @AutoService
這是一個其它註解處理器中引入的註解。AutoService註解處理器是Google開發的,用來生成META-INF/services/javax.annotation.processing.Processor文件的。咱們能夠在註解處理器中使用註解。很是方便
3. 結構體語言
對於java源文件來講,也是一種結構體語言。JDK中提供了用來描述結構體語言元素的接口。
在註解處理過程當中,咱們掃描全部的java源文件。源代碼的每一部分都是一個特定類型的Element。換句話說:Element表明程序的元素,例如包、類或者方法。每一個Element表明一個靜態的、語言級別的構件。
package com.example; // PackageElement public class Foo { // TypeElement private int a; // VariableElement private Foo other; // VariableElement public Foo {} // ExecutableElement public void setA( // ExecutableElement int newA // VariableElement ) {} }
Element程序元素
PackageElement 表示包程序元素。 TypeElement 表示一個類或接口程序元素。 ExecutableElement 表示類或接口的方法,構造函數或初始化器(靜態或實例),包括註釋類型元素。 VariableElement 表示一個字段, 枚舉常量,方法或構造函數參數,局部變量,資源變量或異常參數。 TypeParameterElement 表示通用類,接口,方法或構造函數元素的正式類型參數。
Types
一個用來處理TypeMirror的工具類
Filer
使用Filer你能夠建立java文件
1. process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
至關於每一個處理器的主函數main()。能夠在這裏寫你的掃描、評估和處理註解的代碼,以及生成java文件。輸入參數RoundEnvironment,可讓你查詢出包含特定註解的被註解元素。
2. getSupportedAnnotationTypes()
這個註解處理器是註冊給哪一個註解的。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的註解類型的合法全稱。換句話說,你在這裏定義你的註解處理器註冊到哪些註解上。
3. getSupportedSourceVersion()
用來指定你使用的java版本。一般這裏返回SourceVersion.latestSupported()。若是你有足夠的理由只支持java6的話,你也能夠返回SourceVersion.RELEASE_6
4. getSupportedOptions()
用來指定註解處理器處理的選項參數。須要在gradle文件中配置選項參數值
// 在gradle文件中配置選項參數值(用於APT傳參接收) // 切記:必須寫在defaultConfig節點下 javaCompileOptions { annotationProcessorOptions { arguments = [moduleName: project.getName()] } }
這些API也可使用註解的方式指定:
// AutoService則是固定的寫法,加個註解便可 // 經過auto-service中的@AutoService能夠自動生成AutoService註解處理器,用來註冊 // 用來生成 META-INF/services/javax.annotation.processing.Processor 文件 @AutoService(Processor.class) // 容許/支持的註解類型,讓註解處理器處理(新增annotation module) @SupportedAnnotationTypes({"com.example.modular.annotations.ARouter"}) // 指定JDK編譯版本 @SupportedSourceVersion(SourceVersion.RELEASE_7) // 註解處理器接收的參數 @SupportedOptions("moduleName") public class ARouterProcessor extends AbstractProcessor { // ignore }
// ignore public class ARouterProcessor extends AbstractProcessor { // 操做Element工具類 (類、函數、屬性都是Element) private Elements elementUtils; // type(類信息)工具類,包含用於操做TypeMirror的工具方法 private Types typeUtils; // Messager用來報告錯誤,警告和其餘提示信息 private Messager messager; // 文件生成器 類/資源,Filter用來建立新的源文件,class文件以及輔助文件 private Filer filer; // 模塊名,經過getOptions獲取build.gradle傳過來 private String moduleName; // 該方法主要用於一些初始化的操做,經過該方法的參數ProcessingEnvironment能夠獲取一些列有用的工具類 @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); // processingEnv是父類受保護屬性,能夠直接拿來使用。 // 其實就是init方法的參數ProcessingEnvironment // processingEnv.getMessager(); //參考源碼64行 elementUtils = processingEnvironment.getElementUtils(); messager = processingEnvironment.getMessager(); filer = processingEnvironment.getFiler(); typeUtils = processingEnvironment.getTypeUtils(); // 經過ProcessingEnvironment去獲取build.gradle傳過來的參數 Map<String, String> options = processingEnvironment.getOptions(); if (options != null && !options.isEmpty()) { moduleName = options.get("moduleName"); // 有坑:Diagnostic.Kind.ERROR,異常會自動結束,不像安卓中Log.e那麼好使 messager.printMessage(Diagnostic.Kind.NOTE, "moduleName=" + moduleName); } } }
// ignore public class ARouterProcessor extends AbstractProcessor { /** * 至關於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) { // ignore } // ignore return true; } }
https://github.com/google/auto
1. Android Studio 3.2.1 + Gradle 4.10.1 臨界版本
dependencies { // 註冊註解,並對其生成META-INF的配置信息,rc2在gradle5.0後有坑 // As-3.2.1 + gradle4.10.1-all + auto-service:1.0-rc2 implementation 'com.google.auto.service:auto-service:1.0-rc2' }
2. Android Studio 3.4.1 + Gradle 5.1.1 向下兼容
dependencies { // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4 compileOnly'com.google.auto.service:auto-service:1.0-rc4' annotationProcessor'com.google.auto.service:auto-service:1.0-rc4' }
1. ARouter註解
新建java library工程,工程名爲:annotations。而後建立ARouter註解
/** * <ul> * <li>@Target(ElementType.TYPE) // 接口、類、枚舉、註解</li> * <li>@Target(ElementType.FIELD) // 屬性、枚舉的常量</li> * <li>@Target(ElementType.METHOD) // 方法</li> * <li>@Target(ElementType.PARAMETER) // 方法參數</li> * <li>@Target(ElementType.CONSTRUCTOR) // 構造函數</li> * <li>@Target(ElementType.LOCAL_VARIABLE)// 局部變量</li> * <li>@Target(ElementType.ANNOTATION_TYPE)// 該註解使用在另外一個註解上</li> * <li>@Target(ElementType.PACKAGE) // 包</li> * <li>@Retention(RetentionPolicy.RUNTIME) <br>註解會在class字節碼文件中存在,jvm加載時能夠經過反射獲取到該註解的內容</li> * </ul> * * 生命週期:SOURCE < CLASS < RUNTIME * 一、通常若是須要在運行時去動態獲取註解信息,用RUNTIME註解 * 二、要在編譯時進行一些預處理操做,如ButterKnife,用CLASS註解。註解會在class文件中存在,可是在運行時會被丟棄 * 三、作一些檢查性的操做,如@Override,用SOURCE源碼註解。註解僅存在源碼級別,在編譯的時候丟棄該註解 */ @Target(ElementType.TYPE) // 該註解做用在類之上 @Retention(RetentionPolicy.CLASS) // 要在編譯時進行一些預處理操做。註解會在class文件中存在 public @interface ARouter { // 詳細路由路徑(必填),如:"app/MainActivity" String path(); // 路由組名(選填,若是不填寫,能夠從path中截取) String group() default ""; }
2. 自定義ARouterProcessor註解處理器
新建java library工程,工程名爲:compiler。建立ARouterProcessor類繼承AbstractProcessor。
爲了使用APT技術生成代碼,首先要設計咱們想生成的代碼模版,下面的代碼是由APT生成的。
package com.example.modular.shop; public class ARouter$$ShopActivity { public static Class<?> findTargetClass(String path) { if (path.equals("/shop/ShopActivity")) { return ShopActivity.class; } return null; } }
在ARouterProcessor類中實現process方法,處理支持的註解,生成咱們想要的代碼。
/** * * 至關於main函數,開始處理註解 * 註解處理器的核心方法,處理具體的註解,生成Java文件 * * @param set 支持註釋類型的集合,如:@ARouter註解 * @param roundEnvironment 當前或是以前的運行環境,能夠經過該對象查找找到的註解 * @return true 表示後續處理器不會再處理(已經處理完成) */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { // set集合,就是支持的註解集合,如:ARouter註解 if (set.isEmpty()) return false; // 獲取全部被@ARouter註解註釋的元素 Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class); if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) { for (Element element : elementsAnnotatedWith) { // 經過類節點獲取包節點,(全路徑名,如:com.example.modular.shop) String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); // 獲取被@ARouter註解的簡單類名 String simpleName = element.getSimpleName().toString(); // 注: 包名:com.example.modular.shop 被註解的類名:ShopActivity messager.printMessage(Diagnostic.Kind.NOTE, "包名:" + packageName + " 被註解的類名:" + simpleName); // 最終生成的類文件名 String finalClassName = "ARouter$$" + simpleName; try { // 建立一個新的源文件並返回一個JavaFileObject對象以容許寫入它 JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName); // 獲取此文件對象的Writer,開啓寫入功能 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註解 ARouter aRouter = element.getAnnotation(ARouter.class); writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n"); writer.write("return " + simpleName + ".class;\n"); writer.write("}\n"); writer.write("return null;\n"); writer.write("}\n}"); // 關閉寫入流 writer.close(); } catch (IOException e) { e.printStackTrace(); } } } return true; }
使用時要在build.gradle文件添加依賴,如購物shop模塊中
dependencies { implementation project(path: ':ARouter:annotations') annotationProcessor project(path: ':ARouter:compiler') }
而後在ShopActivity類上添加ARouter註解
@ARouter(path = "/shop/ShopActivity") public class ShopActivity extends AppCompatActivity {}
最後build -> Make Project,就會生成咱們想要的代碼。
注意:若是出現中文亂碼,在build.gradle中添加以下配置:
// java控制檯輸出中文亂碼 tasks.withType(JavaCompile) { options.encoding = "UTF-8" }
javapoet是square公司推出的開源java代碼生成框架,提供java api生成.java源文件。這個框架很是實用,也是咱們習慣的java面向對象OOP語法。能夠很方便的使用它根據註解生成對於的代碼。經過這種自動化生成代碼的方式,可讓咱們用更加簡潔優雅的方式替代繁瑣冗雜的重複工做。
項目主頁及源碼
https://github.com/square/jav...
依賴javapoet庫
dependencies { // 幫助咱們經過類調用的方式來生成java代碼 implementation 'com.squareup:javapoet:1.11.1' }
先來看下javapoet官網提供的一個簡單的例子,生成HelloWorld類
package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }
使用javapoet生成上面這段代碼:
MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); javaFile.writeTo(System.out);
javapoet jar中提供了8個經常使用的類
javapoet字符串格式化規則
$L 字面量,如:"int value=$L", 10 $S 字符串,如:$S, "hello" $T 類、接口,如:$T, MainActivity $N 變量,如:user.$N, name
接下來仍是生成ARouter$$ShopActivity
類
package com.example.modular.shop; public class ARouter$$ShopActivity { public static Class<?> findTargetClass(String path) { return path.equals("/shop/ShopActivity") ? ShopActivity.class : null; } }
使用javapoet生成ARouter$$ShopActivity
類
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { // 獲取全部被@ARouter註解註釋的元素 Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class); if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) { for (Element element : elementsAnnotatedWith) { // 經過類節點獲取包節點,(全路徑名,如:com.example.modular.shop) String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); // 獲取被@ARouter註解的簡單類名 String simpleName = element.getSimpleName().toString(); // 注: 包名:com.example.modular.shop 被註解的類名:ShopActivity messager.printMessage(Diagnostic.Kind.NOTE, "包名:" + packageName + " 被註解的類名:" + simpleName); // 最終生成的類文件名 String finalClassName = "ARouter$$" + simpleName; ARouter aRouter = element.getAnnotation(ARouter.class); ClassName targetClassName = ClassName.get((TypeElement) element); // 構建方法體 MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(Class.class) // 返回值Class<?> .addParameter(String.class, "path") .addStatement("return path.equals($S) ? $T.class : null", aRouter.path(), targetClassName) .build(); // 構建類 TypeSpec finalClass = TypeSpec.classBuilder(finalClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(findTargetClass) .build(); // 生成文件 JavaFile javaFile = JavaFile.builder(packageName, finalClass) .build(); try { javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } } return true; }
若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)