【Android】一鍵生成MVP程式碼-DevMvp快速開發框架

LuLiangDev發表於2018-04-04

序言

每個程式設計師看到一堆爛程式碼都有一顆重構的心。爛程式碼寫起來嘴上 笑嘻嘻,心裡mmp。特別是有程式碼潔癖的人。重構不易且行且珍惜,此框架將減少開發時間。如果你們的專案結構跟我的不一樣,這也不用擔心,你看了我這個,簡單修改一下模板,照樣能生成你想要的程式碼。

GitHub專案地址

MVP架構

相信大家對於MVP耳熟能詳了,理解的直接往下看,如果概念比較模糊,可以網上查一查理解理解,我這邊簡單的介紹一下,可以配合下圖來理解

  • View層: View層也是檢視層,只負責對資料的展示,提供友好的介面與使用者進行互動。開發中通常將Activity或者Fragment作為View層。
  • Model層: Model層也是資料層。它區別於MVC架構中的Model,在這裡Model它負責對資料的存取操作,例如對資料庫的讀寫,網路的資料的請求等。
  • Presenter層: 是連線View層與Model層的橋樑並對業務邏輯進行處理。在MVP架構中Model與View無法直接進行互動。所以在Presenter層它會從Model層獲得所需要的資料,進行一些適當的處理後交由View層進行顯示。這樣通過Presenter將View與Model進行隔離,使得View和Model之間不存在耦合,同時也將業務邏輯從View中抽離
    mvp.png

專案介紹

專案採用MVP架構,使用RxAndroid2+Retrofit開源框架封裝,結合Android Studio模板快速生成MVP基礎程式碼。新專案或者重構專案值得擁有。此開發框架是我2017年底重構專案開發的,使用了幾個月,專案重構完成了,完美使用,特別省事省時省心

專案結構

DevMvp
    ├─api//URL、介面管理、網路請求封裝類
    ├─mvp//專案主體
    │  ├─base//基礎類封裝
	   ├─bean//實體類
    │  ├─contract//契約類 用於統一管理view和presenter的介面
    │  ├─model//M層-資料處理
    │  ├─presenter//P層-邏輯業務處理
    │  └─view//V層-頁面渲染
    │      ├─activity
		   ├─adapter
    │      └─fragment
		    ...//類似Dialog、PopupWindow也可以放在view下
    └─utils//工具類
        └─rxhelper//Rx封裝工具
複製程式碼

程式碼詳解

1.api-網路請求

Retrofit網路請求封裝,專案裡面只是對Retrofit網路請求基本引數,需要header、cache等引數,可在網上查查資料,這類文章介紹很多。Retrofit 2.0使用文件

  • DevMvpApi -Retrofit初始化
    public static Retrofit createApi() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder().
                connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS);

        httpClientBuilder.addInterceptor(new HttpLoggingInterceptor()
                .setLevel(HttpLoggingInterceptor.Level.BODY));
        mRetrofit = new Retrofit.Builder()
                .client(httpClientBuilder.build())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(Url.BASE_URL)
                .build();
        return mRetrofit;
    }
複製程式碼
  • DevMvpService-介面管理 這邊直接使用BookBean物件接收返回值
@GET(Url.BOOK_CLASSIFY)
Observable<BookBean> bookClassify();
複製程式碼
2.contract-契約類,v層和p層介面管理
  • CBook-測試契約類 Contract用於存放mvp三層介面類 放在一起便於管理,不用生成太多的類(因為model沒有需要處理的資料,所以沒有使用介面,可自行生成實現)
interface IPBook extends IBasePresenter {
        void pBook();
    }
interface IVBook extends IBaseView {
        void vBookSuccess(BookBean bean);
        void vBookError(String reason);
    }
複製程式碼
3.model-M層
  • BaseModel-M層基類 網路請求初始化和網路請求取消處理
    public CompositeDisposable mDisposable = new CompositeDisposable();
    /**
     * 初始化呼叫網路請求
     * @return
     */
    public DevMvpService apiService() {
        return DevMvpApi.createApi().create(DevMvpService.class);
    }
    /**
     * 取消網路請求
     */
    public void onDestroy() {
        if (mDisposable != null) {
            mDisposable.isDisposed();
            mDisposable.clear();
        }
    }
複製程式碼
  • MBookImpl-M層資料處理 由於不需要對資料進行處理,我就直接返回給P層,如果資料可能會從資料庫、快取、網路獲取,需要在這裡進行處理。
public void mBook(RxObservable rxObservable) {
        apiService()
                .bookClassify()
                .compose(RxTransformer.switchSchedulers(this))
                .subscribe(rxObservable);
    }

複製程式碼
4.presenter-P層
  • BasePresenter-P層基類 連線V層和M層
public class BasePresenter<V extends IBaseView, M extends BaseModel> {
    protected V mView;
    protected M mModel;
    protected Context mContext;

    public BasePresenter(Context mContext, V mView, M mModel) {
        this.mView = mView;
        this.mModel = mModel;
        this.mContext = mContext;
    }
    
    public void onDestroy() {
        if (mModel!=null) {
            mModel.onDestroy();
        }
    }
}
複製程式碼
  • PBookImpl-P層業務邏輯處理
/**
 * Created by Liang_Lu on 2017/12/21.
 * P層 此類只用於處理業務邏輯 然後把最終的結果回撥給V層
 */

public class PBookImpl extends BasePresenter<CBook.IVBook, MBookImpl> implements CBook.IPBook {
    public PBookImpl(Context mContext, CBook.IVBook mView) {
        super(mContext, mView, new MBookImpl());
    }
    @Override
    public void pBook() {
        mView.showLoading();
        mModel.mBook(new RxObservable<BookBean>() {
            @Override
            public void onSuccess(BookBean bean) {
                mView.hideLoading();
                mView.vBookSuccess(bean);
            }
            @Override
            public void onFail(String reason) {
                mView.hideLoading();
                mView.vBookError(reason);
            }
        });
    }
}
複製程式碼
5.view-V層
  • BaseActivity-V層基類(BaseFragment類似) 在基類跟P層建立連線,這裡可以根據專案需求豐富BaseActivity。這裡只把新增基礎必備功能。
public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity {
    public Context mContext;
    public T mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        if (setContentViewId() != 0) {
            setContentView(setContentViewId());
        } else {
            throw new RuntimeException("layoutResID==-1 have u create your layout?");
        }
        createPresenter();
        ButterKnife.bind(this);
        initView();
    }
    /**
     * 初始化方法
     */
    protected void initView() {
    }
    /**
     * 獲取contentView 資源id
     */
    public abstract int setContentViewId();
    /**
     * 建立presenter例項
     */
    public abstract void createPresenter();
    /**
     * activity跳轉(無引數)
     *
     * @param className
     */
    public void startActivity(Class<?> className) {
        Intent intent = new Intent(mContext, className);
        startActivity(intent);
    }
    /**
     * activity跳轉(有引數)
     *
     * @param className
     */
    public void startActivity(Class<?> className, Bundle bundle) {
        Intent intent = new Intent(mContext, className);
        intent.putExtras(bundle);
        startActivity(intent);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.onDestroy();//頁面銷燬 網路請求同銷燬
        }
    }
}
複製程式碼
  • BookActivity-V層頁面渲染處理 V層只需要操作頁面控制元件,網路資料顯示到頁面上等等。
/**
 * Created by Liang_Lu on 2017/12/21.
 * V層 用於資料和頁面UI展示(Fragment Dialog 同理)
 */
public class BookActivity extends BaseActivity<PBookImpl> implements CBook.IVBook {
    private TextView mTv;
    private Button mBtn;
    @Override
    protected void initView() {
        super.initView();
        mBtn = findViewById(R.id.btn);
        mTv = findViewById(R.id.tv);
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.pBook();
            }
        });
    }
    @Override
    public int setContentViewId() {
        return R.layout.activity_book;
    }
    @Override
    public void createPresenter() {
        mPresenter = new PBookImpl(mContext, this);
    }
    @Override
    public void showLoading() {
    }
    @Override
    public void hideLoading() {
    }
    @Override
    public void vBookSuccess(BookBean bean) {
        mTv.setText("網路請求成功");
    }
    @Override
    public void vBookError(String reason) {
        mTv.setText(reason);
    }
}
複製程式碼

基礎設定模板詳解(Fragment類似)

globals.xml.ftl-宣告全域性變數
<globals>
	<#assign Collection=extractLetters(ActivityClass)>//從輸入的title中獲取輸入字元
    <#assign collection_name=Collection?lower_case>//獲取到的字元轉成小寫
	<!-- 這裡宣告全域性變數-->
	<global id="activity_layout" value="${Collection?lower_case}" />//作為activity的layout的命名
    <global id="ActivityName" value="${Collection}Activity" />//作為activity類名
    <global id="PresenterName" value="P${Collection}Impl" />//作為presenter類名
    <global id="ModelName" value="M${Collection}Impl" />//作為model類名
    <global id="ContractName" value="C${Collection}" />//契約類-contract類名
	<global id="IViewName" value="IV${Collection}" />//契約類-view層介面名
	<global id="IPresenterName" value="IP${Collection}" />//契約類-presenter層介面名
	<global id="packageName" value="com.luliang.devmvp" />//專案包名(此處填寫為自己的專案包名)
</globals>
複製程式碼
recipe.xml.ftl-檔案生成指定目錄

PS.這裡需要注意一下,這裡的根目錄(root)是包名的那個目錄,此專案例:com.luliang.devmvp

<recipe>
<!--merge 表示需要合併到指定檔案的內容 (表示AndroidManifest宣告新建的Activity) -->
   <merge from="root/AndroidManifest.xml.ftl"
             to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />

	<!-- instantiate 表示建立檔案到指定資料夾	(把需要建立檔案的模板放在根目錄資料夾下對應資料夾,針對自己的專案修改路徑)	  -->
    <instantiate from="root/res/layout/activity_main.xml.ftl" 
					to="${escapeXmlAttribute(resOut)}/layout/activity_${activity_layout}.xml" />
    <instantiate from="root/src/app_package/MvpActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/mvp/view/activity/${ActivityName}.java" />
    <instantiate from="root/src/app_package/MvpPresenter.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/mvp/presenter/${PresenterName}.java" />
    <instantiate from="root/src/app_package/MvpContract.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/mvp/contract/${ContractName}.java" />
	<instantiate from="root/src/app_package/MvpModel.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/mvp/model/${ModelName}.java" />

</recipe>
複製程式碼
template.xml-建立模板頁面設定

這裡主要設定模板名稱、分類、生成模板需要填寫的資訊等等

<template
    format="5"
    revision="5"
    name="Mvp Activity"
    minApi="9"
    minBuildApi="14"
    description="Creates a new mvp activity">
    
    <category value="Activity" />
    <formfactor value="Mobile" />
    
	<!--parameter 標籤表示建立時需要輸入的屬性 -->
	<parameter
        id="ActivityClass"
        name="Activity ClassName"
        type="string"
        constraints="nonempty"
        default="name" />
        
	<!--核取方塊屬性-->
	<parameter
        id="isTitleBar"
        name="是否需要titleBar"
        type="boolean"
        default="false"
        help="選中即新增預設TitleBar" />
    <thumbs>
        <!-- 模板預覽圖片 -->
        <thumb>template_blank_activity.png</thumb>
    </thumbs>
    <globals file="globals.xml.ftl" />
    <execute file="recipe.xml.ftl" />
</template>
複製程式碼

Mvp基礎程式碼生成設定詳解

檔案的名稱對應-檔案生成指定目錄檔案(recipe.xml.ftl)裡面的名稱。

MvpActivity.java.ftl-類基礎檔案程式碼生成設定

MvpContract.java.ftl、MvpModel.java.ftl、MvpPresenter.java.ftl幾個檔案類似,就不一一列出來 ${ContractName} 等,對應全域性變數檔案(globals.xml.ftl)的變數。

package ${packageName}.mvp.view.activity;

import ${packageName}.R;
import ${packageName}.mvp.base.BaseActivity;
import ${packageName}.mvp.contract.${ContractName};
import ${packageName}.mvp.presenter.${PresenterName};
import android.os.Bundle;
/**
 * Created by Liang_Lu on 2017/12/21.
 * @author LuLiang
 * @github https://github.com/LiangLuDev
 */

public class ${ActivityName} extends BaseActivity<${PresenterName}> implements ${ContractName}.${IViewName}{
	
	@Override
    protected void initView() {
        super.initView();
    }
	@Override
    public int setContentViewId() {
        return R.layout.activity_${activity_layout};
    }
     @Override
     public void createPresenter() {
        mPresenter = new ${PresenterName}(mContext, this);
     }
	
	@Override
    public void showLoading() {
    }
    @Override
    public void hideLoading() {
    }		
}

複製程式碼
activity_main.xml.ftl-佈局基礎檔案程式碼生成設定
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
<!-- 根據template檔案的parameter屬性判斷是否需要新增 -->
	<#if isTitleBar>
	<include layout="@layout/toolbar_layout"/>
	</#if>
</LinearLayout>
複製程式碼
AndroidManifest.xml.ftl-Activity宣告檔案設定

PS.宣告只針對於Activity,Fragment不需要此檔案

<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <application>
        <activity android:name=".mvp.view.activity.${ActivityName}"/>
    </application>
</manifest>
複製程式碼

一鍵生成MVP基礎程式碼

  • copy專案下的MvpActivity資料夾到Android Studio安裝目錄 例:C:\Android\Android Studio 3.0 release\plugins\android\lib\templates\activities資料夾下.
  • MvpFragment 的路徑是 C:\Android\Android Studio 3.0 release\plugins\android\lib\templates\other
  • 重啟Android Studio。
  • 重啟Android Studio之後,選中包名路徑

mvp-1.png

  • 生成MvpActivity基礎程式碼(MvpFragment可選中Fragment裡面生成)

mvp-2.png

依賴庫使用

將所有依賴的版本控制提取到根目錄下的config.gradle做統一管理

  • supportVersion : "26.1.0"
  • retrofit : "2.2.0",
  • rxjava : "2.0.1",
  • rxandroid : "2.0.1",
  • okhttp3 : "3.4.1",
  • constraint_layout: "1.0.2",
  • rxjava2_adapter : "1.0.0",
  • logging : "3.4.0-RC1",
  • butterknife : "8.8.1",

意見反饋

如果遇到問題或者好的優化建議,請反饋到:927195249@qq.com 或者LiangLuDev@gmail.com

如果覺得還行的話,贊一下吧! 謝謝啦!

相關文章