组件化架构深入剖析<三>-----Arouter核心功能手写实现、Arouter官方核心源码分析
注册处理器生成组映射:
继续跟着上一次https://www.cnblogs.com/webor2006/p/12275672.html的代码进行接下来的实现,上一次已经将路由表的映射的工具类的生成代码编写完了,接下来则需要编写它:
对应咱们的代码也就是它:
其写法跟那个生成映射表类差不多,这里就直接贴出来既可:
此时需要在我们每个模块下的gradle中增加一个配置,这样我们在注解处理器中是可以动态读取到的,如下:
此时就可以在我们的注解处理器中来动态获取modulename啦,如下:
其中processingEnv是父类中的成员变量:
好,整个生成代码写完,接下来咱们来测试一下,看是否能帮我们生成:
编译一下:
妥妥的,看一下它里面的内容:
public class MyRouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("app", MyRouter$$Group$$app.class); } }
public class MyRouter$$Group$$app implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/app/main", RouteMeta.build(RouteMeta.Type.ACTIVITY,MainActivity.class, "/app/main", "app")); } }
接下来再来试一下module1:
编译发生报异常了。。
这是因为在主app中生成了一个,我们看一下在module1中生成的:
这是为啥呢?其实是由于路们注解定义的值有问题:
咱们修改一下这个路径既可以解决这个错,如下:
再编译一下:
妥妥的,生成代码内容也看一下:
public class MyRouter$$Root$$module1 implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("module1", MyRouter$$Group$$module1.class); } }
public class MyRouter$$Group$$module1 implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module1/activity", RouteMeta.build(RouteMeta.Type.ACTIVITY,MainActivity.class, "/module1/activity", "module1")); } }
妥妥的,接下来再测最后一个module3的情况:
看下里面生成的代码:
public class MyRouter$$Root$$module2 implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("module2", MyRouter$$Group$$module2.class); } }
public class MyRouter$$Group$$module2 implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module2/activity", RouteMeta.build(RouteMeta.Type.ACTIVITY,MainActivity.class, "/module2/activity", "module2")); } }
嗯,整个Route注解处理器生成路由表的逻辑一切正常。
手写Arouter路由框架核心功能:
接下来则需要着手开始编写路由框架了,其代码的编写主要是在这个router_core模块当中:
路由分组Map汇总:
在之前的路由原理阐述中说到最终每个模块的路由信息最终是要汇总到一起的,那怎么来汇总呢?以其中主app来说,目前不是已经生成了这两个工具类了嘛:
而这俩类中的方法参数都是传的一个Map,然后方法体中则是直接将映射添加到这个Map中:
那咱们只要在一个类中定义好相印的Map,然后通过反射来调用这生成的工具类的方法不就可以了么?所以接下来咱们开始实现一下,由于整个框架的核心代码量不少,所以有些细节就不一一去解释了,重点是思路搞明白:
那接下来一步步收集所有的路由表信息,首先得要找到注解处理器所生成的文件嘛,怎么找呢,如下:
其查找细节如下:
package com.android.router_core.utils; import android.app.Application; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import com.android.router_core.thread.DefaultPoolExecutor; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import dalvik.system.DexFile; public class ClassUtils { /** * 获得程序所有的apk(instant run会产生很多split apk) */ public static List<String> getSourcePaths(Context context) throws PackageManager .NameNotFoundException, IOException { ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context .getPackageName(), 0); List<String> sourcePaths = new ArrayList<>(); sourcePaths.add(applicationInfo.sourceDir); //instant run if (null != applicationInfo.splitSourceDirs) { sourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs)); } return sourcePaths; } /** * 从apk中的dex中查找指定包名下的class文件 */ public static Set<String> getFileNameByPackageName(Application context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException { final Set<String> classNames = new HashSet<>(); List<String> paths = getSourcePaths(context); //使用同步计数器判断均处理完成 final CountDownLatch parserCtl = new CountDownLatch(paths.size()); ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(paths .size()); for (final String path : paths) { threadPoolExecutor.execute(new Runnable() { @Override public void run() { DexFile dexfile = null; try { //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类 dexfile = new DexFile(path); Enumeration<String> dexEntries = dexfile.entries(); while (dexEntries.hasMoreElements()) { String className = dexEntries.nextElement(); if (className.startsWith(packageName)) { classNames.add(className); } } } catch (IOException e) { e.printStackTrace(); } finally { if (null != dexfile) { try { dexfile.close(); } catch (IOException e) { e.printStackTrace(); } } //释放1个 parserCtl.countDown(); } } }); } //等待执行完成 parserCtl.await(); return classNames; } }
其中线程池做了一个封装:
/** * 线程池 */ public class DefaultPoolExecutor { private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "MyRouter #" + mCount.getAndIncrement()); } }; //核心线程和最大线程都是cpu核心数+1 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int MAX_CORE_POOL_SIZE = CPU_COUNT + 1; //存活30秒 回收线程 private static final long SURPLUS_THREAD_LIFE = 30L; public static ThreadPoolExecutor newDefaultPoolExecutor(int corePoolSize) { if (corePoolSize == 0) { return null; } corePoolSize = Math.min(corePoolSize, MAX_CORE_POOL_SIZE); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, corePoolSize, SURPLUS_THREAD_LIFE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(64), sThreadFactory); //核心线程也会被销毁 threadPoolExecutor.allowCoreThreadTimeOut(true); return threadPoolExecutor; } }
为啥要开线程池,因为一个APK中类太多了,如果单独new个线程性能不太好。其中查找类的核心就是从apk中查到所有的class文件中进行遍历,细节就不多说了,比较好理解,接下来则先来调用分组信息:
仓库定义如下:
那接下来是不是得来收集路由信息了?否,这里先只收集组信息既可,路由的待之后再来。
实现模块Activity路由跳转:
接下来先来实现从主app跳到module1界面的跳转功能,实现了的话那基本上整个路由的核心就已经完成了,先来搭建一下界面框架:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:contextpackageNamckage="MainActivity"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="module1Jump" android:text="跳转模块1" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="module2Jump" android:text="跳转模块2" /> </LinearLayout>
其中跳转的样式代码已经写出来了,下面来实现一下,先来实现这个build(),看它里面具体是要干些啥?
最终返回的是一个Postcard跳卡对象,它里面的定义如下:
其实也没干嘛,就是把我们要跳的path和组信息封装成了一个对像,接下来再来看它里面的navigation()在干嘛了,这应该才是真正的发起跳转逻辑了:
上面有飘红的,是因为接下来就要来定义它,其中有个callback定义如下:
接下来就需要在MyRouter中来实现navigation的具体方法了:
下面开始实现该核心跳转逻辑,很显然我们需要根据path来获得真正要跳转的Activity class才行,所以这里则需要开启解析路由信息了,如下:
其具体的解析过程如下:
从上面实现可以看出,只有真正要跳转时才会去解析路由表信息,好了,此时路由信息也已经有了:
接下来则来处理跳转:
我们知道在跳转Activity时有可能是startActivity,也有可能是startActivityForResult,也有可能在跳转时需要加入场动画,所以,接下来对于跳卡Postcard还得增加一些属性才行:
public class Postcard extends RouteMeta { private int flags = -1;
//新版动画 md风格 private Bundle optionsCompat; //老版 private int enterAnim; private int exitAnim; public Postcard(String path, String group) { this(path, group, null); } public Postcard(String path, String group, Bundle bundle) { setPath(path); setGroup(group); } public int getEnterAnim() { return enterAnim; } public int getExitAnim() { return exitAnim; } /** * Intent.FLAG_ACTIVITY** * * @param flag * @return */ public Postcard withFlags(int flag) { this.flags = flag; return this; } public int getFlags() { return flags; } /** * 跳转动画 * * @param enterAnim * @param exitAnim * @return */ public Postcard withTransition(int enterAnim, int exitAnim) { this.enterAnim = enterAnim; this.exitAnim = exitAnim; return this; } /** * 转场动画 * * @param compat * @return */ public Postcard withOptionsCompat(ActivityOptionsCompat compat) { if (null != compat) { this.optionsCompat = compat.toBundle(); } return this; } public Bundle getOptionsBundle() { return optionsCompat; } public Object navigation() { return navigation(null, null); } public Object navigation(Context context) { return navigation(context, null); } public Object navigation(Context context, NavigationCallback callback) { return MyRouter.getInstance().navigation(context, this, -1, callback); } public Object navigation(Activity activity, int requestCode) { return navigation(activity, requestCode, null); } public Object navigation(Activity activity, int requestCode, NavigationCallback callback) { return MyRouter.getInstance().navigation(activity, this, requestCode, callback); } }
ok,整个跳转的逻辑写完了,还是挺清晰的,接下来则可以运行看一下了,不过为了看到跳转的效果,先将module1中的界面文本改个文案:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我是模块1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
好,下面来瞅下效果:
呃,咋跳的不是module1的界面呢?其实原因是由于module1的activity的资源名用的跟主app的是一样的,如下:
因为最终生成的apk是一个,所以此时要重新写一个名称才行,这里需要特别注意:
再运行:
妥妥的~~这样对于路由Activity的跳转就完美实现了。
多参数传递:
对于跳转Activity实际在用时肯定会携带一些参数,所以接下来再来实现这样的功能,首先跳卡实体类肯定得要增加一些属性了,如下:
public class Postcard extends RouteMeta { private Bundle mBundle; private int flags = -1; //新版动画 md风格 private Bundle optionsCompat; //老版动画 private int enterAnim; private int exitAnim; public Postcard(String path, String group) { this(path, group, null); } public Postcard(String path, String group, Bundle bundle) { setPath(path); setGroup(group); this.mBundle = (null == bundle ? new Bundle() : bundle); } public Bundle getExtras() { return mBundle; } public int getEnterAnim() { return enterAnim; } public int getExitAnim() { return exitAnim; } /** * Intent.FLAG_ACTIVITY** * * @param flag * @return */ public Postcard withFlags(int flag) { this.flags = flag; return this; } public int getFlags() { return flags; } /** * 跳转动画 * * @param enterAnim * @param exitAnim * @return */ public Postcard withTransition(int enterAnim, int exitAnim) { this.enterAnim = enterAnim; this.exitAnim = exitAnim; return this; } /** * 转场动画 * * @param compat * @return */ public Postcard withOptionsCompat(ActivityOptionsCompat compat) { if (null != compat) { this.optionsCompat = compat.toBundle(); } return this; } public Postcard withString(@Nullable String key, @Nullable String value) { mBundle.putString(key, value); return this; } public Postcard withBoolean(@Nullable String key, boolean value) { mBundle.putBoolean(key, value); return this; } public Postcard withShort(@Nullable String key, short value) { mBundle.putShort(key, value); return this; } public Postcard withInt(@Nullable String key, int value) { mBundle.putInt(key, value); return this; } public Postcard withLong(@Nullable String key, long value) { mBundle.putLong(key, value); return this; } public Postcard withDouble(@Nullable String key, double value) { mBundle.putDouble(key, value); return this; } public Postcard withByte(@Nullable String key, byte value) { mBundle.putByte(key, value); return this; } public Postcard withChar(@Nullable String key, char value) { mBundle.putChar(key, value); return this; } public Postcard withFloat(@Nullable String key, float value) { mBundle.putFloat(key, value); return this; } public Postcard withParcelable(@Nullable String key, @Nullable Parcelable value) { mBundle.putParcelable(key, value); return this; } public Postcard withStringArray(@Nullable String key, @Nullable String[] value) { mBundle.putStringArray(key, value); return this; } public Postcard withBooleanArray(@Nullable String key, boolean[] value) { mBundle.putBooleanArray(key, value); return this; } public Postcard withShortArray(@Nullable String key, short[] value) { mBundle.putShortArray(key, value); return this; } public Postcard withIntArray(@Nullable String key, int[] value) { mBundle.putIntArray(key, value); return this; } public Postcard withLongArray(@Nullable String key, long[] value) { mBundle.putLongArray(key, value); return this; } public Postcard withDoubleArray(@Nullable String key, double[] value) { mBundle.putDoubleArray(key, value); return this; } public Postcard withByteArray(@Nullable String key, byte[] value) { mBundle.putByteArray(key, value); return this; } public Postcard withCharArray(@Nullable String key, char[] value) { mBundle.putCharArray(key, value); return this; } public Postcard withFloatArray(@Nullable String key, float[] value) { mBundle.putFloatArray(key, value); return this; } public Postcard withParcelableArray(@Nullable String key, @Nullable Parcelable[] value) { mBundle.putParcelableArray(key, value); return this; } public Postcard withParcelableArrayList(@Nullable String key, @Nullable ArrayList<? extends Parcelable> value) { mBundle.putParcelableArrayList(key, value); return this; } public Postcard withIntegerArrayList(@Nullable String key, @Nullable ArrayList<Integer> value) { mBundle.putIntegerArrayList(key, value); return this; } public Postcard withStringArrayList(@Nullable String key, @Nullable ArrayList<String> value) { mBundle.putStringArrayList(key, value); return this; } public Bundle getOptionsBundle() { return optionsCompat; } public Object navigation() { return navigation(null, null); } public Object navigation(Context context) { return navigation(context, null); } public Object navigation(Context context, NavigationCallback callback) { return MyRouter.getInstance().navigation(context, this, -1, callback); } public Object navigation(Activity activity, int requestCode) { return navigation(activity, requestCode, null); } public Object navigation(Activity activity, int requestCode, NavigationCallback callback) { return MyRouter.getInstance().navigation(activity, this, requestCode, callback); } }
接下来在调用时需要传递一个参数,如下:
// 跳转模块1 public void module1Jump(View view) { ArrayList<Integer> integers = new ArrayList<Integer>(); integers.add(1); integers.add(2); ArrayList<String> strings = new ArrayList<String>(); strings.add("1"); strings.add("2"); MyRouter.getInstance().build("/module1/activity").withString("a", "从MainActivity").withInt("b", 1).withShort("c", (short) 2).withLong("d", 3) .withFloat("e", 1.0f).withDouble("f", 1.1).withByte("g", (byte) 1).withBoolean ("h", true).withChar("i", '好').withStringArray("aa", new String[]{"1", "2"}).withIntArray("bb", new int[]{1, 2}).withShortArray ("cc", new short[]{(short) 2, (short) 2}).withLongArray("dd", new long[]{1, 2}) .withFloatArray("ee", new float[]{1.0f, 1.0f}).withDoubleArray("ff", new double[]{1.1, 1.1}).withByteArray("gg", new byte[]{(byte) 1, (byte) 1}).withBooleanArray ("hh", new boolean[]{true, true}).withCharArray("ii", new char[]{'好', '好'}) .withStringArrayList("k3", strings).withIntegerArrayList("k4", integers) .withInt("hhhhhh", 1).navigation(); }
这里为了充分测试,携带了各种参数,然后在跳转时要增加参数的处理,如下:
然后在module1那进行接收,怎么接收呢?我们第一时间能想到的就是手动的通过getIntent()来一一进行获取呗,但是!!对于这种手工获取的方式其实可以同样利用注解来帮我们生成一个工具类,用butterknife的思想将findViewById的代码通过一句话来搞定,接下来咱们来实现一下,还记得之前已经定义了一个相关的注解么,如下:
然后在我们要接收的Activity中变量上面加上它既可,如下:
@Route(path = "/module1/activity") public class MainActivity extends Activity { @Extra String a; @Extra int b; @Extra short c; @Extra long d; @Extra float e; @Extra double f; @Extra byte g; @Extra boolean h; @Extra char i; @Extra String[] aa; @Extra int[] bb; @Extra short[] cc; @Extra long[] dd; @Extra float[] ee; @Extra double[] ff; @Extra byte[] gg; @Extra boolean[] hh; @Extra char[] ii; @Extra List<String> k3; @Extra List<Integer> k4; @Extra(name = "hhhhhh") int test; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_module1); } }
然后最终形态应该是这样:
好,接下来就得新建一个Extra的注解处理器来生成相关获取Intent中数据的工具代码了,如下:
然后里面框架如下:
package com.android.router_compiler.processor; import com.android.router_annotation.Extra; import com.android.router_compiler.processor.utils.Constants; import com.android.router_compiler.processor.utils.Log; import com.android.router_compiler.processor.utils.Utils; import com.google.auto.service.AutoService; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; @AutoService(Processor.class) @SupportedOptions(Constants.ARGUMENTS_NAME) @SupportedSourceVersion(SourceVersion.RELEASE_7) @SupportedAnnotationTypes(Constants.ANN_TYPE_Extra) public class ExtraProcessor extends AbstractProcessor { /** * 节点工具类 (类、函数、属性都是节点) */ private Elements elementUtils; /** * type(类信息)工具类 */ private Types typeUtils; /** * 类/资源生成器 */ private Filer filerUtils; /** * 记录所有需要注入的属性 key:类节点 value:需要注入的属性节点集合 */ private Map<TypeElement, List<Element>> parentAndChild = new HashMap<>(); private Log log; /** * 初始化 从 {@link ProcessingEnvironment} 中获得一系列处理器工具 * * @param processingEnvironment */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); //获得apt的日志输出 log = Log.newLog(processingEnvironment.getMessager()); elementUtils = processingEnv.getElementUtils(); typeUtils = processingEnvironment.getTypeUtils(); filerUtils = processingEnv.getFiler(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (!Utils.isEmpty(set)) { Set<? extends Element> elements = roundEnvironment .getElementsAnnotatedWith(Extra.class); if (!Utils.isEmpty(elements)) { try { categories(elements); generateAutoWired(); } catch (Exception e) { e.printStackTrace(); } } return true; } return false; } private void generateAutoWired() throws IOException { //todo:生成相印的类 } /** * 记录需要生成的类与属性 */ private void categories(Set<? extends Element> elements) throws IllegalAccessException { //TODO } }
接下来先来查找所有类中有Extra注解的元素:
接下来则需要生成工具类,为了方便调用,也得先声明一个接口:
而最终生成的类的原形长这样:
具体写法其实跟Route的注解处理器的差不多,就不详细解释了,贴出代码来:
private void generateAutoWired() throws IOException { TypeMirror type_Activity = elementUtils.getTypeElement(Constants.ACTIVITY).asType(); TypeElement IExtra = elementUtils.getTypeElement(Constants.IEXTRA); // 参数 Object target ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build(); if (!Utils.isEmpty(parentAndChild)) { // 遍历所有需要注入的 类:属性 for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) { // 类 TypeElement rawClassElement = entry.getKey(); if (!typeUtils.isSubtype(rawClassElement.asType(), type_Activity)) { throw new RuntimeException("[Just Support Activity Field]:" + rawClassElement); } //封装的函数生成类 LoadExtraBuilder loadExtra = new LoadExtraBuilder(objectParamSpec); loadExtra.setElementUtils(elementUtils); loadExtra.setTypeUtils(typeUtils); ClassName className = ClassName.get(rawClassElement); loadExtra.injectTarget(className); //遍历属性 for (int i = 0; i < entry.getValue().size(); i++) { Element element = entry.getValue().get(i); loadExtra.buildStatement(element); } // 生成java类名 String extraClassName = rawClassElement.getSimpleName() + Constants.NAME_OF_EXTRA; // 生成 XX$$Autowired JavaFile.builder(className.packageName(), TypeSpec.classBuilder(extraClassName) .addSuperinterface(ClassName.get(IExtra)) .addModifiers(PUBLIC).addMethod(loadExtra.build()).build()) .build().writeTo(filerUtils); log.i("Generated Extra: " + className.packageName() + "." + extraClassName); } } }
其中生成代码用一个类单独封装了一下:
package com.android.router_compiler.processor.utils; import com.android.router_annotation.Extra; import com.squareup.javapoet.ArrayTypeName; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; public class LoadExtraBuilder { private static final String INJECT_TARGET = "$T t = ($T)target"; private MethodSpec.Builder builder; private Elements elementUtils; private Types typeUtils; // private TypeMirror parcelableType; public LoadExtraBuilder(ParameterSpec parameterSpec) { // 函数 public void loadExtra(Object target) builder = MethodSpec.methodBuilder(Constants .METHOD_LOAD_EXTRA) .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(parameterSpec); } public void setElementUtils(Elements elementUtils) { this.elementUtils = elementUtils; parcelableType = elementUtils.getTypeElement(Constants .PARCELABLE).asType(); } public void setTypeUtils(Types typeUtils) { this.typeUtils = typeUtils; } public void buildStatement(Element element) { TypeMirror typeMirror = element.asType(); int type = typeMirror.getKind().ordinal(); //属性名 String text 获得text String fieldName = element.getSimpleName().toString(); //获得注解 name值 String extraName = element.getAnnotation(Extra.class).name(); extraName = Utils.isEmpty(extraName) ? fieldName : extraName; String defaultValue = "t." + fieldName; String statement = defaultValue + " = t.getIntent()."; if (type == TypeKind.BOOLEAN.ordinal()) { statement += "getBooleanExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.BYTE.ordinal()) { statement += "getByteExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.SHORT.ordinal()) { statement += "getShortExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.INT.ordinal()) { statement += "getIntExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.LONG.ordinal()) { statement += "getLongExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.CHAR.ordinal()) { statement += "getCharExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.FLOAT.ordinal()) { statement += "getFloatExtra($S, " + defaultValue + ")"; } else if (type == TypeKind.DOUBLE.ordinal()) { statement += "getDoubleExtra($S, " + defaultValue + ")"; } else { //数组类型 if (type == TypeKind.ARRAY.ordinal()) { addArrayStatement(statement, fieldName, extraName, typeMirror, element); } else { //Object addObjectStatement(statement, fieldName, extraName, typeMirror, element); } return; } builder.addStatement(statement, extraName); } /** * 添加对象 String/List/Parcelable * * @param statement * @param extraName * @param typeMirror * @param element */ private void addObjectStatement(String statement, String fieldName, String extraName, TypeMirror typeMirror, Element element) { //Parcelable if (typeUtils.isSubtype(typeMirror, parcelableType)) { statement += "getParcelableExtra($S)"; } else if (typeMirror.toString().equals(Constants.STRING)) { statement += "getStringExtra($S)"; } else { //List TypeName typeName = ClassName.get(typeMirror); //泛型 if (typeName instanceof ParameterizedTypeName) { //list 或 arraylist ClassName rawType = ((ParameterizedTypeName) typeName).rawType; //泛型类型 List<TypeName> typeArguments = ((ParameterizedTypeName) typeName) .typeArguments; if (!rawType.toString().equals(Constants.ARRAYLIST) && !rawType.toString() .equals(Constants.LIST)) { throw new RuntimeException("Not Support Inject Type:" + typeMirror + " " + element); } if (typeArguments.isEmpty() || typeArguments.size() != 1) { throw new RuntimeException("List Must Specify Generic Type:" + typeArguments); } TypeName typeArgumentName = typeArguments.get(0); TypeElement typeElement = elementUtils.getTypeElement(typeArgumentName .toString()); // Parcelable 类型 if (typeUtils.isSubtype(typeElement.asType(), parcelableType)) { statement += "getParcelableArrayListExtra($S)"; } else if (typeElement.asType().toString().equals(Constants.STRING)) { statement += "getStringArrayListExtra($S)"; } else if (typeElement.asType().toString().equals(Constants.INTEGER)) { statement += "getIntegerArrayListExtra($S)"; } else { throw new RuntimeException("Not Support Generic Type : " + typeMirror + " " + element); } } else { throw new RuntimeException("Not Support Extra Type : " + typeMirror + " " + element); } } builder.addStatement(statement, extraName); } /** * 添加数组 * * @param statement * @param fieldName * @param typeMirror * @param element */ private void addArrayStatement(String statement, String fieldName, String extraName, TypeMirror typeMirror, Element element) { //数组 switch (typeMirror.toString()) { case Constants.BOOLEANARRAY: statement += "getBooleanArrayExtra($S)"; break; case Constants.INTARRAY: statement += "getIntArrayExtra($S)"; break; case Constants.SHORTARRAY: statement += "getShortArrayExtra($S)"; break; case Constants.FLOATARRAY: statement += "getFloatArrayExtra($S)"; break; case Constants.DOUBLEARRAY: statement += "getDoubleArrayExtra($S)"; break; case Constants.BYTEARRAY: statement += "getByteArrayExtra($S)"; break; case Constants.CHARARRAY: statement += "getCharArrayExtra($S)"; break; case Constants.LONGARRAY: statement += "getLongArrayExtra($S)"; break; case Constants.STRINGARRAY: statement += "getStringArrayExtra($S)"; break; default: //Parcelable 数组 String defaultValue = "t." + fieldName; //object数组 componentType获得object类型 ArrayTypeName arrayTypeName = (ArrayTypeName) ClassName.get(typeMirror); TypeElement typeElement = elementUtils.getTypeElement(arrayTypeName .componentType.toString()); //是否为 Parcelable 类型 if (!typeUtils.isSubtype(typeElement.asType(), parcelableType)) { throw new RuntimeException("Not Support Extra Type:" + typeMirror + " " + element); } statement = "$T[] " + fieldName + " = t.getIntent()" + ".getParcelableArrayExtra" + "($S)"; builder.addStatement(statement, parcelableType, extraName); builder.beginControlFlow("if( null != $L)", fieldName); statement = defaultValue + " = new $T[" + fieldName + ".length]"; builder.addStatement(statement, arrayTypeName.componentType) .beginControlFlow("for (int i = 0; i < " + fieldName + "" + ".length; " + "i++)") .addStatement(defaultValue + "[i] = ($T)" + fieldName + "[i]", arrayTypeName.componentType) .endControlFlow(); builder.endControlFlow(); return; } builder.addStatement(statement, extraName); } /** * 加入 $T t = ($T)target * * @param className */ public void injectTarget(ClassName className) { builder.addStatement(INJECT_TARGET, className, className); } public MethodSpec build() { return builder.build(); } }
另外增加了一些常量:
package com.android.router_compiler.processor.utils; import com.squareup.javapoet.ClassName; public class Constants { public static final ClassName ROUTER = ClassName.get( "com.android.router_core", "MyRouter"); public static final String ANN_TYPE_ROUTE = "com.android.router_annotation.Route"; public static final String ANN_TYPE_Extra = "com.android.router_annotation.Extra"; public static final String ACTIVITY = "android.app.Activity"; public static final String IROUTE_GROUP = "com.android.router_core.template.IRouteGroup"; public static final String IROUTE_ROOT = "com.android.router_core.template.IRouteRoot"; public static final String IEXTRA = "com.android.router_core.template.IExtra"; public static final String METHOD_LOAD_INTO = "loadInto"; public static final String METHOD_LOAD_EXTRA = "loadExtra"; public static final String SEPARATOR = "$$"; public static final String PROJECT = "MyRouter"; public static final String NAME_OF_ROOT = PROJECT + SEPARATOR + "Root" + SEPARATOR; public static final String NAME_OF_GROUP = PROJECT + SEPARATOR + "Group" + SEPARATOR; public static final String NAME_OF_EXTRA = SEPARATOR + "Extra"; public static final String PACKAGE_OF_GENERATE_FILE = "com.android.router_core"; public static final String ARGUMENTS_NAME = "moduleName"; private static final String LANG = "java.lang"; public static final String BYTE = LANG + ".Byte"; public static final String SHORT = LANG + ".Short"; public static final String INTEGER = LANG + ".Integer"; public static final String LONG = LANG + ".Long"; public static final String FLOAT = LANG + ".Float"; public static final String DOUBEL = LANG + ".Double"; public static final String BOOLEAN = LANG + ".Boolean"; public static final String STRING = LANG + ".String"; public static final String ARRAY = "ARRAY"; public static final String BYTEARRAY = "byte[]"; public static final String SHORTARRAY = "short[]"; public static final String BOOLEANARRAY = "boolean[]"; public static final String CHARARRAY = "char[]"; public static final String DOUBLEARRAY = "double[]"; public static final String FLOATARRAY = "float[]"; public static final String INTARRAY = "int[]"; public static final String LONGARRAY = "long[]"; public static final String STRINGARRAY = "java.lang.String[]"; public static final String ARRAYLIST = "java.util.ArrayList"; public static final String LIST = "java.util.List"; public static final String PARCELABLE = "android.os.Parcelable"; }
好,接下来编译一下看能否生成相关的代码:
妥妥的生成了,那接下来咱们再回到MyRouter类中来调用一下生成的这个类,如下:
具体调用的逻辑再封装了一下,代码如下:
package com.android.router_core; import android.app.Activity; import android.util.LruCache; import com.android.router_core.template.IExtra; public class ExtraManager { public static final String SUFFIX_AUTOWIRED = "$$Extra"; private static ExtraManager instance; private LruCache<String, IExtra> classCache; public static ExtraManager getInstance() { if (instance == null) { synchronized (MyRouter.class) { if (instance == null) { instance = new ExtraManager(); } } } return instance; } public ExtraManager() { classCache = new LruCache<>(66); } /** * 注入 */ public void loadExtras(Activity instance) { //查找对应activity的缓存 String className = instance.getClass().getName(); IExtra iExtra = classCache.get(className); try { if (null == iExtra) { iExtra = (IExtra) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); } iExtra.loadExtra(instance); classCache.put(className, iExtra); } catch (Exception e) { e.printStackTrace(); } } }
比较容易理解,好,接下来咱们来见证一下奇迹:
嗯,完美实现了~~还是挺不错的。
方法调用:
对于实际场景有木有主app想直接去调用module1的某个方法的情况呢?其实是有的,所以对于这种需求在路由中也是能做到的,接下来咱们来实现一下,该怎么来实现呢?其实使用方式也是用Route这个注解来开放要调用的类,然后方法肯定也得要抽象一下这样才能用统一的方式来对其进行调用,但是呢它跟上面路由表及参数的情况还不一样,定义一个统一的方法就可以了,实际往往会暴露很多业务方法,这是未知道的,那该如何办呢?下面一点点来,首先定义一个抽象接口:
接下来则需要暴露一个业务方法,由于是需要在所有不同的module相互调用,很明显需要再用接口来对这业务方法进行抽象,所以base模块应运而生,如下:
然后原来依赖于router_core的则都要改成这个base啦:
此时整个APP的架构层次则:
此时定义一个抽象的业务接口在base上:
此时则可以在module1中定义一个具体的业务接口了:
注意此时的service不是指Android的Service哈,这里指业务服务,接下来route注解器则需要处理对于这个业务注解路径的处理,因为最终也需要汇总到MAP路由中,如下:
其中这个注解器中有一处有BUG,如下:
修改为:
private void generatedGroup(TypeElement iRouteGroup) throws IOException { //1、准备方法的参数类型 Map<String,RouteMeta> ParameterizedTypeName atlas = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(RouteMeta.class) ); //2、准备方法的参数全称 Map<String,RouteMeta> atlas ParameterSpec groupParamSpec = ParameterSpec.builder(atlas, "atlas") .build(); //3、遍历分组,每一个分组创建一个 $$Group$$ 类 for (Map.Entry<String, List<RouteMeta>> entry : groupMap.entrySet()) { //类成员函数loadInfo声明构建 public void loadInfo(Map<String,RouteMeta> atlas) MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder (Constants.METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(groupParamSpec); //分组名 与 对应分组中的信息 List<RouteMeta> groupData = entry.getValue(); //遍历分组中的条目 数据 for (RouteMeta routeMeta : groupData) { // 组装函数体: // atlas.put(地址,RouteMeta.build(Class,path,group)) // $S 可参考:https://github.com/square/javapoet#s-for-strings // $T 可参考:https://github.com/square/javapoet#t-for-types loadIntoMethodOfGroupBuilder.addStatement( "atlas.put($S, $T.build($T.$L,$T.class, $S, $S))", routeMeta.getPath(), ClassName.get(RouteMeta.class), ClassName.get(RouteMeta.Type.class), routeMeta.getType(), ClassName.get((TypeElement) routeMeta.getElement()), routeMeta.getPath().toLowerCase(), routeMeta.getGroup().toLowerCase()); } // 创建java文件($$Group$$) 组 String groupName = entry.getKey(); String groupClassName = Constants.NAME_OF_GROUP + groupName; JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(groupClassName) .addSuperinterface(ClassName.get(iRouteGroup)) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfGroupBuilder.build()) .build() ).build().writeTo(filer); log.i("Generated RouteGroup: " + Constants.PACKAGE_OF_GENERATE_FILE + "." + groupClassName); //分组名和生成的对应的Group类类名 rootMap.put(groupName, groupClassName); } }
此时编译一下看是否生效了:
package com.android.router_core; import com.android.module1.MainActivity; import com.android.module1.TestServiceImpl; import com.android.router_annotation.model.RouteMeta; import com.android.router_core.template.IRouteGroup; import java.lang.Override; import java.lang.String; import java.util.Map; public class MyRouter$$Group$$module1 implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module1/service", RouteMeta.build(RouteMeta.Type.ISERVICE,TestServiceImpl.class, "/module1/service", "module1")); atlas.put("/module1/activity", RouteMeta.build(RouteMeta.Type.ACTIVITY,MainActivity.class, "/module1/activity", "module1")); } }
好,没问题,接下来就是收集那块需要处理一下了,如下:
其中仓库增加了一个MAP:
并且跳卡实体中增加了一个业务接口字段:
然后在处理跳转时需要进行修改:
好,接下来应用看是否可以直接调用了:
完美被调用,至此,又经过了大篇幅的学习就已经把Arouter框架的核心路由功能就已经研究透了,说了这么久的Arouter但一直还木有对它进行介绍那整篇还是有缺陷的,接下来就完美收官一下,当然只是稍加过一下既可,清楚了原理对于Arouter的使用就非常之easy了,再加篇幅有限。
欣赏官方Arouter核心实现:
上下官方网址:
大致瞅一下它的使用方法,当然它里面有很多高级用法,这里就不多说了,照着官方文档来还是很简单的,这里主要是看一下它的用法是否跟我们上面实现的差不多:
是不是几乎一模一样~~此时再来看这些调用,是不是对于它底层的实现也非常清楚了,好,最后再来看一下官方的代码框架,看跟咱们实现有也差不多:
然后看一下它的annotation:
当然它实现的功能要多,但是也是有Route、Param注解,继续来看一下它的api是啥?
再看一下它的注解处理器:
最后再来瞅一下它是如何收集注解处理器所生成的那个路由表信息的:
ok,关于官方框架的就跑马观花式的看了一下,手写了框架最核心的代码,对于怎么应用其实在实际工作中再去深挖既可。通过这三篇的学习记录,真心觉得还是挺累的,但是收获也大大滴,对于注解处理器的使用又上了一个台阶~~只有这样一点点的去研究才能让自己沉下心来去感受技术的魅力,下个专题学习再见~~