本文主要内容:
组件之间如何进行通信?暂不涉及组件单独编译的动态配置。
一、新建一个项目
除app
模块之外,再新建四个模块,分别为usercenter
(用户中心),order
(订单),payment
(支付)和common
(统一工具);由于本框架考虑使用注解+APT技术,所以再新建两个java模块,分别为router
(注解),router_compiler
(注解处理器)。项目结构大致如下:
二、模块分工
app
: 主模块,负责展示商品;
common
:基础工具层,提供网络、统一封装;
usercenter
:用户中心,用户信息操作相关;
order
:订单模块,用户订单相关操作;
payment
:支付模块,用户支付订单相关操作;
router
:定义组件化相关注解;
router_compiler
:注解处理器模块;
app的主要流程:用户打开app,进入到app模块,进行商品的选购;选购完成之后,下单进入order模块,order模块操作完成之后,进入支付模块进行付款;付款完成之后返回订单模块。
app模块可以进入到个人中心,个人中心可以查看订单;
三、开始代码编写
组件与组件之间是相互没有依赖的,那么,我们组件之间如何进行通信呢?
一般有两种思路:
- 像EventBus那样,做一个事件总线,使用发布-订阅的模式,各组件向总线订阅消息,有消息时进行处理。
- 做一个路由框架,用一个路由表来一一对应各组件对外公开的业务,其他组件可以通过这个路由表与相应的组件进行通信。
这里我们选择第二种做法。
一种比较简单的实现就是在common
模块,手动维护一个路由表,每个模块有对外的业务就要这个路由表里增加一条路由。当然,这是可以实现组件之间的通信的,不过每个模块都去手动修改同一个路由表,比较麻烦,也容易造成冲突,不易维护。
这里我们使用注解+注解处理器的方式,自动的生成路由,可以避免这种情况。
首先,定义注解
在router
模块里,定义一个注解:
@Target(ElementType.TYPE) //只作用于类
@Retention(RetentionPolicy.CLASS) //编译期保留
public @interface Router {
/**
* 获取路径
*
* @return 路径,做为路由表的key
*/
String path();
}
需要提供服务的页面就加上这个注解,如payment
模块需要对外提供支付服务,那么我们在payment
模块里的PaymentActivity上加上Router
注解,并传入一个path:
/**
* 支付组件首页,展示支付方式,调起具体支付流程,并返回给调用方支付结果
*/
@Router(path = "com.example.payment.PaymentActivity")
public class PaymentActivity extends BaseActivity {
//...
}
这里的path我们直接使用类的全类名。
那么,Router注解标记的类,我们应该怎么处理呢?
我们应该拿到注解标记的类,对应的path,生成一个把这些信息添加进路由表,注解处理器就可以做到这个工作。
注解处理器,处理注解
@AutoService(Processor.class) //注册
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) //指明需要处理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})
public class RouterProcessor extends AbstractProcessor {
// 操作Element的工具类(类,函数,属性,其实都是Element)
private Elements elementTool;
// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
private Types typeTool;
// Message用来打印 日志相关信息
private Messager messager;
// 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
private Filer filer;
//
private String options; // (模块传递过来的)模块名 app,personal
private String aptPackage; // (模块传递过来的) 包名
@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壳 传递过来的书籍,才能证明我们的 APT环境搭建完成
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环境搭建完成");
} else {
print("APT环境有问题,请检查options 和 aptPackage");
}
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 获取到系统类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("发现@Router注解的地方" + element.getSimpleName());
// Router router = element.getAnnotation(Router.class);
// 必须是Activity
TypeMirror elementMirror = element.asType(); // 当前 == Activity
if (typeTool.isSubtype(elementMirror, activityMirror)) {
print("检查当前被注解的类,确认是Activity类或及子类");
} else {
// 不匹配抛出异常,这里谨慎使用!考虑维护问题
throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
}
}
//获取到使用注解的类
//通过得到的信息,自动生成一个路由表
TypeElement implementType = elementTool.getTypeElement(ProcessorConfig.IROUTER_PATH);
try {
createPathFile(elementSet, implementType);
} catch (IOException e) {
e.printStackTrace();
print("自动创建路由表失败");
}
return true;
}
return false;
}
/**
* PATH 生成
*
* @param implementType
* @throws IOException
*/
private void createPathFile(Set<? extends Element> elementSet, TypeElement implementType) throws IOException {
// 判断 map仓库中,是否有需要生成的文件
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();
// 生成 和 类 等等,结合一体
JavaFile.builder(aptPackage, // 包名
className) // 类构建完成
.build() // JavaFile构建完成
.writeTo(filer); // 文件生成器开始生成类文件
print("自动生成" + clazzName + "成功,请在build/generated/ap_generated_sources/debug/out" + "目录下查看");
}
private void print(String msg) {
messager.printMessage(Diagnostic.Kind.NOTE, msg);
}
如上,我们在router_compiler
模块里新建一个处理器类RouterProcessor
继承自AbstractProcessor
,并实现其两个方法,
init()
: 做一些初始化操作,如获取工具类,Messager(打印日志),filer(文件生成器)等等
process()
: 对注解进行处理。
这里需要说明一下
RouterProcessor
类使用到的注解:
@AutoService(Processor.class) : 把此注解处理类,进行注册。不注册不能发挥作用。
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) :指明需要处理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) :指明Java版本
process()
方法里,我们拿到Router
注解处理的类集合,根据这个集合,调用createPathFile()
生成我们的路由表。
createPathFile()
生成路由表的过程,可以通过硬编码的形式写文件,不过这种方法容易出错。这里我们使用javapoet
来生成代码,如何使用请移步https://github.com/square/javapoet
下面是自动生成的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;
}
}
payment模块路由表目录
说明:IRouter是在模块
router
里定义的一个接口,用于给各模块的路由表制定一个标准。里面定义一个方法,返回一个Map<String,Class>对象。
public interface IRouter {
Map<String, Class> onCreate();
}
到此,我们已经完成一大部分了,第个模块都生成了自己的路由表。但是这些路由表都是在各自的模块中,依然无法被其他模块引用到。此时我们想到,是否可以在common模块里,定义一个RouterManager
处理类,把各模块的路由表集中到这个类里进行管理呢?
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, "要启动的Activity未注册: " + 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, "要启动的Activity未注册: " + path);
}
}
}
}
可以看到,这里是使用枚举定义了一个单例,提供了三个方法:
addRouterTable()
:往mRouterMap
里添加路由表;
startActivityWith()
与startActivityForResultWith()
则是根据根据传入的path,对路由进行查找,匹配之后,进行跨组件跳转。
上在只是提供了添加路由的方法,但是在什么时候执行呢?
当然是在app启动越早越好,因为你并不知道用户从启动app到需要跨组件需要多久,也许很快。
所以我们想到,自定义一个Application,在Application的onCreate方法里,开个异步线程,通过反射获取实例化对应模块的路由表,然后调用RouterManager.instance.addRouterTable(routerMap);
把路由添加进行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();
}
}
执行跨组件通信
在order
模块的OrderActivity
里,通过RouterManager.instance.startActivityForResultWith(this, "com/example/payment/PaymentActivity", bundle, payRequestCode);
调用到支付模块进行支付:
@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)之间的通信就完成了。
你可能会问了,如果与另一个组件的非Activity类进行通信,怎么做呢?其实原理都是一样的,这里就不贴代码了。
最后,再附上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文件中配置选项参数值(用于APT传参接收)
// 切记:必须写在defaultConfig节点下
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT] //packageNameForAPT只是一个字符串,一个包名,存入模块生成的路由类。
}
}
}
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'
// 公共基础库
implementation project(path: ":common")
// router 专用 注解模块
implementation project(path: ":router")
// router 专用 注解处理器
annotationProcessor project(path: ':router_compiler')
}
网友评论