【Android 组件化】使用 ARoute 实现组件化 ( ARoute 初始化 及 界面跳转 源码分析 )

简介: 【Android 组件化】使用 ARoute 实现组件化 ( ARoute 初始化 及 界面跳转 源码分析 )

一、ARoute 初始化源码分析


引入了 ARoute 的应用 , 一般会在主应用的 Application 中的 onCreate 方法中初始化 ARoute ;


package kim.hsl.component;
import android.app.Application;
import com.alibaba.android.arouter.launcher.ARouter;
public class MyApplication extends Application {
    public static MyApplication mApplication;
    public MyApplication() {
        super();
        mApplication = this;
    }
    public static MyApplication getInstance() {
        return mApplication;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
            ARouter.openLog();     // 打印日志
            ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        ARouter.init(this); // 尽可能早,推荐在Application中初始化
    }
    public boolean isDebug(){
        return BuildConfig.DEBUG;
    }
}


其中


ARouter.init(this);

1

是 ARoute 初始化的核心方法 , 其调用了 com.alibaba.android.arouter.launcher.ARouter 类的 init 方法 , com.alibaba.android.arouter.launcher.ARouter 类是一个空壳类 , 实际的代码逻辑都定义在 com.alibaba.android.arouter.launcher._ARouter 类中 ;


package com.alibaba.android.arouter.launcher;
public final class 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.");
        }
    }
}

在 ARoute 类的 init 方法中调用了 _ARoute 的 init 方法


_ARouter.init(application);

1

_ARoute 相关源码如下 :


final class _ARouter {
    protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    }
}


其中的


LogisticsCenter.init(mContext, executor);

1

是核心的逻辑 , 在该方法中 , 加载了路由表 ,


路由表类是 注解处理器 在编译时生成的类 , 生成的目录是 " D:\002_Project\002_Android_Learn\ComponentDemo\app\build\generated\ap_generated_sources\debug\out\com\alibaba\android\arouter\routes " ;

9.png


生成的路由表示例 :


package com.alibaba.android.arouter.routes;
import com.alibaba.android.arouter.facade.enums.RouteType;
import com.alibaba.android.arouter.facade.model.RouteMeta;
import com.alibaba.android.arouter.facade.template.IRouteGroup;
import java.lang.Override;
import java.lang.String;
import java.util.Map;
import kim.hsl.component.MainActivity;
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$app implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/app/MainActivity", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/mainactivity", "app", null, -1, -2147483648));
  }
}


生成的对应的 Root 表 , 作为路由表的索引 ;

package com.alibaba.android.arouter.routes;
import com.alibaba.android.arouter.facade.template.IRouteGroup;
import com.alibaba.android.arouter.facade.template.IRouteRoot;
import java.lang.Class;
import java.lang.Override;
import java.lang.String;
import java.util.Map;
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("app", ARouter$$Group$$app.class);
  }
}



LogisticsCenter.init(mContext, executor) 方法执行的操作就是创建 ARouter$$Group$$app 类 , 调用该类的 loadInto 方法 , 导入路由表 , 将路由表加载到内存中 ;


二、ARoute 界面跳转源码分析


ARoute 使用时的示例如下 , 在该 Activity 类中 , 涉及到注解使用 , 界面跳转 ;
package kim.hsl.component;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.launcher.ARouter;
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void onClick(View view) {
        Log.i(TAG, "跳转到 Module1");
        ARouter.getInstance().build("/module1/Module1Activity").navigation();
        finish();
    }
}


当调用到


ARouter.getInstance().build("/module1/Module1Activity").navigation();

1

代码进行界面跳转时 ,


public final class ARouter {
    /**
     * Build the roadmap, draw a postcard.
     *
     * @param path Where you go.
     */
    public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }
}



ARouter.getInstance().build 方法实际上调用的是 _ARoute 的 build(String path) 方法 , 在 该方法中又调用了 build 的重载函数 build(String path, String group, Boolean afterReplace) , 最终返回了一个跳卡 Postcard 对象 ;


final class _ARouter {
    /**
     * 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);
            }
            // 核心是调用了 build 的重载函数 
            // extractGroup 函数的作用明显是取出路径中的分组 , 就是前两个 / 之间的字符串内容
            return build(path, extractGroup(path), true);
        }
    }
    /**
     * Build postcard by path and group
     */
    protected Postcard build(String path, String group, Boolean afterReplace) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            if (!afterReplace) {
                PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
                if (null != pService) {
                    path = pService.forString(path);
                }
            }
            // 最终创建了一张跳卡 
            return new Postcard(path, group);
        }
    }
}


跳转的实际操作是调用的跳卡的 navigation 方法 , 此时该跳卡中 , 只有 path 路由地址 , group 路由分子名称 , 其它信息还是空的 ;


public final class Postcard extends RouteMeta {
    public Postcard(String path, String group) {
        this(path, group, null, null);
    }
    /**
     * Navigation to the route with path in postcard.
     * No param, will be use application context.
     */
    public Object navigation() {
        return navigation(null);
    }
    /**
     * Navigation to the route with path in postcard.
     *
     * @param context Activity and so on.
     */
    public Object navigation(Context context) {
        return navigation(context, null);
    }
    /**
     * Navigation to the route with path in postcard.
     *
     * @param context Activity and so on.
     */
    public Object navigation(Context context, NavigationCallback callback) {
        return ARouter.getInstance().navigation(context, this, -1, callback);
    }
}


其中的 NavigationCallback 参数 , 传入一个监听回调接口 , 可以监听跳转操作是否完成了 ;


/**
 * Callback after navigation.
 *
 * @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
 * @version 1.0
 * @since 2016/9/22 14:15
 */
public interface NavigationCallback {
    /**
     * Callback when find the destination.
     *
     * @param postcard meta
     */
    void onFound(Postcard postcard);
    /**
     * Callback after lose your way.
     *
     * @param postcard meta
     */
    void onLost(Postcard postcard);
    /**
     * Callback after navigation.
     *
     * @param postcard meta
     */
    void onArrival(Postcard postcard);
    /**
     * Callback on interrupt.
     *
     * @param postcard meta
     */
    void onInterrupt(Postcard postcard);
}


调用 Postcard 跳卡对象的 navigation() 方法 , 之后调用了一系列的 navigation 重载方法 , navigation(Context context) , navigation(Context context, NavigationCallback callback) , 最终又调用了 ARouter.getInstance().navigation(context, this, -1, callback) 方法 ;


在 ARouter 的 navigation 方法中 , 依旧是调用了 _ARoute 的 navigation 方法 ;


public final class ARouter {
    /**
     * Launch the navigation.
     *
     * @param mContext    .
     * @param postcard    .
     * @param requestCode Set for startActivityForResult
     * @param callback    cb
     */
    public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
        return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
    }
}


跳卡 Postcard 对象中目前有 路由地址 , 路由分组 两个信息 , 先调用


LogisticsCenter.completion(postcard);

1

函数, 将跳卡 Postcard 对象补全 , 最重要的是该路由地址对应的 Class 类对象 , 通过之前 ARoute 初始化到内存的路由表补充跳卡中的数据 ;


然后判断该跳转是否是绿色通道 , 如果是继续执行跳转 ;


如果不是 , 则触发拦截器 , 拦截器判定未通过 , 则中断跳转 , 拦截器判定通过 , 则继续执行跳转 ;


这里的拦截器一般用于权限鉴定 , 比如用户是否购买会员 , 是否购买服务 , 是否拥有权限等等 ;


拦截器的详细用法自行去 GitHub 上查看 ;


最终调用 _navigation 方法进行跳转 ;


在 _navigation 方法中 , 跳卡 Postcard 的 Activity 跳转就是 创建 Intent , 并执行 startActivity 方法进行界面跳转 ;


final class _ARouter {
    /**
     * Use router navigation.
     *
     * @param context     Activity or null.
     * @param postcard    Route metas
     * @param requestCode RequestCode
     * @param callback    cb
     */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            // Pretreatment failed, navigation canceled.
            return null;
        }
        try {
          // 补充跳卡数据 
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }
            if (null != callback) {
                callback.onLost(postcard);
            } else {
                // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }
            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(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;
    }
    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.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
              // 从跳卡中取出 , 反射获取对象 
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }
        return null;
    }
}


插个插曲 , 分析下从内存中查找路由信息并填充到 跳卡 Postcard 对象的过程 ;


在 LogisticsCenter 中获取跳卡 Postcard 完整数据的方法 , 首先从 Warehouse.routes 中获取相关数据 , 之前 ARoute 的 init 初始化方法中将路由表加载到了内存中的该 Warehouse 对应的静态成员中 , 这里直接从该静态成员中获取 路由 数据 ;


如果从 Warehouse 中获取 路由信息 失败 , 说明路由表还没有加载 , 那么先加载路由表 , 路由表加载成功后 , 再获取跳卡对应的路由信息 , 并填充到 跳卡 Postcard 对象中 ;


public class LogisticsCenter {
    /**
     * 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 {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    // 将路由表加载到内存中 
                    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.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();
                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }
                    // Save params name which need auto inject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }
                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }
            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                    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;
            }
        }
    }
}



目录
相关文章
|
15天前
|
程序员 开发工具 Android开发
Android|WebView 禁止长按,限制非白名单域名的跳转层级
如何限制 WebView 仅域名白名单网址能随意跳转,并禁用长按选择文字。
27 2
|
1月前
|
XML 数据可视化 Android开发
Android应用界面
Android应用界面中的布局和控件使用,包括相对布局、线性布局、表格布局、帧布局、扁平化布局等,以及AdapterView及其子类如ListView的使用方法和Adapter接口的应用。
20 0
Android应用界面
|
2月前
|
Android开发 UED Kotlin
Android中如何跳转到Wi-Fi开关设置页
本文介绍如何在Android应用开发中使用隐式Intent引导用户至特定系统设置页面,如Wi-Fi设置页,并提供Kotlin代码示例。通过设置Intent的Action属性并检查设备兼容性,可轻松实现跳转功能,提升用户体验。此外,还列举了其他常用设置页面的Intent Action及注意事项。
73 15
|
2月前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
33 3
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
44 2
|
2月前
|
XML Android开发 UED
💥Android UI设计新风尚!掌握Material Design精髓,让你的界面颜值爆表!🎨
随着移动应用市场的蓬勃发展,用户对界面设计的要求日益提高。为此,掌握由Google推出的Material Design设计语言成为提升应用颜值和用户体验的关键。本文将带你深入了解Material Design的核心原则,如真实感、统一性和创新性,并通过丰富的组件库及示例代码,助你轻松打造美观且一致的应用界面。无论是色彩搭配还是动画效果,Material Design都能为你的Android应用增添无限魅力。
58 1
|
6月前
|
Android开发
定制Android关机界面
定制Android关机界面
87 0
|
3月前
|
存储 安全 物联网
Android经典实战之跳转到系统设置页面或其他系统应用页面大全
本文首发于公众号“AntDream”,关注获取更多技巧。文章总结了Android开发中跳转至系统设置页面的方法,包括设备信息、Wi-Fi、显示与声音设置等,并涉及应用详情与电池优化页面。通过简单的Intent动作即可实现,需注意权限与版本兼容性。每日进步,尽在“AntDream”。
366 2
|
4月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
143 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
3月前
|
Android开发 iOS开发 C#
Xamarin.Forms:从零开始的快速入门指南——打造你的首个跨平台移动应用,轻松学会用C#和XAML构建iOS与Android通用界面的每一个步骤
【8月更文挑战第31天】Xamarin.Forms 是一个强大的框架,让开发者通过单一共享代码库构建跨平台移动应用,支持 iOS、Android 和 Windows。使用 C# 和 XAML,它简化了多平台开发流程并保持一致的用户体验。本指南通过创建一个简单的 “HelloXamarin” 应用演示了 Xamarin.Forms 的基本功能和工作原理。
75 0
下一篇
无影云桌面