阿里巴巴開源路由框架 - ARouter 分析

slyser發表於2018-10-10

Arouter 一個用於幫助 Android App 進行元件化改造的框架 —— 支援模組間的路由、通訊、解耦

技術準備

  1. SPI
  2. APT

原始碼分析

我們直接看官方 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);
    }
}
複製程式碼

小結一下:

  1. 在一個module中,自動生成的 IRouteRoot 類有且只有一個,字尾為 module 名
  2. 在一個module中,自動生成的 IRouteGroup 類可以有多個,且都會在 IRouteRoot 類中註冊
  3. IProviderGroup 只有一個,字尾為 module 名

準確來說字尾並不是 module 名,實際上你可以在 gradle 中指定,後續在講解 parseRoutes 方法時會講到具體原因

接下來將講解 RouteProcessor#parseRoutes 方法:

  1. 拿到 Route,根據 RouteType 構建 RouteMeta,並通過 #categoryies 方法構建對映關係儲存到 groupMap(groupMap 的 key 為 routeName ,value 則是 RouteMeta 集合)
  2. 遍歷 groupMap 建立 ARouter$$Group$$groupName 類, 同時將 groupName 與生成的 File 的全類名的對映關係儲存到 rootName
  3. 建立 ARouter$$Providers$$moduleName 類
  4. 建立 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),該方法的實現主要做了兩件事:

  1. AutoWired 註解的 filed 如果是 service (Arouter 中繼承自 IProvider), 則初始化物件:
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);
複製程式碼
  1. AutoWried 註解的 filed 非 service,則從 bundle 中獲取相應的 value 賦值給該 field:
	substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class); // activity
	substitute.name = substitute.getArguments().getString("name"); // fragment
複製程式碼
  1. 最後生成類名$$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 中 以下為時序圖:

WX20181010-163208

通過呼叫#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 中的 routesproviders也被我們放入了相應資訊

_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();
                }
            });
        }
複製程式碼

常見錯誤分析

  1. 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 物件不會被回收,造成了記憶體洩漏

  2. 混淆導致 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 這種情況常常被我們所忽略,在開發中一定要注意,建議採用註解方式來保持類名不被混淆。

相關文章