Arouter從使用到原理

這是我參與8月更文挑戰的第2天,活動詳情查看:8月更文挑戰html

前言

凡是A,B無依賴關係,且想要互相通訊的,其最基本的原理就是經過一個彼此都依賴的第三方C,不論是binder,socket,file,provider仍是EventBus,Arouter等,都是這個原理,若是有人說不,那麼要麼是槓精,要麼是想引人注意,要麼就是放棄了治療。java

咱們假設現有: app,login,以及common三個module,其中app跟login無關聯,而且都依賴於common,其中app module中有個MainActivity,login module想要調用MainActivity,根據上述,只能經過共同依賴的common 或 其餘公有依賴來實現。android

1 經過common來實現

  • 1 在common中定義一個Map<String,Class<? extends Activity>>
  • 2 在app module中,將MainActivity.class添加到這個map中去:
map.put("main",MainActivity.class)
複製代碼
  • 3 在login module中,經過map.get("main")獲得MainActivity.class,而後建立Intent來啓動MainActivity
Class<? extends Activity> mainActivityClass= map.get("main")
startActivity(new Intent(this,mainActivityClass))
複製代碼

核心就兩點: 注入獲取。說白了就是: 你把本身想讓別人用的放進公共倉庫中(注入),並提供一個憑證(這裏的憑證就是字符串"main"),也就是key,我想要的時候就拿着憑證從公共倉庫中取出來(獲取),公共倉庫就是彼此都能訪問的,那確定是彼此都依賴的。git

接着,咱們來看Arouter的實現。github

2 經過Arouter來實現。

經過Arouter的實現思路跟上述是同樣的,只不過Arouter更簡單,更省事,更"愚人",它把咱們的"注入"和"獲取"都實現了(開發把本身當白癡調API就好了),而且還加了不少優化,可是道理都是同樣的,咱們來看下。api

簡單使用

1 在gradle文件添加依賴:緩存

若是是java則添加:markdown

api 'com.alibaba:arouter-api:1.5.0'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
  
android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}
複製代碼

若是是kotlin,則添加:app

api 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'

android {
    defaultConfig {
         kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }
    }
}
複製代碼

java和kotlin的區別,就是kotlin使用kapt關鍵字,別的都同樣。socket

2 在Application的onCreate()裏面初始化

// 初始化(注入)
ARouter.init(this);
複製代碼

3 定義path,也就是憑證

@Route(path = "/app/activity_main") // 這個就是憑證,也就是key
public class MainActivity {}
複製代碼

4 根據path啓動對應的Activity,也就是獲取

ARouter.getInstance().build(path).navigation(); // 根據憑證path獲取並啓動Activity
複製代碼

其中ARouter.init(this)就是注入的過程,注入的key就是咱們經過@Route(path)定義的path;而後就拿着path去調用navigation()來獲取並啓動對應的Activity了。

上面只是簡單示例Arouter的使用,不過多介紹,本篇重點是講解原理。想看詳細使用能夠訪問: Arouter官網

那麼,Arouter的注入是怎麼作的呢,獲取又是怎麼獲取的呢,且看下文。

原理剖析

1 編譯時乾的事 - 生成中間代碼

apt技術: apt技術就是 先設定一套代碼模版,而後在類加載期間,動態根據指定的代碼模版生成一個.java文件,這個文件在運行時能夠直接訪問,能夠看這裏加深瞭解。

以下圖:

類加載過程

因此,當咱們在gralde中添加了Arouter的依賴後,那麼在編譯時就會 在對應module的 /build/generated/source/kapt/debug/ 下生成 "com.alibaba.android.arouter.routes" 目錄,Arouter生成的代碼都放在這裏,好比:

// 這一個IRouteRoot,看名字"ARouter$$Root$$app",其中"ARouter$$Root"是前綴,"app"是group名字,也就是path裏面以"/"分隔獲得的第一個字符串,而後經過"$$"鏈接,
// 那麼這玩意兒的完整類名就是"com.alibaba.android.arouter.routes.ARouter$$Root$$app"
public class ARouter$$Root$$app implements IRouteRoot {

    // 參數是一個map,value類型是 IRouteGroup
    @Override
    public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        // "app"就是@Route(path = "/app/activity_main") 中的"app",在Arouter中叫作group,是以path中的"/"分隔獲得的
        // 這個的value是:ARouter$$Group$$app.class,也就是下面的類
        routes.put("app", ARouter$$Group$$app.class);
    }
}

// 這是一個IRouteGroup,同理,前綴是"ARouter$$Group"
public class ARouter$$Group$$app implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> atlas) {
        // "app/activity_main"就是咱們經過@Route指定的path,後面RouteMeta保存了要啓動的組件類型,以及對應的.class文件
        // 這個 RouteMeta.build()的參數很重要,後面要用到
        atlas.put("/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648));
    }
}

// RouteMeta.build()方法,參數後面有用
// type就是: RouteType.ACTIVITY,
// destination就是MainActivity.class,
// path就是"/app/activity_main",
// group就是"app"
// paramsType是null
// priority是-1
// extra是-2147483648
public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
    return new RouteMeta(type, null, destination, null, path, group, paramsType, priority, extra);
}
複製代碼

注意,上述代碼所有是在app module中的build()中生成的。也就是說,這些代碼對於login module來講,是徹底透明的,不可達的。

並且,咱們發現生成的.java文件,都有個共同的前綴"ARouter " ,好比 " A R o u t e r ",好比"ARouter Root"。又由於它們是在Arouter生成的目錄下面,因此它們的完整類名都有個前綴:"com.alibaba.android.arouter.routes.ARouter$$"。好,如今假設編譯完了,咱們啓動app。

2 運行時乾的事 - 注入

如今咱們已經編譯完了,直接點擊run啓動了app,如今來到了運行時,此時咱們已經在/build/generated/source/kapt/debug/ 下生成了 "com.alibaba.android.arouter.routes"目錄,而且裏面還有一堆Arouter生成的代碼。

接下來代碼順序執行,跑到了Application的onCreate()裏面,因而就執行了初始化:

// 初始化,此時會調用編譯時生成的那一堆代碼,來"注入"須要的相關信息
Arouter.init(this)

// 調到了這裏
protected static synchronized boolean init(Application application) {
    // 保存了mContext,後面有用
    mContext = application;

    // 初始化
    LogisticsCenter.init(mContext, executor);
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());

    return true;
}
複製代碼

咱們跟着代碼,發現最終調了:

// executor是內置的一個線程池
LogisticsCenter.init(mContext, executor);
複製代碼

接下來咱們就來看這個代碼,這裏刪除了日誌以及部分次要邏輯:

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;
        try {
            Set<String> routerMap;
            
            // 若是是debugable()或者更新了app的版本
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                // 那麼就會從新獲取全部的class,因此,當你的Arouter出現了route not found時候,更新版本號 或者 開啓Arouter的debug就ok了。

                // 這裏會獲取全部"com.alibaba.android.arouter.routes"目錄下的class文件的類名。
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

                // 這裏緩存到SharedPreferences裏面,方便下次獲取。
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }

                // 將app的版本號緩存到SharedPreferences,方便下次使用。
                PackageUtils.updateVersion(context);
            } else {
                // 若是版本號沒有更新,而且沒開啓debug,則從緩存中取出以前緩存的全部class
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }

            // 遍歷剛剛拿到的全部類名,而且反射調用它們的loadInto()方法,那麼app module中的那些生成的類,它們的loadinto()就被調用了,而且注入到參數裏面了。
            for (String className : routerMap) {
                // 拼接的字符串其實就是"com.alibaba.android.arouter.routes.ARouter$$Root",這不就是編譯時生成的那個"ARouter$$Root$$app"的前綴嗎。
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // 因而,調用了它的loadInto(map),也就等價於調用了:map.put("app", ARouter$$Group$$app.class),這個鍵值對 就放在了Warehouse.groupsIndex裏面。
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);

                    // 這個是"com.alibaba.android.arouter.routes.ARouter$$Interceptors"
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);

                    // 這個是"com.alibaba.android.arouter.routes.ARouter$$Providers"
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }
複製代碼

根據上述,咱們知道:

  • 1 獲取全部"com.alibaba.android.arouter.routes"目錄下的類名,這一步有個緩存操做。
  • 2 遍歷全部獲取到的類名,而後調用它們的loadInto(map)方法。
  • 3 調用loadInto(map)的結果就是將多有的(key,group.class)類存入Warehouse.groupsIndex裏面

咱們看下Warehouse的代碼:

class Warehouse {
    // Cache route and metas

    // 這個就是咱們剛剛注入的那個map,果真接收一個IRouteGroup,對上了。
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}
複製代碼

接下來咱們看下獲取全部"com.alibaba.android.arouter.routes"目錄下的class文件路徑的邏輯,重點!

// 咱們傳進來的packageName是 "com.alibaba.android.arouter.routes"
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    final Set<String> classNames = new HashSet<>();

    // 獲取全部dex文件的路徑,重點,下面有
    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());

    // 遍歷全部dex文件的路徑
    for (final String path : paths) {
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;

                try {
                    // 是不是".zip"文件
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        // 若是是.zip文件,就調用loadDex來加載
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                        // 不然,直接根據路徑建立便可
                        dexfile = new DexFile(path);
                    }

                    // 遍歷dexfile下面的元素
                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        // 若是是以"com.alibaba.android.arouter.routes"開頭,就添加
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    if (null != dexfile) {
                        try {
                            dexfile.close();
                        } catch (Throwable ignore) {
                        }
                    }

                    parserCtl.countDown();
                }
            }
        });
    }

    parserCtl.await();

    // 返回
    return classNames;
}
複製代碼

上面代碼的邏輯很簡單:

  • 1 獲取全部Dex文件路徑,而且遍歷建立DexFile
  • 2 遍歷DexFile,而且將全部以"com.alibaba.android.arouter.routes"文件開頭的添加,而後返回。

根據上面章節,咱們又知道,ARouter在編譯時生成的文件都是以"com.alibaba.android.arouter.routes"爲前綴的,因此這個函數的結果就是獲取全部Arouter編譯時生成的文件名。

而後咱們來看,怎麼獲取全部DexFile文件路徑:

public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
    ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
    File sourceApk = new File(applicationInfo.sourceDir);

    List<String> sourcePaths = new ArrayList<>();

    // 添加apk的默認路徑,能夠理解爲apk文件的路徑
    sourcePaths.add(applicationInfo.sourceDir);

    // EXTRACTED_NAME_EXT 就是 ".classes",因此這個結果相似於 "test.classes"這樣
    String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;


    // 若是開啓了MultiDex,那麼就遍歷獲取每個dex文件的路徑
    if (!isVMMultidexCapable()) {
        // 獲取全部Dex文件的總數
        int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
        File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

        // 遍歷獲取路徑
        for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
            //EXTRACTED_SUFFIX 就是 ".zip",因此fileName就相似於 test.classes2.zip, test.classes3.zip這樣
            String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
            File extractedFile = new File(dexDir, fileName);
            if (extractedFile.isFile()) {
                // 添加路徑
                sourcePaths.add(extractedFile.getAbsolutePath());
            } else {
                throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
            }
        }
    }

    // 返回
    return sourcePaths;
}
複製代碼

上述代碼的邏輯很簡單,就是獲取app對應的全部的Dex文件的路徑,其實Android的代碼打包出來就是一堆Dex文件,能夠當作是.class文件的合集。也就是說,咱們寫代碼到打包的時候,是:.java -> .class -> .dex這樣的包裝,而如今,咱們要反過來,從 .dex -> .class這樣搞回去,固然咱們只須要獲得.class就足矣。

好,咱們再來回顧一下流程:

  • 1 在Application的onCreate()裏面咱們調用了Arouter.init(this)。
  • 2 接着調用了ClassUtils.getFileNameByPackageNam()來獲取全部"com.alibaba.android.arouter.routes"目錄下的dex文件的路徑。
  • 3 而後遍歷這些dex文件獲取全部的calss文件的完整類名。
  • 4 而後遍歷全部類名,獲取指定前綴的類,而後經過反射調用它們的loadInto(map)方法,這是個注入的過程,都注入到參數Warehouse的成員變量裏面了。
  • 5 其中就有Arouter在編譯時生成的"com.alibaba.android.arouter.routes.ARouter R o o t . A R o u t e r Root.ARouter Root a p p " 類,它對應的代碼 : < " a p p " , A R o u t e r app"類,它對應的代碼:<"app", ARouter Group$$app.class>就被添加到Warehouse.groupsIndex裏面了。

好,如今咱們的注入過程就完事了,說白了就是: app包名 -> 獲取.dex -> 獲取.class -> 找對應的.class -> 反射調用方法 -> 存入Warehouse中,這個過程就是注入,Warehouse就是倉庫,裏面保存了須要的key和.class,好,咱們來看獲取的過程

3 調用時乾的事 - 獲取

調用的代碼很簡單:

// 這裏的path就是咱們經過@Route指定的,也就是"/app/activity_main"
ARouter.getInstance().build(path).navigation();
複製代碼

其中Arouter.getInstance()很簡單,就是個單例,我看看build(path)函數:

public Postcard build(String path) {
    // 就一行代碼
    return _ARouter.getInstance().build(path);
}

// 調到了這裏
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //...省略一些代碼

        // 到這裏,extractGroup(path)是獲取group名字的,path是"/app/activity_main",那麼group就是app
        return build(path, extractGroup(path));
    }
}


// 獲取group名字,參數就是"/app/activity_main"
private String extractGroup(String path) {
    // 校驗path的合法性,好比:若是不是以"/"開頭,就報錯
    if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
        throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
    }

    try {
        // 獲取group名字,結果就是"app"
        String defaultGroup = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(defaultGroup)) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
        } else {
            return defaultGroup;
        }
    } catch (Exception e) {
        logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
        return null;
    }
}

// 接着走build,咱們已經知道參數是("/app/activity_main","app")了
protected Postcard build(String path, String group) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        
        // ... 省略一些代碼

        // 直接建立了個玩意,咱們就叫它明信片吧
        return new Postcard(path, group);
    }
}

// 來看下明信片的構造
 public Postcard(String path, String group) {
     // 調下面
    this(path, group, null, null);
}

// 最終走到這裏
public Postcard(String path, String group, Uri uri, Bundle bundle) {
    // 保存了path,就是"/app/activity_main"
    setPath(path);
    // 保存了group,就是"app"
    setGroup(group);
    // uri是null !!!
    setUri(uri);
    // 建立了個Bundle()
    this.mBundle = (null == bundle ? new Bundle() : bundle);
}
複製代碼

好,完事,如今咱們知道,ARouter.getInstance().build(path);最終是建立了個Postcard,保存了path和group,而後咱們看下Postcard的navigation()函數:

public Object navigation() {
    // 這裏的參數傳個null
    return navigation(null);
}

// 參數是null
public Object navigation(Context context) {
    // 重載調用,這個context是null
    return navigation(context, null);
}

// 通過一番調用,最終走到這裏
public Object navigation(Context context, NavigationCallback callback) {
    // 這裏將this做爲參數調用下去,this就是Postcard,包含了剛剛的path和group。
    return ARouter.getInstance().navigation(context, this, -1, callback);
}

// 來到了這裏
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    // 又是個甩鍋函數
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}

// 接着來到了這裏
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //...

    try {

        // 核心函數1
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        if (null != callback) {
            callback.onLost(postcard);
        } else {
            DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
            if (null != degradeService) {
                degradeService.onLost(context, postcard);
            }
        }

        return null;
    }

    if (null != callback) {
        callback.onFound(postcard);
    }

    // 判斷是否須要調用攔截器
    if (!postcard.isGreenChannel()) {
        // 須要調用攔截器
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }

            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }

                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
    } else {
        // 不須要調用攔截器

        // 核心函數2
        return _navigation(context, postcard, requestCode, callback);
    }

    return null;
}

複製代碼

接着,咱們來看那兩個核心函數:

// 核心函數1
public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }

    // 先從Warehouse.routes裏面獲取RouteMeta,咱們上述代碼的經歷只用到了Warehouse.groupsIndex,因此確定是null

    // 第二次過來了,如今Warehouse.routes有值了,就是根據path拿到的。
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        
        // 接着跑這裏,從Warehouse.groupsIndex獲取IRouteGroup的class!終於用到咱們前面注入的玩意兒了,postcard.getGroup()就是"app",
        // 而咱們前面調過 Warehouse.groupsIndex.put("app", ARouter$$Group$$app.class),這裏就直接取出來了。
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());

        if (null == groupMeta) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            try {
                // 開始反射了
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                
                // 調了loadInfo,咱們回去看下ARouter$$Group$$app.class的loadInto方法:
                // atlas.put("/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648));
                // 直接put了,key是"/app/activity_main",跟Postcard的path同樣,這下就放在Warehouse.routes裏面了,下次就能拿到了。
                iGroupInstance.loadInto(Warehouse.routes);

                // 把group扔掉,group的意義就是用來拿route的,如今拿到了已經沒用了,刪除省內存。
                Warehouse.groupsIndex.remove(postcard.getGroup());
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }

            // 又調了本身,回到這個函數頭從新看
            completion(postcard);
        }
    } else {
        // 第二次進來,跑這裏,設置一堆屬性,還記得很重要的那一堆參數嗎
        // RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648)
        postcard.setDestination(routeMeta.getDestination()); // MainActivity.class
        postcard.setType(routeMeta.getType()); // RouteType.ACTIVITY
        postcard.setPriority(routeMeta.getPriority()); // -1
        postcard.setExtra(routeMeta.getExtra()); // -2147483648


        // uri是null,不用看
        Uri rawUri = postcard.getUri();
        if (null != rawUri) {   // Try to set params into bundle.
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();
            if (MapUtils.isNotEmpty(paramsType)) {
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard,
                            params.getValue(),
                            params.getKey(),
                            resultMap.get(params.getKey()));
                }

                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
            }

            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }

        // 根據類型執行邏輯,咱們的類型是RouteType.ACTIVITY,下面好像都沒有
        switch (routeMeta.getType()) {
            case PROVIDER: 
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // There's no instance of this provider
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();
                break;
            case FRAGMENT:
                postcard.greenChannel();
            default:
                break;
        }
    }
}
複製代碼

核心函數1搞完了,總共跑了兩次,

  • 第一次,咱們從Warehouse.groupsIndex取出注入時保存的數據,而後loadInto()相關數據到Warehouse.routes裏面去了。
  • 第二次,咱們僅僅是給參數postcard賦值,Destination和Type等。

好,如今咱們的Warehouse.routes有數據了,而且參數postcard有destination和type了,因而接着執行核心函數2:

// 核心函數2
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {

    // 咱們知道參數context是null,因而就取Arouter.init(context)的context,也就是application
    final Context currentContext = null == context ? mContext : context;

    // 直接根據類型執行邏輯了
    switch (postcard.getType()) {
        case ACTIVITY: // 這就是咱們的類型
            // 建立intent,destination就是MainActivity.class,咱們在覈心函數1裏面指定過了
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());

            // Flags,咱們沒有設置,就是-1
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) { // 咱們的context不是Activity
                // 因而就添加FLAG_ACTIVITY_NEW_TASK這個Flag(不然用application啓動Activity,有的版本會崩潰),若是navigation()傳遞了Activity做爲context,就不會添加這個flag
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // 設置Action,咱們是沒有的
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }

            // 切換到UI線程去啓動Activity,Activity啓動了,完事。
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });

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

核心函數2的邏輯很簡答,直接用postcard裏面的type去執行對應邏輯,而後執行到了Activity分支,因而就取出destination,也就是MainActivity.class,來啓動。

好,咱們來小結一下:

咱們經過path去navigation(),此path="app/activity_main",此時會根據這個path構造一個postCard,其中以group="app"(以"/"分割獲得的),而後以group(也就是"app")從Werehouse的groupIndex裏面獲取值,就獲得了"Arouter G r o u p Group app.class"這個class對象, 接着,使用反射建立一個實例,並調用loadInto()函數,因而就執行到了:

atlas.put("/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648));
複製代碼

此後,咱們的Werehouse的routes裏面就有了:

{"/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app"},
複製代碼

接着,根據postCard的類型去進行switch-case,當case到RouteType.ACTIVITY時,就進行Activity的啓動,此時咱們有了對應Activity的class,若是navigation(context)傳遞了context,則就用這個context來啓動Activity,不然就用Arouter.init(context)這個context來啓動,若是這個context不是activity,則會添加Intent.FLAG_ACTIVITY_NEW_TASK這個flag來啓動Activity。

總結

咱們一路追蹤了Arouter的源碼流程:

  • 1 在編譯時經過Apt技術來 給代碼中含有@Route(path)註解的類 生成中間代碼
  • 2 Arouter.init(context)初始化時,進行注入操做,key就是path
  • 3 Arouter.getIntstance().build(path).navigation()時候用path來進行獲取操做,最終獲取到要啓動的Activity的class對象。

總之,這跟咱們一開始用的common公共依賴是同樣的,Arouter自己也是被app和login兩個module依賴的,因此Arouter自己就是第三方公共依賴。

因此,一個大思想就是: 頂層兩個互不相通的模塊想要通訊,能夠藉助公共依賴的底層模塊來進行,這裏的模塊是廣義的泛指,好比兩個應用程序互相通訊能夠藉助系統程序,兩個activity互相通訊能夠藉助application,甚至兩個類互相通訊能夠藉助父類。兩個小技術就是Apt和類加載,固然也能夠不用這兩個技術,技術是手段,思想是目標,只要能實現目標,什麼都無所謂。

相關文章
相關標籤/搜索