Arouter 一個用於幫助 Android App 進行元件化改造的框架 —— 支援模組間的路由、通訊、解耦
技術準備
原始碼分析
我們直接看官方 demo,核心模組包括api,compiler,annotation, 首先先簡單講一下各模組的作用:
- Annoation: 定義註解和 Route 相關的基本資訊
- Compiler: 主要是用來在編譯期間處理註解Router/Interceptor/Autowire三個註解,在編譯期間自動註冊註解標註的類,成員變數等。
- Api: 使用者呼叫的核心 api
annoation
Autowired 用於註解 field, 可實現路由件傳遞引數的自動注入,無需使用者在程式碼中去獲取傳遞的引數,注意,註解物件的修飾符不能是 private。
Interceptor 用於註解攔截器,攔截器的概念請參考官方文件
Route 路由註解,可以是 Activity,也可以是 Service
RouteMeta 路由相關的資料,包括路由型別,路徑,組,權重,引數等資訊
compiler
RouteProcessor
路由註解器
關鍵方法 #parseRoutes#
,我們先看這個方法最後生成的檔案到底是啥,弄清楚了才能更容易的理清程式碼。
public class ARouter$$Root$$app implements IRouteRoot {
public ARouter$$Root$$app() {
}
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("service", service.class);
routes.put("test", test.class);
}
}
複製程式碼
注意map 存入的key 分別是 service 和 test,valude 則對應為 ARouter$$Group$$service.class 和 ARouter$$Group$$test.class。一個元件有且只有一個 Root 類,不同的元件以字尾作為區分。
public class ARouter$$Group$$test implements IRouteGroup {
public ARouter$$Group$$test() {
}
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/*", RouteMeta)
}
}
複製程式碼
public class ARouter$$Group$$service implements IRouteGroup {
public ARouter$$Group$$service() {
}
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/service/*", RouteMeta)
}
}
複製程式碼
注意 Arouter$$Group$$test 中 map 的 key 必然是以 "/test" 為字首,剛好與其字尾相同
public class ARouter$$Providers$$app implements IProviderGroup {
public ARouter$$Providers$$app() {
}
public void loadInto(Map<String, RouteMeta> providers) {
providers.put(className, RouteMeta)
}
}
複製程式碼
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
public ARouter$$Interceptors$$app() {
}
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(privority, Class);
}
}
複製程式碼
小結一下:
- 在一個module中,自動生成的 IRouteRoot 類有且只有一個,字尾為 module 名
- 在一個module中,自動生成的 IRouteGroup 類可以有多個,且都會在 IRouteRoot 類中註冊
- IProviderGroup 只有一個,字尾為 module 名
準確來說字尾並不是 module 名,實際上你可以在 gradle 中指定,後續在講解 parseRoutes 方法時會講到具體原因
接下來將講解 RouteProcessor#parseRoutes
方法:
- 拿到 Route,根據 RouteType 構建 RouteMeta,並通過 #categoryies 方法構建對映關係儲存到 groupMap(groupMap 的 key 為 routeName ,value 則是 RouteMeta 集合)
- 遍歷 groupMap 建立 ARouter$$Group$$groupName 類, 同時將 groupName 與生成的 File 的全類名的對映關係儲存到 rootName
- 建立 ARouter$$Providers$$moduleName 類
- 建立 ARouter$$Rout$$moudleName
其中 groupName 就是你在註解中設定的 group,但我們平時在使用 ARouter 的時候很少回去設定 group, 如果你沒有指定 group,Arouter 會預設使用 path 的主路徑,比如你指定了 path = "/test/main", 那 groupName 就是 test; moudleName 則由你在 gradle 中指定。
AutoWiredProccesor
首先看一下生成的檔案:
public class Test1Activity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
public Test1Activity$$ARouter$$Autowired() {
}
public void inject(Object target) {
this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
Test1Activity substitute = (Test1Activity)target;
substitute.name = substitute.getIntent().getStringExtra("name");
...
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);
}
}
複製程式碼
生成的 class 繼承之 ISyring
只有一個方法 inject(Object),該方法的實現主要做了兩件事:
AutoWired
註解的 filed 如果是service
(Arouter 中繼承自 IProvider), 則初始化物件:
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);
複製程式碼
AutoWried
註解的 filed 非service
,則從 bundle 中獲取相應的 value 賦值給該 field:
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class); // activity
substitute.name = substitute.getArguments().getString("name"); // fragment
複製程式碼
- 最後生成類名$$Arouter$$AutoWired.class 檔案
InterceptorProccessor
攔截註解生成器
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
public ARouter$$Interceptors$$app() {
}
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(7, Test1Interceptor.class);
}
}
複製程式碼
api
ARouter & _ARouter
launcher 下只有兩個類 ARouter
, _ARouter
, ARouter
採用單例模式,對外提供 ARouter 相關 API,我們在呼叫ARouter.getInstance().build()
時 真正呼叫的是_ARouter
的相關方法,這裡採用了代理模式,現在我們就開始以 ARouter 向外提供的方法來逐步分析:
首先看init()
:
在 Arouter 庫中,有一個 Warehose
類,它儲存了路由相關的資訊,我們在使用 ARouter 之前必須要先呼叫 init 方法,而 init
方法就是需要將資訊儲存到 Warehose
中
以下為時序圖:
通過呼叫#loadInto(Warehose)
方法,成功地將資訊儲存到 Warehose
。
現在Warehose
的 groupsIndex, providersIndex, interceptorsIndex 已經被我們存入了路由資訊
最後再呼叫_ARouter#afterInit()
例項化 interceptorService
接下來是 navigation()
方法:
我們需要先了解Postcard
類,Postcard
繼承 RouteMeta
,擴充套件了一些屬性,例如:flag(activity 的 flag),uri,provider,anim(動畫)。我們平時在是用 Arouter 時 build 方法返回的就是 Postcasrd
, postcard#navigation
方法,最終呼叫的是就是 _Arouter#navaigation
LogisticsCenter.completion(postcard);
if(!postcard.isGreenChannel()) {
// 攔截器相關,暫時跳過
interceptorSevice.doInterceptions(post, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
})
} else {
return _navigation(context, postcard, requestCode, callback);
}
複製程式碼
首先我們來看LogisticsCenter#completion(Postcard)
方法
...
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance(); // ARouter$$Group%%test 物件例項化
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
completion(postcard); // Reload 進入 else 分支
} else {
... // postcadrd 設定屬性如:destination,type,priority等
switch(routeMeta.getType) {
case PROVIDER:
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if(instance == null) {
IProvider povider = providerMeta.getConstructor().newInstance();
provider.init(mContext); // init 方法只在建立 provider 物件後呼叫一次,
Warehose.providers.put(providerMeta, provider); // 存入 map,除非呼叫 ARouter#destory, 該 provider 物件會一直存在 map 中
postcard.setProvider(instance);
postcard.greenChannel();
}
break;
case FRAGMENT:
...
break;
}
}
複製程式碼
到這裡面 Warehose
中的 routes
和providers
也被我們放入了相應資訊
_navigation()
方法:
switch(postcard.getType()) {
case ACTIVITY:
... // build intent
startActivity(requestCode, currentCotext, intent, postcard, callback);
break;
case POSTCARD:
return postcard.getProvider();
case Fragment:
Fragment fragment = fragmentMeta.getConstructor().newInstance();
fragment.setArguments(postcard.getExtras());
return fragment;
default:
return null;
}
return null;
複製程式碼
接下來分析 ARroute#inject(Object)方法:
AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
autowiredService.autowire(objec);
複製程式碼
程式碼很簡單,關鍵是 AutowiredService 的實現
@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(); // $$ARouter$$Autowired = $$ARouter$$Autowired
}
autowiredHelper.inject(instance); // Text1Activity$$ARouter$$Autowired.inject(this) 取出 bundle 中傳遞的引數
classCache.put(className, autowiredHelper);
}
} catch (Exception ex) {
blackList.add(className); // This instance need not autowired.
}
}
複製程式碼
到這裡,所有的註解生成的 class 的作用及其實現方法的呼叫時機都已經介紹到了,相信讀者應該對 ARouter 有了新的瞭解了吧。接下來講一下我們平時比較少用到的 interceptor
ARouter
官方為我們提供了一個攔截器的使用場景:
比較經典的應用就是在跳轉過程中處理登陸事件,這樣就不需要在目標頁重複做登陸檢查 複製程式碼
還記得在講解 ARroute#navigation
時我們跳過了攔截器相關的內容嗎
if(!postcard.isGreenChannel()) {
// 攔截器相關,暫時跳過
interceptorSevice.doInterceptions(post, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
})
}
複製程式碼
只要是攔截器,一般都會使用責任鏈設計模式,在 ARouter
中也不例外,我們先看 interceptorService
的建立過程,它的 type 是 Provider,在建立後會首先呼叫 init
方法
@Override
public void init(final Context context) {
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
for(entry : Warehose.interceptorsIndex) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
Warehose.interceptors.add(iInterceptor);
}
}
}
}
複製程式碼
interceptorService#doInterceptions 呼叫 _excute 方法
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown();
_excute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
}
@Override
public void onInterrupt(Throwable exception) {
// Last interceptor excute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // save the exception message for backup.
counter.cancel();
}
});
}
複製程式碼
常見錯誤分析
-
service 記憶體洩漏, 考慮以下程式碼:
public class HelloServiceImpl implements HelloService { private Context context; @Override public void init(Context context) { this.context = context } @Override public void say() { Toast.makeText(context, R.string.hello, Toast.LENGTH_LONG).show(); } } 複製程式碼
之前我們分析原始碼的時候就已經講過了,service 的實現類在初始化後會被放入
Warehose.providers
,後續通過呼叫ARouter.inject
都是從Warehose.providers
中取出。在上面的程式碼中如果 context 是一個 activity 物件,將該 activity 賦值給了成員變數 context,則該 activity 物件不會被回收,造成了記憶體洩漏 -
混淆導致 service 為 null
public class HelloHolder { @Autowired(name = "/service/hello") HelloService helloService; public HelloHolder() { ARouter.getInstance().inject(this); } public void sayHello() { helloSerice.say; } } 複製程式碼
該程式碼本身並沒有問題,問題出在在 android 開發中我們需要混淆程式碼,大家還記得之前講的 AutowiredService 的實現吧,關鍵程式碼在這裡,
if (null == autowiredHelper) { // No cache. autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); // $$ARouter$$Autowired = $$ARouter$$Autowired } 複製程式碼
本來 ARouter 構建的類名應該是
HelloHolder$$ARouter$$Autowired
, 然後例項化該物件,呼叫該物件的#inject
方法,可是你現在將HelloHolder
混淆成了H
,導致建立物件失敗,這就是你為什麼在程式碼中呼叫了ARouter.getInstance().inject(this)
,但是 service 還是為 null 的原因在 android 開發中,Activity,Fragment 我們在混淆規則中是配置了 keep 等規則的,所以不會有問題。但是像上文中 HelloHolder 這種情況常常被我們所忽略,在開發中一定要注意,建議採用註解方式來保持類名不被混淆。