组件化
可以理解为解耦复杂系统时将多个功能模块拆分,重组的过程。在Android工程中,表现就是把app按照其业务的不同,划分为不同的Module。举例一点,比如我在开发58同城时,具有很多个业务,房产,招聘,新车,每一个业务线都是独立的,可以单独运行,相互之间并不依赖,但是在平台中,可以做到横向的互相跳转。所以组件化有点有很多:
- 1.各组件只关系自己功能开发,模块内代码高度聚合,只负责一项任务;
- 2.业务之间不干扰,提升协作的效率;
- 3.业务组件可插拔,比如招聘,可以随时集成到58同镇平台;
- 4.业务之间不直接依赖,各个业务更独立,降低了耦合;
- 5.可以单独编译,加快编译速度,提高开发效率;
- 6.。。。。。。
路由
ARouter,阿里开源,一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
手写实现简单ARouter
项目目录
router实现
具体原理,就是在我们router有一个单例,这个单例保存了一个Map,而这个map存了key-value,key是我们的自定义的字符串,value就是我们的Activity.class。然后我们的业务模块就可以把自己的Activity,put到这个map里,而其他的业务就可以根据key拿到Activity,从而就可以实现Intent跳转了。路由代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| private static volatile CRouter mInstance; // 存储所有的注册Activity private static Map<String,Class<? extends Activity>> routers = new HashMap<>();
private CRouter(){ } public static CRouter getInstance(){ if (mInstance == null){ synchronized (CRouter.class){ if (mInstance == null){ mInstance = new CRouter(); } } } return mInstance; }
public static void init(Application application){ // new JobRouter().loadRouters(routers); // new CarRouter().loadRouters(routers); //这里可以用ClassLoader和DexFile便利拿到所有com.example.router包下的类,再通过反射,拿到Router并调用loadRouters注册所有Activity,// 就不可以不用手动执行new JobRouter().loadRouters(routers);当然我们也拿不到JobRouter,CarRouter; try { Set<String> classNames = ClassUtils.getFileNameByPackageName(application,"com.example.routers"); for (String clsName : classNames) { Class<?> cls = Class.forName(clsName); if (IRouteLoad.class.isAssignableFrom(cls)){ IRouteLoad iRouteLoad = (IRouteLoad)cls.newInstance(); iRouteLoad.loadRouters(routers); } } } catch (Exception e) { e.printStackTrace(); } }
public void startActivity(Activity activity,String path){ Class<? extends Activity> cls = routers.get(path); if (cls != null){ Intent intent = new Intent(activity,cls); activity.startActivity(intent); } } public void register(String path,Class<? extends Activity> cls ){ routers.put(path,cls); } }
|
工具类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class ClassUtils { //获取程序所欲apk public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException { ApplicationInfo applicationInfo = context.getPackageManager() .getApplicationInfo(context.getPackageName(),0); List<String> sourcePaths = new ArrayList<>(); //当前应用的apk sourcePaths.add(applicationInfo.sourceDir); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ if (null != applicationInfo.splitSourceDirs){ sourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs)); } } return sourcePaths; } public static Set<String> getFileNameByPackageName(Application context, String packagename) throws PackageManager.NameNotFoundException{ Set<String> classNames = new HashSet<>(); List<String> paths = getSourcePaths(context); for (String path: paths){ DexFile dexFile = null; try{ //加载apk中dex,并便利获得所有的packageName的类 dexFile = new DexFile(path); Enumeration<String> dexEntries = dexFile.entries(); //遍历整个apk中的所有类 while (dexEntries.hasMoreElements()){ String className = dexEntries.nextElement(); if (className.startsWith(packagename)){ classNames.add(className); } } }catch (Exception e){
}finally { if (null!= dexFile){ try{ dexFile.getClass(); }catch (Exception e){
} } } } return classNames; } }
|
业务模块注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // 车业务 package com.example.router;
public class CarRouter implements IRouteLoad { public void loadRouters(Map<String,Class<? extends Activity>> routers){ routers.put("/path/car", CarActivity.class); } }
// 招聘业务 package com.example.router;
public class JobRouter implements IRouteLoad { public void loadRouters(Map<String,Class<? extends Activity>> routers){ routers.put("/path/job", JobActivity.class); } }
|
注册好了,这样我们就可以跳转了:在车跳转到招聘业务,完成:
1
| CRouter.getInstance().startActivity(this,"/path/job");
|
然后我们发现,注册的时候,需要每个业务线,都要手动注册put自己的key-value;而且注册的保命,类名不一样外,其他的代码实现都差不多。那有没有一种方式,动态生成呢?
APT注解
注解的两个实现模块,都不能是android module。
注解类:
1 2 3 4 5 6 7
| // 用在什么地方 @Target(ElementType.TYPE) //注解保留到:源码 @Retention(RetentionPolicy.SOURCE) public @interface CRouterAnnotation { String value();// 我们需要的时字符串 }
|
自定义注解实现:自动生成的类,是什么?就是一个.java文件.所以我们其实就是拿到内容,生成一个文件。
写入依赖:
1 2 3 4 5 6 7
| dependencies { implementation "com.google.auto.service:auto-service:1.0-rc7" annotationProcessor "com.google.auto.service:auto-service:1.0-rc7" implementation 'com.squareup:javapoet:1.13.0'
implementation project(":apt-annotation") }
|
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| @AutoService(Processor.class) //指定我们自己的注解处理器:CRouterAnnotation,全路径 @SupportedAnnotationTypes("com.example.apt_annotation.CRouterAnnotation") @SupportedOptions("AROUTER_MODULE_NAME") public class RouterProcessor extends AbstractProcessor { private Messager messager;//打印日志 private Filer filer;//文件持有,管理者 private String moduleName;//业务线的名称 @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); filer = processingEnv.getFiler(); Map<String,String> options = processingEnv.getOptions(); moduleName = options.get("AROUTER_MODULE_NAME"); }
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //使用了@CRouterAnnotation注解类的节点的集合,包括包名、类名、属性、方法等节点。 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(CRouterAnnotation.class); //拼装类内容 StringBuilder sb = new StringBuilder("package com.example.routers;" + "\n import android.app.Activity; \n"); for (Element element : elements){ TypeElement typeElement = (TypeElement)element;//获取类信息 sb.append("import "); sb.append(typeElement.getQualifiedName());// com.example.mycar.CarActivity sb.append(";\n"); } sb.append( "import com.example.myrouteapi.IRouteLoad;\n"); sb.append("import java.util.Map;\n"); sb.append("public class "+ moduleName+ "Router implements IRouteLoad {\n"); sb.append(" public void loadRouters(Map<String,Class<? extends Activity>> routers){\n");
for (Element element : elements){ CRouterAnnotation cRouterAnnotation = element.getAnnotation(CRouterAnnotation.class); String value = cRouterAnnotation.value(); Name clsName = element.getSimpleName(); sb.append("routers.put(\""+value+ "\",").append(clsName+".class);\n"); } sb.append(" }\n"); sb.append("}\n");
messager.printMessage(Diagnostic.Kind.NOTE,"moduleName--------:"+moduleName); try { //生成文件 JavaFileObject sourceFile = filer.createSourceFile("com.example.routers."+moduleName+"Router"); OutputStream os = sourceFile.openOutputStream(); os.write(sb.toString().getBytes()); os.close(); } catch (Exception e) { e.printStackTrace(); } return true; } }
|
注意模块名称需要配置:AROUTER_MODULE_NAME,就是RouterProcessor中需要的类名前缀。这个是在模块业务中配置的:
1 2 3 4 5
| javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } }
|
还需要配置依赖:
1 2 3 4 5
| dependencies { implementation project(':myrouteapi') implementation project(':apt-annotation') annotationProcessor project(":apt-processor") }
|
设置协议key:”/path/job”
1 2 3
| @CRouterAnnotation("/path/job") public class JobActivity extends AppCompatActivity { }
|
最终在各业务模块生成:
OK,实现完成。
AutoService是什么?RouterProcessor是怎么执行的?看我写的SPI原理
总结
其实如上只是简单的实现了ARouter,还有很多改进的地方,比如可以再次基础上根据业务改进我们的框架;比如我们可以通过Gradle 字节码插桩提升我们的初始化速度,通过URI实现我们的跳转配置,增加协议的参数等等。
参考资料
ARouter中文文档
组件化视频
Ursprünglicher Link: http://nunu03.github.io/2022/06/09/手写实现ARouter/
Copyright-Erklärung: 转载请注明出处.