美文网首页
阿里ARouter简单分析

阿里ARouter简单分析

作者: 燶澧 | 来源:发表于2020-03-09 21:35 被阅读0次

ARouter是一个路由框架,来实现组件化过程中不同module之间的相互跳转。本文简单分析ARouter初始化以及跳转Activity的最基本过程,不包含额外的参数设置等。
一、ARouter涉及的知识:
1.门面模式
2.双重检验锁单例
3.注解以及APT注解处理器
4.DexFile

  1. 以后再补

二、可能遇到的坑:
1.必须为每一个支持跳转的module单独添加依赖,并指定
 AROUTER_MODULE_NAME 
2.不同的Activity相同的layout会混乱???
3.kotlin使用kapt而不是annotation processor添加依赖
4.添加kotlin-kapt依赖时

apply plugin: 'kotlin-android'
//kotlin-kapt要放在kotlin-android 下面,否则编译出错???
apply plugin: 'kotlin-kapt'

三、准备工作
1.在Application下执行Arouter的初始化

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        初始化//
        ARouter.init(this);
    }

2.在支持跳转的Activity中添加注解,并设置path

@Route(path = "/a/b")
class EmptyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_empty)
        btn_2.setOnClickListener {
            val postCard = ARouter.getInstance().build( "/a/b")
            postCard.navigation()
        }
    }

3.make project,然后在/Users/king/Documents/workspace/asworkspace/MyDemo/app/build/generated/source/kapt/debug目录下自动生成相关类文件(Android Studio 3.6, kotlin)。本文只关注com.alibaba.android.arouter.routes.ARouter$$Group$$a 以及 com.alibaba.android.arouter.routes.ARouter$$Root$$app两个文件。(名字与注解中的path参数有关)
ARouter$$Group$$a.java

public class ARouter$$Group$$a implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/a/b", RouteMeta.build(RouteType.ACTIVITY, EmptyActivity.class, "/a/b", "a", null, -1, -2147483648));
  }
}

ARouter$$Root$$app.java

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("a", ARouter$$Group$$a.class);
  }
}

四、正式开始
1.初始化

 //ARouter.java 省略部分代码...
//采用门面模式,初始化实际在 _Arouter.java
public static void init(Application application) {
    if (!hasInit) {
        hasInit = _ARouter.init(application);
    }
}

直接进入_ARouter.java

//_Arouter.java 省略部分代码...
protected static synchronized boolean init(Application application) {
    mContext = application;
    //核心代码
    LogisticsCenter.init(mContext, executor);
    mHandler = new Handler(Looper.getMainLooper());
    return true;
}

接着看LogisticsCenter.init方法

//LogisticsCenter.java 省略部分代码...
public synchronized static void init(Context context, ThreadPoolExecutor tpe){
  try {
      //TODO 通过ARouter插件加载??  
      loadRouterMap();
      if (registerByPlugin) {
          logger.info(TAG, "Load router map by arouter-auto-register plugin.");
      } else {
      //普通方式
          Set<String> routerMap;
          //如果ARouter在debug模式下或者首次初始化时
          if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
              //ROUTE_ROOT_PAKCAGE=“com.alibaba.android.arouter.routes”
              //这个方法返回该包名下所有的类集合
              routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
              if (!routerMap.isEmpty()) {
                  //保存到SP中
                  context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
              }
          } else {
                  //非debug模式下或者非首次初始化时,从SP中取
                  routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
              }
         //遍历routerMap
          for (String className : routerMap) {
              //以SUFFIX_ROOT=“ARouter$$Root”开头的类,并调用 loadInto 方法
             //IRouteRoot类型,对应生成的ARouter$$Root$$app.java
              if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                  ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
              }
              //TODO 通过反射初始化“ARouter$$Interceptors”开头的类
              else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { 
                  ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
              } 
              //TODO 以“ARouter$$Providers”开头的类
              else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                  ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
              }
          }
      }
}

先分析ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)这一行

    //ClassUtils.java 省略部分代码...
   /**
     * 通过指定包名,扫描包下面包含的所有的ClassName
     *
     * @param context     U know
     * @param packageName 包名
     * @return 所有class的集合
     */
    //packageName=com.alibaba.android.arouter.routes
    public static Set<String> getFileNameByPackageName(Context context, final String packageName)  {
        final Set<String> classNames = new HashSet<>();
        //获取apk内所有dex所在路径,可能有多个
        List<String> paths = getSourcePaths(context);
        //使用CountDownLatch
        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 {
                        //EXTRACTED_SUFFIX=".zip"
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
                        //创建DexFile
                            dexfile = new DexFile(path);
                        }
                        //遍历dexFile内的Class文件,找到目标包名开头的文件,保存到集合中
                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
                    } finally {
                        dexfile.close();
                        }
                        //完成一个dexFiel解析CountDownLatch减一,减到0时取消阻塞
                        parserCtl.countDown();
                    }
                }
            });
        }
        //阻塞
        parserCtl.await();
        //返回对应包名下class的集合,包含apt为我们生成的类以及ARouter自己的部分类
        return classNames;
    }

再分析((IRouteRoot(Class.forName(className).getConstructor().newInstance())) .loadInto(Warehouse.groupsIndex);这一行。以ARouter$$Rout$$开头的类,这里以Arouter帮我们生成的类ARouter$$Root$$app.java为例

/**
 * 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即Warehouse.groupsIndex(HashMap)
    //将ARouter$$Group$$a.class保存到Warehouse.groupsIndex中
    routes.put("a", ARouter$$Group$$a.class);
  }
}

至此,初始化完成。
总结
ARouter初始化时,将我们apk中所有以com.alibaba.android.arouter.routes为包名的类名保存到 routerMap中,并将routerMap保存在SP中。
然后遍历 routerMap,通过反射初始化ARouter$$Root开头的类,并调用其loadInto方法,传入一个Map类型的参数Warehouse.groupsIndex
ARouter$$Root$$app为例,在loadInto方法中,将ARouter$$Group$$a.class保存到Warehouse.groupsIndex中。


2.使用

 val postCard = ARouter.getInstance().build("/a/b")
//门面模式
 public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }

进入_Arouter.java类 省略部分代码...

    // _ARouter.getInstance() 使用了双重检验锁单例
    //直接看build方法
    protected Postcard build(String path) {
            //TODO PathReplaceService
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            //重载,extractGroup(path)内部为字符串操作,拿到path中第一个/后的String
            //path="/a/b" ,extractGroup(path)="a"
            return build(path, extractGroup(path));
    }

查看重载的build方法

 protected Postcard build(String path, String group) {
            //...省略部分代码
            //TODO PathReplaceService
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            //返回一个PostCard对象,只添加了path和group属性
            return new Postcard(path, group);
    }

接着看navigation跳转

    //跳转 postCard.navigation(),经过几次重载,最终到达
    public Object navigation(Context context, NavigationCallback callback) {
        return ARouter.getInstance().navigation(context, this, -1, callback);
    }

因为Arouter为门面,_Arouter为核心,所以直接进入_Arouter

    //参数 requestCode=-1,callback=null
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
      //TODO PretreatmentService 这几个内部的跳转未分析
      //省略部分代码...
        try {
          //为postcard 设置更多属性信息
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
         //这部分为异常的回调,我们传入的callback=null,暂时不看🙈
         //TODO PretreatmentService 这几个内部的跳转未分析
         //省略部分代码...
            return null;
        }
        //省略部分代码...
        //TODO 是否为GreenChannel,当为Fragment和PROVIDER时为true。
        if (!postcard.isGreenChannel()) { 
        } else {
          //重载
            return _navigation(context, postcard, requestCode, callback);
        }
        return null;
    }

先分析LogisticsCenter.completion(postcard)这一行,这里对postcard添加更多的属性值

//分析  LogisticsCenter.completion(postcard);
//首次进入时,postcard包含path,group属性
    public synchronized static void completion(Postcard postcard) {
       //省略部分代码...
       //Warehouse.routes首次是一个空的HashMap
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
          //从Warehouse.groupsIndex中获取,上面我们分析到 Warehouse.groupsIndex是一个HashMap
          //保存了ARouter$$Group$$a.class 
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); 
            if (null == groupMeta) {
             //抛出异常
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                  //以groupMeta=ARouter$$Group$$a.class 为例
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    //调用loadInto方法,以key=path,value=RouteMeta保存到 Warehouse.routes中,具体见下面分析
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());
                } catch (Exception e) {
                }
                //再次执行completion方法,此时Warehouse.routes中包含内容,进入下面的else分支
                completion(postcard);   // Reload
            }
        } else {
            //为postcard赋值
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());
                        
            if (null != rawUri) {   // Try to set params into bundle.
              //TODO 省略uri跳转相关
            }

            switch (routeMeta.getType()) {
                case PROVIDER:
                 //TODO 省略PROVIDER 跳转相关,这里设置了greenChannel
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                //这里设置了greenChannel
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

分析iGroupInstance.loadInto(Warehouse.routes);这一行,以iGroupInstance
ARouter$$Group$$a类为例

public class ARouter$$Group$$a implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> atlas) {
        //以key为Activity上注解内的path,value为RouteMeta
        atlas.put("/a/b", RouteMeta
                .build(RouteType.ACTIVITY, EmptyActivity.class,
                        "/a/b", "a", null, -1,
                        -2147483648));
    }
}

查看RouteMetabuild方法,最终调用了其构造方法

  //对应我们的类中
  public RouteMeta(RouteType type, Element rawType, Class<?> destination, String name, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
        this.type = type; //RouteType.ACTIVITY
        this.name = name; //
        this.destination = destination; // EmptyActivity.class
        this.rawType = rawType; //null
        this.path = path; // “/a/b”
        this.group = group;// ”a“
        this.paramsType = paramsType; //null
        this.priority = priority; //-1
        this.extra = extra;  //-2147483648??
    }

回来继续继续看重载的_navigation方法,这里的postcard已经是添加过额外属性的postcard

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:
                // postcard.getDestination() 拿到目标Activity
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                //省略部分设置FLAG,ACTION等代码...
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                      //实现Activity跳转
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            //TODO 其余组件处理
            case PROVIDER:
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

总结
通过ARouter.getInstance().build("/a/b")创建了一个只添加了pathgroup属性的PostCard对象(PostCard继承自RouteMeta)。
当调用 PostCard.navigation()实现跳转时,最终仍然调用的_Arouter
navigation,在这个方法从Warehouse.groupsIndex中取出IRouteGroup类型的对象,并调用IRouteGroup内部的loadInto方法,创建RouteMeta并保存到Warehouse.routes ``中,方便以后使用。 然后为PostCard添加更多的属性(待启动的组件、待启动的组件类型、启动模式等等)。 最后根据routeMetatype```属性分别调用对应的启动方法,完成组件之间的跳转

ARouter初始化.png ARouter 跳转.png

参考:
通过ApplicationInfo下的sourceDir获取APK所有的class


简书的短信验证码机制真垃圾哦,一分钟只能发送一条。登陆的验证码和重置密码这种不同类型的短信验证码默认不区分

相关文章

网友评论

      本文标题:阿里ARouter简单分析

      本文链接:https://www.haomeiwen.com/subject/heupdhtx.html