Android 组件化 路由跳转(上)

一.简介

 

组件化中,每个Module之间的页面跳转是个问题。因为每个Module不可能知道其他Module的Activity路径。所以我们没有办法像普通的工程一样使用Intent方式跳转。那么组件化开发中每个Module之间如何完成页面跳转呢?

路由,对的我们可以使用路由完成这个工作。市面上常见的路由有阿里的ARouter。

 

 

 

 

 

 

 

二.ARouter集成

 

1.组件的Module的Gradle文件添加一下依赖

apply plugin: 'com.android.application'

android {

    compileSdkVersion 28

    defaultConfig {

        ...

        javaCompileOptions {
            annotationProcessorOptions {
                //ARouter编译的时候需要的module名字
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }

    }

    

}



dependencies {
    ...

    implementation 'com.alibaba:arouter-api:1.5.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.5.1'
}

 

注意:kotlin配置

plugins {
    ...
    id 'kotlin-kapt'
}

dependencies {
    ...
    implementation 'com.alibaba:arouter-api:1.5.1'
    kapt 'com.alibaba:arouter-compiler:1.5.1'
}

 

 

 

2.初始化(一般在Application中初始化)

public class MyApplication extends Application {


    @Override
    public void onCreate() {
        super.onCreate();
        ...

        ARouter.init(this);

        ...
    }

    ...

}

 

 

 

3.目标页配置跳转注解

@Route(path = "/anim/AnimActivity")
public class AnimActivity extends AppCompatActivity implements View.OnClickListener {

    ....

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

        ....
    }


    ....
    
}

 

 

 

4.跳转页面跳转

 

<1> 普通跳转

ARouter.getInstance().build("/anim/AnimActivity").navigation();

 

 

<2> 基本传值跳转

ARouter.getInstance().build("/anim/AnimActivity").withInt("IntKey", 12).withString("StringKey", "张三").navigation();

接收

Intent intent = getIntent();
int i = intent.getIntExtra("IntKey", -1);
String s = intent.getStringExtra("StringKey");
Log.d("AnimActivity", "i----:" + i);
Log.d("AnimActivity", "s----:" + s);

结果

D/AnimActivity: i----:12


D/AnimActivity: s----:张三

 

 

 

<3> Bundle传值

Bundle bundle = new Bundle();
bundle.putInt("IntKey", 20);
bundle.putString("StringKey", "李四");
bundle.putLong("LongKey", 123456);
bundle.putDouble("DoubleKey", 123.456);
ARouter.getInstance().build("/anim/AnimActivity").withBundle("BundleKey", bundle).navigation();

接收

Intent intent = getIntent();
Bundle bundle = intent.getBundleExtra("BundleKey");
int i = bundle.getInt("IntKey");
String s = bundle.getString("StringKey");
long l = bundle.getLong("LongKey");
double d = bundle.getDouble("DoubleKey");
Log.d("AnimActivity", "i----:" + i);
Log.d("AnimActivity", "s----:" + s);
Log.d("AnimActivity", "l----:" + l);
Log.d("AnimActivity", "d----:" + d);

结果

D/AnimActivity: i----:20


D/AnimActivity: s----:李四


D/AnimActivity: l----:123456


D/AnimActivity: d----:123.456

 

说明:

ARouter页面跳转时通过.withXXX方法给我们提供了类似于原生Intent的所有带参数的传值。接收的方式和原生Intent跳转一样。

 

 

 

5.ARouter的其他注解

刚刚配置目标Activity时使用了ARouter的@Route注解,使用path配置目标Activity的路径。其实ARouter为我们配置了很多的注解。

 

<1> @Route注解

Route注解是作用在类上面,里面会携带path路径。

 

 

<2> @Interceptor注解

它能拦截你的路由,什么时候让路由通过什么时候让路由不通过,项目中如果需要拦截,可以使用此注解。

 

 

<3> @Autowired注解

Autowired注解是定义在目标页的属性上,通常用来定义目标页接收的值,还可以定义上面说到的接收服务方。

 

 

<4> ...

 

 

 

6.总结

到此,ARouter的基本使用就完成了,注意是基本使用。因为ARouter的功能还有很多,比如ARouter实现了Gradle插件路由的自动加载功能(它主要是在编译期通过gradle插装把需要依赖arouter注解的类自动扫描到arouter的map管理器里面),比如配置混淆规则等等。

 

详情参考官网:https://github.com/alibaba/ARouter

 

 

 

 

 

 

 

 

三.ARouter源码解析

 

1.简介

在上述使用Arouter过程中,我们的Module依赖了arouter-api、arouter-compiler,两个Jar包。在 编译期 arouter-compiler通过扫描项目中用到的Route、Autowired、Interceptor等注解来生成对应的class文件。然后将这些Class文件放到Map中。跳转的时候根据Key(就是我们@Route时使用path确定的路径,比如path = "/anim/AnimActivity")。

 

其实,Activity,Service,等等四大组件的跳转,使用系统Intent的方式完全可以。比如

Intent intent=new Intent(MainActivity.this, RxBusActivity.class);
intent.putExtra("bean",student);
startActivity(intent);

但是,组件化中。不同的Module之间一般没有相互依赖。所以拿不到目标的Class<?>对象。所以我们在组件化中不可能使用Intent的方式跳转。下面我们从源码层面看一下ARouter是如何做到的。

 

 

 

 

2.源码解析之 初始化

ARouter.init(this);

调用ARouter类的init方法

 

public static void init(Application application) {
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info("ARouter::", "ARouter init start.");
        hasInit = _ARouter.init(application);
        if (hasInit) {
            _ARouter.afterInit();
        }

        _ARouter.logger.info("ARouter::", "ARouter init over.");
    }

}

 

我们重点看下面一行代码

hasInit = _ARouter.init(application);
protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info("ARouter::", "ARouter init success!");
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());
    return true;
}

 

接下来,我们看下面一行代码

LogisticsCenter.init(mContext, executor);
public static synchronized void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        long startInit = System.currentTimeMillis();
        loadRouterMap();
         
        //如果使用了Gradle配置路由列表 直接打印不继续执行
        if (registerByPlugin) {
            ARouter.logger.info("ARouter::", "Load router map by arouter-auto-register plugin.");
        } else {

            //没有使用Gradle配置路由列表 也就是使用我们上面的方法
            Object routerMap;
            
            //debuggable=false或者没有新的版本 SharedPreferences取出HashSet
            if (!ARouter.debuggable() && !PackageUtils.isNewVersion(context)) {
                ARouter.logger.info("ARouter::", "Load router map from cache.");
                routerMap = new HashSet(context.getSharedPreferences("SP_AROUTER_CACHE", 0).getStringSet("ROUTER_MAP", new HashSet()));
            } else {

                //创建新的集合存放到SharedPreferences中
                ARouter.logger.info("ARouter::", "Run with debug mode or new install, rebuild router map.");
                routerMap = ClassUtils.getFileNameByPackageName(mContext, "com.alibaba.android.arouter.routes");
                if (!((Set)routerMap).isEmpty()) {
                    context.getSharedPreferences("SP_AROUTER_CACHE", 0).edit().putStringSet("ROUTER_MAP", (Set)routerMap).apply();
                }

                PackageUtils.updateVersion(context);
            }

            ARouter.logger.info("ARouter::", "Find router map finished, map size = " + ((Set)routerMap).size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
            startInit = System.currentTimeMillis();
            Iterator var5 = ((Set)routerMap).iterator();

            while(var5.hasNext()) {
                String className = (String)var5.next();
                if 
            (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Root")) {// 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中
                   ((IRouteRoot)((IRouteRoot)Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if // 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中
        
   (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Interceptors")) {
                    ((IInterceptorGroup)((IInterceptorGroup)Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if // 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中
            (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Providers")) {
                    ((IProviderGroup)((IProviderGroup)Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }

        ARouter.logger.info("ARouter::", "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
        if (Warehouse.groupsIndex.size() == 0) {
            ARouter.logger.error("ARouter::", "No mapping files were found, check your configuration please!");
        }

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

    } catch (Exception var7) {
        throw new HandlerException("ARouter::ARouter init logistics center exception! [" + var7.getMessage() + "]");
    }
}

 

 

小结1

不是通过Gradle自动加载路由列表的情况下:

<1> 通过ClassUtils.getFileNameByPackageName获取到apk中前缀为com.alibaba.android.arouter.routes的类,然后压缩成zip文件,然后通过DexFile.loadDex转化成DexFile对象,然后过滤出com.alibaba.android.arouter.routes前缀的class并返回。

 

<2> 拿到了需要的class类后,放到sp里面,方便下次不去扫描apk拿class,更新版本号。

      <2.1> 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中。

      <2.2> 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中。

      <2.3> 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中。

 

 

 

3.源码解析之 配置目标页面路径

@Route(path = "/anim/AnimActivity")
public class AnimActivity extends AppCompatActivity {

   ...

}

配置后,编译后,在具体Module的build目录下会自动生成ARouter相关的类。如图

 

自动生成的ARouter$$Group$$anim类

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

也就是说,ARouter自动把使用@Route注解中配置的path当成Key,然后把目标Activity当成Value存放在Map中。

 

 

小结2

ARouter会在apt阶段,生成的class类有Arouter$$Root$$模块名,模块名是在gradle中配置的arg("AROUTER_MODULE_NAME", "${project.getName()}")属性,把所有组的信息放到传进来的map中,这个组是通过我们在Route注解的path属性拆分的,比如定义/anim/AnimActivity,会认为组名是anim,Arouter$$Root$$组名放的是该组下,所有的路由表信息,包括route、provider注解通过RouteMeta包装对应的class类信息,provider注解会放在Arouter$$Providers$$模块名下面。

 

 

 

 

4.源码解析之 跳转页面

ARouter.getInstance().build("/anim/AnimActivity").withBundle("BundleKey", bundle).navigation();

 

public Object navigation() {
    return this.navigation((Context)null);
}

 

最终调用

public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    PretreatmentService pretreatmentService = (PretreatmentService)ARouter.getInstance().navigation(PretreatmentService.class);
    if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
        return null;
    } else {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException var8) {
            logger.warning("ARouter::", var8.getMessage());
            if (debuggable()) {
                this.runInMainThread(new Runnable() {
                    public void run() {
                        Toast.makeText(_ARouter.mContext, "There's no route matched!\n Path = [" + postcard.getPath() + "]\n Group = [" + postcard.getGroup() + "]", 1).show();
                    }
                });
            }

            if (null != callback) {
                callback.onLost(postcard);
            } else {
                DegradeService 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() {
                public void onContinue(Postcard postcardx) {
                    _ARouter.this._navigation(context, postcardx, requestCode, callback);
                }

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

                    _ARouter.logger.info("ARouter::", "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
            return null;
        } else {
            return this._navigation(context, postcard, requestCode, callback);
        }
    }
}

 

private Object _navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;
    switch(postcard.getType()) {
    case ACTIVITY:
        final Intent intent = new Intent(currentContext, postcard.getDestination());
        intent.putExtras(postcard.getExtras());
        int flags = postcard.getFlags();
        if (-1 != flags) {
            intent.setFlags(flags);
        } else if (!(currentContext instanceof Activity)) {
            intent.setFlags(268435456);
        }

        String action = postcard.getAction();
        if (!TextUtils.isEmpty(action)) {
            intent.setAction(action);
        }

        this.runInMainThread(new Runnable() {
            public void run() {
                _ARouter.this.startActivity(requestCode, currentContext, intent, postcard, callback);
            }
        });
        return null;
    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 var11) {
            logger.error("ARouter::", "Fetch fragment instance error, " + TextUtils.formatStackTrace(var11.getStackTrace()));
            }
    case METHOD:
    case SERVICE:
    default:
        return null;
    }
}

 

 

小结3

最终,还是通过Intent的方式完成传值跳转。通过上述ARouter在编译期间的Map集合,获取相应的Class文件。最终完成跳转。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值