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、跳轉

 

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