android 组件化之ARouter《二》

本文详细解析了阿里巴巴ARouter路由框架的使用步骤及初始化过程,包括依赖添加、注解处理器引入、初始化方法详解等,并深入分析了页面跳转的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 ARouter的使用步骤

第一步 添加依賴

因为ARouter在各个模块都会用到,因此可以在ModuleBase 里面添加依赖。

 api 'com.alibaba:arouter-api:1.4.0'

第二步 引入注解处理器

ARouter 使用了编译时注解,这里需要在各个子模块引入处理器。

annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'

 为编译期间生成路径映射。同时也需要在各子模块的build中加入

    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }
这段配置主要是给编译注解处理器传递一个参数。

第三步  初始化ARouter:

public class MyApplication extends Application {
@Override
public void onCreate() {
    super.onCreate();
    initRouter(this);
}

public static void initRouter(Application application) {
    if (BuildConfig.DEBUG) {
       // 打印日志
        ARouter.openLog();     
    // 开启调试模式
        ARouter.openDebug();   
    }
    ARouter.init(application);
}

第四步  添加注解:

@Route(path = "/test/testActivity")
public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    public static void startLoginActivity() {
        ARouter.getInstance().build("/test/testActivity2").navigation();
    }
}

以上就是基本使用过程,更多详情大家可以查阅相关api。 我们下面进入ARouter的源码,分析其原理。

二 ARouter初始化

/**
 * Init, it must be call before used router.
 */
public static void init(Application application) {
    if (!hasInit) {
        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.");
    }
}

下面我们看下init 方法

2.1 初始化Arouter.init

protected static synchronized boolean init(Application application) {
    mContext = application;
//executor 是个线程池
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    return true;
}

 这里调用了LogisticsCenter的init 方法。

LogisticsCenter  主要由两个功能

  • 搜集所有的组件信息并且注册到项目中(把组件信息读到内存中),方便后续的调用。所谓组件可以理解Path 注解生成的类。
  • 统一封装Postcard逻辑。

/**
 * LogisticsCenter init, load all metas in memory. Demand initialization
 */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        long startInit = System.currentTimeMillis();
        //billy.qi modified at 2017-12-06
        //load by plugin first
//
        loadRouterMap();
        if (registerByPlugin) {
//使用插件初始化了,就不需要继续往下面走了
            logger.info(TAG, 
"Load router map by arouter-auto-register plugin.");
        } else {
            Set<String> routerMap;

            // 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 generate by arouter-compiler.
  //或者指定包路径下面的所有类
                routerMap = ClassUtils.getFileNameByPackageName(mContext, 
ROUTE_ROOT_PAKCAGE);
                if (!routerMap.isEmpty()) {
 //缓存数据
                    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 finish.
            } 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();

//分类加载进入仓库里面,Warehouse
            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);
                }
            }
        }
xxxxx 删除部分代码
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

首先调用了loadRouterMap

    private static void loadRouterMap() {
        registerByPlugin = false;
        //auto generate register code by gradle plugin: arouter-auto-register
        // looks like below:
        // registerRouteRoot(new ARouter..Root..modulejava());
        // registerRouteRoot(new ARouter..Root..modulekotlin());
    }

因为初始化的时候需要变遍历dex文件找到我们需要要的类,这个阶段是很费时间的,因此采用在插件,在编译的时候遍历class文件,然后修改字节码。最终会在loadRouterMap这里插入初始化的代码。插入的代码就是方法中注释的代码。

插入的方法registerRouteRoot 会将registerByPlugin 设置为true。ARouter 通过gradle 插件修改字节码的实现我们放到后面介绍。

这里分析registerRouteRoot 为false的情况。

registerRouteRoot 为false 的时候会进入else 分支,这里主要是通过ClassUtils 来获取所有的在编译期间根据注解生成的类。

    /**
     * 通过指定包名,扫描包下面包含的所有的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<>();

        //获取所有的dex 文件,dex内部包含编译后的源码
        List<String> paths = getSourcePaths(context);
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());

        //遍历所有的dex文件
        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);
                        }

                        //获取dex 里面的文件。
                        //这里可以将dex看成是一个压缩文件,里面有很多的class  文件。
                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            //获取类的名字
                            String className = dexEntries.nextElement();
                            //该类处于com.alibaba.android.arouter.routes 包下面。
                            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();
                    }
                }
            });
        }
        //等待所有的dex文件被遍历完。

        parserCtl.await();

        Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }

dex是Android平台上(Dalvik虚拟机)的可执行文件, 相当于Windows平台中的exe文件, 每个Apk安装包中都有dex文件, 里面包含了该app的所有源码, 通过反编译工具可以获取到相应的java源码。

早期的是时候一个apk里面只有一个dex 文件后来andorid 支持分包之后,一个apk可以包含多个dex 文件。getSourcePaths 获取所有的dex的路径,感兴趣的可以自己研究一下。

因为整个遍历的过程是很耗费时间的因此在获取到所有处于com.alibaba.android.arouter.routes 包下面的类之后,会将这些类的名字保存在sp 文件里面,当这次读取的时候就直接从sp 文件读取。加快速度。

之后会遍历所有的类,根据类对应的注解的不同,通过反射创建一个对象然后将之加入到不同的仓库内。这里主要是Route 与Interceptor 的注解。

最终创建的对象都保存在了Warehouse 里面。

2.1 初始化_ARouter.afterInit

static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}

内部会最终调用到_Arouter.build

/**
 * Build postcard by path and default group
 */
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
//重定向服务
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
//extractGroup 是通过path截获group
//例如path="/app/main"  app就是group
        return build(path, extractGroup(path));
    }
}
    /**
     * Build postcard by path and group
     */
    protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

如果我们的应应用中没有实现PathReplaceService这个接口,则pService=null。

可以看到此时刚创建PostCard的时候 ,PostCard仅仅只有path,group字段,其余的字段都还没有赋值呢。

build 创建一个PostCard 之后紧接着调用其navigation 方法。

最终会调用到_ARouterd的navigation ,

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
                xxx
            return null;
        }
        if (null != callback) {
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(xxx)
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

这里主要是调用LogisticsCenter.completion 方法。

    /**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should complete by this method.
     */
    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                //第一次调用走这个分支。
                try {

                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    //创建IRouteGroup并添加到Warehouse.routes
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                //递归调用,此时会走下面那个分支。
                completion(postcard);   // Reload
            }
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                xxx
            }

            switch (routeMeta.getType()) {
                case PROVIDER:
                    //进入这里
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            //通过反射创建一个对象
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

第一次进入的时候routeMeta为null 然后通过发射创建一个IRouteGroup并将之保存在Warehouse.routes里面。 IRouteGroup 实际类是编译期间根据生成注解生成的类,里面包含被注解的类的名字。

例如

之后递归调用completion 方法,递归进入之后routeMeta就不是null 会进入else 分支。

这里实际就是获取到被注解的类的构造方法通过反射创建一个对象。

自此completion 方法就分析完了,这里主要是获取到被注解的类的构造方法并创建一个对象保存在WareHouse 里面。

当completion 知心完之后会调用_navigation 方法。

_navigation 就不在展开介绍了,对于Proiver 类型而言其会返回postcard.getProvider()的返回值。

总结:

interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();

返回的对象就是通过Route 注解的路径为"/arouter/service/interceptor"的类一个对象。

"/arouter/service/interceptor" 是ARouter 定义的一个特殊的路径,

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService

InterceptorServiceImpl 主要用来创建用户自定义的拦击器。

for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
    Class<? extends IInterceptor> interceptorClass = entry.getValue();
    try {
 //反射创建拦截器
        IInterceptor iInterceptor = 
interceptorClass.getConstructor().newInstance();
//初始化这个拦截器
        iInterceptor.init(context);
//缓存这个拦截器
        Warehouse.interceptors.add(iInterceptor);
    } catch (Exception ex) {
        throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
    }
}

有感兴趣的可以查看一下InterceptorServiceImpl。

ARouter 的初始化到此就介绍完了,总结一下就是遍历所有的dex 文件获取到所有在编译期间生成的类并保存在爱WareHouse 里面。之后找到注解为@Route(path = "/arouter/service/interceptor")的类也就是InterceptorServiceImpl类然后创建一个对象,调用这个类的init方法,该方法内部会创建用户注册的Interceptor。

二 页面跳转

  ARouter.getInstance().build("/test/test1").navigation();

这个方法的流程的大部分在初始的过程中都介绍了,

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            xxx

            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {  
          //会进入这个分支 
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                 //执行完用户自定义的拦截器之后进入这里
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    //
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

页面跳转的时候postcard.isGreenChannel()返回的是false,因此会进入执行用户注册的拦截器,当用户注册的拦截器执行完之后调用_navigation 方法。

    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                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.
                if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            startActivity(requestCode, currentContext, intent, postcard, callback);
                        }
                    });
                } else {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }

                break;
            xxxx
            default:
                return null;
        }

        return null;
    }
    private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  // Need start for result
            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
        } else {
            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
        }

        if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
        }

        if (null != callback) { // Navigation over.
            callback.onArrival(postcard);
        }
    }

此处会进入ACTIVITY 分支,到了这里就是大家都熟悉的页面跳转也就死通过Intent 来跳转页面。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值