Android组件化搭建分享

作者: 沈敏杰 | 来源:发表于2017-09-07 17:52 被阅读600次

    前言:
    许久没写过文章,距离上一次写文章都快有8个月了,深深的感受到自己到底是有多懒,自8月初从深圳回广州工作,加入新的团队,新的学习方式。在此记录、总结、分享。

    1.组件化开发

    组件化开发这个名词并不陌生,但真正什么才是组件化开发,大家在网上搜可以查看很多相应的文章,我概念中,模块化的开发,就是把很多模块独立出来,基础模块,业务模块等。什么是基础模块,基础模块就是公司常用的一些sdk,一些封装好的基类,业务模块就是基于基础模块进行开发的。在以往的开发中,我并未真正的去使用组件化开发,直到加入新的团队可以说是开启新世界的大门,给我的感觉,组件化开发,贼爽,为什么爽?

    我总结了好几点:

    1.各自负责业务模块独立开发,以application进行开发,后期再以library引入项目
    2.因为每个模块独立出来,以最小模块的进行开发,编译速度快
    3.有利于单元测试,对业务模块进行单元测试
    4.降低耦合,提高模块的复用

    以下为基础模块包:

    package.png

    整个项目结构:

    Android框架模块分布图.png

    Android studio:

    图片2.png

    在gradle.properties中,我们可以设置一个变量,控制是否使用模块化来开发

    #是否使用模块化开发
    isModule=false
    

    然后在settings.gradle中设置项目引入包

    //默认都打开基础模块
    include ':sdk', ':model', ':widget', ':module-basic'
    //根据自己负责的模块分别进行相应的引入
    include ':module-user'
    include ':module-business'
    //根据是否模块开发,是否引入app 模块
    if (!isModule.toBoolean()) {
        include ':app'
    }
    

    业务模块gradle进行模块判断

    图片3.png
    //通过之前设定的变量进行设置是application还是library
    if (isModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    

    根据结构图,我们基础模块的依赖,默认引入sdk、model、widget、module-baisc
    然后根据自己负责的业务模块,分别引入不同的业务,如果我是负责用户模块,我在开发就只需要引入用户模块即可,这样开发每个模块的时候可以提高每个模块的编译效率。

    最后模块合并的时候,在gradle.properties中关闭模块开发,在settings.gradle引入项目相应的模块包,并设置app的build-gradle:


    图片4.png

    build-gradle:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:26.+'
        compile 'com.android.support.constraint:constraint-layout:1.0.2'
        compile 'com.android.support:design:26.+'
        testCompile 'junit:junit:4.12'
    
        //如果不使用模块化开发,就引入所有的业务模块
        if (!isModule.toBoolean()) {
            compile project(':module-business')
            compile project(':module-user')
        }
    }
    

    现在的问题,不同模块的activity怎么跳转,以前我的做法都会在每个activity中写一个静态方法,把入参设定好.

    /**
     * 跳转
     *
     * @param context 上下文
     * @param param   参数
     */
    public static void toAcitivty(Context context, String param) {
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra("param", param);
        context.startActivity(intent);
    }
    

    因为使用模块化开发的话,不同业务模块是不能调用其activity,因此我们使用阿里的Arouter,
    在每个activity头部使用注解进行跳转,就像Spring mvc 的controller一样,使用路由进行设置跳转,在模块化的开发中,这个很关键,一方面使用arouter可以降低activity之间的耦合,另一方面可以对模块进行单元测试。

    Arouter具体的使用方法:
    https://github.com/alibaba/ARouter

    2.Retrofit+RxJava+MVP模式

    关于Retrofit跟RxJava,具体详细的用法就不在这里介绍,网上有很多现有的文章,为什么使用Retrofit跟RxJava,Retrofit是基于Okhttp封装一层的客户端,配合RxJava线程调度,很好的控制网络请求,使用RxJava可以提高代码的可读性,这里我分享一下retrofit+Rxjava封装。

    1.基于Retrofit的Api工厂

    ApiFactory如下图:

    api工厂.png

    图中的ApiFactory的职责是提供所有业务Api接口,具体提供的Api是通过接口ApiProvider提供每个业务接口,如果用户接口,交易接口,令牌接口等,ApiFactory通过单例,获取api提供者,ApiProvider具体的实现类ApiProvideImpl继承于网络引擎RetrofitApi,RetrofitApi用于初始化一些网络引擎。ApiProvideImpl中使用retrofit来初始化各种Api接口。

    ApiProviderImpl.java:

    class ApiProviderImpl extends RetrofitApi implements ApiProvider {
    
        private OkHttpClient httpClient;
    
        ApiProviderImpl(Context applicationContext) {
            super(applicationContext);
        }
    
        private <T> T create(Class<T> cls) {
            return mRetrofit.create(cls);
        }
    
    
        @Override
        public ITokenApi getTokenApi() {
            return create(ITokenApi.class);
        }
    
        @Override
        public IUserApi getUserApi() {
            return create(IUserApi.class);
        }
    
        @Override
        public IProductApi getProductApi() {
            return create(IProductApi.class);
        }
    
        @Override
        public IPushApi getPushApi() {
            return create(IPushApi.class);
        }
    
        @Override
        public IQuotationApi getQuotationApi() {
            return create(IQuotationApi.class);
        }
    
        @Override
        public ITradeApi getTradeApi() {
            return create(ITradeApi.class);
        }
    
        .....
    }
    

    2.MVP

    使用mvp可以解耦,结构清晰,对于业务复杂的场景来说,可以提高代码可读性,结构清晰,降低后期维护成本。如下图登录模块所示:

    mvp.png

    View跟presenter都抽象成接口,这样相互不依赖于细节,有易于做单元测试,降低耦合。这里有两个基础接口,LoginView跟LoginPresenter分别继承于IView跟IPresenter,LoginViewImpl以及LoginPresenterImpl分别实现LoginView跟LoginPresenter,其依赖于抽象不依赖于实现的细节。

    /**
     * 登录契约类
     */
    public interface LoginContract {
    
        /**
         * 表现层接口
         */
        interface Presenter extends IPresenter {
    
            /**
             * 登录操作
             */
            void login();
        }
    
        /**
         * 视图层接口
         */
        interface View extends IPresenterView {
    
            /**
             * 获取密码
             *
             * @return return
             */
            String getPassword();
    
            /**
             * 获取用户信息
             *
             * @return return
             */
            String getUsername();
    
            /**
             * 登录成功
             */
            void loginSuccess();
    
            /**
             * 登录失败
             *
             * @param msg msg
             */
            void loginFailed(String msg);
        }
    }
    
    

    我们通过定义一个Contract契约类,来制定接口,在定Presenter跟view接口的同时,我们可以很清晰的知道,表现层需要什么东西,view层需要提供什么东西,包括网络请求后相应的响应,这样在我们做一个业务逻辑的时候思路可以更清晰,同事在进行presenter复用以及单元测试会更方便。

    3.结合Retrofit+RxJava+Mvp

    结合之前谈到的Api跟mvp,在这个基础上进行封装Presenter的实现基础类。

    /**
     * presenter基础实现类的封装
     * 1.跟视图view进行绑定与解绑
     * 2.对rx事件进行加工处理与释放资源
     */
    public class BasicPresenterImpl<T extends IPresenterView> implements IPresenter {
    
        /**
         * 视图
         */
        protected T mView;
    
        /**
         * 上下文
         */
        protected Context mContext;
    
        /**
         * 记录标识,用于此presenter所有的任务进行标识
         */
        private String mTag = this.getClass().getName();
    
        public BasicPresenterImpl(Context context, T view) {
            this.mView = view;
            this.mContext = context;
        }
    
        public void start() {
        }
    
        /**
         * 销毁资源,一般用于与view解绑操作
         * 如activity作为view中,activity 销毁的时候调用
         * 避免错误引用,避免内存泄露
         */
        public void destroy() {
            this.mView = null;
            this.mContext = null;
            this.cancelRequest();
        }
    
        /**
         * 根据tag清掉任务,如清掉未完成的网路请求
         */
        protected void cancelRequest() {
            RxObservable.dispose(this.mTag);
            RxObservable.dispose("PageDataObservable");
        }
    
    
        /**
         * rxJava  多数用于创建网络请求
         * 如createObservable(mUser.login())
         * retorfit结合rxJava
         *
         * @param observable observable
         * @param <T>        t
         * @return return
         */
        protected <T> Observable<T> createObservable(Observable<T> observable) {
            //创建任务
            return RxObservable.create(observable, this.mTag);
        }
    }
    

    基础Presenter封装了绑定与解绑的操作,presenter跟view解绑时调用destory释放资源,并把此presenter中使用rxJava处理得事件全部清掉,释放资源,例如一些网络请求,当view跟presenter解绑后网络请求未来得及返回处理,容易出现view空指针的操作。

    接着介绍一下RxObservable的封装:

    /**
     * 用于封装rx事件,通过键值对的方式保存任务
     * 对任务进行初始化,释放等操作所
     */
    public final class RxObservable {
    
        /**
         * 全局变量,使用tag标识保存Disposable集合
         * Disposable?Observer(观察者)与Observable(被观察者)切断链接
         */
        private static final Map<String, List<Disposable>> sObservableDisposableList = new WeakHashMap();
    
        public RxObservable() {
        }
    
        /**
         * 创建被观察者,如retrofit集合rxJava返回的网络请求,
         * 此方法用于事件在初始化时进行处理,把此事件保存到sObservableDisposableList集合中,
         * 以tag为key,以为List<Disposable>为值,订阅被观察者时可以获其Disposable
         */
        public static <T> Observable<T> create(Observable<T> observable, final String tag) {
            return observable.doOnSubscribe(new Consumer() {
                public void accept(@NonNull Disposable disposable) throws Exception {
                    //在集合中判断是否存在集合
                    //没有则创建,并以key-tag保存到sObservableDisposableList中
                    List disposables = (List) RxObservable.sObservableDisposableList.get(tag);
                    if (disposables == null) {
                        ArrayList disposables1 = new ArrayList();
                        RxObservable.sObservableDisposableList.put(tag, disposables1);
                    }
                    //把此事件的Disposable添加到对应的tag的集合中
                    ((List) RxObservable.sObservableDisposableList.get(tag)).add(disposable);
                }
                //订阅过程在Io线程处理,发送在主线程处理
            }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
        }
    
        /**
         * 释放所有资源
         */
        public static void dispose() {
            try {
                Iterator e = sObservableDisposableList.values().iterator();
                while (e.hasNext()) {
                    List disposables = (List) e.next();
                    Iterator var2 = disposables.iterator();
    
                    while (var2.hasNext()) {
                        Disposable disposable = (Disposable) var2.next();
                        if (disposable != null && !disposable.isDisposed()) {
                            disposable.dispose();
                        }
                    }
    
                    disposables.clear();
                }
            } catch (Exception var7) {
                Log.e("rae", "释放HTTP请求失败!", var7);
            } finally {
                sObservableDisposableList.clear();
            }
    
        }
    
        /**
         * 根据tag标识进行释放资源
         *
         * @param tag tag
         */
        public static void dispose(String tag) {
            try {
                if (!TextUtils.isEmpty(tag) && sObservableDisposableList.containsKey(tag)) {
                    List e = (List) sObservableDisposableList.get(tag);
                    Iterator var2 = e.iterator();
    
                    while (var2.hasNext()) {
                        Disposable disposable = (Disposable) var2.next();
                        if (disposable != null && !disposable.isDisposed()) {
                            disposable.dispose();
                        }
                    }
    
                    e.clear();
                    sObservableDisposableList.remove(tag);
                    return;
                }
            } catch (Exception var7) {
                Log.e("rae", "释放HTTP请求失败!", var7);
                return;
            } finally {
                sObservableDisposableList.remove(tag);
            }
    
        }
    }
    

    在RxObservable中,创建一个sObservableDisposableList用于保存每个presenter中处理的事件,通过tag作为标识创建,每个presenter中会通过tag找到对应的Disposable集合,Disposable集合中保存了此presenter中的所有任务,如网络请求、io操作等,通过此方法可以统一管理tag的任务,在presenter解绑的时候可以及时的销毁资源,避免内存泄露。

    登录的一个小例子:

    public class LoginPresenterImpl extends BasicPresenterImpl<LoginContract.View> implements LoginContract.Presenter {
    
        IUserApi mUserApi;
    
        public LoginPresenterImpl(Context context, LoginContract.View view) {
            super(context, view);
            //初始化变量....
        }
    
        @Override
        public void login() {
            //在view层获取手机号跟密码
            final String mobile = mView.getMobile();
            final String password = mView.getPassword();
            if (TextUtils.isEmpty(mobile)) {
                mView.onLoginFailed("请输入手机号码");
                return;
            }
            if (TextUtils.isEmpty(password)) {
                mView.onLoginFailed("请输入密码");
                return;
            }
            if (!mPhoneValidator.isMobile(mobile)) {
                mView.onLoginFailed("请输入正确的手机号码");
                return;
            }
            createObservable(mUserApi.login(mobile, password)).subscribe(new ApiDefaultObserver<UserInfo>() {
                @Override
                protected void onError(String msg) {
                    //登录失败
                    mView.onLoginFailed(msg);
                }
    
                @Override
                protected void accept(UserInfo userInfo) {
                    //登录成功等操作
                }
            });
        }
    
        
    }
    

    Anyway:

    此文章用于记录学习的一个心得和一些收获,在此感谢队友Keegan小钢跟Rae提供的帮助。感兴趣的朋友可以关注他们博客,能学到很多全栈的知识。

    对于我来说,写博客就好像写日记一般,回来了广州后发现了感觉很充实,虽然团队没有像以往在深圳的996般,但整个团队的研发效率很高,很有活力的一个团队,还是觉得工作嘛,高效沟通、高效工作、开心工作、开心生活。

    Keegan小钢

    http://keeganlee.me/
    微信订阅号:keeganlee_me

    Rae

    http://www.raeblog.com

    相关文章

      网友评论

        本文标题:Android组件化搭建分享

        本文链接:https://www.haomeiwen.com/subject/yjoijxtx.html