阿里巴巴arouter组件化总结

第1节 组件化原因

  1. 抽离出常用的公共模块,如公司内部封装的工具库,网络访问库,公共资源等。各公共模块单独维护,逐渐完善。工作多年深刻感觉到公共模块代码对一个公司的重要性:
    1. 抽离出公共模块,通过共同维护和使用能明显提高公司内部项目开发效率,减少内部消耗。
    2. 抽离出公共模块,能达到强调的效果,通过逐步的版本迭代,达到公司内部技术积累的目的。
    3. 把功能模块放入内部版本仓库,使代码更简洁,各版本修改bug追踪有日志可查,使代码更健壮。
  2. 各业务模块间解耦
    1. 能达到各模块组合快速完成一个App,现实开发场景中,一个公司很多项目并行开发,其实很多业务模块是可以共用的。
    2. 能动态替换某一模块;
  3. 页面跳转,服务调用URL化,更切合hybird和多端开发的场景,如
    1. 由于路径化Web和原生界面相互跳转会很方便;
    2. Web调用Android\IOS原生页面统一化提高开发效率;
    3. 通过动态加载路由,能实现动态化路由这种黑科技;
  4. 路由跳转监听和拦截
    1. 通过路由拦截的功能,可以在很轻松的实现权限分组,权限细分及权限动态配置;
    2. 通过路由监听可以把一些埋点统一放到jiant
  5. 路由降级寻址
    1. 处理寻址异常降级寻址的问题;
    2. 在原生页面崩溃异常时,可以通过动态路由表,拦截原生路由跳转到H5;

第2节 组件化须考虑的问题

  1. 组件化之间的通信;

  2. 组件生命周期管理(模块能处理Application的生命周期);

  3. 混合开发,网页端跳转到Android 和IOS统一跳转参数;

    注意:实践心得体会,统一参数必须用String类型

    举个栗子:

    用Html 传递json格式的5.0参数{“key”:5.0}, JavaScript语法会自动把5.0变成5也就是, double 变成 int,如果Android 和IOS有做类型区分就会传参失败,Android经典场景如下:

    intent.getIntExtra();
    intent.getDoubleExtra()

    然后本来是逻辑走double的没有走。

    第二栗子:
    json数据中false 和true 专递也最好用”1”和”0”代替
    跟IOS同事讨论了很久,IOS Oject-C 处理true和false的机制和Android 不同
    故为了防止不同编程语言中处理方式不同尽量使用字符会少很多麻烦

  4. 统一各模块依赖版本库

  5. 混淆文件管理

第3节 ARouter使用

3.1 初始化配置

第一步:build.gradle(project) 设置 使用gradle插件自动化注册

 classpath "com.alibaba:arouter-register:$arouter_register_version" //1.0.2

gradle.properties

arouter_register_version=1.0.2

第二步:新建lib_base 模块存放各子模块公用类,如路由表

libs_base build.gradle 配置

apply plugin: 'com.alibaba.arouter'

android {
   defaultConfig {
          javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
            }
    }

    compileOptions {
        sourceCompatibility = '1.8'
        targetCompatibility = '1.8'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //可以定义一个公共资源模块,用于各模块资源共享
    implementation project(':lib_resource')
    api ('com.alibaba:arouter-api:1.4.1')
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'

    //json解析框架
    api 'com.google.code.gson:gson:2.8.2'
}

第三步: 子模块build.gradle 配置

apply plugin: 'com.alibaba.arouter' //ARouter 路径注册优化
android {
   defaultConfig {
          javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
            }
    }

    compileOptions {
        sourceCompatibility = '1.8'
        targetCompatibility = '1.8'
    }
}
dependencies{
  //必须使用注解,自动生成路由
  annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
  //引用父模块,用于共享类存放
  implementation project(':lib_base')
}

第三步:让组件自动运行

Build.gradle

if (!isNeedMeModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

gradle.properties

isNeedMeModule=true

第四步:壳工程配置

if (!isNeedMeModule.toBoolean()) {
  compile project(":module_home")
}

第五步: 初始化

 if (BuildConfig.DEBUG) {
      ARouter.openDebug();
      ARouter.openLog();
 }
  ARouter.init(this);

子模块引用gradle.properties

isNeedMeModule=true

3.2 高阶使用

1. 模块间调用

第一步: lib_base 模块定义接口

public interface IHelloModuleService extends IProvider {
    String getUserName(String usrID);
}

第二步:子模块实现接口

@Route(path = Router.SERVICE_HELLO,name = "测试服务")
public class HelloServiceImpl implements IHelloModuleService {
    @Override
    public String getUserName(String usrID) {
        return "Test provider";
    }

    @Override
    public void init(Context context) {

    }
}

第三步:调用其他模块提供的接口

  public static String getUserName(String userId) {
        IHelloModuleService chatModuleService = ARouterHelper.getInstance().navigation(IHelloModuleService.class);
        if (chatModuleService != null) {
            return chatModuleService.getUserName(userId);
        }
        return "";
    }

2. 全局分组拦截

@Interceptor(priority = 1,name = "全局分组拦截")
public class GlobalDefaultInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
//        获取当前组
//        postcard.getGroup()
//        直接执行
//        callback.onContinue(postcard);
//        onLost
//        callback.onInterrupt(null);
        callback.onContinue(postcard);
    }

    @Override
    public void init(Context context) {

    }
}

3. 降级处理

注意: path 必须有值否则,安装Arouter的架构无法实例化GlobalDegradeServiceImpl

@Route(path = "/root/degrade_service",name = "全局降级策略")
public class GlobalDegradeServiceImpl implements DegradeService {
    private static final String TAG = "GlobalDegradeServiceImp";
    @Override
    public void onLost(Context context, Postcard postcard) {
        Log.d(TAG, String.format("Degrade path %s", postcard.getPath()));
    }

    @Override
    public void init(Context context) {

    }
}

4. 路径替换

@Route(path = "/root/path_replace") // 必须标明注解
public class PathReplaceServiceImpl implements PathReplaceService {

    /**
     * 重写路径
     * @param path raw path
     * @return new path
     */
    @Override
    public String forString(String path) {
        return path;// 按照一定的规则处理之后返回处理后的结果
    }

    @Override
    public Uri forUri(Uri uri) {
        return uri;
    }

    @Override
    public void init(Context context) {

    }
}

5. 默认JavaBean序列化控制类

@Route(path = "/root/serialization_service",name = "默认JavaBean序列化控制类")
public class JsonServiceImpl implements SerializationService {
    private final Gson gson = new Gson();

    @Override
    public <T> T json2Object(String input, Class<T> clazz) {
        return gson.fromJson(input,clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return gson.toJson(instance);
    }

    @Override
    public <T> T parseObject(String input, Type clazz) {
        return gson.fromJson(input,clazz);
    }

    @Override
    public void init(Context context) {

    }
}

第4节 Arouter采坑记录

前言

开源库遇到问题到issue里找

开源库遇到问题到issue里找

开源库遇到问题到issue里找

4.1 AutoWrite

错误写法

@Autowired
private int type ;

:question:原因: 自动注入是不能用private关键字的

4.2 navigation

navigation() vs navigation(Context)

使用过ARouter的同学,有知道区别的吗?当然我也是被坑才知道,之前也扫了一遍源码,还是没有get关键点

4.3 finish 时机不对产生白屏

第5节 实战总结 ARouter封装类

Postcard 的包装类

因为Postcard 是区分类型的在改造的时候用

  • Java的多态的言语特性能简化很多工作量
  • 可以在后期全局修改添加属性

PostcardWrapper.java

public class PostcardWrapper {

    private final Postcard mPostcard;

    public PostcardWrapper(Uri uri) {
        mPostcard = ARouter.getInstance().build(uri);
    }

    public Postcard getPostcard() {
        return mPostcard;
    }

    public Bundle getOptionsBundle() {
        return mPostcard.getOptionsBundle();
    }

    public int getEnterAnim() {
        return mPostcard.getEnterAnim();
    }

    public int getExitAnim() {
        return mPostcard.getExitAnim();
    }

    public IProvider getProvider() {
        return mPostcard.getProvider();
    }

    public PostcardWrapper setProvider(IProvider provider) {
        mPostcard.setProvider(provider);
        return this;
    }

    public PostcardWrapper(String path){
        this.mPostcard = ARouter.getInstance().build(path);
    }

    public PostcardWrapper(String path, String group) {
        mPostcard = new Postcard(path, group);
    }

    public PostcardWrapper(String path, String group, Uri uri, Bundle bundle){
        mPostcard = new Postcard(path, group, uri, bundle);
    }

    public boolean isGreenChannel(){
        return mPostcard.isGreenChannel();
    }

    public Object getTag(){
        return mPostcard.getTag();
    }

    public PostcardWrapper setTag(Object tag){
        mPostcard.setTag(tag);
        return this;
    }

    public int getTimeout(){
        return mPostcard.getTimeout();
    }

    public PostcardWrapper setTimeout(int timeout){
        mPostcard.setTimeout(timeout);
        return this;
    }

    public Uri getUri(){
        return mPostcard.getUri();
    }

    public PostcardWrapper setUri(Uri uri){
        mPostcard.setUri(uri);
        return this;
    }

    public PostcardWrapper greenChannel() {
        mPostcard.greenChannel();
        return this;
    }

    public Object navigation(){
        return navigation(null);
    }


    public Object navigation(Context context){
        return navigation(context,null);
    }

    public Object navigation(Context context, NavigationCallback callback) {
        return ARouter.getInstance().navigation(context, mPostcard, -1, callback);
    }


    public void navigation(Activity mContext, int requestCode) {
        navigation(mContext, requestCode,null);
    }

    public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
        ARouter.getInstance().navigation(mContext, mPostcard, requestCode, callback);
    }

    public PostcardWrapper with(Bundle bundle) {
        mPostcard.with(bundle);
        return this;
    }

    public PostcardWrapper withFlags(int flag) {
        mPostcard.withFlags(flag);
        return this;
    }

    public PostcardWrapper addFlags(int flags) {
        this.mPostcard.addFlags(flags);
        return this;
    }

    public int getFlags() {
        return mPostcard.getFlags();
    }


    public PostcardWrapper with(@Nullable String key, @Nullable Object value){
        mPostcard.withObject(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable String value){
        mPostcard.withString(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, boolean value){
        mPostcard.withBoolean(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, short value){
        mPostcard.withShort(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, int value){
        mPostcard.withInt(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, long value){
        mPostcard.withLong(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, double value){
        mPostcard.withDouble(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, char value){
        mPostcard.withChar(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, float value){
        mPostcard.withFloat(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable CharSequence value){
        mPostcard.withCharSequence(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable Parcelable value){
        mPostcard.withParcelable(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable Parcelable[] value){
        mPostcard.withParcelableArray(key, value);
        return this;
    }


    public PostcardWrapper with(@Nullable String key, @Nullable SparseArray<? extends Parcelable> value) {
        mPostcard.withSparseParcelableArray(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable Serializable value) {
        mPostcard.withSerializable(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable byte[] value) {
        mPostcard.withByteArray(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable short[] value) {
        mPostcard.withShortArray(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable char[] value) {
        mPostcard.withCharArray(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable float[] value) {
        mPostcard.withFloatArray(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable CharSequence[] value) {
        mPostcard.withCharSequenceArray(key, value);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable Bundle value) {
        mPostcard.withBundle(key, value);
        return this;
    }

    /**
     * 设置动画效果
     * @param enterAnim 进入动画
     * @param exitAnim 退出动画
     * @return
     */
    public PostcardWrapper with(int enterAnim, int exitAnim) {
        mPostcard.withTransition(enterAnim, exitAnim);
        return this;
    }

    public PostcardWrapper withTransition(int enterAnim, int exitAnim) {
        mPostcard.withTransition(enterAnim, exitAnim);
        return this;
    }

    /**
     * 默认转换动画效果
     * @return
     */
    public PostcardWrapper withDefaultTrans(){
        mPostcard.withTransition(R.anim.anim_activity_slide_in_right, R.anim.anim_activity_slide_out_left);
        return this;
    }

    @RequiresApi(16)
    public PostcardWrapper withOptionsCompat(ActivityOptionsCompat compat) {
        mPostcard.withOptionsCompat(compat);
        return this;
    }

    /**
     * Set options compat
     *
     * @param compat compat
     * @return this
     */
    @RequiresApi(16)
    public PostcardWrapper with(ActivityOptionsCompat compat) {
        mPostcard.withOptionsCompat(compat);
        return this;
    }

    @Override
    public String toString() {
        return "PostcardWrapper ==> s\ns" + mPostcard.toString();
    }

    public String getAction(){
        return mPostcard.getAction();
    }

    public PostcardWrapper withAction(String action) {
        mPostcard.withAction(action);
        return this;
    }

    public PostcardWrapper with(String action) {
        mPostcard.withAction(action);
        return this;
    }

    public PostcardWrapper with(@Nullable String key, @Nullable ArrayList value) {
        if (value != null && value.size() > 0) {
            Object o = value.get(0);
            if (o instanceof String){
                mPostcard.withStringArrayList(key, value);
            }else if (o instanceof CharSequence) {
                mPostcard.withCharSequenceArrayList(key, value);
            }else if (o instanceof Integer){
                mPostcard.withIntegerArrayList(key, value);
            } else if (o instanceof Parcelable) {
                mPostcard.withParcelableArrayList(key, value);
            }
        }
        return this;
    }

}

ArouterHelper 帮助类

ARouterHelper.java

public class ARouterHelper {

    private ARouterHelper() {
    }

    private static class InstanceHolder {
        // static 只执行一次,并严格保证按照顺序执行
        private final static ARouterHelper instance = new ARouterHelper();
    }

    public static ARouterHelper getInstance() {
        return InstanceHolder.instance;
    }

    public static void init(Application application) {
        if (BuildConfig.DEBUG) {
            ARouter.openDebug();
            ARouter.openLog();
            ARouter.printStackTrace();
        }
        ARouter.init(application);
    }

    public void inject(Object thiz) {
        ARouter.getInstance().inject(thiz);
    }

    /**
     * CMUBaseActivity CMUBaseFragment 两个base类重写 startActivity 默认跳转动画
     * @param path
     * @return
     */
    public PostcardWrapper build(String path) {
        return build(path,true);
    }

    /**
     * @param path
     * @param useDefaultTransition 使用 CMUBaseActivity CMUBaseFragment 配置的跳转方式
     * @return
     */
    public PostcardWrapper build(String path, boolean useDefaultTransition) {
        PostcardWrapper postcardWrapper = new PostcardWrapper(path);
        if (useDefaultTransition){
            postcardWrapper.withDefaultTrans();
        }
        return postcardWrapper;
    }

    /**
     * 使用主题默认跳转动画
     * @param path
     * @return
     */
    public PostcardWrapper buildWithThemeAnim(String path) {
        return new PostcardWrapper(path);
    }


    /**
     * 无动画转换
     * @param path
     * @return
     */
    public PostcardWrapper buildWithoutAnim(String path){
        return new PostcardWrapper(path).withTransition(0, 0);
    }

    public PostcardWrapper build(Uri uri) {
        return new PostcardWrapper(uri);
    }

    public <T> T navigation(Class<? extends T> service) {
        return ARouter.getInstance().navigation(service);
    }

    public Object navigation(Context mContext, PostcardWrapper postcard, int requestCode, NavigationCallback callback) {
        return ARouter.getInstance().navigation(mContext, postcard.getPostcard(), requestCode, callback);
    }

}

附录

参考

学到知识点

api与implementation的区别

implementation 不分享自己的依赖,只有在runtime是其他模块的依赖才可见
api 分享自己的依赖,但是在其变化时,其他依赖需重新编译

Android Studio添加文件注释头模板?

/**
 *    @Author  linz
 *    @Date    $date$ $time$
 *    @Description   
 *    @Version: 1.0
 */

Arouter 源码

/**
 * Provide degrade service for router, you can do something when route has lost.
 *
 * @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
 * @version 1.0
 * @since 2016/9/22 14:51
 */