每一個程序員看到一堆爛代碼都有一顆重構的心。爛代碼寫起來嘴上 笑嘻嘻,內心mmp。特別是有代碼潔癖的人。重構不易且行且珍惜,此框架將減小開發時間。若是大家的項目結構跟個人不同,這也不用擔憂,你看了我這個,簡單修改一下模板,照樣能生成你想要的代碼。java
相信你們對於MVP耳熟能詳了,理解的直接往下看,若是概念比較模糊,能夠網上查一查理解理解,我這邊簡單的介紹一下,能夠配合下圖來理解android
- 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架構,使用RxAndroid2+Retrofit開源框架封裝,結合Android Studio模板快速生成MVP基礎代碼。新項目或者重構項目值得擁有。此開發框架是我2017年末重構項目開發的,使用了幾個月,項目重構完成了,完美使用,特別省事省時省心git
DevMvp
├─api//URL、接口管理、網絡請求封裝類
├─mvp//項目主體
│ ├─base//基礎類封裝
├─bean//實體類
│ ├─contract//契約類 用於統一管理view和presenter的接口
│ ├─model//M層-數據處理
│ ├─presenter//P層-邏輯業務處理
│ └─view//V層-頁面渲染
│ ├─activity
├─adapter
│ └─fragment
...//相似Dialog、PopupWindow也能夠放在view下
└─utils//工具類
└─rxhelper//Rx封裝工具
複製代碼
Retrofit網絡請求封裝,項目裏面只是對Retrofit網絡請求基本參數,須要header、cache等參數,可在網上查查資料,這類文章介紹不少。Retrofit 2.0使用文檔程序員
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;
}
複製代碼
@GET(Url.BOOK_CLASSIFY)
Observable<BookBean> bookClassify();
複製代碼
interface IPBook extends IBasePresenter {
void pBook();
}
interface IVBook extends IBaseView {
void vBookSuccess(BookBean bean);
void vBookError(String reason);
}
複製代碼
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();
}
}
複製代碼
public void mBook(RxObservable rxObservable) {
apiService()
.bookClassify()
.compose(RxTransformer.switchSchedulers(this))
.subscribe(rxObservable);
}
複製代碼
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();
}
}
}
複製代碼
/** * 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);
}
});
}
}
複製代碼
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();//頁面銷燬 網絡請求同銷燬
}
}
}
複製代碼
/** * 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);
}
}
複製代碼
<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>
複製代碼
PS.這裏須要注意一下,這裏的根目錄(root)是包名的那個目錄,此項目例:com.luliang.devmvpgithub
<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 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>
複製代碼
文件的名稱對應-文件生成指定目錄文件(recipe.xml.ftl)裏面的名稱。api
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() {
}
}
複製代碼
<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>
複製代碼
PS.聲明只針對於Activity,Fragment不須要此文件bash
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name=".mvp.view.activity.${ActivityName}"/>
</application>
</manifest>
複製代碼
- 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。
將全部依賴的版本控制提取到根目錄下的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
若是以爲還行的話,贊一下吧! 謝謝啦!