動(dòng)手?jǐn)]一個(gè)ARouter (ARouter源碼分析)

背景

為什么要重復(fù)造輪子呢钞澳?

  • 我認(rèn)為只有站在作者的角度才能更透徹的理解框架的設(shè)計(jì)思想
  • 去踩大神們所踩過的坑旧蛾。
  • 才能深入的理解框架的所提供的功能
  • 學(xué)習(xí)優(yōu)秀的作品中從而提高自己

在開始之前我先提出關(guān)于ARouter的幾個(gè)問題

  • 為什么要在module的build.gradle文件中增加下面配置? 它的作用是什么?它跟我們定義的url中的分組有什么關(guān)系谅摄?
javaCompileOptions {
    annotationProcessorOptions {
        arguments = [moduleName: project.getName()]
    }
}
  • 有這么一種業(yè)務(wù)場(chǎng)景民宿,新建一個(gè)業(yè)務(wù)組件user往枣,user組件中有頁(yè)面UserActivity,配置url /user/main;有一個(gè)服務(wù)接口用爪,其實(shí)現(xiàn)類在app中原押,配置url為/user/info;代碼如下:
//module:user
@Route(path = "/user/main")
public class UserActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.user_activity);
    }
}

public interface IUserService extends IProvider {
    void test(String s);
}

//module:app
//user服務(wù)
@Route(path = "/user/info")
public class UserServiceImpl implements IUserService {

    public void test(String test) {
        Log.d("xxxx->",test);
    }
}

好了開發(fā)完成,讓我們編譯一下項(xiàng)目看看偎血,編譯結(jié)果如下圖(ps:這里編譯的是我自己的項(xiàng)目诸衔,但效果和ARouter是一樣的):

Why?颇玷?笨农?

讓我們帶著這兩個(gè)問題開始RouterManager之旅。

第一步架構(gòu)設(shè)計(jì)思路(處理頁(yè)面跳轉(zhuǎn))

我們的目標(biāo)是根據(jù)一個(gè)url來(lái)打開指定的頁(yè)面亚隙,該如何做呢磁餐?很簡(jiǎn)單违崇,我們把url和對(duì)應(yīng)的頁(yè)面做一個(gè)對(duì)應(yīng)關(guān)系阿弃,比如放到map中以u(píng)rl為key,對(duì)應(yīng)的頁(yè)面activity為value即可羞延;這樣當(dāng)我們要打開這個(gè)activity時(shí)渣淳,根據(jù)傳給我們的url去map中找到對(duì)應(yīng)的activity,然后調(diào)用startActivity就OK了伴箩。

你可能會(huì)問那我們這個(gè)map該如何維護(hù)呢入愧?我們?cè)趺窗堰@個(gè)對(duì)應(yīng)關(guān)系存到map中呢?總不能手動(dòng)去put吧嗤谚,你別說貌似還真行棺蛛,我們?cè)赼pp啟動(dòng)的時(shí)候先把我的映射關(guān)系手動(dòng)初始化好,這樣在打開頁(yè)面是直接通過url來(lái)獲取就行了巩步。那么問題來(lái)了旁赊,大哥你累不累啊椅野?對(duì)于一個(gè)懶人來(lái)說首先會(huì)想到的是能不能自動(dòng)生成這個(gè)映射關(guān)系表呢终畅?答案是肯定的。

思路總結(jié)

我們可以利用編譯注解的特性竟闪,新增一個(gè)注解,給每個(gè)需要通過url打開的activity加上此注解。在注解處理器中獲取所有被注解的類梆暖,動(dòng)態(tài)生成映射關(guān)系表咐旧,然后在app啟動(dòng)時(shí)把所生成的映射關(guān)系load到內(nèi)存(其實(shí)就是讀到一個(gè)map中)

第二部擼代碼

0x01

首先我們需要?jiǎng)?chuàng)建三個(gè)module,如下圖:

為什么要三個(gè)項(xiàng)目呢理朋?原因如下:

  • 我們需要用到的注解處理器AbstractProcessor是在javax包下赠涮,而android項(xiàng)目中是沒有這個(gè)包的子寓,因此我們需要建一個(gè)java library,也就是router-compiler笋除,它的作用是幫我們動(dòng)態(tài)生成代碼斜友,只存在于編譯期間

  • 既然router-compiler只存在于編譯期間,那我們的注解是需要在項(xiàng)目中用到的垃它,這個(gè)類應(yīng)該放在那里呢鲜屏?這就有了第二個(gè)java library,router-annotation国拇,用來(lái)專門存放我們定義的注解和一些要被打進(jìn)app中代碼洛史。

  • 由于上述兩個(gè)library都是java項(xiàng)目,而我們最終是要用到android工程中的酱吝,因此對(duì)外提供api時(shí)肯定會(huì)用到android工程中的類也殖,如Context。所以就有了第三個(gè)module router-api用于處理生成產(chǎn)物务热。如把生成映射關(guān)系表load到內(nèi)存忆嗜,并提供統(tǒng)一的調(diào)用入口。

0x02

我們先定義我們自己的注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    String path();

    String group() default "";

    String name() default "";

    int extras() default Integer.MIN_VALUE;

    int priority() default -1;
}

定義自己的route處理器RouterProcessor

@AutoService(Processor.class)       //自動(dòng)注冊(cè)注解處理器
@SupportedOptions({Consts.KEY_MODULE_NAME})     //參數(shù)
@SupportedSourceVersion(SourceVersion.RELEASE_7)        //指定使用的Java版本
@SupportedAnnotationTypes({ANNOTATION_ROUTER_NAME}) //指定要處理的注解類型
public class RouterProcessor extends AbstractProcessor{

    private Map<String,Set<RouteMeta>> groupMap = new HashMap<>();  //收集分組
    private Map<String,String> rootMap = new TreeMap<>();
    private Filer mFiler;
    private Logger logger;
    private Types types;
    private TypeUtils typeUtils;
    private Elements elements;
    private String moduleName = "app"; //默認(rèn)app
    private TypeMirror iProvider = null; //IProvider類型
    
    //......

其中SupportedAnnotationTypes指定的就是我們上面定義的注解Route

接下來(lái)就是收集所有被注解的類崎岂,生成映射關(guān)系,代碼如下:

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if(CollectionUtils.isNotEmpty(set)) {
            //獲取到所有被注解的類
            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routers,start... <<<");
                parseRoutes(elementsAnnotatedWith);
            } catch (IOException e) {
                logger.error(e);
            }
            return true;
        }
        return false;
    }

獲取完之后交給了parseRoutes方法:

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if(CollectionUtils.isNotEmpty(routeElements)) {

            logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");
            rootMap.clear();
            //.......
            TypeMirror type_activity = elements.getTypeElement(ACTIVITY).asType();

            for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMeta;

                if(types.isSubtype(tm,type_activity)) { //activity
                    logger.info(">>> Found activity route: "+ tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route,element,RouteType.ACTIVITY,null);
                } else if(types.isSubtype(tm,iProvider)) { //IProvider
                    logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route,element,RouteType.PROVIDER,null);
                } else if(types.isSubtype(tm,type_fragment) || types.isSubtype(tm,type_v4_fragment)) { //Fragment
                    logger.info(">>> Found fragment route: " + tm.toString() + " <<< ");
                    routeMeta = new RouteMeta(route,element,RouteType.parse(FRAGMENT),null);
                } else {
                    throw new RuntimeException("ARouter::Compiler >>> Found unsupported class type, type = [" + types.toString() + "].");
                }

                categories(routeMeta);
            }
            
            //.......

這個(gè)方法比較長(zhǎng)捆毫,我們先看看最主要的處理,遍歷routeElements冲甘,判斷當(dāng)前被注解的類的類型绩卤,分別是activity,IProvider,Fragment這三中江醇,也就是說注解Route可以用來(lái)注解activity 濒憋,IProvider,和Fragment(注意這里fragment包括原生包中的和v4包中的fragment)然后根據(jù)類型構(gòu)造出routeMate對(duì)象陶夜,構(gòu)造完之后傳給了categories方法:

private void categories(RouteMeta routeMete) {
        if (routeVerify(routeMete)) {
            logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
            //groupMap是一個(gè)全局變量凛驮,用來(lái)按分組存儲(chǔ)routeMeta
            Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
            if (CollectionUtils.isEmpty(routeMetas)) {
                Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
                    @Override
                    public int compare(RouteMeta r1, RouteMeta r2) {
                        try {
                            return r1.getPath().compareTo(r2.getPath());
                        } catch (NullPointerException npe) {
                            logger.error(npe.getMessage());
                            return 0;
                        }
                    }
                });
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMete);
            }
        } else {
            logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
        }
    }

我們看到這個(gè)方法中首先根據(jù)當(dāng)前url分組去groupMap中查找,也就是看是否有該分組律适,如果有取出對(duì)應(yīng)的RouterMeta集合辐烂,把本次生成的routeMeta放進(jìn)去;沒有就新存一個(gè)集合捂贿。

到這里我們已經(jīng)把所有的注解類都獲取到并且已經(jīng)按分組分類纠修。接下來(lái)就是生成java類來(lái)存放這些信息:

這里暫且只看對(duì)activity映射關(guān)系處理的代碼:

// (1)
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                String groupName = entry.getKey();

                // (2)
                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(groupParamSpec);

                Set<RouteMeta> groupData = entry.getValue();

                for (RouteMeta meta : groupData) {
                    ClassName className = ClassName.get((TypeElement) meta.getRawType());
                    
                   //......   (3)

                    loadIntoMethodOfGroupBuilder.addStatement(
                            "atlas.put($S," +
                                    "$T.build($T." + meta.getType() + ",$T.class,$S,$S," + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + meta.getPriority() + "," + meta.getExtra() + "))",
                            meta.getPath(),
                            routeMetaCn,
                            routeTypeCn,
                            className,
                            meta.getPath().toLowerCase(),
                            meta.getGroup().toLowerCase());
                }

                //Generate groups   (4)
                String groupFileName = NAME_OF_GROUP + groupName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(groupFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(type_IRouteGroup))
                                .addModifiers(Modifier.PUBLIC)
                                .addMethod(loadIntoMethodOfGroupBuilder.build())
                                .build()
                ).build().writeTo(mFiler);

                logger.info(">>> Generated group: " + groupName + "<<<");
                rootMap.put(groupName, groupFileName);
            }

            // (5)
            if(MapUtils.isNotEmpty(rootMap)) {
                for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                    loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
                }
            }

            // ......

            // Write root meta into disk.   (6)
            String rootFileName = NAME_OF_ROOT + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elements.getTypeElement(IROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated root, name is " + rootFileName + " <<<");
        }

現(xiàn)將上述這段代碼解釋如下:

  • 遍歷我們之前存儲(chǔ)的groupMap,取出對(duì)應(yīng)的集合,如注釋(1)
  • 生成一個(gè)方法體厂僧,并且把集合中的所有映射關(guān)系都put到參數(shù)map中扣草。如 (2)(3)
  • 生成java類,類名為RouterManagerGroup + moduleName,這里的moduleName就是在build.gradle文件中配置的,如不配置辰妙,活獲取為null 如(4)
  • 把每個(gè)分組和所生成的類做個(gè)映射關(guān)系鹰祸,作用就是為了實(shí)現(xiàn)按分組加載功能 如 (5)(6)

下面我們看下一生成的產(chǎn)物

/**
 DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY ROUTERMANAGER. */
public class RouterManager$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("service", RouterManager$$Group$$service.class);
  }
}

存儲(chǔ)分組對(duì)應(yīng)關(guān)系

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY ROUTERMANAGER. */
public class RouterManager$$Group$$service implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/service/test/main",RouteMeta.build(RouteType.ACTIVITY,OtherActivity.class,"/service/test/main","service",null, -1,-2147483648));
  }
}

就這樣映射關(guān)系自動(dòng)生成好了,那么該如何使用呢密浑?下面就讓我隆重介紹一下我們Api

0x03

由于我們的映射關(guān)系表是全局存在的蛙婴,所以肯定需要在Application中做初始化操作,其目的就是把映射關(guān)系load到內(nèi)存尔破,下面讓我們看看具體實(shí)現(xiàn)代碼

首先我們得需要一個(gè)容器來(lái)存儲(chǔ)我們的映射關(guān)系街图,因此就有了Warehouse類

class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();
    
    //......

    static void clear() {
        providers.clear();
        providersIndex.clear();
    }
}

我們?cè)诖祟愔袑?shí)例化兩個(gè)map用來(lái)存儲(chǔ)我們的分組信息和每個(gè)分組中的對(duì)應(yīng)關(guān)系信息

groupIndex:用來(lái)存放分組信息,這個(gè)會(huì)優(yōu)先load數(shù)據(jù)
routes:用來(lái)存儲(chǔ)對(duì)應(yīng)關(guān)系數(shù)據(jù)

接下來(lái)我們?cè)贏pp初始化時(shí)會(huì)調(diào)用如下代碼來(lái)初始化:

RouterManager.init(this);

那么我們進(jìn)去init方法中看看具體干了什么懒构?

public static synchronized void init(Application application){
        if(!hasInit) {
            hasInit = true;
            mContext = application;
            mHandler = new Handler(Looper.getMainLooper());
            logger = new DefaultLogger();
            LogisticsCenter.init(mContext,logger);
        }
    }

可以看到這里最關(guān)鍵的一行代碼是 LogisticsCenter.init(mContext,logger)

那就讓我們繼續(xù)去LogisticsCenter.init(mContext,logger);方法中看看:

public synchronized static void init(Context context, ILogger log) {
        logger = log;
        Set<String> routeMap;
        try {
            if(RouterManager.debuggable() || PackageUtils.isNewVersion(context)) { //開發(fā)模式或版本升級(jí)時(shí)掃描本地件
                logger.info(TAG,"當(dāng)前環(huán)境為debug模式或者新版本餐济,需要重新生成映射關(guān)系表");
                //these class was generated by router-compiler
                routeMap = ClassUtils.getFileNameByPackageName(context, Consts.ROUTE_ROOT_PAKCAGE);
                if(!routeMap.isEmpty()) {
                    PackageUtils.put(context,Consts.ROUTER_SP_KEY_MAP,routeMap);
                }
                PackageUtils.updateVersion(context);
            } else{ //讀取緩存
                logger.info(TAG,"讀取緩存中的router映射表");
                routeMap = PackageUtils.get(context,Consts.ROUTER_SP_KEY_MAP);
            }

            logger.info(TAG,"router map 掃描完成");
            //將分組數(shù)據(jù)加載到內(nèi)存
            for (String className : routeMap) {
                //Root
                if(className.startsWith(Consts.ROUTE_ROOT_PAKCAGE + Consts.DOT + Consts.SDK_NAME + Consts.SEPARATOR + Consts.SUFFIX_ROOT)) {
                    ((IRouteRoot)(Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } 
                //......
            }

            logger.info(TAG,"將映射關(guān)系讀到緩存中");

            if(Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG,"No mapping files,check your configuration please!");
            }

            if (RouterManager.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.providersIndex.size()));
            }

        } catch (Exception e) {
            e.printStackTrace();
            logger.error(TAG,"RouterManager init logistics center exception! [" + e.getMessage() + "]");
        }
    }

具體解釋如下:

1)、首先是根據(jù)包名去掃描所有生成的類文件胆剧,并放在routeMap中絮姆。當(dāng)然這里會(huì)根據(jù)版本判斷然后緩存到本地,目的是為了避免重復(fù)掃描
2)秩霍、遍歷掃描到的數(shù)組篙悯,將所有分組信息緩存到Warehouse.groupIndex中

可以看到初始化時(shí)只干了這兩件事,掃描class文件前域,讀取分組信息辕近;仔細(xì)想想你會(huì)發(fā)現(xiàn)這里并沒有去讀取我們的url和activity映射關(guān)系信息韵吨,這就是所謂的按需加載匿垄。

到這里我們所有的準(zhǔn)備工作都已完成了,那么該怎么使用呢归粉?

下面讓我們看看具體的用法

0x04

我們先來(lái)看一段代碼:

RouterManager.getInstance().build("/user/main").navigation(MainActivity.this);

上述代碼是我們打開UserActivty頁(yè)面所使用的方式椿疗,可以發(fā)現(xiàn)這里只傳了一個(gè)url。那就讓我們看看內(nèi)部是如何實(shí)現(xiàn)的糠悼?

首先我們?nèi)uild方法中看看具體的代碼:

public Postcard build(String path) {
        if(TextUtils.isEmpty(path)) {
            throw new HandlerException("Parameter is invalid!");
        } else {
            return build(path,extractGroup(path));
        }
    }

    public Postcard build(String path,String group) {
        if(TextUtils.isEmpty(path)) {
            throw new HandlerException("Parameter is invalid!");
        } else {
            return new Postcard(path,group);
        }
    }

發(fā)現(xiàn)這里是一個(gè)重載方法届榄,最后返回的是一個(gè)Postcard對(duì)象,然后調(diào)用Postcard的navigation方法倔喂÷撂酰可以看到這里Postcard其實(shí)只是一個(gè)攜帶數(shù)據(jù)的實(shí)體。下面看看navigation方法:

 public Object navigation(Context context) {
        return RouterManager.getInstance().navigation(context,this,-1);
    }

可以發(fā)現(xiàn)這里只是做了一個(gè)中轉(zhuǎn)席噩,最終調(diào)用的是RouterManager的navigation方法:

Object navigation(final Context context,final Postcard postcard,final int requestCode) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (HandlerException e) {
            e.printStackTrace();
            return null;
        }

        final Context currentContext = context == null ? mContext : context;
        switch (postcard.getType()) {
            case ACTIVITY:
                final Intent intent = new Intent(currentContext,postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                int flags = postcard.getFlags();
                if(flags != -1) {
                    intent.setFlags(flags);
                } else if(!(currentContext instanceof Activity)) { //如果當(dāng)前上下文不是activity班缰,則啟動(dòng)activity時(shí)需要new一個(gè)新的棧
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode,currentContext,intent,postcard);
                    }
                });
                break;
            //......
        }
        return null;
    }

由上述代碼可以看出首先調(diào)用的是LogisticsCenter.completion()方法把postcard對(duì)象傳進(jìn)去,那讓我們先去這個(gè)方法中看個(gè)究竟:

/**
     * 填充數(shù)據(jù)
     * @param postcard
     */
    public synchronized static void completion(Postcard postcard) {
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if(routeMeta != null) {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            //......
        } else {
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
            if(groupMeta == null) {
                throw new NoRouteFoundException("There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                try {
                    //按組加載數(shù)據(jù)悼枢,美其名曰-按需加載
                    IRouteGroup iRouteGroup = groupMeta.getConstructor().newInstance();
                    iRouteGroup.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());
                } catch (Exception e) {
                    throw new HandlerException("Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
            }

            completion(postcard); //分組加載完成后重新查找
        }
    }

這里首先去根據(jù)url去Warehouse.routes中查找對(duì)應(yīng)的RouteMeta信息埠忘,如何是首次調(diào)用的話這里一定是沒有的,所以會(huì)執(zhí)行else方法,else方法里先根據(jù)分組獲取對(duì)應(yīng)的分組class莹妒,然后反射其實(shí)例對(duì)象并調(diào)用loadInfo()方法名船,把該分組中的所有映射關(guān)系讀取到Warehouse.routes中,然后繼續(xù)調(diào)用當(dāng)前方法填充相關(guān)的信息旨怠。

信息填充完成之后繼續(xù)回到navigation方法中:

switch (postcard.getType()) {
            case ACTIVITY:
                final Intent intent = new Intent(currentContext,postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                int flags = postcard.getFlags();
                if(flags != -1) {
                    intent.setFlags(flags);
                } else if(!(currentContext instanceof Activity)) { //如果當(dāng)前上下文不是activity渠驼,則啟動(dòng)activity時(shí)需要new一個(gè)新的棧
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode,currentContext,intent,postcard);
                    }
                });
                break;
            //......
        }

可以看到這里使用的是常規(guī)的啟動(dòng)方式startActivity去啟動(dòng)一個(gè)新activity。

Ok到此為止整個(gè)流程算是走完了鉴腻,至于傳遞參數(shù)渴邦,獲取fragment,以及服務(wù)IProvider什么的套路都一樣拘哨,這里不再重復(fù)贅述谋梭。

總結(jié)

ARouter的思路很好簡(jiǎn)單,就是通過編譯時(shí)注解生成url與頁(yè)面的映射關(guān)系表倦青,然后在程序啟動(dòng)時(shí)將該映射關(guān)系表load到內(nèi)存中瓮床,使用時(shí)直接去內(nèi)存中查找然后執(zhí)行常規(guī)的頁(yè)面啟動(dòng)方式。

下面我們來(lái)回答前面提出的兩個(gè)問題

第一:為什么要在每個(gè)build.gradle文件中配置一個(gè)moduleName呢产镐?

這是因?yàn)榫幾g時(shí)注解是以module為單位去生成代碼的隘庄,也就是說我們需要給每個(gè)module項(xiàng)目都配置該注解生成器的依賴,為了保證生成java文件的名字不會(huì)重復(fù)需要加上module為后綴癣亚。此配置和分組沒有任何關(guān)系丑掺。只是為了避免生成的分組類重復(fù)。

第二:為什么會(huì)報(bào)多個(gè)類重名的問題述雾?

我們知道Router的映射表有兩張表街州,第一張是用來(lái)存儲(chǔ)分組和分組對(duì)應(yīng)的class的,第二張是用來(lái)存儲(chǔ)每個(gè)分組中具體url映射關(guān)系的玻孟。而在第一個(gè)問題中我們根據(jù)moduleName來(lái)避免存放分組的class重名的問題唆缴。那么每個(gè)分組class本身有沒有重名的可能呢?答案是一定有的黍翎。比如:我們?cè)趗ser組件中配置的url:/user/main分組為user面徽,這個(gè)時(shí)候在編譯user組件時(shí)就會(huì)自動(dòng)生成一個(gè)類名為 RouterManager$$Group$$user的類,用來(lái)存放所有的以u(píng)ser為分組的頁(yè)面映射關(guān)系匣掸。那么當(dāng)我們?cè)赼pp的中也配置分組名為user的分組后趟紊,編譯app時(shí)就會(huì)在app中生成類名為RouterManager$$Group$$user的類。而我們app項(xiàng)目是依賴的user組件的碰酝,這就導(dǎo)致有兩個(gè)類名一樣的文件霎匈。編譯時(shí)自然就會(huì)報(bào)錯(cuò)。

對(duì)RouterManager的幾點(diǎn)思考

  • RouterManager能否用于夸進(jìn)程調(diào)用:

我認(rèn)為是可以的砰粹,RouterManager的關(guān)系映射表是存在一個(gè)全局靜態(tài)變量中的唧躲,當(dāng)我們需要在其他進(jìn)程訪問時(shí)只需要提供一個(gè)接口來(lái)得到映射關(guān)系即可造挽。

  • RouterManager能否在RePlugin中的使用:

答案也是可以的,由于RePlugin采用的是多個(gè)classloader機(jī)制弄痹,這就導(dǎo)致我們?cè)谥黜?xiàng)目的classloader獲取的對(duì)象和在插件classloader中獲取的是兩個(gè)獨(dú)立的對(duì)象饭入,如果想在插件中使用RouterManager去打開一個(gè)宿主的頁(yè)面,直接調(diào)用的話肯定是沒有對(duì)應(yīng)的映射關(guān)系的肛真,因?yàn)樵诓寮铽@取的RouterManager對(duì)象并不是宿主的單例對(duì)象谐丢,而是創(chuàng)建了一個(gè)新的對(duì)象。那怎么辦呢蚓让?答案很簡(jiǎn)單乾忱,我們?cè)诓寮惺褂梅瓷浍@取到宿主的RouterManager實(shí)例即可正常使用。

注:RouterManager框架的思路來(lái)源與ARouter历极,這里只實(shí)現(xiàn)了頁(yè)面跳轉(zhuǎn)窄瘟,fragment獲取和服務(wù)Provider的獲取功能。至于其他的降級(jí)策略趟卸,依賴注入功能就不在一一實(shí)現(xiàn)了

項(xiàng)目源碼請(qǐng)移駕到本人的github倉(cāng)庫(kù)查看:https://github.com/qiangzier/RouterManager

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹄葱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锄列,更是在濱河造成了極大的恐慌图云,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邻邮,死亡現(xiàn)場(chǎng)離奇詭異竣况,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)筒严,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門丹泉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人萝风,你說我怎么就攤上這事嘀掸∽涎遥” “怎么了规惰?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)泉蝌。 經(jīng)常有香客問我歇万,道長(zhǎng),這世上最難降的妖魔是什么勋陪? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任贪磺,我火速辦了婚禮,結(jié)果婚禮上诅愚,老公的妹妹穿的比我還像新娘寒锚。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布刹前。 她就那樣靜靜地躺著泳赋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喇喉。 梳的紋絲不亂的頭發(fā)上祖今,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音拣技,去河邊找鬼千诬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛膏斤,可吹牛的內(nèi)容都是我干的徐绑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼莫辨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泵三!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起衔掸,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤烫幕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后敞映,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體较曼,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年振愿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捷犹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冕末,死狀恐怖萍歉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情档桃,我是刑警寧澤枪孩,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站藻肄,受9級(jí)特大地震影響蔑舞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嘹屯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一攻询、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧州弟,春花似錦钧栖、人聲如沸低零。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毁兆。三九已至,卻和暖如春阴挣,著一層夾襖步出監(jiān)牢的瞬間气堕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工畔咧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茎芭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓誓沸,卻偏偏與公主長(zhǎng)得像梅桩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拜隧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理宿百,服務(wù)發(fā)現(xiàn),斷路器洪添,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 垦页? MyBatis 是支持定制化 SQL、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,510評(píng)論 0 4
  • 01 愛自己,和誰(shuí)結(jié)婚都一樣 偶然中看到有一本書忿峻,書名叫《愛自己薄啥,和誰(shuí)結(jié)婚都一樣》,我和我的好友說我很想寫一篇這個(gè)...
    木子琴空間閱讀 1,184評(píng)論 13 23
  • 延小狼閱讀 163評(píng)論 0 1
  • 光纖是光導(dǎo)纖纖維的簡(jiǎn)寫逛尚,是一種由玻玻璃或塑塑料制成的纖纖維垄惧,可作為光傳導(dǎo)工具。隨著科技的發(fā)展绰寞,光纖以其優(yōu)越的性能擊...
    廣州輝澎信息科技閱讀 1,366評(píng)論 0 0