Android组件化搭建分享

2,334 阅读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具体的使用方法: github.com/alibaba/ARo…

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) {
                //登录成功等操作
            }
        });
    }

    
}