元件化
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子子產品中,模拟跳轉代碼
-
為什麼需要組名?
隻加載相關類,節省記憶體
-
生成這些檔案有什麼用?
根據path,擷取類對象
根據組名,找到對應的list,根據從組名截取的path,擷取類對象。