阿里巴巴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. 在原生页面崩溃异常时,可以通过动态路由表,<font color=red>拦截原生路由跳转到H5</font>;

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

  1. 组件化之间的通信;
  2. 组件生命周期管理(模块能处理Application的生命周期);
  3. 混合开发,网页端跳转到Android 和IOS统一跳转参数;

<font color="red"> 注意:实践心得体会,统一参数必须用String类型</font>

举个栗子:

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

intent.getIntExtra();
intent.getDoubleExtra();

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

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

  1. 统一各模块依赖版本库
  2. 混淆文件管理

第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采坑记录

前言

<font color=red size= 22>开源库遇到问题到issue里找</font>

<font color=green size= 22>开源库遇到问题到issue里找</font>

<font size=22 color=grey>开源库遇到问题到issue里找</font>

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
 */
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345