Android平臺中對頁面、服務提供路由功能的中間件,個人目標是 —— 簡單且夠用。具體的使用能夠參考:https://github.com/alibaba/ARouter。 若是對內部的詳細原理感興趣能夠參考:http://www.jianshu.com/p/3c4f4e3e621f。 下文分析的代碼來源於ARouter官方Demo,閱讀下文以前建議先下載一份源碼運行並對照源碼進行比對,由於下文中我會用官方demo的源碼截圖。java
APT技術可讓咱們經過自定義註解動態生成編譯後的class代碼,具體的使用我在這裏就不詳細說了,感興趣的能夠參考我之前寫的:編寫最基本的APT Demo。 我這裏直接來看下ARouter說涉及到的幾個註解,以及編譯以後動態生成的代碼。android
Route:用於標記咱們須要跳轉的四大組件(能夠經過Intent跳轉的,由於其實ARouter 內部最後也是經過Intent來進行跳轉)、service(此處的sevice是相似於後臺的服務,須要繼承IProvider)。 Interceptor:主要的做用是經過AOP技術(面向切面編程)在咱們進行頁面跳轉以前能夠進行一系列的攔截操做 Autowired:主要的做用是經過IOC技術(依賴注入)獲取頁面跳轉的參數。git
**ARouter$$Root$$app:**由於Arouter採起的是懶加載技術,因此咱們須要對router進行分組,這裏的Root內部就是經過Map以組名爲key存儲了每組router的信息信息。 **ARouter$$Group$$xxx:**咱們按不一樣的router類型進行分組,內部經過Map以router path存儲了具體router的信息 **ARouter$$Interceptors$$app:**其中app是咱們的module名(經過查看源碼可知),內部 以priority優先級爲key存儲了具體的Interceptors的class信息。 **ARouter$$Providers$$app:**其中app是咱們的module名(經過查看源碼可知),內部以類的完整限定名爲key保存了service的信息,結構同ARouter$$Group$$xxx一致,只是用於不一樣的功能。github
關於ARouter的APT分支就到這了,下面來看下ARouter的初始化。編程
這正式分析初始化以前咱們先了解幾個類json
ARouter:_ARouter的代理類,這裏採用了代理模式,其實ARouter對外只開放了這一個api,全部的操做基本上都是經過ARouter來完成了。 **_ARouter:**ARouter所代理獲得類,功能的具體執行者。 **LogisticsCenter:**物流調度中心,對路由信息進行解析和加工。 **Warehouse:**倉庫,存儲了全部具體的router、interceptors、service等信息,內部是一系列的Map。api
ARouter的初始化流程:ARouter#init ──》_ARouter#init ──》LogisticsCenter#init ──》Warehouse#Map#put bash
// 得到指定包名下的全部類名
List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
for (String className : classFileNames) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root. 經過反射找到Arouter$$Root$$xx 加載根路由集合
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta 經過反射找到Arouter$$Interceptors$$xx 加載攔截器集合
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex 經過反射找到Arouter$$Providers$$xx 加載服務集合 此處的service對應後臺的概念 不是四大組件的service
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
複製代碼
其中第三步和第四步的代碼會根據指定報名查找下面全部的類信息,而後根據類獲得全限定名進行功能分組,並把信息保存在Warehouse的Map中。到此Arouter的初始化過程就完成。app
ARouter.getInstance().build("/test/activity1")
.withString("name", "老王")
.withInt("age", 18)
.withBoolean("boy", true)
.withLong("high", 180)
.withString("url", "https://a.b.c")
.withParcelable("pac", testParcelable)
.withObject("obj", testObj)
.navigation();
public class Test1Activity extends AppCompatActivity {
@Autowired
String name;
@Autowired
int age;
@Autowired(name = "boy")
boolean girl;
@Autowired
TestParcelable pac;
@Autowired
TestObj obj;
private long high;
@Autowired
String url;
@Autowired
HelloService helloService;
}
複製代碼
其中Test1Activity 經過APT動態生成的代碼以下:框架
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("pac", 9); put("obj", 10); put("name", 8); put("boy", 0); put("age", 3); put("url", 8); }}, -1, -2147483648));
複製代碼
全部的跳轉參數都保存在map中,其中key是一一對應,而value是參數類型,對題對照以下:
public enum TypeKind {
// Base type
BOOLEAN,
BYTE,
SHORT,
INT,
LONG,
CHAR,
FLOAT,
DOUBLE,
// Other type
STRING,
PARCELABLE,
OBJECT;
}
複製代碼
下面咱們來看具體的跳轉流程。
ARouter.getInstance().build("/test/activity1")升溫咱們說過ARouter是_ARouter的代理的類,全部的api最終都會進入到真正的執行類_ARouter。 _ARouter#build
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
//查找是否存在重定向服務
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
複製代碼
這裏咱們先來看下PathReplaceService這個類
public interface PathReplaceService extends IProvider {
/**
* For normal path.
*
* @param path raw path
*/
String forString(String path);
/**
* For uri type.
*
* @param uri raw uri
*/
Uri forUri(Uri uri);
}
複製代碼
PathReplaceService我稱它爲重定向服務(具體怎麼使用請參考官網文檔),它繼承IProvider,那IProvider是什麼,其實他是用來實現service的,因此PathReplaceService就至關於咱們本身的自定義服務,惟一的區別是,自定義服務須要咱們顯示去調用,跟調用router同樣,可是PathReplaceService不須要顯示調用,他是做用於全部服務和路由的,並且無論你實現了幾個PathReplaceService,最終全局都只會保存在APT時掃描到的最後一個服務。爲何這麼說,請看下面的代碼:
public class ARouter$$Providers$$app implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
//第一個重定向服務
providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/redirect/r1", "redirect", null, -1, -2147483648));
//第二個重定向服務
providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl2.class, "/redirect/r2", "redirect", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
}
}
複製代碼
由於第一個和第二個重定向服務的key是同樣都是PathReplaceService的類全限定名,因此第一個服務會被覆蓋掉。好了關於PathReplaceService咱們就說到這,他是咱們一個重定向服務,做用域全部的跳轉,並且全局只有一個。 咱們繼續往下分析,若是單例沒有實現自定義重定向服務的時候,PathReplaceService pService == null,因此會直接調用兩個參數的重載的build方法。
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return new Postcard(path, group);
}
}
複製代碼
這裏有調用了一遍ARouter.getInstance().navigation(PathReplaceService.class),感受不必啊,可是無論確定仍是同樣的返回空。因此最終ARouter.getInstance().build()會返回一個Postcard(個包含跳轉信息的容器,包含跳轉參數和目標類的信息)。下面進入真正的跳轉。其實真正的跳轉位於_ARouter#navigation。 ###_ARouter#navigation
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//完善跳轉信息
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
……………………
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
//不是綠色通道因此會進入默認interceptorService.doInterceptions
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
複製代碼
InterceptorService是咱們在初始化完成之後ARouter爲咱們自動註冊的攔截器服務,由於咱們並無爲咱們獲得路由匹配相應的攔截器,因此應該會進入onContinue方法,通過斷點調試確實和咱們想的同樣,但是onContinue是個回調函數,它又具體是在哪被調用的呢?咱們通過查找發現是在InterceptorServiceImpl中
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
_excute(0, interceptorCounter, postcard);
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings. callback.onInterrupt(new HandlerException("The interceptor processing timed out.")); } else if (null != postcard.getTag()) { // Maybe some exception in the tag. callback.onInterrupt(new HandlerException(postcard.getTag().toString())); } else { callback.onContinue(postcard); } } catch (Exception e) { callback.onInterrupt(e); } } }); 複製代碼
這裏開了一個線程用來執行攔截器或者普通的跳轉因此調用了callback.onContinue,接下來就進入到咱們真正的跳轉執行的地方了。
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:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// 由於上文咱們是在子線程中檢查是否有匹配的攔截器,因此咱們要在這裏切換到UI線程執行具體的跳轉
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
複製代碼
這裏的代碼比較簡單,就是調用了Android原生的Intent進行跳轉,而後根據不一樣的狀態,調用一些回調函數。到此關於ARouter的跳轉到這裏就結束了,下面咱們來看下目標對象的參數是如何獲取的。
這裏在分析參數獲取以前咱們先廢話2句,在看到Autowired註解的時候,是否是感受似曾相識,沒錯這裏的原理跟ButterKnife是一毛同樣的,我強烈懷疑Arouter做者是參考ButterKnife代碼寫的,因此當咱們分析完Autowired的時候,其實就至關於把ButterKnife也給分析了,哈哈,正式一箭雙鵰啊。還有,這種開發思想其實在後臺開發中很是廣泛,好比大名鼎鼎的Spring就是這種IOC(控制反轉)思想的最佳表明。好了,下面進入正題。
當咱們在編譯過程當中,系統會是掃描有Autowired註解的成員變量類,而後生成自動生成如下代碼:
public class Test1Activity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);;
Test1Activity substitute = (Test1Activity)target;
substitute.name = substitute.getIntent().getStringExtra("name");
substitute.age = substitute.getIntent().getIntExtra("age", 0);
substitute.girl = substitute.getIntent().getBooleanExtra("boy", false);
substitute.pac = substitute.getIntent().getParcelableExtra("pac");
if (null != serializationService) {
substitute.obj = serializationService.json2Object(substitute.getIntent().getStringExtra("obj"), TestObj.class);
} else {
Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
}
substitute.url = substitute.getIntent().getStringExtra("url");
substitute.helloService = ARouter.getInstance().navigation(HelloService.class);
}
}
複製代碼
這裏的代碼很簡單,應該能直接看懂,咱們先來看他的父類ISyringe,他其實至關於一個模板類,爲了便於編程ARouter內核提供了許多的模板類,存儲在以下路徑中:
那麼爲何要提供模板類呢?簡單了來講,當咱們在變成過程當中,因爲框架做者並不知道哪些具體類被標註了註解,因此要動態獲取對象,只能經過反射動態來獲取實例,而後調用接口的方法來執行具體的操做,這就是多態的概念,以下代碼所示: 這裏插一句,反射是會影響性能的,因此通常咱們在編程中除非萬不得已,不然儘可能不要採用反射,可是這裏是activity初始化的時候反射,原本就會進行大量耗時的操做,哪怕有一點點的性能損耗也是能夠接受的。還記得Arouter的初始化嗎?官網上有一句原話是這麼說的: 你們有沒有想過,爲何要儘量早的初始化,我想除了要掃描大量的對象並保存到全局的map集合中之外,跟初始化的時候用到反射也有關係吧,畢竟仍是有性能損耗的。以下所示好了,到這咱們已經把頁面跳轉和參數綁定都分析完了,剩下的重定向,攔截器,降級等不少其餘功能,其實都是在跳轉的過程當中插入的一些攔截操做而已,我相信只要你們只要耐下心來看代碼都是能夠看明白的。