组件化就是将一个app分成多个Module,每个Module都是一个组件,开发的过程中可以单独调试部分组件,也可以集成到一个app中,组件间不需要互相依赖。组件化中会涉及到几个关键点:路由、组件间通信、组件初始化、组件/集成模式切换。
一、组件间通信
组件间通信主要利用SPI技术,SPI全称Service Provider Interface。实质就是为某个接口寻找服务的机制,将装配的控制权移交给ServiceLoader。Module间需要通信的部分基于接口编程,并开放接口给其他Module进行通信调用。
每个模块有不同的实现service provider,然后通过SPI机制自动注册到一个配置文件中,就可以实现在程序运行时扫描加载同一接口的不同service provider。这样模块之间不会基于实现类硬编码,可插拔。
如上图所示,首先定义一个接口Module。里面存放的是各个Module对外暴露的接口。而接口的实现类在各个Module中。
再看下订单Order Module,其Module对外暴露接口IOrderEntry的实现类如下:
import com.example.aninterface.IOrderEntry;
import com.example.order.OrderActivity;
public class OrderEntry implements IOrderEntry {
@Override
public String sayHello() {
String helloWorld = "hello world from Module Order";
return helloWorld;
}
@Override
public void gotoOrderEntry(Context context){
Intent intent = new Intent();
intent.setClassName(context,OrderActivity.class.getName());
context.startActivity(intent);
}
}
OrderEntry实现了IOrderEntry对外暴露的接口方法,返回一个字符串以及执行一个界面跳转。
然后是要将接口实现类注册到配置文件中,spi的机制就是注册到META-INF/services中。需要新建一个以接口全路径名作为文件名的文件,文件保存在与res同级的resources/META-INF/services目录下。
com.example.aninterface.IOrderEntry文件内容如下:
com.example.order.entry.OrderEntry
文件内容就是接口实现类的全限定名。
通过ServiceLoader加载实现类,然后得到迭代器,通过迭代器来调用实现类中的方法,从而达到调用Module暴露的接口的目的。
ServiceLoader orderEntry = ServiceLoader.load(IOrderEntry.class);
Iterator iterator = orderEntry.iterator();
if (iterator.hasNext()){
IOrderEntry orderEntryImp = iterator.next();
String result = orderEntryImp.sayHello();
orderEntryImp.gotoOrderEntry(MainActivity.this);
Toast.makeText(getApplicationContext(),result,Toast.LENGTH_LONG).show();
}else{
Log.e(TAG, "onClick: did not find service iml");
}
路由
通过注解Annotation(编译时注解)以及APT(Annotation Processing Tool)来实现路由的收集。APT是一种处理注解的工具,可以对源代码文件进行检测并找出其中的Annotation,获取注解中的信息。
首先定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String value();
}
在需要进行路由跳转的Activity中增加注解标注。
@Route("order/OrderActivity")
public class OrderActivity extends AppCompatActivity {
路由路径中order代表组件名称。
在Module router_complier主要进行注解的处理。build.gradle配置如下:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation project(':router_annotation')
implementation 'com.squareup:javapoet:1.11.1'//方便编写代码的
}
sourceCompatibility = "7"
targetCompatibility = "7"
AutoService 主要的作用是注解 processor 类,自动生成。JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码(用代码写代码,生成类)。router_annotation中是定义的注解。
router_complier中增加一个注解处理器RouteProcess。
@AutoService(Processor.class)
public class RouteProcess extends AbstractProcessor {
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv){
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
mMessager.printMessage(Diagnostic.Kind.WARNING, "initlizing...");
}
@Override
public Set getSupportedAnnotationTypes() {
mMessager.printMessage(Diagnostic.Kind.WARNING, "get supported types");
HashSet supportedTypes = new LinkedHashSet<>();
supportedTypes.add(Route.class.getCanonicalName());
return supportedTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
if(set.size() <=0){
return false;
}
Messager messager = processingEnv.getMessager();
mMessager.printMessage(Diagnostic.Kind.WARNING,"=======Route annotation processor");
HashMap mapUrls = new HashMap<>();
//获取注解
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Route.class);
if (elements.size() <=0){
return false;
}
for (Element element:elements){
TypeElement variableElement = (TypeElement) element;
String fullClassName = variableElement.getQualifiedName().toString();//value
mMessager.printMessage(Diagnostic.Kind.WARNING, "className ="+fullClassName+" elemntSize="+elements.size());
mapUrls.put(element.getAnnotation(Route.class).value(),fullClassName);
}
//成员变量path
FieldSpec fieldPath = FieldSpec.builder(String.class,"path",Modifier.PUBLIC).build();
//成员变量pathMap
ClassName setName = ClassName.get("java.util","HashMap");
ClassName stringName = ClassName.get("java.lang","String");
TypeName setType = ParameterizedTypeName.get(setName,stringName,stringName);
FieldSpec fieldPathMap = FieldSpec.builder(setType,"pathMap").build();
//成员方法
MethodSpec methodAdd = MethodSpec.methodBuilder("addPath")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class,"path")
.addParameter(String.class,"value")
.beginControlFlow("if(pathMap == null)")
.addStatement("$N = new HashMap()",fieldPathMap)
.endControlFlow()
.addStatement("$N.put("+"path,value"+")",fieldPathMap)
.returns(void.class).build();
MethodSpec.Builder methodAddBuidler = MethodSpec.methodBuilder("getAllPath")
.addModifiers(Modifier.PUBLIC)
.beginControlFlow("if(pathMap == null)")
.addStatement("$N = new HashMap()",fieldPathMap)
.endControlFlow();
String groupName = "";
for (Map.Entry entry:mapUrls.entrySet()){
groupName = entry.getKey();
String key = entry.getKey();
String value = entry.getValue();
methodAddBuidler.addStatement("pathMap.put($S,$S)",key,value);
}
//确定分组名称,如果以“/”能得到分组名称,则以此作为生成的路由名称,若为空,则以跟时间相关的来取名称
if (groupName != null && !groupName.isEmpty() && groupName.contains("/")){
groupName = groupName.split("/")[0];
}else{
groupName = System.currentTimeMillis()%100+"";
}
methodAddBuidler.addStatement("return $N",fieldPathMap);
MethodSpec methodAddAll = methodAddBuidler.returns(ParameterizedTypeName.get(ClassName.get(Map.class)
,ClassName.get(String.class),ClassName.get(String.class))).build();
int randNum = (int) (System.currentTimeMillis()%10);
messager.printMessage(Diagnostic.Kind.WARNING,"randomInt"+randNum);
//构建类 以下划线作为分隔,以$作为分隔,后面根据包名查找类时匹配有问题
TypeSpec routerClass = TypeSpec.classBuilder("MyRouterClass_"+groupName)
.addModifiers(Modifier.PUBLIC)
.addField(fieldPath)
.addField(fieldPathMap)
.addMethod(methodAdd)
.addMethod(methodAddAll)
.build();
// TypeSpec autoClass = TypeSpec.classBuilder("MyRouterClass").addModifiers(Modifier.PUBLIC).addField(String.class,"path",Modifier.PUBLIC).build();
JavaFile javaFile = JavaFile.builder("com.apt.demo.routes", routerClass).build();
try{
Filer filer = processingEnv.getFiler();
javaFile.writeTo(filer);
}catch (IOException e){
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.WARNING,"generate apt class file fail");
}
return true;
}
}
生成的类中使用HashMap保存了相应Module中路由路径和activity实际类名的对应关系。需要注意的是每个Module中都需要生成一个独立的类来保持路由映射关系,因为每个Module都会执行RouteProcess注解处理器,如果都用同一个类来保存映射关系,每个Module构建时会导致重复生成同一个类而报错。
调用DRouter的init2方法,将所有Module的路径保存到一个map中。
public boolean init2(Context context){
try {
//将此包名下所有的类实例化,得到路由路径
List routeClassList = ClassUtils.getClassesList(context,"com.apt.demo.routes");
for(Class clz : routeClassList){
Object routeInstance = clz.newInstance();
Method method = clz.getDeclaredMethod("getAllPath");
Map groupRoutes = (Map) method.invoke(routeInstance);
routes.putAll(groupRoutes);
}
return true;
} catch (IllegalAccessException e) {
e.printStackTrace();
return false;
} catch (InstantiationException e) {
e.printStackTrace();
return false;
} catch (NoSuchMethodException e) {
e.printStackTrace();
return false;
} catch (InvocationTargetException e) {
e.printStackTrace();
return false;
}
}
ClassUtils.getClassesList方法查找指定包名下的所有类。处理路由注解时,apt生成的java文件(类)都在同一个包名下面。
路由跳转:
DRouter.getInstance().build("order/OrderActivity").navigation(new DRouter.NavCallback() {
@Override
public void onLost(String routePath) {
Toast.makeText(MainActivity.this,"路由未找到",Toast.LENGTH_SHORT).show();
}
});
跳转过程就是根据路由路径查找到对应的activity,然后执行跳转。在build方法中找到对应activity。在navigation中执行activity跳转。
public DRouter build(String routePath){
if (routePath != null && !routes.isEmpty()){
selRoutePath = routes.get(routePath);
}
return this;
}
public void navigation(NavCallback callback){
this.callback = callback;
try{
if(TextUtils.isEmpty(selRoutePath)){
throw new ActivityNotFoundException();
}
Intent intent = new Intent();
intent.setClassName(context,selRoutePath);
((Activity)context).startActivity(intent);
}catch(ActivityNotFoundException exp){
if (callback != null){
callback.onLost(selRoutePath);
}
}
}
组件初始化
不同Module在执行前可能会需要一些初始化工作,如一些第三方库的初始化等。没组件化之前可以统一放在主工程中的Application中进行,但是组件化之后,由于各个Module之间都解耦了,只能通过接口或者路由进行通信,这种情况下再以强耦合方式将各Module的初始化放在Application中肯定不合适。
可以有2种方案可供参考:
1、定义一个接口,各个Module在同一个包名下增加一个该接口的实现类,实现类中增加Module的初始化逻辑。在app的Application初始化时,查找该包名下的实现类,调用其中的方法完成初始化。
2、跟方法1类似,查找每个Module初始化的接口实现类,只不过通过注解来进行标记和查找。通过编译时注解(apt)记录接口初始化的接口实现类,在app的Application初始化时,拿到这些实现类,然后调用其中方法完成初始化。
初始化方法1
模块初始化接口如下:
/**
* 【说明】:app生命周期接口
*/
public interface IAppLifecycleService {
void onCreate();
}
在每个Module的固定包名下增加初始化实现类,在com.example.applife包名下
package com.example.applife;
import android.util.Log;
import com.example.router.annotation.RouterAppService;
import com.example.router.applife.IAppLifecycleService;
@RouterAppService(value = "order")
public class OrderAppLifecycleService implements IAppLifecycleService {
private static final String TAG = "OrderAppLifecycleServic";
@Override
public void onCreate() {
Log.e(TAG, "onCreate: 订单模块初始化");
}
}
如上面代码所示,在订单Order模块,在指定包名下增加Module的初始化实现类,以及初始化逻辑。RouterAppService是初始化方法2中所使用的注解。
如何查找指定包名下、指定接口的所有实现类,增加一个ClassUtils类。
public class ClassUtils {
public static List getClassesList(Context mContext, String packageName,Class clzzInterface) {
ArrayList classes = new ArrayList<>();
ArrayList classList = new ArrayList<>();
try {
String packageCodePath = mContext.getPackageCodePath();
DexFile df = new DexFile(packageCodePath);
String regExp = "^" + packageName + ".\\w+$";
for (Enumeration iter = df.entries(); iter.hasMoreElements(); ) {
String className = (String) iter.nextElement();
if (className.matches(regExp)) {
Class> clazz = Class.forName(className);
Log.e(TAG, "getClasses: "+clazz.getName());
//父类.class.isAssignableFrom(子类.class) 调用者为父类,参数为本身或者其子类。
//用于判断继承关系
if (clzzInterface.isAssignableFrom(clazz) && !clazz.isInterface()){
classes.add(className);
classList.add(clazz);
// continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return classList;
}
}
在主工程app的Application中调用上述方法找到初始化实现类,完成初始化。
public class MainApplication extends Application {
private static final String TAG = "MainApplication";
@Override
public void onCreate() {
super.onCreate();
// 法1 - 模块初始化,每个模块在固定包名下定义一个接口实现类
List appServiceList = ClassUtils.getClassesList(this,"com.example.applife", IAppLifecycleService.class);
for (int i = 0; i < appServiceList.size(); i++) {
try {
IAppLifecycleService service = (IAppLifecycleService) appServiceList.get(i).newInstance();
service.onCreate();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
初始化方法2
在Module router_annotation增加一个注解RouterAppService。里面的value值是对应的Module名称。
/**
* 【说明】:app生命周期初始化注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RouterAppService {
String value();
}
在Module router_complier中增加一个注解处理器RouterAppServiceProcess。
@SupportedAnnotationTypes("com.example.router.annotation.RouterAppService")
@AutoService(Processor.class)
public class RouterAppServiceProcess extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
if(set.size() <=0){
return false;
}
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.WARNING,"========RouterAppService annotation process start");
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(RouterAppService.class);
if (elements.size() <=0){
return false;
}
List fullClassNameList = new ArrayList<>();
String groupName = "";
for(Element element:elements){
TypeElement variableElement = (TypeElement) element;
String fullClassName = variableElement.getQualifiedName().toString();
messager.printMessage(Diagnostic.Kind.WARNING,"className="+fullClassName+" elemntSize="+elements.size());
fullClassNameList.add(fullClassName);
groupName = variableElement.getAnnotation(RouterAppService.class).value();
}
//构建方法
MethodSpec.Builder methodAppBuilder = MethodSpec.methodBuilder("getAppServices")
.addModifiers(Modifier.PUBLIC)
.addStatement("List serviceList = new $T<>()",ArrayList.class);
for (String className : fullClassNameList){
methodAppBuilder.addStatement("serviceList.add($S)",className);
}
methodAppBuilder.addStatement("return serviceList");
MethodSpec methodGetService = methodAppBuilder.returns(ParameterizedTypeName.get(ClassName.get(List.class),ClassName.get(String.class))).build();
if (groupName == null || groupName.isEmpty()){
groupName = System.currentTimeMillis()%100+"";
}
//构建类
TypeSpec serviceClass = TypeSpec.classBuilder("DAppServiceClass_"+groupName)
.addModifiers(Modifier.PUBLIC)
.addMethod(methodGetService)
.build();
JavaFile javaFile = JavaFile.builder("com.dj.apt.service",serviceClass).build();
try{
Filer filer = processingEnv.getFiler();
javaFile.writeTo(filer);
}catch (IOException e){
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.WARNING,"generate apt class file fail");
}
return true;
}
}
将被注解的类的类名全限定名保存下来,保存到一个List中。每个Module生成一个DAppServiceClass_组件名.java文件,里面getAppServices方法可以获取到该Module里初始化实现类的全限定名。生成的文件如下所示:
package com.dj.apt.service;
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
public class DAppServiceClass_order {
public List getAppServices() {
List serviceList = new ArrayList<>();
serviceList.add("com.example.applife.OrderAppLifecycleService");
return serviceList;
}
}
在DRouter类中增加方法,获取所有组件初始化的实现类的实例,进行初始化。
public List getAppServices(Context context){
List serviceList = new ArrayList<>();
List serviceNameList = new ArrayList<>();
try {
List serClassList = ClassUtils.getClassesList(context,"com.dj.apt.service");
for(Class clz : serClassList){
Object myRouterInstance = clz.newInstance();
Method methodGetService = clz.getDeclaredMethod("getAppServices");
List serNameList= (List) methodGetService.invoke(myRouterInstance);
serviceNameList.addAll(serNameList);
}
for(String serName:serviceNameList){
Class clSer = Class.forName(serName);
IAppLifecycleService instance = (IAppLifecycleService) clSer.newInstance();
serviceList.add(instance);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return serviceList;
}
ClassUtils.getClassesList方法获取指定包名下的所有类信息。getAppServices方法获取到所有Module中实现IAppLifecycleService接口的所有实现类的实例。
public class MainApplication extends Application {
private static final String TAG = "MainApplication";
@Override
public void onCreate() {
super.onCreate();
//法 2 - 模块初始化,每个模块定义一个接口实现类,用注解获取实现类,然后调用进行初始化
Log.e(TAG, "onCreate: 使用注解进行初始化");
List serviceList = DRouter.getInstance().getAppServices(getApplicationContext());
for (IAppLifecycleService service : serviceList){
service.onCreate();
}
}
}
在app的Application进行所有组件的初始化操作,还可以根据实际需求在application销毁时增加相关反初始化操作。
组件、集成模式切换
组件化开发中需要能进行组件单独开发调试以及集成到app中进行。
可以在gradle.properties中增加一个变量来控制是组件还是集成模式。
#true代表集成模式 false代表组件模式
isModule=false
moduleOrder的build.gradle配置
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
if (isModule.toBoolean()) {
multiDexEnabled true
applicationId "com.example.dj.order"
}
}
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug*//**'
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
如上所示,如果是组件模式,则gradle中应用application插件,并在sourceSets中配置不同的AndroidManifest文件,组件模式的AndroidManifest中设置了本地组件的application,可以在其中执行组件的初始化操作,指定LaunchActivity等。
moduleOrder的工程目录结构如下图所示:
如上图所示组件模式下有组件自己的application以及与集成模式不同的清单文件。
总结
本文主要对组件化中可能涉及到的路由、组件间通信、组件初始化、组件/集成模式切换等要点进行了阐述,并对其中涉及到的apt、spi等技术做了说明和简单实践。
代码参见:https://github.com/godtrace12/DRouter