一.什么是组件化
1.解决打包速度慢;
2.组件化模块功能,加速测试流程;
3.多人协同开发,任务分离。

二.组件化的实现
1.公共配置
在项目的目录下添加config.gradle配置文件;
1.版本控制
ini
rootProject.ext {
androidId = [
compileSdkVersion: 33,
minSdkVersion : 22,
targetSdkVersion : 33,
versionCode : 1,
versionName : "1.0"
]
appId = [
app : "com.seven.componenttest",
library: "com.seven.componentlibrary",
library2: "com.seven.componentlibrary2"
]
def ktx = "1.9.0"
def appcompat = "1.6.1"
def material = "1.8.0"
def constraintlayout = "2.1.4"
dependencies = [
"ktx" : "androidx.core:core-ktx:$ktx",
"appcompat" : "androidx.appcompat:appcompat:$appcompat",
"material" : "com.google.android.material:material:$material",
"constraintlayout": "androidx.constraintlayout:constraintlayout:$constraintlayout",
]
}
2.依赖控制、打包配置
ruby
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
apply {
from("../config.gradle")
}
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.androidId
def support =rootProject.ext.dependencies
def url=rootProject.ext.url
android {
namespace 'com.seven.componenttest'
compileSdk androidId.compileSdkVersion
defaultConfig {
applicationId appId.app
minSdk androidId.minSdkVersion
targetSdk 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'
buildConfigField("String","url",""${url.debug}"")
}
debug{
minifyEnabled false
buildConfigField("String", "url", ""${url.release}"")
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
support.each { k, v -> implementation v }
}
库配置
java
plugins {
id 'org.jetbrains.kotlin.android'
}
def isLibrary=componentLibray.toBoolean()
if (isLibrary) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId.library
def support =rootProject.ext.dependencies
android {
namespace 'com.seven.componentlibrary'
compileSdk androidId.compileSdkVersion
defaultConfig {
if(!isLibrary)
applicationId appId
versionCode androidId.versionCode
versionName androidId.versionName
minSdk androidId.minSdkVersion
targetSdk androidId.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (isLibrary) {
manifest.srcFile('src/main/AndroidManifest.xml')
} else {
manifest.srcFile('src/main/DEBUG/AndroidManifest.xml')
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
support.each { k, v -> implementation v }
}
2.实现模块间跳转
跳转方案与参数传递:
方案1:
1.EventBus 一对多 可能回有点混乱
2.BroadCastReceiver 动态注册
3.反射 维护成本高
4.隐式意图 比较麻烦
5.类加载 需要全类名,维护成本高 class.forName("xxxActivity")
方案2:定义一个基础模块使用Map将需要跳转的Activity的对应的path和class注册进去。
typescript
public class RecordPathManager {
private static Map<String, List<PathBean>> groupMap = new HashMap<>();
public static void joinGroup(String groupName, String pathName, Class<?> clazz) {
List<PathBean> pathBeans = groupMap.get(groupName);
if (pathBeans == null) {
pathBeans = new ArrayList<>();
pathBeans.add(new PathBean(pathName, clazz));
groupMap.put(groupName, pathBeans);
} else {
for (PathBean pathBean : pathBeans) {
if (!pathBean.getPath().equals(pathName)) {
pathBeans.add(new PathBean(pathName, clazz));
groupMap.put(groupName, pathBeans);
}
}
}
}
public static Class<?> getTargetClazz(String groupName, String pathName) {
List<PathBean> pathBeans = groupMap.get(groupName);
if (null == pathBeans)
return null;
for (PathBean pathBean : pathBeans) {
if (pathBean.getPath().equalsIgnoreCase(pathName)) {
return pathBean.getClazz();
}
}
return null;
}
加入到路由表中去
kotlin
class App :Application() {
override fun onCreate() {
super.onCreate()
RecordPathManager.joinGroup("app","main",MainActivity::class.java)
RecordPathManager.joinGroup("component1","main",com.seven.componentlibrary.MainActivity::class.java)
RecordPathManager.joinGroup("component2","main",com.seven.componentlibrary2.MainActivity::class.java)
}
}
跳转测试
kotlin
fun main2C1(view: View) {
startActivity(Intent(this, RecordPathManager.getTargetClazz("component1", "main")))
}
fun main2C2(view: View) {
startActivity(Intent(this, RecordPathManager.getTargetClazz("component2", "main")))
}
3.APT技术实现模块跳转
上面的代码帮助我们做了简单的路由跳转的实现,但是我们需要手动的添加或者删除路由信息,比较麻烦,所以我们使用APT技术帮助我们实现:

kotlin
fun main2C1(view: View) {
startActivity(Intent(this, com.seven.componentlibrary.`MainActivity$$ARouter`.findTargetClass("/component1/main/MainActivity")))
//startActivity(Intent(this, RecordPathManager.getTargetClazz("component1", "main")))
}
fun main2C2(view: View) {
startActivity(Intent(this,
com.seven.componentlibrary2.`MainActivity$$ARouter`.findTargetClass("/component2/main/MainActivity")))
//startActivity(Intent(this, RecordPathManager.getTargetClazz("component2", "main")))
}
ini
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils=processingEnv.getElementUtils();
typeUtils=processingEnv.getTypeUtils();
messager=processingEnv.getMessager();
filer=processingEnv.getFiler();
String content = processingEnv.getOptions().get("content");
messager.printMessage(Diagnostic.Kind.NOTE,content+"----.>>>>>>");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE,"process");
if(set.isEmpty()){
messager.printMessage(Diagnostic.Kind.NOTE,"set empty");
return false;
}
Set<? extends Element> elements=roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element:elements) {
String packageName=elementUtils.getPackageOf(element).getQualifiedName().toString();
String clazzName=element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE,"注解的类:"+clazzName);
MainActivity$$ARouter.findTargetClass("/app/main/MainActivity")来查找clazz
String finalClzName=clazzName+"$$ARouter";
try{
JavaFileObject sourceFile = filer.createSourceFile( packageName +"."+ finalClzName);
Writer writer = sourceFile.openWriter();
writer.write( "package "+ packageName +";\n");
writer.write( "public class "+ finalClzName + "{\n");
writer. write( "public static Class<?> findTargetClass(String path){\n");
ARouter aRouter = element.getAnnotation(ARouter.class);
writer.write("if(path.equals(""+ aRouter.path()+"")){\n");
writer.write( "return "+ clazzName +".class;\n}\n");
writer.write( "return null;\n");
writer.write( "}\n}");
writer.close();
}catch (Exception e){
e.printStackTrace();
}
}
return true;
}
但是这种无法在组件间路由,因为每个路由虽能被app拿到,但是各个组件间是无法拿到彼此的路由的。
4.javapoet增加基类接口实现模块跳转

1.顶层接口定义
vbnet
public interface ARouterLoadGroup {
/**
* 获取分组的对应的clz
* String 为group
* @return
*/
Map<String,Class<? extends ARouterLoadPath>> loadGroup();
}
public interface ARouterLoadPath {
/**
* 分组下的 String 为path
* @return
*/
Map<String, RouterBean> loadPath();
}
2.定义注解和注解处理器
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {
String path();
String group() default "";
}
arduino
public class RouterBean {
public enum TYPE {
ACTIVITY
}
public TYPE getType() {
return type;
}
public Element getElement() {
return element;
}
public Class<?> getClazz() {
return clazz;
}
public String getGroup() {
return group;
}
public String getPath() {
return path;
}
public void setType(TYPE type) {
this.type = type;
}
public void setGroup(String group) {
this.group = group;
}
public void setPath(String path) {
this.path = path;
}
private TYPE type;
private Element element;
private Class<?> clazz;
private String group;
private String path;
@Override
public String toString() {
return "RouterBean{" +
"group='" + group + ''' +
", path='" + path + ''' +
'}';
}
private RouterBean(Builder builder) {
this.element = builder.element;
this.group = builder.group;
this.path = builder.path;
}
private RouterBean(TYPE type, Class<?> clazz, String group, String path) {
this.type = type;
this.clazz = clazz;
this.group = group;
this.path = path;
}
public static RouterBean create(TYPE type, Class<?> clazz, String group, String path) {
return new RouterBean(type, clazz, group, path);
}
public final static class Builder {
private Element element;//结点
private String group;
private String path;
public Builder setElement(Element element) {
this.element = element;
return this;
}
public Builder setGroup(String group) {
this.group = group;
return this;
}
public Builder setPath(String path) {
this.path = path;
return this;
}
public RouterBean builder() {
if (null == path || path.length() == 0) {
throw new IllegalArgumentException("RouterBean 路由地址 不可以为空");
}
return new RouterBean(this);
}
}
}
解析注解并且生成对应的实现
typescript
public class ARouterProcessor extends AbstractProcessor {
private Elements elementUtils;
private Types typeUtils;
private Messager messager;
private Filer filer;
String moduleName;
String packageNameForAPT;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
typeUtils = processingEnv.getTypeUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
moduleName = processingEnv.getOptions().get(Constants.MODULE_NAME);
packageNameForAPT = processingEnv.getOptions().get(Constants.APT_PACKAGE_NAME);
messager.printMessage(Diagnostic.Kind.NOTE, "module" + moduleName + "----.>>>>>>packageNameForAPT:" + packageNameForAPT);
if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
throw new RuntimeException("注解处理器的参数ModuleName或者packageName为空");
}
}
//处理注解
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (!EmptyUtils.isEmpty(set)) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (!EmptyUtils.isEmpty(elements)) {
try {
parseElements(elements);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.NOTE,"error:"+e.getMessage());
e.printStackTrace();
}
}
return true;
}
return true;
}
最终我们生成的模板代码样例如下:
typescript
public class ARouter$$Group$$User implements ARouterLoadGroup {
@Override
public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
Map<String ,Class<? extends ARouterLoadPath>> groupMap=new HashMap<>();
groupMap.put("user",ARouter$$Path$$User.class);
return groupMap;
}
}
public class ARouter$$Path$$User implements ARouterLoadPath {
@Override
public Map<String, RouterBean> loadPath() {
Map<String,RouterBean> pathMap=new HashMap<>();
pathMap.put("/user/UserActivity",RouterBean.create(RouterBean.TYPE.ACTIVITY,
UserActivity.class,"user","/user/UserActivity"));
return pathMap;
}
}
3.提供跳转api
apt帮助我们生成的代码在各自的组件中,所以我们需要在公共router_api中添加查找和跳转的接口:
csharp
public class RouterManager {
private static volatile RouterManager instance;
private String group;
private String path;
private LruCache<String, ARouterLoadGroup> groupLruCache;
private LruCache<String, ARouterLoadPath> pathLruCache;
private static final String group_file_prefix = ".ARouter$$Group$$";
private RouterManager() {
groupLruCache = new LruCache<>(100);
pathLruCache = new LruCache<>(100);
}
public static RouterManager getInstance() {
if (null == instance) {
synchronized (RouterManager.class) {
if (null == instance) {
instance = new RouterManager();
}
}
}
return instance;
}
public BundleManager build(String path) {
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new IllegalArgumentException("未按照规范配置");
}
this.group = subFromPath2Group(path);
System.out.println("BundleManager build:"+group);
this.path = path;
return new BundleManager();
}
private String subFromPath2Group(String path) {
if (path.lastIndexOf("/") == 0) {
throw new IllegalArgumentException("未按照规范配置");
}
String finalGroup = path.substring(1, path.indexOf("/", 1));
if (finalGroup.contains("/")) {
throw new IllegalArgumentException("未按照规范配置");
}
if (TextUtils.isEmpty(finalGroup)) {
throw new IllegalArgumentException("未按照规范配置");
}
return finalGroup;
}
public Object navigation(Context context, BundleManager bundleManager, int code) {
String groupClassName = /*context.getPackageName() + */"com.seven.arouter.apt" + group_file_prefix+group;
System.out.println("RouterManager navigation groupClazzName:" + groupClassName);
try {
ARouterLoadGroup groupLoad = groupLruCache.get(group);
if (groupLoad == null) {
Class<?> clazz = Class.forName(groupClassName);
groupLoad = (ARouterLoadGroup) clazz.newInstance();
groupLruCache.put(group, groupLoad);
}
if (groupLoad.loadGroup().isEmpty()) {
throw new RuntimeException("路由表group加载失败!");
}
ARouterLoadPath pathLoad = pathLruCache.get(path);
if (pathLoad == null) {
Class<? extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
if (null != clazz)
pathLoad = clazz.newInstance();
if (pathLoad != null)
pathLruCache.put(group, pathLoad);
}
if (pathLoad != null) {
if (pathLoad.loadPath().isEmpty()) {
throw new RuntimeException("路由表path加载失败");
}
RouterBean routerBean = pathLoad.loadPath().get(path);
if (null != routerBean) {
switch (routerBean.getType()) {
case ACTIVITY:
Intent intent = new Intent(context, routerBean.getClazz());
intent.putExtras(bundleManager.getBundle());
if (bundleManager.isResult()) {
((Activity) (context)).setResult(code,intent);
((Activity) (context)).finish();
break;
}
if(code>0){
((Activity) context). startActivityForResult(intent, code, bundleManager.getBundle());
} else {
context.startActivity(intent, bundleManager.getBundle());
}
break;
default:
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
三.Arouter的使用与基本原理
1.基本配置
javascript
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
api 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
2.使用教程
1.跳转带参数请求码
kotlin
ARouter.getInstance().build(RouterConstant.ORDER.mainPath)
.withString("main", "main 2 user")
.navigation(this, requestCode)
参数需要在接收方使用@AutoWired注解标志,并且使用ARoute.getInstance.inject(this)注入,如下所示:
scss
@Autowired(name = "router")
String router;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
ARouter.getInstance().inject(this);
System.out.println("UserActivity rec router----->>>>>:" + router);
}
2.跳转回调和拦截器
回调会接收到跳转的状态,拦截器是基于切面AOP编程思想,可以帮助我们统一处理。
kotlin
fun gotoUser(view: View) {
ARouter.getInstance().build(RouterConstant.User.mainPath)
.withString("router", "main 2 user")
.navigation(this, object : NavigationCallback {
override fun onFound(postcard: Postcard?) {
}
override fun onLost(postcard: Postcard?) {
}
override fun onArrival(postcard: Postcard?) {
}
override fun onInterrupt(postcard: Postcard?) {
}
})
}
@Interceptor(priority = 1, name = "测试拦截器1")
class NavigationLogInterceptor : IInterceptor {
private var context: Context? = null
override fun init(context: Context?) {
println("NavigationLogInterceptor---->init$context")
this.context = context
}
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
println("NavigationLogInterceptor---->process$postcard")
if (postcard?.path.equals(RouterConstant.User.mainPath)) {
callback?.onContinue(postcard)
} else {
Handler(Looper.getMainLooper()).post {
println("NavigationLogInterceptor---->process$--->>show")
Toast.makeText(
context, "拦截跳转:${postcard?.path}",
Toast.LENGTH_SHORT
).show
}
}
}
}
3.Fragment跳转
kotlin
//加载fragment
fun loadBlankFragment(view: View) {
val fragment: Fragment =
ARouter.getInstance().build(RouterConstant.Main.blankFragmentPath).navigation() as Fragment
supportFragmentManager.beginTransaction().add(R.id.container,fragment).commit()
}
4.URI跳转
kotlin
fun gotoSchemeFilter(view: View) {
// 1. 默认路径
val uri = Uri.parse("feather://www.seven.com:12345${RouterConstant.Main.schemeFilterPath}")
ARouter.getInstance().build(uri).withString("str","schemeFilterStr---<").navigation()
}
5.带动画的跳转
scss
ARouter.getInstance()
.build(uri)
.withTransition(
android.R.animator.fade_in, android.R.animator.fade_out
)
.withString("str", "schemeFilterStr---<").navigation()
3.基本原理
APT技术