【Android】ARouter

【笔记】【Android】ARouter

介绍

一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

  • 支持直接解析标准 URL 进行跳转,并自动注入参数到目标页面中
  • 支持多模块工程使用
  • 支持添加多个拦截器,自定义拦截顺序
  • 支持依赖注入,可单独作为依赖注入框架使用
  • 支持 InstantRun
  • 支持 MultiDex (Google 方案)
  • 映射关系按组分类、多级管理,按需初始化
  • 支持用户指定全局降级与局部降级策略
  • 页面、拦截器、服务等组件均自动注册到框架
  • 支持多种方式配置转场动画
  • 支持获取 Fragment
  • 完全支持 Kotlin 以及混编
  • 支持第三方 App 加固 (使用 arouter-register 实现自动注册)
  • 支持生成路由文档
  • 提供 IDE 插件便捷的关联路径和目标类

典型应用

  • 从外部 URL 映射到内部页面,以及参数传递与解析
  • 跨模块页面跳转,模块间解耦
  • 拦截跳转过程,处理登陆、埋点等逻辑
  • 跨模块 API 调用,通过控制反转来做组件解耦

优势

  • 直接解析 URL 路由,解析参数并赋值到对应目标字段的页面中。
  • 支持多模块项目,因为现在很少有 APP 是单模块的项目,一般都是多模块单工程的,由不同的团队负责不同的模块开发,这时候支持多模块项目开发就显得尤为重要
  • 支持 InstantRun,目前很多路由框架并不支持 InstantRun,而 InstantRun 是 Google 在 AndroidStudio2.0 阿尔法版本中提供的新功能,其类似于代码的日更新,其只不过面向的是开发过程,这样做可以在开发的过程中减少开发和编译的次数,可以简单地将代码修改即时地同步到 APK 中,从而可以大规模降低开发复杂度。
  • 允许自定义拦截器,ARouter 是支持拦截器的,而拦截器其实就是 AOP 的实现,可以自定义多个拦截器解决一些面向行为编程上出现的问题。
  • ARouter 可以提供 IoC 容器,IoC 其实就是控制反转,这一部分做过服务端开发的朋友可能比较了解,因为服务端开发经常用到的 Spring 框架能够提供的一个非常重要的能力就是控制反转。
  • 映射关系自动注册,在页面不是很多的小型 APP 上面,自动注册并不会体现出太大优势,但是对于大型 APP 而言,可能页面数量已经达到的几十个或者数百个,在这样的情况下,自动注册就显得非常重要了,因为不可能将每一个页面都通过代码的方式进行注册。
  • 灵活的降级策略,ARouter 可以提供很多种降级策略供用户自行选择,而原生的路由方案存在无法灵活降级的问题,StartActivity () 一旦失败将会抛出运营级异常。

使用

基本使用及页面注册源码解析

  • 在使用之前需要先配置 gradle,目前 api 最新的版本是 1.3.1,处理器的最新版本是 1.1.4
dependencies {
    // 替换成最新版本, 需要注意的是api
    // 要与compiler匹配使用,均使用最新版可以保证兼容
    implementation 'com.alibaba:arouter-api:1.3.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
    ...
}
  • 接着需要尽早的初始化 ARouter,可以考虑在 Application 中进行初始化:
ARouter.init(mApplication);
  • 在需要支持路由跳转的页面上添加 @Route 注解,路径 path 至少需要有两级,比如需要跳转到 LoginActivity, 那么需要在 Activity 上面配置 path, 例如 /activity/LoginActivity。
@Route(path = "/activity/LoginActivity")
public class LoginActivity extends BaseActivity< LoginActivityIMP> implements LoginActivityContract.View
  • 然后直接像下面这样就就可以实现简单的跳转了
ARouter.getInstance().build("/activity/LoginActivity").navigation();
  • 如果需要携带参数跳转的话就像下面这样
  • ARouter 提供了很多的 withXX 方法,可以携带基本类型,Object,Parcelable 等等,里面的原理也是通过 Bundle 进行携带 mBundle.putXX 实现
ARouter.getInstance().build("/activity/LoginActivity").withString("key", "value").navigation();
  • 如果需要实现类似 startActivityForResult, 只需要在 navigation 中传入两个参数,第一个就是 Context,第二个参数是 requestCode。
ARouter.getInstance().build("/activity/LoginActivity").navigation(this,1);

页面跳转源码分析

  • ARouter.getInstance (), 是获得 ARouter.init (mApplication) 初始化的事例
public static ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouter::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (ARouter.class) {
                    if (instance == null) {
                        instance = new ARouter();
                    }
                }
            }
            return instance;
        }
}
  • 然后.build () 构造路由信息的容器 Postcard
public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
}
  • 得到 ARouter 实例后调用 build 方法,传入目标页面的 path (“/activity/LoginActivity”),我们来看看 build 的源码。其实是调用_ARouter 的 build 方法,这里需要提的一点是 ARouter.init 也是调用的_ARouter 的 init 方法
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.");
        }
}
  • 然后在看_ARouter 的 build 方法
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);
            }
            return build(path, extractGroup(path));
        }
}
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);
        }
}
  • 在 new Postcard (path, group) 中,第一个参数就是路径,第二个参数是组别信息。 比如 “/activity/LoginActivity” 来说 activity 就是 group。ARouter 框架是分组管理,按需加载。解释起来就是,在编译期框架扫描了所有的注册页面/服务/字段/拦截器等,那么很明显运行期不可能一股脑全部加载进来, 这样就太不和谐了。所以就分组来管理,ARouter 在初始化的时候只会一次性地加载所有的 root 结点,而不会加载任何一个 Group 结点,这样就会极大地降低初始化时加载结点的数量。 比如某些 Activity 分成一组,组名就叫 activity,然后在第一次需要加载组内的某个页面时再将 activity 这个组加载进来。
  • 最后一个方法.navigation (), 方法最后会来到_ARouter 的 navigation 方法
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            ……
            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()) { //如果需要拦截
        ……
        } else {//不需要拦截
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
}
  • 首先调用 LogisticsCenter.completion 完成 postcard 的补充
/**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should completion 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 be implememt 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;
            }
        }
}
  • 因为是按需加载,所以 RouteMeta routeMeta = Warehouse.routes.get (postcard.getPath ()); 一开始肯定是 null 的,所以需要到 Warehouse.groupsIndex 中找到组别的信息,这里的 postcard.getGroup () 就是 test.
Class< ? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
  • 然后通过反射加载这一组类别的映射关系。然后从仓库中删除这个组别信息节点,防止重复加载。
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
  • 然后看看 apt 生成的文件 ARouter$$Group$$activity 的内容,包含有目标页面,类型,路径,组别,参数,优先级等信息。
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$Group$activity implements IRouteGroup {
  @Override
  public void loadInto(Map< String, RouteMeta> atlas) {
    atlas.put("/activity/ClassifyActivity", RouteMeta.build(RouteType.ACTIVITY, ClassifyActivity.class, "/activity/classifyactivity", "activity", null, -1, -2147483648));
    atlas.put("/activity/CreateNoteActivity", RouteMeta.build(RouteType.ACTIVITY, CreateNoteActivity.class, "/activity/createnoteactivity", "activity", new java.util.HashMap< String, Integer>(){{put("keyName", 8); put("mNote", 10); }}, -1, -2147483648));
    atlas.put("/activity/LoginActivity", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/activity/loginactivity", "activity", null, -1, -2147483648));
    atlas.put("/activity/SearchNoteActivity", RouteMeta.build(RouteType.ACTIVITY, SearchNoteActivity.class, "/activity/searchnoteactivity", "activity", new java.util.HashMap< String, Integer>(){{put("keyName", 8); }}, -1, -2147483648));
    atlas.put("/activity/TODOActivity", RouteMeta.build(RouteType.ACTIVITY, TODOActivity.class, "/activity/todoactivity", "activity", new java.util.HashMap< String, Integer>(){{put("keyName", 8); }}, -1, -2147483648));
  }
}
  • 接下来会递归调用 completion (postcard),现在 routeMeta 就不为空了,会走到 else 中,首先给 postcard 补充信息,有了这些信息 postcard 就可以愉快的工作了。我们这个栗子中 type 很明显是 activity,所以就走到 default 中 break 出来了。
  • 然后最后到了真正的跳转,_navigation () 跳转,就是常见的 intent 和 startActivity、startActivityForResult
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);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

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

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

                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;

Uri 跳转解析 (官方 demo)

  • 官方demo的html.body
< body>

< h2>跳转测试< /h2>

< h2>自定义Scheme[通常来说都是这样的]< /h2>
< p>< a href="arouter://m.aliyun.com/test/activity1">arouter://m.aliyun.com/test/activity1< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity1?url=https%3a%2f%2fm.abc.com%3fa%3db%26c%3dd">测试URL Encode情况< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180&obj=%7b%22name%22%3a%22jack%22%2c%22id%22%3a666%7d">arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180&obj={"name":"jack","id":"666"}< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity2">arouter://m.aliyun.com/test/activity2< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity2?key1=value1">arouter://m.aliyun.com/test/activity2?key1=value1< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity3?name=alex&age=18&boy=true&high=180">arouter://m.aliyun.com/test/activity3?name=alex&age=18&boy=true&high=180< /a>< /p>

< h2>App Links[防止被App屏蔽]< /h2>
< p>< a href="http://m.aliyun.com/test/activity1">http://m.aliyun.com/test/activity1< /a>< /p>
< p>< a href="http://m.aliyun.com/test/activity2">http://m.aliyun.com/test/activity2< /a>< /p>

< /body>
  • ARouter 就是通过注册一个没有 UI 的界面来统一处理 scheme 是 arouter 的跳转请求,类似隐式注册
< activity android:name=".SchemeFilterActivity">


            < intent-filter>
                < data
                        android:host="m.aliyun.com"
                        android:scheme="arouter"/>

                < action android:name="android.intent.action.VIEW"/>

                < category android:name="android.intent.category.DEFAULT"/>
                < category android:name="android.intent.category.BROWSABLE"/>
            < /intent-filter>


            < intent-filter android:autoVerify="true">
                < action android:name="android.intent.action.VIEW"/>

                < category android:name="android.intent.category.DEFAULT"/>
                < category android:name="android.intent.category.BROWSABLE"/>

                < data
                        android:host="m.aliyun.com"
                        android:scheme="http"/>
                < data
                        android:host="m.aliyun.com"
                        android:scheme="https"/>
            < /intent-filter>
        < /activity>
  • 在清单文件中注册了 SchemeFilterActivity 这个 activity,scheme 就是 arouter, host 就是 m.aliyun.com,其中有个 category 是 android.intent.category.BROWSABLE,这样就可以在 WebView 中跳转 SchemeFilterActivity 这个界面了。有个注意点,能跳转 SchemeFilterActivity 的必要条件就是需要匹配 intent-filter 中的 data, action, category 的标签,其中 category 匹配任意一个即可。
public class SchemeFilterActivity extends Activity {

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

//        直接通过ARouter处理外部Uri
        Uri uri = getIntent().getData();
        ARouter.getInstance().build(uri).navigation(this, new NavCallback() {
            @Override
            public void onArrival(Postcard postcard) {
                finish();
            }
        });
    }
}
  • Uri uri = getIntent ().getData (); 获取到点击的那个值,例如 arouter://m.aliyun.com/test/activity2, 然后交给路由框架进行跳转

获取服务和 Fragment (官方 demo)

  • 获取服务
  • 这里说到的服务不是 Android 四大组件中的 Service,这里的服务是服务端开发的概念,就是将一部分功能和组件封装起来成为接口,以接口的形式对外提供能力,所以在这部分就可以将每个功能作为一个服务,而服务的实现就是具体的业务功能
  • ARouter 发现服务主要有两种方式,ByName 和 ByType。先看看这两种方式分别是怎么使用。ByName 就是需要传递 path 路径来进行发现,ByType 就是通过服务 class 来进行查找。
case R.id.navByName:
    ((HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");
    break;
case R.id.navByType:
    ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
    break;
public interface HelloService extends IProvider {
    void sayHello(String name);
}
@Route(path = "/yourservicegroupname/hello")
public class HelloServiceImpl implements HelloService {
    Context mContext;

    @Override
    public void sayHello(String name) {
        Toast.makeText(mContext, "Hello " + name, Toast.LENGTH_SHORT).show();
    }

    /**
     * Do your init work in this method, it well be call when processor has been load.
     *
     * @param context ctx
     */
    @Override
    public void init(Context context) {
        mContext = context;
        Log.e("testService", HelloService.class.getName() + " has init.");
    }
}
  • 获取 Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
  • ByName 和 Fragment 的获取和 Activity 的跳转很像。先看看 ByType 的获取过程。
ARouter.getInstance().navigation(HelloService.class);
  • 跳到到_ARouter 的 navigation (Class< ? extends T> service) 中。
protected < T> T navigation(Class< ? extends T> service) {
        try {
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // Compatible 1.0.5 compiler sdk.
            if (null == postcard) { // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }

            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
}

-上面代码主要就是两步,第一构造 postcard,第二 LogisticsCenter 跳转

  • 构造 postcard
  • 首先还是熟悉的先构造 postcard,只不过这里时直接在 navigation 中进行构造,之前 activity 或者 url 跳转都是通过 build 构造,意思都差不多。看看 LogisticsCenter.buildProvider
public static Postcard buildProvider(String serviceName) {
        RouteMeta meta = Warehouse.providersIndex.get(serviceName);

        if (null == meta) {
            return null;
        } else {
            return new Postcard(meta.getPath(), meta.getGroup());
        }
}
  • 打个断点发现 meta 中有个 destination 属性可以拿到具体的实现类 HelloServiceImpl。

图片描述

  • 上面的 meta 是直接从仓库 Warehouse 中获取服务,仓库的 providersIndex 就是在_ARouter.init () 方法里的 LogisticsCenter .init () 中加载进来的
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 generated 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 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);
                        }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }
  • 在 apt 生成的文件中找到 ARouter$$Providers$$app, 其中就有我们上面使用到的 HelloService,对应的注册类就是 HelloServiceImpl .class
public class ARouter$Providers$app implements IProviderGroup {
  @Override
  public void loadInto(Map< String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
  }
}

@Autowired 注解与依赖注入

  • 使用 @Autowired 注解接收跳转传递过来的值
(CreateNoteActivity)
    @Autowired
    String keyName;//分组类别名

    @Autowired
    Note mNote;
ARouter.getInstance().build("/activity/CreateNoteActivity").withString("keyName","Note").navigation();

    ARouter.getInstance().build("/activity/CreateNoteActivity")
                .withObject("mNote",mDatas.get(position))
                .navigation(this,Constant.EDITE_NOTE_CODE);
  • @Autowired String keyName 的变量名要和传递来的 key 名一样,或者是用 @Autowired (name = “keyName”) String suibian 才能接收到值
  • 在传递对象的时候,报了这个错误
java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String com.alibaba.android.arouter.facade.service.SerializationService.object2Json(java.lang.Object)' on a null object reference
        at com.alibaba.android.arouter.facade.Postcard.withObject(Postcard.java:244)
  • 关于对象的传递有两种,一种是 withParcelable () 方法,不过此方法需要传递的对象实现 Parcelable 接口,达到序列化的目的;另外一种是 withObject () 方法,此方法的原理是将实体类转换成 json 字符串,通过 String 的方式进行传递,而且使用这种方式需要实现 SerializationService
@Route(path = "/ARouter/SerializationService")
public class JsonServiceImp implements SerializationService {
    @Override
    public < T> T json2Object(String input, Class< T> clazz) {
        return new Gson().fromJson(input,clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return new Gson().toJson(instance);
    }

    @Override
    public < T> T parseObject(String input, Type clazz) {
        return new Gson().fromJson(input,clazz);
    }

    @Override
    public void init(Context context) {

    }
}
  • @Autowired 还可以注入对象
(CreateNoteActivity)
    @Autowired
    DBManager mDBManager;
@Route(path = "/utils/DBManager")
public class DBManager implements IProvider {

    public DBManager(){
    }

    @Override
    public void init(Context context) {

    }
}
  • 对应的类一定要实现 IProvider 接口并有空构造函数
  • 最后,要 @Autowired 注解生效还要在使用注解的地方加入 ARouter.getInstance ().inject (this);
  • 来到 ARouter 的代理类_ARouter 中,首先通过以来查找获取 AutowiredService 的具体实现类
static void inject(Object thiz) {
        AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
}
@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    private LruCache< String, ISyringe> classCache;
    private List< String> blackList;

    @Override
    public void init(Context context) {
        classCache = new LruCache<>(66);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                autowiredHelper.inject(instance);
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            blackList.add(className);    // This instance need not autowired.
        }
    }
}
  • 通过反射得到辅助注入类 CreateNoteActivity$$ARouter$$Autowired 的实例后就调用 inject 方法注入实例。我们来看下这个类的代码。到这里就水落石出了,在跳转时将需要传递的参数写入 postcard 的 bundle 中,然后成功跳转到目标页面后就可以通过 getIntent 取出参数,然后分别给目标页面的每个需要注入的成员变量赋值。
public class CreateNoteActivity$ARouter$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    CreateNoteActivity substitute = (CreateNoteActivity)target;
    substitute.keyName = substitute.getIntent().getStringExtra("keyName");
    if (null != serializationService) {
      substitute.mNote = serializationService.parseObject(substitute.getIntent().getStringExtra("mNote"), new com.alibaba.android.arouter.facade.model.TypeWrapper< Note>(){}.getType());
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'mNote' in class 'CreateNoteActivity' , then you should implement 'SerializationService' to support object auto inject!");
    }
  }
}

图片描述

@Interceptor 拦截器使用

  • 拦截器注册
  • 拦截器使用和 Activity@Route 差不多,只不过拦截器是使用另外一个注解 @Interceptor。有两个变量,priority 是拦截器的优先级,值越小优先级越高,会优先拦截。
@Interceptor(priority = 1)
public class LoginInterceptor implements IInterceptor {
    Context mContext;
    List< String> mNeedLoginActivityList;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        if (mNeedLoginActivityList.contains(postcard.getPath())){
            if ("".equals(SPUtil.getString("uId"))||
                    SPUtil.getString("uId")==null||
                    "".equals(SPUtil.getString("userName"))||
                    SPUtil.getString("userName")==null){
                ToastUtil.show(mContext.getString(R.string.plz_first_login));
                Log.i("binbin", "process: 被拦截了");
                callback.onInterrupt(null);
                ARouter.getInstance().build(Constant.LOGIN_ACTIVITY).navigation();
            }else{
                callback.onContinue(postcard);
            }
        }else{
            callback.onContinue(postcard);
            //postcard.withString("extra", "我是在拦截器中附加的参数");
            //callback.onContinue(postcard);
        }
    }

    @Override
    public void init(Context context) {
        this.mContext=context;
        //需要登录的活动
        String[] array={Constant.SS_ACTIVITY};
        mNeedLoginActivityList= Arrays.asList(array);
    }
}
  • 拦截器都默认实现 IInterceptor 接口,在拦截器实现类使用注解 @Interceptor 会自动注册
public interface IInterceptor extends IProvider {

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    void process(Postcard postcard, InterceptorCallback callback);
}

降级策略 (官方 demo)

  • 降级策略的实现方式
  • 一、单独降级 – 回调的方式
ARouter.getInstance().build("/xxx/xxx").navigation(this, new NavCallback() {
     @Override
     public void onFound(Postcard postcard) {
          Log.d("ARouter", "找到了");
     }

     @Override
     public void onLost(Postcard postcard) {
          Log.d("ARouter", "找不到了");
     }

     @Override
     public void onArrival(Postcard postcard) {
          Log.d("ARouter", "跳转完了");
     }

     @Override
      public void onInterrupt(Postcard postcard) {
           Log.d("ARouter", "被拦截了");
      }
});
  • 二、全局降级 – 服务接口的方式:
ARouter.getInstance().build("/xxx/xxx").navigation();
  • 这种方式从跳转上看不出来,主要的处理逻辑在内部,这种降级策略主要是实现服务接口 DegradeService,就一个方法就是 onLost
public interface DegradeService extends IProvider {

    /**
     * Router has lost.
     *
     * @param postcard meta
     */
    void onLost(Context context, Postcard postcard);
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇