Android組件化搭建分享

1.組件化開發

組件化開發這個名詞並不陌生,但真正什麼纔是組件化開發,你們在網上搜能夠查看不少相應的文章,我概念中,模塊化的開發,就是把不少模塊獨立出來,基礎模塊,業務模塊等。什麼是基礎模塊,基礎模塊就是公司經常使用的一些sdk,一些封裝好的基類,業務模塊就是基於基礎模塊進行開發的。在以往的開發中,我並未真正的去使用組件化開發,直到加入新的團隊能夠說是開啓新世界的大門,給個人感受,組件化開發,賊爽,爲何爽?java

我總結了好幾點:android

1.各自負責業務模塊獨立開發,以application進行開發,後期再以library引入項目 2.由於每一個模塊獨立出來,以最小模塊的進行開發,編譯速度快 3.有利於單元測試,對業務模塊進行單元測試 4.下降耦合,提升模塊的複用git

如下爲基礎模塊包:github

package.png

整個項目結構:api

Android框架模塊分佈圖.png

Android studio:bash

圖片2.png

在gradle.properties中,咱們能夠設置一個變量,控制是否使用模塊化來開發網絡

#是否使用模塊化開發
isModule=false
複製代碼

而後在settings.gradle中設置項目引入包mvc

//默認都打開基礎模塊
include ':sdk', ':model', ':widget', ':module-basic'
//根據本身負責的模塊分別進行相應的引入
include ':module-user'
include ':module-business'
//根據是否模塊開發,是否引入app 模塊
if (!isModule.toBoolean()) {
    include ':app'
}
複製代碼

業務模塊gradle進行模塊判斷app

圖片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) {
                //登陸成功等操做
            }
        });
    }

    
}
複製代碼
相關文章
相關標籤/搜索