ARouter原始碼淺析

-l囧l-發表於2017-12-01

簡介

Android平臺中對頁面、服務提供路由功能的中介軟體,我的目標是 —— 簡單且夠用。具體的使用可以參考:https://github.com/alibaba/ARouter。 如果對內部的詳細原理感興趣可以參考:http://www.jianshu.com/p/3c4f4e3e621f。 下文分析的程式碼來源於ARouter官方Demo,閱讀下文之前建議先下載一份原始碼執行並對照原始碼進行比對,因為下文中我會用官方demo的原始碼截圖。

APT

APT技術可以讓我們通過自定義註解動態生成編譯後的class程式碼,具體的使用我在這裡就不詳細說了,感興趣的可以參考我以前寫的:編寫最基本的APT Demo。 我這裡直接來看下ARouter說涉及到的幾個註解,以及編譯之後動態生成的程式碼。

annotation

image.png
其中Param已被Autowired代替

Route:用於標記我們需要跳轉的四大元件(可以通過Intent跳轉的,因為其實ARouter 內部最後也是通過Intent來進行跳轉)、service(此處的sevice是類似於後臺的服務,需要繼承IProvider)。 Interceptor:主要的作用是通過AOP技術(面向切面程式設計)在我們進行頁面跳轉之前可以進行一系列的攔截操作 Autowired:主要的作用是通過IOC技術(依賴注入)獲取頁面跳轉的引數。

註解解析

image.png
可以看到對應了上面的三個註解。這裡具體的程式碼我就不分析了,感興趣的可以直接去看原始碼(雖然不算很難但是比較繁瑣,一定要耐心),當我們全域性編譯以後會動態生成以下程式碼
image.png

**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一致,只是用於不同的功能。

關於ARouter的APT分支就到這了,下面來看下ARouter的初始化。

init

這正式分析初始化之前我們先了解幾個類

ARouter:_ARouter的代理類,這裡採用了代理模式,其實ARouter對外只開放了這一個api,所有的操作基本上都是通過ARouter來完成了。 **_ARouter:**ARouter所代理得到類,功能的具體執行者。 **LogisticsCenter:**物流排程中心,對路由資訊進行解析和加工。 **Warehouse:**倉庫,儲存了所有具體的router、interceptors、service等資訊,內部是一系列的Map。

ARouter的初始化流程:ARouter#init ──》_ARouter#init ──》LogisticsCenter#init ──》Warehouse#Map#put

image.png

          // 獲得指定包名下的所有類名
            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的初始化過程就完成。

Router的跳轉和引數注入

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

下面我們來看具體的跳轉流程。

#build

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中

InterceptorServiceImpl#doInterceptions

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,接下來就進入到我們真正的跳轉執行的地方了。

_ARouter#_navigation

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的跳轉到這裡就結束了,下面我們來看下目標物件的引數是如何獲取的。

Autowired

這裡在分析引數獲取之前我們先廢話2句,在看到Autowired註解的時候,是不是感覺似曾相識,沒錯這裡的原理跟ButterKnife是一毛一樣的,我強烈懷疑Arouter作者是參考ButterKnife程式碼寫的,所以當我們分析完Autowired的時候,其實就相當於把ButterKnife也給分析了,哈哈,正式一舉兩得啊。還有,這種開發思想其實在後臺開發中非常普遍,比如大名鼎鼎的Spring就是這種IOC(控制反轉)思想的最佳代表。好了,下面進入正題。

Autowired註解處理器

當我們在編譯過程中,系統會是掃描有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核心提供了許多的模板類,儲存在如下路徑中:

image.png
那麼為什麼要提供模板類呢?簡單了來說,當我們在變成過程中,由於框架作者並不知道哪些具體類被標註了註解,所以要動態獲取物件,只能通過反射動態來獲取例項,然後呼叫介面的方法來執行具體的操作,這就是多型的概念,如下程式碼所示:
image.png
這裡插一句,反射是會影響效能的,所以一般我們在程式設計中除非萬不得已,否則儘量不要採用反射,但是這裡是activity初始化的時候反射,本來就會進行大量耗時的操作,哪怕有一點點的效能損耗也是可以接受的。還記得Arouter的初始化嗎?官網上有一句原話是這麼說的:
image.png
大家有沒有想過,為什麼要儘可能早的初始化,我想除了要掃描大量的物件並儲存到全域性的map集合中以外,跟初始化的時候用到反射也有關係吧,畢竟還是有效能損耗的。如下所示
image.png
image.png

總結

好了,到這我們已經把頁面跳轉和引數繫結都分析完了,剩下的重定向,攔截器,降級等很多其他功能,其實都是在跳轉的過程中插入的一些攔截操作而已,我相信只要大家只要耐下心來看程式碼都是可以看明白的。

請參考ARouter 原始碼淺析第二篇

相關文章