《Android原理》ARouter

2021-03-14  本文已影响0人  育智者

一、概述

一个支持模块间的路由、通信、解耦,帮助Android App进行组件化改造的框架。
GitHub:https://github.com/alibaba/ARouter


二、由来

说起来由,就不得不提起Android中组件化开发的思想,组件化是最近Android比较流行的架构设计方案,它对模块(module)间进行高度的解耦、分离等,能极大的改善开发效率。但是组件化架构设计,导致模块(module)间没有相互之间的引用,或则只是单向的持有引用,所以给模块(module)间的跳转startActivity带来了不小的阻力。这时候就需要一个框架,既不会破坏组件化的架构设计方案,又能解决模块(module)间路由跳转。而ARouter就可以很好的解决这个问题。

可能有人会想到隐式跳转也可以解决这个问题,当然这也是一种方案,但是如果整个项目都用隐式跳转,这样Manifest文件会有很多过滤配置,不利于后期的维护。

通过反射机制拿到Activity的class文件也可以实现跳转,其实,大量的使用反射,会对性能有很大的影响,其次因为组件开发的时候组件module之间是没有相互引用的,你只能通过找到类的路径去反射拿到这个class。


三、回顾使用

既然相较于隐式跳转和反射机制,ARouter更容易解决模块(module)间路由跳转的问题。那么它又是如何做到的呢?在介绍它如何做到之前,先回顾一下我们是如何使用它实现路由跳转的,具体步骤参考https://github.com/alibaba/ARouter/blob/master/README_CN.md

//模块Main
@Route(path = "/Main/MainActivity")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

//模块Login
@Route(path = "/Login/LoginActivity")
public class LoginActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
    }
}
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ARouter.init(this);
    }
}
// 1. 模块Main跳转Login
ARouter.getInstance().build("/Login/LoginActivity").navigation();

// 2. 跳转并携带参数
ARouter.getInstance().build("/Login/LoginActivity")
            .withLong("key", aaa)
            .withString("value", "bbbb")
            .navigation();

ARouter只做了三件事情就解决了组件间路由跳转:


四、细节

我们心里有这三步大概的概念之后,再来看看每一步中的细节。

在第一步中 -----在程序编译时候完成

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     * Path of route
     */
    String path();

    /**
     * Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
     */
    String group() default "";

    /**
     * Name of route, used to generate javadoc.
     */
    String name() default "";

    /**
     * Extra data, can be set by user.
     * Ps. U should use the integer num sign the switch, by bits. 10001010101010
     */
    int extras() default Integer.MIN_VALUE;

    /**
     * The priority of route.
     */
    int priority() default -1;
}

(2)第二步,在Activity上使用注解

//模块Main
@Route(path = "/Main/MainActivity")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

(3)第三步,注解处理器,在编译器找到加入注解的类文件,进行处理,这里只展示关键代码,具体代码

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
    private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.(key:模块名,value:路由表)
    private Map<String, String> rootMap = new TreeMap<>();  // Map of root metas, used for generate class file in order.(根元的映射,用于生成类文件的顺序。)
}

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
   // Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
  for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
    //将所有@Route()注解存入docSource
   rootMap.put(groupName, groupFileName);
   docSource.put(groupName, routeDocList);
  }
}
// Generate groups
String groupFileName = NAME_OF_GROUP + groupName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(groupFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(type_IRouteGroup))
                                .addModifiers(PUBLIC)
                                .addMethod(loadIntoMethodOfGroupBuilder.build())
                                .build()
                ).build().writeTo(mFiler);

// Write provider into disk
String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(providerMapFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IProviderGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfProviderBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

// Write root meta into disk.
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);
public class ARouter$$Group$$Main implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/Main/MainActivity", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/main/mainactivity", "main", null, -1, -2147483648));
  }
}
image.png

在第二步中-----在程序初始化时候完成

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        ARouter.init(this);//第二步,初始化
        super.onCreate();
    }
}

那我们就沿着ARouter.init(this)点进去看看,是如何拿到映射关系。
_ARouter源码地址

    //com.alibaba.android.arouter.launcher.ARouter
//https://github.com/alibaba/ARouter/blob/master/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/ARouter.java
    /**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {//我们可以知道,初始化时找到这些类文件会有一定的耗时,ARouter这里会有一些优化,只会遍历找一次类文件,找到之后就会保存起来,下次app进程启动会检索是否有保存这些文件,如果有就会直接调用保存后的数据去初始化。
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }


//com.alibaba.android.arouter.launcher._ARouter
//https://github.com/alibaba/ARouter/blob/master/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/_ARouter.java
protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);//关进代码,注意这个executor是个线程池
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;//拿到之后,将hasInit置为true。
    mHandler = new Handler(Looper.getMainLooper());
    return true;
}

//com.alibaba.android.arouter.core.LogisticsCenter
//https://github.com/alibaba/ARouter/blob/4660c11bab2b91515451ada04f943f8ea4b79ace/arouter-api/src/main/java/com/alibaba/android/arouter/core/LogisticsCenter.java
/**
* LogisticsCenter init, load all metas in memory. Demand initialization,,
* LogisticsCenter init,在内存中加载所有metas。需求初始化。
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
                // It will rebuild router map every times when debuggable.
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);//关键代码
                    if (!routerMap.isEmpty()) {
                        //SharedPreferences做保存记录,优化初始化。
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
}

    //com.alibaba.android.arouter.utils.ClassUtils
//https://github.com/alibaba/ARouter/blob/4660c11bab2b91515451ada04f943f8ea4b79ace/arouter-api/src/main/java/com/alibaba/android/arouter/utils/ClassUtils.java
    /**
     * 通过指定包名,扫描包下面包含的所有的ClassName
     *
     * @param context     U know
     * @param packageName 包名
     * @return 所有class的集合
     */
    public static Set<String> getFileNameByPackageName(Context 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());

        for (final String path : paths) {
            DefaultPoolExecutor.getInstance().execute(new Runnable() {
                @Override
                public void run() {
                    DexFile dexfile = null;

                    try {
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
                            //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
                            dexfile = new DexFile(path);
                        }

                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close();
                            } catch (Throwable ignore) {
                            }
                        }

                        parserCtl.countDown();
                    }
                }
            });
        }

        parserCtl.await();
        return classNames;
    }

总结:
(1)通过ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)得到apt生成的所有实现IRouteRoot接口的类文件集合,通过上面的讲解我们知道,拿到这些类文件便可以得到所有的路由地址和Activity映射关系。
(2)getFileNameByPackageName()通过开启子线程,去扫描apk中所有的dex,遍历找到所有包名为packageName的类名,然后将类名再保存到classNames集合里。

在第三步中-----在程序跳转时候完成

ARouter.getInstance().build("/Login/LoginActivity").navigation();

在build的时候,传入要跳转的路由地址,build()方法会返回一个Postcard对象,我们称之为跳卡。然后调用Postcard的navigation()方法完成跳转。用过ARouter的对这个跳卡都应该很熟悉吧!Postcard里面保存着跳转的信息,Postcard继承于RouteMeta。下面我把Postcard类和RouteMeta类的代码实现粘下来:

//https://github.com/alibaba/ARouter/blob/4660c11bab2b91515451ada04f943f8ea4b79ace/arouter-api/src/main/java/com/alibaba/android/arouter/facade/Postcard.java
public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to transform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannel;
    private SerializationService serializationService;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim = -1;
    private int exitAnim = -1;
    //.....省略后面的代码
}

//https://github.com/alibaba/ARouter/blob/4660c11bab2b91515451ada04f943f8ea4b79ace/arouter-annotation/src/main/java/com/alibaba/android/arouter/facade/model/RouteMeta.java
public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route(原始路由类型)
    private Class<?> destination;   // Destination(目的地,终点)
    private String path;            // Path of route(路由path)
    private String group;           // Group of route(路由分组)
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;
    //.....省略后面的代码
}

Postcard类和RouteMeta里面有了跳转的目的地Class<?> destination,接下的方法navigation()我想都能猜到,他是用啥跳转了吧——startActivity。

//https://github.com/alibaba/ARouter/blob/4660c11bab2b91515451ada04f943f8ea4b79ace/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/_ARouter.java#L351
  final Intent intent = new Intent(currentContext, postcard.getDestination());//获取目的地Class<?> destination
  intent.putExtras(postcard.getExtras());

  // Set flags.
  int flags = postcard.getFlags();
  if (-1 != flags) {
    intent.setFlags(flags);
  } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  }

  // Set Actions
  String action = postcard.getAction();
  if (!TextUtils.isEmpty(action)) {
    intent.setAction(action);
  }
   // Navigation in main looper.
  runInMainThread(new Runnable() {
    @Override
    public void run() {
       startActivity(requestCode, currentContext, intent, postcard, callback);//跳转
    }
  });

五、结束语

(1)本文只是大概的分析了ARouter的跳转流程的实现原理,里面还有很多的小细节没有讲到,如过滤器等。
(2)ARouter涉及到几个关键的技术点,如:APT、Javapoet,可以再深入研究。他们在很多的框架中都是关键点。如我们常用的ButterKnife,其原理就是通过注解处理器在编译期扫描代码中加入的@BindView、@OnClick等注解进行扫描处理,然后生成XXX_ViewBinding类,实现了view的绑定。
(3)本文Dome地址:https://github.com/yuzhizhe/ARouter_Sample

上一篇下一篇

猜你喜欢

热点阅读