Arouter系列(一):总体流程分析

目录

Arouter路由的设计思想

Arouter初始化过程分析

Arouter路由跳转分析

跳转方式

流程分析

Arouter.getInstance()

Arouter.getInstance().build()

_Arouter.getInstance().build(String path, String group)

小总结:

ARouter.getInstance().build(String).navigation()

_Arouter.getInstance().navigation(.....)

_Arouter._navigation

_Arouter.startActivity(int , Context , Intent intent, Postcard , NavigationCallback )


 

Arouter设计思想

         假如有一个需求:

         有两个模块,烹饪模块Cook和食材模块Food。Cook需要使用Food中的一个Fish类。正常来说,我们只需要建立模块间的依赖关系,Cook依赖Food之后,就可以使用Food中的Fish类了。but, 当项目足够复杂时,很容易出现大量的模块间的相互依赖,导致工程无法编译,难以维护的问题。

         Arouter提出了一种模块间的解耦方案。核心思想就是:通过注解处理器,编译时动态生成代码的方式解耦代码(JavaPoet),最终实现模块间的解耦。

         流程为:

  •   Arouter编译时动态生成类,这些类均提供类方法——将Class(其他模块用的类)插入Map中。
  •   Arouter初始化的时候,会反射所有动态生成的类,调用反射类的方法,将Class(其他模块用的类)缓存到Map。
  •  其他模块在全局Map中,通过key找Class,并通过Class的接口调用Class的实现方法,最终达到模块解耦的目的。

          说起来可能还是有点抽象,我们来举个简单的例子吧

  •         我们对Food的Fish注解。编译期间,生成了类FishCompile,这个类提供了一个方法loadInto

FishCompile.class

public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("fish", Fish.class);
  }
  •      另外我们提供了一个初始化方法Arouter.init。init会根据FishCompile的包名找到FishCompile的类名,通过反射创建FishCompile对象f,通过f.loadInto方法,将“fish”和Fish.class插入到全局Map中。这样Cook模块就可以通过“fish”找到Fish.class并使用了。

          当然,Arouter的具体实现是没有这么简单的。具体解耦的流程如下

         

Arouter初始化过程

在Application中调用Arouter的init方法,如下:

Arouter.init(Context context)

Arouter.init方法调动了_Arouter.init方法。

_Arouter.java

 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;
    }

_Arouter初始化都是在LogisticsCenter.init方法中实现,跟进去...

LogisticsCenter.java

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //loadRouterMap返回false
            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)) {
                   
                    // 标记1
                    // 获取Apk中所有Arouter框架在编译时生成的class文件的类名(全路径名+包名)    
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

                    // sp缓存
                    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 {
                    // 如果已经执行过上面的解析,后续重启app直接读取sp缓存
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

              
                startInit = System.currentTimeMillis();

                // 反射创建对象,并缓存到Warehouse(路由相关的缓存、拦截缓存、服务的缓存),className对标图1中的Arouter$$Group$$app、Arouter$$Provider$$app、Arouter$$Root$$app

                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);
                    }
                }
            }

            ......

        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }
图1
图1

 

LogitisticCenter的init方法中,我们留了一个悬念——标记1。我们来分析下getFileNameByPackagename方法。

 /**
     * 通过指定包名,扫描包下面包含的所有的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();
                        // 编译class文件,检查是否为:com.alibaba.android.arouter.routes路径下的类,如果是,将其类名加到classNames中。
                        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();

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

 

Arouter路由过程

跳转代码

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

流程分析

Arouter.getInstance()

Arouter.java

返回Arouter对象。

Arouter.getInstance().build()

Arouter.java

public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

返回了_Arouter实体以及调用了_Arouter的build方法——Postcard对象

Arouter.getIntance.build(path)

_Arouter.java

protected Postcard build(String path) {
...
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        
        if (null != pService) {
            path = pService.forString(path);
        }
        
        return build(path, extractGroup(path));
    }
}

按照demo的中调用,应该是走到Line9

_Arouter.getInstance().build(String path, String group)

_Arouter.java

/**
 * Build postcard by path and group
 */
protected Postcard build(String path, String group) {
   ...
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return new Postcard(path, group);
    }
}

依然会走到Line10,返回一个PostCard对象。

PostCard是什么?

继承自RouteMeta,包含了路由的属性。后面详细介绍

小总结:

ARouter.getInstance().build("/base/LoginActivity")执行到这里时,返回的是一个Postcard对象,包括了当前路由的group信息和path信息。

ARouter.getInstance().build(String).navigation()

ARouter.getInstance().build(String).navigation()等效于PostCard.navigation()方法。

PostCard.java

/**
 * Navigation to the route with path in postcard.
 * No param, will be use application context.
 */
 public Object navigation() {
    return navigation(null);
}

        navigation是路由的具体实现入口,共有4个路由方式,如下。

PostCard.java

 

// 通过Application的Context来路由,

public Object navigation()

// 如果能拿到Activity推荐使用这种方式跳转

public Object navigation(Context context)

// 返回路由的结果

public Object navigation(Context context, NavigationCallback callback)

//对标Activity的startActivityForResult

public void navigation(Activity mContext, int requestCode)

// 结合了上述3,4

public void navigation(Activity mContext, int requestCode, NavigationCallback callback)

无论使用哪种路由方式,其结果都是调用了Arouter.navigation方法,区别就是Arouter.navigaiton的入参不一样罢了。有些是null,有些有其他参数。

Arouter.java

public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
        return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
    }
}

Arouter的navigation方法调用了_Arouter的navigation方法。这里采用了门面设计模式,_Arouter是Arouter的门面。

_Arouter.getInstance().navigation(.....)

_Arouter.java

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
      try {
          LogisticsCenter.completion(postcard);
      } catch (NoRouteFoundException ex) {
          // 注明的没有找到路由的报错
         Toast.makeText(mContext, "There's no route matched!\n" + " Path = [" + postcard.getPath() + "]\n" + " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
    
       ...
        // 还记得上面Arouter.getInstance.build.navigation(callback)的callback方法吗? 如果callback不为null,就调用onLost方法返回给调用者
        if (null != callback) {
            callback.onLost(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() {
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }
            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()方法无论是true还是false,最后都是调用_Arouter._navigation方法。

_Arouter._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.
            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 n

当postCard.getType方法返回的是Activity时,调用startActivity方法执行跳转。

_Arouter.startActivity(int , Context , Intent intent, Postcard , NavigationCallback )

这个方法很简单,如果Context不是Activity,会通过这种方式跳转。intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    /**
     * Start activity
     * @see ActivityCompat
     */
    private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  // Need start for result 
            /**
              * requestCode >=0 要返回result给当前Activity
              * 所以,currentContext必须是Activity,否则报错
             */ 
            if (currentContext instanceof Activity) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
            } else {
                logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
            }
        } else {
             /**
              * 不需要返回result给当前Activity,所以currentContext是什么就无所谓了
             */ 
            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);
        }
    }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值