ARouter系列4:面试题

0、相关文章

面试必问框架之ARouter源码解析

1、简单说一下使用ARouter跳转到一个Activity的流程

我们先写一个测试项目,如下:

有三个module:app、base、module-test1,其中app依赖base和test1,test1也依赖base。

base下面写了一个BaseConstant类,用于存放公共字段

public class BaseConstant {
    public static final String AROUTER_PATH_MODULE1_TEST1 = "/module1/Module1Test1Activity";
    public static final String AROUTER_PATH_MODULE1_TEST2 = "/module1/Module1Test2Activity";
    public static final String AROUTER_PATH_MODULE1_WEBVIEW = "/module1/TestWebViewActivity";
    public static final String AROUTER_PATH_MODULE1_TEST_INTERCEPTOR = "/module1/TestInterceptorActivity";
}

test1的Module1Test1Activity.java

@Route(path = BaseConstant.AROUTER_PATH_MODULE1_TEST1)
public class Module1Test1Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_module1_test1);
    }
}

app的MainActivity.java(只有一个跳转,其他略过)

        //不携带参数跳转
        findViewById(R.id.btn1).setOnClickListener(v -> {
            // 1. 应用内简单的跳转
            ARouter.getInstance()
                    .build(BaseConstant.AROUTER_PATH_MODULE1_TEST1)
                    .navigation();
        });

1.1、运行项目

项目成功运行后,发现项目自动生成了一些代码:

自动生成的代码: 

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

public class ARouter$$Providers$$moduletest1 implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
  }
}

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

主要看第一个类,这里把路径“/module1/Module1Test1Activity” 放到了一个map中。

 

1.2、初始化

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 尽可能早,推荐在Application中初始化
        ARouter.init(this);
    }
}

我们知道,ARouter框架使用的第一个步骤,是要先初始化,也就是调用:ARouter.init( Application.this );这个API,那么,它的初始化究竟是做了那些东西?我们先点进源码看看:

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.init(application),还有一个是 _ARouter.afterInit( )

1.2.1、_ARouter . init()

我们首先点进 _ARouter . init()看看,

A:红色箭头的是类注释,翻译过来就是:ARouter核心( 外观模式 )

B:绿色箭头代表的是一个线程池,对线程池概念不是很清楚的朋友可以点进 必须要理清的Java线程池 先了解一下

C:蓝色箭头代表的是 这个_ARouter init()实际是调用的LogisticsCenter里面的init方法。

首先,什么是外观模式?

简单点理解就是,通过创建一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以通过调用外观类的方法来调用内部子系统中所有方法。大概意思就是这样,想深入理解的话可以自行查阅资料。

其次,这个线程池做了什么功能?

点进去DefaultPoolExecutor这个类看看:

由源码得知就是创建了一个数组阻塞队列的线程池

最后,我们点进LogisticsCenter,看看里面的init方法执行了什么操作,该方法里面主要有两个核心代码段:

第一个核心代码段:

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();
      }
      // Save new version name when router map update finishes.
      PackageUtils.updateVersion(context);    
} 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>()));
}

从以上代码可知,如果是debug模式或者第一次启动,则走if模块,否则走else模块。

在if模块中,获取arouter-compiler生成的文件,然后将该文件,存储在sp中,下次启动应用的时候,直接从sp缓存中读取。

 第二个核心代码段:

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

首先遍历arouter-compiler生成的文件,将他们按照类型分别存储到Warehouse的对应字段中。也就是,如果类名前缀符合文件拼接规则,比如为com.alibaba.android.arouter.routes.ARouter$$Root的文件,就将其添加到具体的Warehouse里面的集合中。也就是对应的这里

Warehouse又是什么?点进源码看看

其中,Warehouse的类注释写的非常好,翻译过来就是:路由元数据和其他数据的存储。这个类本质就是路由文件映射表。里面提供了各种HashMap集合(Map不允许重复的key),去存储SP存储的值。

综上,针对 _ARouter init( ) 这个初始化的源码我们可以得知以下:

  • 初始化这一操作,表面上是_ARouter ,实则是LogisticsCenter 在帮我们管理逻辑
  • LogisticsCenter 内部通过先存储SP,然后遍历、匹配(满足条件则添加到具体的集合中,按照文件的前缀不同,将他们添加到路由映射表Warehouse的groupsIndex、interceptorsIndex、providersIndex 中)
  • 具体的路由清单是Warehouse ,不仅保存实例,还给我们提供了缓存。也就是说 同一个目标(RouteMeta、IProvider、IInterceptor)仅会在第一次使用的时候创建一次,然后缓存起来。后面都是直接使用的缓存。

1.2.2、_ARouter.afterInit()

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

首先,它实例化了一个InterceptorService。这里的InterceptorService下面会说。 这里的build方法,最终返回的是一个Postcard对象:

从源码可以得知,实际上它返回的却是,_ARouter.getInstance().build(path)这个方法,这个方法是一个方法重载(一般用的最多的就是这一个,也就是默认分组,不进行自定义分组),跟进去看看(源码很长,分为以下两个截图):

1.2.2.1、build


让我们绿色箭头,其中,navigation(clazz)这种方式是属于根据类型查找,而build(path)是根据名称进行查找。如果应用中没有实现PathReplaceService这个接口,则pService=null。PathReplaceService可以对所有的路径进行预处理,然后返回一个新的值(返回一个新的String和Uri)。绿色箭头有一个extractGroup()方法,点进去看看:

extractGroup(path)这个方法,核心逻辑是红色矩形内的代码,这个方法主要是获取分组名称。切割path字符串,默认为path中第一部分为组名。这就证明了如果我们不自定义分组,默认就是第一个分号的内容。

发现这里有一个PathReplaceService(也就是红色矩形),这个PathReplaceService又是什么,点进去看看

这个类注释翻译过来就是:预处理路径。这个接口是IProvider的子类。

分析完了build,我们在看看navigation(Postcard.java)

1.2.2.2、navigation(Postcard.java)

继续点进源码看看,发现进入了_ARouter这个类里面的navigation方法:

红色矩形代表的意思是,如果(两个)路径没写对,ARouter会Toast提示客户端,路径不对。

红色矩形代表的是忽略拦截器。interceptorService实例化对象的时机,是在_ARouter这类中的afterInit( )进行实例化的。这幅图中的蓝色矩形和箭头的方法真实逻辑是调用了下图的方法:

通过这段代码我们可以得知:

  • 1、如果navigation()不传入Activity作为context,则使用Application作为context
  • 2、内部使用了Intent来进行传递
  • 3、如果在跳转时,设置了flags,且没有设置Activity作为context,则下面的startActivity()方法会发生错误,因为缺少Activity的Task栈;
  • 4、Fragment的判断根据版本不同进行了相应的判断

继续看这个图

如果(两个)路径匹配的话,会执行到截图中的蓝色矩形,点进蓝色矩形里面去看看(也就是 LogisticsCenter.completion( Postcard )):

 

 

 

1.3、跳转

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章