本文主要內(nèi)容:
組件之間如何進(jìn)行通信?暫不涉及組件單獨(dú)編譯的動(dòng)態(tài)配置。
一、新建一個(gè)項(xiàng)目
除app模塊之外,再新建四個(gè)模塊,分別為usercenter(用戶中心),order(訂單),payment(支付)和common(統(tǒng)一工具);由于本框架考慮使用注解+APT技術(shù),所以再新建兩個(gè)java模塊,分別為router(注解),router_compiler(注解處理器)。項(xiàng)目結(jié)構(gòu)大致如下:

二、模塊分工
app: 主模塊,負(fù)責(zé)展示商品;
common:基礎(chǔ)工具層,提供網(wǎng)絡(luò)、統(tǒng)一封裝;
usercenter:用戶中心,用戶信息操作相關(guān);
order:訂單模塊,用戶訂單相關(guān)操作;
payment:支付模塊,用戶支付訂單相關(guān)操作;
router:定義組件化相關(guān)注解;
router_compiler:注解處理器模塊;
app的主要流程:用戶打開app,進(jìn)入到app模塊,進(jìn)行商品的選購(gòu);選購(gòu)?fù)瓿芍?,下單進(jìn)入order模塊,order模塊操作完成之后,進(jìn)入支付模塊進(jìn)行付款;付款完成之后返回訂單模塊。
app模塊可以進(jìn)入到個(gè)人中心,個(gè)人中心可以查看訂單;
三、開始代碼編寫
組件與組件之間是相互沒(méi)有依賴的,那么,我們組件之間如何進(jìn)行通信呢?
一般有兩種思路:
- 像EventBus那樣,做一個(gè)事件總線,使用發(fā)布-訂閱的模式,各組件向總線訂閱消息,有消息時(shí)進(jìn)行處理。
- 做一個(gè)路由框架,用一個(gè)路由表來(lái)一一對(duì)應(yīng)各組件對(duì)外公開的業(yè)務(wù),其他組件可以通過(guò)這個(gè)路由表與相應(yīng)的組件進(jìn)行通信。
這里我們選擇第二種做法。
一種比較簡(jiǎn)單的實(shí)現(xiàn)就是在common模塊,手動(dòng)維護(hù)一個(gè)路由表,每個(gè)模塊有對(duì)外的業(yè)務(wù)就要這個(gè)路由表里增加一條路由。當(dāng)然,這是可以實(shí)現(xiàn)組件之間的通信的,不過(guò)每個(gè)模塊都去手動(dòng)修改同一個(gè)路由表,比較麻煩,也容易造成沖突,不易維護(hù)。
這里我們使用注解+注解處理器的方式,自動(dòng)的生成路由,可以避免這種情況。
首先,定義注解
在router模塊里,定義一個(gè)注解:
@Target(ElementType.TYPE) //只作用于類
@Retention(RetentionPolicy.CLASS) //編譯期保留
public @interface Router {
/**
* 獲取路徑
*
* @return 路徑,做為路由表的key
*/
String path();
}
需要提供服務(wù)的頁(yè)面就加上這個(gè)注解,如payment模塊需要對(duì)外提供支付服務(wù),那么我們?cè)?code>payment模塊里的PaymentActivity上加上Router注解,并傳入一個(gè)path:
/**
* 支付組件首頁(yè),展示支付方式,調(diào)起具體支付流程,并返回給調(diào)用方支付結(jié)果
*/
@Router(path = "com.example.payment.PaymentActivity")
public class PaymentActivity extends BaseActivity {
//...
}
這里的path我們直接使用類的全類名。
那么,Router注解標(biāo)記的類,我們應(yīng)該怎么處理呢?
我們應(yīng)該拿到注解標(biāo)記的類,對(duì)應(yīng)的path,生成一個(gè)把這些信息添加進(jìn)路由表,注解處理器就可以做到這個(gè)工作。
注解處理器,處理注解
@AutoService(Processor.class) //注冊(cè)
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) //指明需要處理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解處理器接收的參數(shù)
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})
public class RouterProcessor extends AbstractProcessor {
// 操作Element的工具類(類,函數(shù),屬性,其實(shí)都是Element)
private Elements elementTool;
// type(類信息)的工具類,包含用于操作TypeMirror的工具方法
private Types typeTool;
// Message用來(lái)打印 日志相關(guān)信息
private Messager messager;
// 文件生成器, 類 資源 等,就是最終要生成的文件 是需要Filer來(lái)完成的
private Filer filer;
//
private String options; // (模塊傳遞過(guò)來(lái)的)模塊名 app,personal
private String aptPackage; // (模塊傳遞過(guò)來(lái)的) 包名
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
this.elementTool = processingEnvironment.getElementUtils();
this.typeTool = processingEnvironment.getTypeUtils();
this.filer = processingEnvironment.getFiler();
this.messager = processingEnvironment.getMessager();
print("RouterProcessor init");
// 只有接受到 App殼 傳遞過(guò)來(lái)的書籍,才能證明我們的 APT環(huán)境搭建完成
options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
print("options = " + options);
print("aptPackage = " + aptPackage);
if (options != null && aptPackage != null) {
print("APT環(huán)境搭建完成");
} else {
print("APT環(huán)境有問(wèn)題,請(qǐng)檢查options 和 aptPackage");
}
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 獲取到系統(tǒng)類Activity的類型
TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
TypeMirror activityMirror = activityType.asType();
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(Router.class);
if (elementSet != null) {
for (Element element : elementSet) {
print("發(fā)現(xiàn)@Router注解的地方" + element.getSimpleName());
// Router router = element.getAnnotation(Router.class);
// 必須是Activity
TypeMirror elementMirror = element.asType(); // 當(dāng)前 == Activity
if (typeTool.isSubtype(elementMirror, activityMirror)) {
print("檢查當(dāng)前被注解的類,確認(rèn)是Activity類或及子類");
} else {
// 不匹配拋出異常,這里謹(jǐn)慎使用!考慮維護(hù)問(wèn)題
throw new RuntimeException("@ARouter注解目前僅限用于Activity類之上");
}
}
//獲取到使用注解的類
//通過(guò)得到的信息,自動(dòng)生成一個(gè)路由表
TypeElement implementType = elementTool.getTypeElement(ProcessorConfig.IROUTER_PATH);
try {
createPathFile(elementSet, implementType);
} catch (IOException e) {
e.printStackTrace();
print("自動(dòng)創(chuàng)建路由表失敗");
}
return true;
}
return false;
}
/**
* PATH 生成
*
* @param implementType
* @throws IOException
*/
private void createPathFile(Set<? extends Element> elementSet, TypeElement implementType) throws IOException {
// 判斷 map倉(cāng)庫(kù)中,是否有需要生成的文件
if (elementSet.isEmpty()) {
return;
}
//類屬性 private Map<String, Class> mRouterMap = new HashMap<>();
TypeName mapTypeName = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map<String,
ClassName.get(Class.class)); // Map<String, Class>
MethodSpec.Builder onCreateMethodBuilder = MethodSpec.methodBuilder(ProcessorConfig.METHOD_ONCREATE)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(mapTypeName);
//方法體 mRouterMap.put(string, clazz.class);
for (Element element : elementSet) {
Router router = element.getAnnotation(Router.class);
onCreateMethodBuilder.addStatement("$T<$T, $T> routerMap = new $T<>()",
ClassName.get(Map.class), // Map
ClassName.get(String.class), // String
ClassName.get(Class.class), // Class
ClassName.get(HashMap.class)); // HashMap
onCreateMethodBuilder.addStatement("routerMap.put($S, $T.class)",
router.path(),
ClassName.get(((TypeElement) element)))
.addStatement("return routerMap")
.returns(mapTypeName);
}
//類定義 public class RouterManager implements IRouter {...}
String clazzName = ProcessorConfig.ROUTER_MANAGER + "$" + options;
TypeSpec className = TypeSpec.classBuilder(clazzName)
.addModifiers(Modifier.PUBLIC)
.addMethod(onCreateMethodBuilder.build())
.addSuperinterface(ClassName.get(implementType))
.build();
// 生成 和 類 等等,結(jié)合一體
JavaFile.builder(aptPackage, // 包名
className) // 類構(gòu)建完成
.build() // JavaFile構(gòu)建完成
.writeTo(filer); // 文件生成器開始生成類文件
print("自動(dòng)生成" + clazzName + "成功,請(qǐng)?jiān)赽uild/generated/ap_generated_sources/debug/out" + "目錄下查看");
}
private void print(String msg) {
messager.printMessage(Diagnostic.Kind.NOTE, msg);
}
如上,我們?cè)?code>router_compiler模塊里新建一個(gè)處理器類RouterProcessor 繼承自AbstractProcessor,并實(shí)現(xiàn)其兩個(gè)方法,
init(): 做一些初始化操作,如獲取工具類,Messager(打印日志),filer(文件生成器)等等
process(): 對(duì)注解進(jìn)行處理。
這里需要說(shuō)明一下
RouterProcessor類使用到的注解:
@AutoService(Processor.class) : 把此注解處理類,進(jìn)行注冊(cè)。不注冊(cè)不能發(fā)揮作用。
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) :指明需要處理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) :指明Java版本
process()方法里,我們拿到Router注解處理的類集合,根據(jù)這個(gè)集合,調(diào)用createPathFile()生成我們的路由表。
createPathFile()生成路由表的過(guò)程,可以通過(guò)硬編碼的形式寫文件,不過(guò)這種方法容易出錯(cuò)。這里我們使用javapoet來(lái)生成代碼,如何使用請(qǐng)移步https://github.com/square/javapoet
下面是自動(dòng)生成的payment模塊的路由表。
public class RouterTable$payment implements IRouter {
@Override
public Map<String, Class> onCreate() {
Map<String, Class> routerMap = new HashMap<>();
routerMap.put("com/example/payment/PaymentActivity", PaymentActivity.class);
return routerMap;
}
}

說(shuō)明:IRouter是在模塊
router里定義的一個(gè)接口,用于給各模塊的路由表制定一個(gè)標(biāo)準(zhǔn)。里面定義一個(gè)方法,返回一個(gè)Map<String,Class>對(duì)象。
public interface IRouter {
Map<String, Class> onCreate();
}
到此,我們已經(jīng)完成一大部分了,第個(gè)模塊都生成了自己的路由表。但是這些路由表都是在各自的模塊中,依然無(wú)法被其他模塊引用到。此時(shí)我們想到,是否可以在common模塊里,定義一個(gè)RouterManager處理類,把各模塊的路由表集中到這個(gè)類里進(jìn)行管理呢?
public enum RouterManager {
instance;
private Map<String, Class> mRouterMap;
public void addRouterTable(Map<String, Class> routerTable) {
if (mRouterMap == null) {
mRouterMap = new HashMap<>();
}
mRouterMap.putAll(routerTable);
}
public void startActivityWith(Context context, String path, Bundle bundle) {
if (path == null || path.isEmpty()) {
throw new RuntimeException("path can not be null or empty!");
}
Class clazz = mRouterMap.get(path);
if (clazz != null) {
Intent intent = new Intent(context, clazz);
if (bundle != null) {
intent.putExtras(bundle);
}
context.startActivity(intent);
} else {
ToastManager.show(context, "要啟動(dòng)的Activity未注冊(cè): " + path);
}
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public void startActivityForResultWith(Context context, String path, Bundle bundle, int requestCode) {
if (context instanceof Activity) {
Class clazz = mRouterMap.get(path);
if (clazz != null) {
Intent intent = new Intent(context, clazz);
if (bundle != null) {
intent.putExtras(bundle);
}
((Activity) context).startActivityForResult(intent, requestCode);
} else {
ToastManager.show(context, "要啟動(dòng)的Activity未注冊(cè): " + path);
}
}
}
}
可以看到,這里是使用枚舉定義了一個(gè)單例,提供了三個(gè)方法:
addRouterTable():往mRouterMap里添加路由表;
startActivityWith()與startActivityForResultWith()則是根據(jù)根據(jù)傳入的path,對(duì)路由進(jìn)行查找,匹配之后,進(jìn)行跨組件跳轉(zhuǎn)。
上在只是提供了添加路由的方法,但是在什么時(shí)候執(zhí)行呢?
當(dāng)然是在app啟動(dòng)越早越好,因?yàn)槟悴⒉恢烙脩魪膯?dòng)app到需要跨組件需要多久,也許很快。
所以我們想到,自定義一個(gè)Application,在Application的onCreate方法里,開個(gè)異步線程,通過(guò)反射獲取實(shí)例化對(duì)應(yīng)模塊的路由表,然后調(diào)用RouterManager.instance.addRouterTable(routerMap);把路由添加進(jìn)行RouterManager
public class BaseApplication extends Application {
private static final String TAG = "BaseApplication >>>>> ";
@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {
@Override
public void run() {
String[] bundles = new String[]{
"com.example.router.RouterTable$payment",
"com.example.router.RouterTable$order",
"com.example.router.RouterTable$usercenter"
};
for (String bundle : bundles) {
try {
Class clazz = Class.forName(bundle);
IRouter iRouter = (IRouter) clazz.newInstance();
Map<String, Class> routerMap = iRouter.onCreate();
RouterManager.instance.addRouterTable(routerMap);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
Log.i(TAG, "路由表已建好...");
}
}).start();
}
}
執(zhí)行跨組件通信
在order模塊的OrderActivity里,通過(guò)RouterManager.instance.startActivityForResultWith(this, "com/example/payment/PaymentActivity", bundle, payRequestCode);調(diào)用到支付模塊進(jìn)行支付:
@Router(path = "com.example.order.OrderActivity")
public class OrderActivity extends BaseActivity {
private static final int payRequestCode = 111;
private Button btnPay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order);
initView();
}
@Override
public void initView() {
btnPay = findViewById(R.id.btnPay);
btnPay.setOnClickListener(this);
}
@Override
public void onClick(View v) {
super.onClick(v);
if (v.getId() == R.id.btnPay) {
Bundle bundle = new Bundle();
bundle.putString("orderSn", "123456789");
bundle.putFloat("orderAmount", 99.99f);
RouterManager.instance.startActivityForResultWith(this, "com.example.payment.PaymentActivity", bundle, payRequestCode);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == payRequestCode) {
if (resultCode != Activity.RESULT_OK) {
return;
}
boolean paySuccess = data != null && data.getBooleanExtra("payResult", false);
toast("訂單'123456789'支付" + (paySuccess ? "成功" : "失敗"));
}
}
}
到此,我們的組件化后,組件(Activity與Activity)之間的通信就完成了。
你可能會(huì)問(wèn)了,如果與另一個(gè)組件的非Activity類進(jìn)行通信,怎么做呢?其實(shí)原理都是一樣的,這里就不貼代碼了。
最后,再附上payment模塊的gradle配置
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
// 都是為了 傳遞給 注解處理器
// 在gradle文件中配置選項(xiàng)參數(shù)值(用于APT傳參接收)
// 切記:必須寫在defaultConfig節(jié)點(diǎn)下
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT] //packageNameForAPT只是一個(gè)字符串,一個(gè)包名,存入模塊生成的路由類。
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// 公共基礎(chǔ)庫(kù)
implementation project(path: ":common")
// router 專用 注解模塊
implementation project(path: ":router")
// router 專用 注解處理器
annotationProcessor project(path: ':router_compiler')
}