Android架構系列-基於MVP建立適合自己的架構

Tsy遠發表於2019-01-30

本文基於MVP整理出了一套適合自己的架構

0 Android架構系列文章

該系列文章會不斷更新Android專案開發中一些好的架構和小技巧

系列一 Android架構系列-基於MVP建立適合自己的架構
系列二 Android架構系列-如何優美的寫Intent
系列三 Android架構系列-開發規範
系列四 Android架構系列-封裝自己的okhttp
系列五 Android架構系列-MVP架構的實際應用

1 為什麼選擇MVP

MVP架構是當前比較成熟的Android架構,還有其他架構比如最初始的MVC和MVVM。MVC相對於較為落後,MVVM使用DataBind,普及性不如MVP。所以最終決定自己設計的框架是基於MVP思想進行總結的框架。

選擇MVP框架的原因之一也是google官方的示例中MVP sample已經是完成,證明google官方對於MVP的承認度。

官方專案地址:
github.com/googlesampl…

一個較為詳細的官方專案原始碼解析的文章:
www.infoq.com/cn/articles…

2 MVP簡介

具體的MVP架構相關文章網上已經非常多了,具體的可以自行查詢。MVP的存在主要是由於普通MVC架構會導致專案中activity過於臃腫,當專案越來越大後,程式碼可讀性大大降低。

MVP的思想是將activity作為view層,只負責與xml的渲染和監聽事件,具體處理資料邏輯放到一個新定義的Present層。減少了activity負責的事情。並且可以強迫開發者養成分模組功能開發的思想。開發前設計好功能模組,而不是像以前一樣寫流水賬一樣寫程式碼。從頭寫到尾。

MVP
MVP

3 我的總結

自己根據MVP的思想和一些好的原始碼總結了一套適合字的框架。真正的架構是依賴義務存在的,所以建議大家能總結出適合自己專案的程式碼。

3.1 目錄分配

在目錄分配上決定採用根據功能模組進行劃分,而不是所有activty在一個目錄的方法。類似google的例子:

目錄
目錄

原因幾點:

  1. 功能模組劃分更為清晰,對於以後程式碼閱讀和新人接手更好
  2. 適用於模組化開發,比如以後又是一個新專案,老專案的登入模組、使用者模組、論壇模組等待可以整個複製出來重用
  3. 雖然網上提過按照多個模組劃分可能會有公用的頁面。我認為複製一份也沒什麼,不會造成很大的冗餘程式碼,並且對於頁面來說萬一以後某個模組頁面有自定義修改不會對其他影響。(畢竟頁面的靈活性要求很高,不適合架構抽出來通用的)
  4. 需要抽出來的獨立於功能模組的應該是common_util 和 common_widget,分別是通用工具層和通用自定義控制元件層

具體例子分配如下:

Sample目錄
Sample目錄

  • GloabApp 全域性Application
  • RootAct 啟動頁面
  • Base目錄 基礎activity fragment存放
  • util目錄 通用工具
  • mywidget 通用自定義控制元件
  • SampleModule Sample功能模組。裡面包含獨立的MVP的介面

3.2 Model層

Model層中又可以分為Api層和Cache層。

3.2.1 Api層

主要是網路獲取資料資訊等介面。

使用了自己二次封裝過的Retrofit+Okhttp+Gson組合。詳細可以參見文章:www.jianshu.com/p/283d1a7a0…

示例SampleApi:

public class SampleApi extends BaseApi {

    private static final String mBaseUrl = "http://192.168.3.1/";

    private ApiStore mApiStore;

    public SampleApi() {
        super(mBaseUrl);
        mApiStore = mRetrofit.create(ApiStore.class);
    }

    /**
     * 獲取xxx資料
     * @param uid
     * @param callback
     */
    public void getSampleInfo(String uid, ApiCallback<GetSampleInfoRet> callback) {
        Call<GetSampleInfoRet> call = ((ApiStore)mApiStore).getSampleInfo(uid);
        call.enqueue(new RetrofitCallback<GetSampleInfoRet>(callback));
    }

    public interface ApiStore {
        @FormUrlEncoded
        @POST("test_retrofit.php")
        Call<GetSampleInfoRet> getSampleInfo(@Field("uid") String uid);
    }
}複製程式碼

3.2.2 Cache層

本地快取部分資料。

使用了ASimpleCache快取開原始碼。詳細可以參見文章:www.jianshu.com/p/25c107ed7…

示例SampleCache:

public class SampleCache  extends BaseCache {

    private final String KEY_NEWEST_SAMPLE_INFO = "sample_newest_info";

    public SampleCache(Context context) {
        super(context);
    }

    /**
     * 儲存sample資訊
     * @param serializable
     */
    public void saveNewestSample(Serializable serializable) {
        mCache.put(KEY_NEWEST_SAMPLE_INFO, serializable);
    }

    /**
     * 獲取sample資訊
     * @return
     */
    public SampleInfo getNewestSampleInfo() {
        return (SampleInfo) mCache.getAsObject(KEY_NEWEST_SAMPLE_INFO);
    }

    /**
     * 移除快取
     */
    public void removeNewestSampleInfo() {
        mCache.remove(KEY_NEWEST_SAMPLE_INFO);
    }
}複製程式碼

3.3 Data層

實體化資料類。

3.4 Presenter層

Presenter層又可以分為Contract協議介面,和具體的Presenter處理

3.4.1 Contract層

負責約定view層和presenter層的介面,view和presenter實現相應介面,最終達到解耦的目的。

SampleContract示例:

public interface SampleContract {

    interface View {
        void showSample(SampleInfo sampleInfo);     //顯示sample

        void errorGetSample(String msg);    //顯示錯誤資訊
    }

    interface Presenter {
        void getNewestSample(); //獲取當前最新的xxx
    }
}複製程式碼

3.4.2 Presenter層

負責從model層獲取資料、整理資料、行為處理等。處理後呼叫view顯示資料。

SamplePresenter示例:

public class SamplePresenter extends BasePresenter implements SampleContract.Presenter {

    private SampleContract.View mView;
    private SampleApi mApi;
    private SampleCache mCache;

    public SamplePresenter(SampleContract.View view) {
        mView = view;

        mApi = new SampleApi();
        mCache = new SampleCache(GlobalApp.getInstance().getContext());
    }

    @Override
    public void getNewestSample() {
        //先從快取獲取
        SampleInfo sampleInfo = mCache.getNewestSampleInfo();

        if(sampleInfo == null) {
            //從網路獲取
            mApi.getSampleInfo("uid", new BaseApi.ApiCallback<GetSampleInfoRet>() {
                @Override
                public void onSuccess(GetSampleInfoRet ret) {
                    //快取
                    mCache.saveNewestSample(ret.data);

                    //頁面顯示
                    mView.showSample(ret.data);
                }

                @Override
                public void onError(int err_code, String err_msg) {
                    //服務端返回錯誤碼
                    mView.errorGetSample(err_msg);
                }

                @Override
                public void onFailure() {
                    //網路請求或者解析錯誤
                    mView.errorGetSample("伺服器請求錯誤");
                }
            });
        } else {
            mView.showSample(sampleInfo);
        }
    }
}複製程式碼

3.5 view層

即平時所說的activity、fragment等。繼承自SampleContract的view介面,只負責UI相關顯示重新整理等。由於拉出了presenter層,view層的程式碼變得極為清晰

SampleActivity示例:

public class SampleActivity extends BaseActivity implements SampleContract.View {

    @BindView(R.id.txtName)
    TextView txtName;

    @BindView(R.id.imgAvatar)
    ImageView imgAvatar;

    private SampleContract.Presenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sample);
        ButterKnife.bind(this);

        mPresenter = new SamplePresenter(this);
    }

    @Override
    public void showSample(SampleInfo sampleInfo) {
        txtName.setText(sampleInfo.sample_name);
        Glide.with(this)
                .load(sampleInfo.avatar)
                .into(imgAvatar);
    }

    @Override
    public void errorGetSample(String msg) {
        //錯誤資訊
    }

}複製程式碼

4 總結

以上程式碼Github地址:
github.com/tsy12321/Ba…

注:該專案會做成一個基礎的專案框架,包含各種封裝好的工具,底層庫和MVP架構,還在不斷更新中,歡迎關注提Issue!

結尾

更多文章關注我的公眾號

我的公眾號
我的公眾號

相關文章