带你封装自己的MVP+Retrofit+RxJava2框架(一)

前言#

文本已经收录到我的Github个人博客,欢迎大佬们光临寒舍:我的GIthub博客

看完本篇文章的,可以看下带你封装自己的MVP+Retrofit+RxJava2框架(二),里面封装得到了改进

本篇文章需要已经具备的知识:#

  • MVP的概念和基本使用
  • Retrofit框架的基本使用
  • RxJava2框架的基本使用
  • ButterKnife框架的基本使用
  • Base基类的概念

学习清单:#

  • ActivityFragment基类的封装
  • MVP的封装使用

一.为什么要封装这套框架呢?#

在搞清楚这个问题之前,我们回顾一下基本概念

RxJava: ReactiveXJVM上的一个实现,ReactiveX使用Observable序列组合异步和基于事件的程序;掌握了它,你可以优美地处理异步任务和事件的回调

Retrofit:一个 RESTfulHTTP网络请求框架的封装,网络请求的工作本质上是OkHttp 完成,而 Retrofit仅负责 网络请求接口的封装:掌握了它,你能优美地进行网络请求。

MVP:一种解耦模型和视图的模式,是现在很多公司的主流模式。

由此可见,在平时的开发中熟练运用这种模式,不仅可以满足生活中大部分应用程序的场景,还可以为将来的工作积攒宝贵的实战经验。

二.核心用法#

本项目基于Android X 进行构建,完整代码可在我的Github上下载:带你封装自己的MVP+Retrofit+RxJava2框架

首先,看一下我们项目的基本结构,下面笔者将为大家详细介绍每个类的相关信息

项目基本结构

2.1 基类Base#

Base基类是封装了一些基类,方便后面新建新的Activity或者Fragment,减少耦合

2.1.1 BaseActivity#

这个类是Activity的基类,注意与下面的BaseMvpActivity区分开

Copy
/** * Description : BaseActivity 基类活动 * * @author XuCanyou666 * @date 2020/2/2 */ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); initPresenter(); initViews(); ButterKnife.bind(this); } /** * 抽象方法:实例化Presenter */ protected abstract void initPresenter(); /** * 抽象方法:初始化控件,一般在BaseActivity中通过ButterKnife来绑定,所以该方法内部一般我们初始化界面相关的操作 * * @return 控件 */ protected abstract void initViews(); /** * 抽象方法:得到布局id * * @return 布局id */ protected abstract int getLayoutId(); /** * 启动Fragment * * @param id id * @param fragment 碎片 */ protected void startFragment(int id, Fragment fragment) { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.add(id, fragment); fragmentTransaction.commit(); } }

2.1.2 BaseView#

一个接口,说明了每一个View基本需要的一些操作

Copy
package com.users.xucanyou666.rxjava2_retrofit_mvp.base; /** * created by xucanyou666 * on 2020/1/31 18:26 * email:913710642@qq.com */ public interface BaseView { /** * 显示进度框 */ void showProgressDialog(); /** * 关闭进度框 */ void hideProgressDialog(); /** * 出错信息的回调 * * @param result 错误信息 */ void onError(String result); }

2.1.3 BaseMvpActivity#

  • MVP活动的基类

  • 继承自BaseActivity,它是MVP活动的基类,封装好了Presenter的相关操作

Copy
package com.users.xucanyou666.rxjava2_retrofit_mvp.base; /** * created by xucanyou666 MVP活动的基类,封装好了presenter的相关操作 * on 2019/12/24 20:53 * email:913710642@qq.com */ public abstract class BaseMvpActivity<V extends BaseView, P extends BasePresenter> extends BaseActivity { private P presenter; /** * 初始化presenter */ @Override protected void initPresenter() { presenter = createPresenter(); if (presenter != null) { presenter.attachView((V) this); } } /** * 创建presenter * * @return Presenter */ protected abstract P createPresenter(); /** * 得到presenter * * @return presenter */ protected P getPresenter() { return presenter; } /** * 销毁 */ @Override protected void onDestroy() { super.onDestroy(); if (presenter != null) { presenter.detachView(); } } }

2.1.4 BaseFragment#

  • Fragment的基类

  • 需要注意的是,这里用了ButterKnife框架,对碎片进行了绑定和解绑操作

Copy
/** * Fragment的基类,封装了一些Fragment的相关操作 * created by xucanyou666 * on 2020/1/31 16:21 * email:913710642@qq.com */ public abstract class BaseFragment<T extends BasePresenter> extends Fragment implements BaseView { protected T mPresenter; protected Context mContext; protected Bundle mBundle; protected Unbinder unbinder; protected View view; /** * 恢复数据 * * @param outState bundle */ @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); if (mBundle != null) { outState.putBundle("bundle", mBundle); } } /** * 绑定activity * * @param context context */ @Override public void onAttach(@NonNull Context context) { super.onAttach(context); mContext = context; } /** * 运行在onAttach之后,可以接收别人传递过来的参数,实例化对象 * 可以解决返回的时候页面空白的bug * * @param savedInstanceState */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mBundle = savedInstanceState.getBundle("bundle"); } else { mBundle = getArguments() == null ? new Bundle() : getArguments(); } //初始化presenter mPresenter = initPresenter(); } protected T getPresenter() { return mPresenter; } /** * 运行在onCreate之后,生成View视图 * * @param inflater * @param container * @param savedInstanceState * @return */ @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { view = initView(inflater, container, savedInstanceState); unbinder = ButterKnife.bind(this, view); return view; } /** * 运行在onCreateView之后 * 加载数据 * * @param savedInstanceState */ @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mPresenter.attachView(this); } /** * 跳转Fragment * * @param toFragment 跳转去的fragment */ public void startFragment(Fragment toFragment) { Log.d(TAG, "haha"); startFragment(toFragment, null); } /** * 跳转Fragment * * @param toFragment 跳转到的fragment * @param tag fragment的标签 */ public void startFragment(Fragment toFragment, String tag) { FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); fragmentTransaction.hide(this).add(android.R.id.content, toFragment, tag); fragmentTransaction.addToBackStack(tag); fragmentTransaction.commitAllowingStateLoss(); } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } /** * fragment进行回退 * 类似于activity的OnBackPress */ public void onBack() { getFragmentManager().popBackStack(); } @Override public void onDetach() { mPresenter.detachView(); super.onDetach(); } /** * 初始化Fragment应有的视图 * * @return view */ public abstract View initView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState); /** * 创建presenter * * @return <T extends BasePresenter> 必须是BasePresenter的子类 */ public abstract T initPresenter(); /** * 得到context * * @return context */ @Override public Context getContext() { return mContext; } /** * 得到bundle * * @return bundle */ public Bundle getBundle() { return mBundle; } /** * 得到fragment * * @return fragment */ public Fragment getFragment() { return this; } }

2.1.5 BasePresenter#

Copy
/** * created by xucanyou666 * on 2020/1/16 17:12 * email:913710642@qq.com */ public abstract class BasePresenter<V extends BaseView> { //将所有正在处理的Subscription都添加到CompositeSubscription中。统一退出的时候注销观察 private CompositeDisposable mCompositeDisposable; private V baseView; /** * 和View绑定 * * @param baseView */ public void attachView(V baseView) { this.baseView = baseView; } /** * 解绑View,该方法在BaseMvpActivity类中被调用 */ public void detachView() { baseView = null; // 在界面退出等需要解绑观察者的情况下调用此方法统一解绑,防止Rx造成的内存泄漏 if (mCompositeDisposable != null) { mCompositeDisposable.dispose(); } } /** * 获取View * * @return view */ public V getMvpView() { return baseView; } /** * 将Disposable添加,在每次网络访问之前初始化时进行添加操作 * * @param subscription subscription */ public void addDisposable(Disposable subscription) { //csb 如果解绑了的话添加 sb 需要新的实例否则绑定时无效的 if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) { mCompositeDisposable = new CompositeDisposable(); } mCompositeDisposable.add(subscription); } }

2.1.6 MyApplication#

  • 封装了一个可以全局获取Context的方法,参考写法自:《第一行代码--第二版》
  • 注意:记得在AndroidManifest中注册Application
Copy
package com.users.xucanyou666.rxjava2_retrofit_mvp.base; import android.app.Application; import android.content.Context; /** * 基类 * created by xucanyou666 * on 2019/11/2 14:46 * email:913710642@qq.com * @author xucanyou666 */ public class MyApplication extends Application { private static Context context; @Override public void onCreate() { super.onCreate(); context = getApplicationContext(); } public static Context getContext() { return context; } }

2.2 工具类 Util#

2.2.1 RetrofitManager#

Retrofit单例工具类

Copy
/** * Retrofit单例工具类 * created by xucanyou666 * on 2020/1/16 16:38 * email:913710642@qq.com */ public class RetrofitManager { private Retrofit mRetrofit; //构造器私有,这个工具类只有一个实例 private RetrofitManager() { OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); httpClientBuilder.connectTimeout(15, TimeUnit.SECONDS); mRetrofit = new Retrofit.Builder() .client(httpClientBuilder.build()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(BASE_URL) .build(); } /** * 静态内部类单例模式 * * @return */ public static RetrofitManager getInstance() { return Inner.retrofitManager; } private static class Inner { private static final RetrofitManager retrofitManager = new RetrofitManager(); } /** * 利用泛型传入接口class返回接口实例 * * @param ser 类 * @param <T> 类的类型 * @return Observable */ public <T> T createRs(Class<T> ser) { return mRetrofit.create(ser); } }

2.2.2 RxJavaUtil#

RxJava的工具类,执行线程调度工作

Copy
/** * created by xucanyou666 * on 2019/11/17 19:20 * email:913710642@qq.com * * @author xucanyou666 */ public class RxJavaUtil { /** * 线程调度工作 * * @param observable 被观察者 * @param <T> 类型 */ public static <T> Observable toSubscribe(Observable<T> observable) { return observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }

2.3 常量类 Contant#

常量池,特别感谢api open网提供的免费API

Copy
/** * created by xucanyou666 * on 2019/11/17 19:01 * email:913710642@qq.com */ public class StaticQuality { public static final String BASE_URL="https://api.gushi.ci/"; }

2.4 接口管理器 Contract#

这里集中了一些Model层,Presenter层,View层的与诗歌相关的接口

Copy
/** * 诗歌的接口管理器 * created by xucanyou666 * on 2020/2/2 15:33 * email:913710642@qq.com */ public interface IPoetryContract { interface IPoetryModel { /** * 得到诗歌 * * @return 诗歌 */ Observable<PoetryEntity> getPoetry(); } interface IPoetryPresenter { void getPoetry(); } interface IPoetryView extends BaseView { /** * @param author 作者 */ void searchSuccess(String author); } }

2.5 实体类 Entity#

Copy
/** * 诗歌的实体类 * created by xucanyou666 * on 2020/1/23 21:23 * email:913710642@qq.com * API返回示例: * { * "content": "胡瓶落膊紫薄汗,碎叶城西秋月团。", * "origin": "从军行七首", * "author": "王昌龄", * "category": "古诗文-天气-月亮" * } */ public class PoetryEntity { private String content; //诗歌内容 private String origin; //来源 private String author; //作者 private String category; //分类 public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }

2.6 Retrofit接口 iApiService#

Copy
/** * retrofit接口 * created by xucanyou666 * on 2020/1/23 21:25 * email:913710642@qq.com */ public interface GetPoetryEntity { /** * 获取古诗词 * * @return 古诗词 */ @GET("all.json") Observable<PoetryEntity> getPoetry(); }

2.7 视图层 View#

这里为了减少代码量,方便读者们掌握核心操作,故View层都是用的同一个PresenterModel,仅作学习参考

2.7.1 MainActivity#

需要注意的是,这里BaseMvpActivity<activity, presenter>Activity填入的是当前的ActivityPresenter填入的是对应的Presenter

Copy
/** * Description : MainActivity * * @author XuCanyou666 * @date 2020/2/3 */ public class MainActivity extends BaseMvpActivity<MainActivity, PoetryPresenter> implements IPoetryContract.IPoetryView { @BindView(R.id.btn_get_poetry) Button btnGetPoetry; @BindView(R.id.tv_poetry_author) TextView tvPoetryAuthor; @BindView(R.id.btn_goto_fragment) Button btnGotoFragment; @BindView(R.id.ll) LinearLayout ll; @Override protected void initViews() { } @Override protected int getLayoutId() { return R.layout.activity_main; } @Override protected PoetryPresenter createPresenter() { return PoetryPresenter.getInstance(); } @Override public void searchSuccess(String author) { tvPoetryAuthor.setText(author); } @Override public void showProgressDialog() { } @Override public void hideProgressDialog() { } @Override public void onError(String result) { Toast.makeText(MyApplication.getContext(), result, Toast.LENGTH_SHORT).show(); } @OnClick({R.id.btn_get_poetry, R.id.btn_goto_fragment}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.btn_get_poetry: getPresenter().getPoetry(); break; case R.id.btn_goto_fragment: startFragment(R.id.ll, new MainFragment()); break; default: break; } } }

2.7.2 MainFragment#

Copy
/** * Description : MainFragment * * @author XuCanyou666 * @date 2020/2/2 */ public class MainFragment extends BaseFragment<PoetryPresenter> implements IPoetryContract.IPoetryView { @BindView(R.id.btn_get_poetry) Button btnGetPoetry; @BindView(R.id.tv_poetry_author) TextView tvPoetryAuthor; @Override public View initView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main, container, false); } @Override public PoetryPresenter initPresenter() { return PoetryPresenter.getInstance(); } @Override public void showProgressDialog() { } @Override public void hideProgressDialog() { } @Override public void onError(String result) { Toast.makeText(MyApplication.getContext(), result, Toast.LENGTH_SHORT).show(); } @OnClick(R.id.btn_get_poetry) public void onViewClicked() { getPresenter().getPoetry(); } @Override public void searchSuccess(String author) { tvPoetryAuthor.setText(author); } }

2.8 Presenter#

Copy
/** * created by xucanyou666 * on 2020/1/16 17:09 * email:913710642@qq.com */ public class PoetryPresenter extends BasePresenter<IPoetryContract.IPoetryView> implements IPoetryContract.IPoetryPresenter { private static final String TAG = "PoetryPresenter"; private PoetryEntity mPoetryEntity; private PoetryModel mPoetryModel; private PoetryPresenter() { mPoetryModel = PoetryModel.getInstance(); } public static PoetryPresenter getInstance() { return Inner.instance; } private static class Inner { private static final PoetryPresenter instance = new PoetryPresenter(); } /** * 得到诗歌 */ @Override public void getPoetry() { Observable observable = mPoetryModel.getPoetry().doOnSubscribe(new Consumer<Disposable>() { @Override public void accept(Disposable disposable) throws Exception { addDisposable(disposable); } }); observable = RxJavaUtil.toSubscribe(observable); observable.subscribe(new Observer<PoetryEntity>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(PoetryEntity poetryEntity) { mPoetryEntity = poetryEntity; } @Override public void onError(Throwable e) { getMvpView().onError(e.getMessage()); Log.d(TAG, "onError: " + e.getMessage()); } @Override public void onComplete() { if (mPoetryEntity != null) { getMvpView().searchSuccess(mPoetryEntity.getAuthor()); } } }); } }

2.9 Model#

Copy
/** * created by xucanyou666 * on 2020/1/16 17:06 * email:913710642@qq.com */ public class PoetryModel implements IPoetryContract.IPoetryModel { private PoetryModel() { } public static PoetryModel getInstance() { return Inner.instance; } private static class Inner { private static final PoetryModel instance = new PoetryModel(); } /** * 获取古诗词 * * @return 古诗词 */ @Override public Observable<PoetryEntity> getPoetry() { return RetrofitManager.getInstance().createRs(GetPoetryEntity.class).getPoetry(); } }

2.10 app.build.gradle#

Copy
apply plugin: 'com.android.application' android { compileSdkVersion 28 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.users.xucanyou666.rxjava2_retrofit_mvp" minSdkVersion 19 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { // RxJava implementation 'io.reactivex.rxjava2:rxjava:2.1.12' implementation 'com.squareup.retrofit2:retrofit:2.6.0' // Retrofit和jxjava关联 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' // Retrofit使用Gson转换 implementation 'com.squareup.retrofit2:converter-gson:2.4.0' // RxAndroid implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' //引入ButterKnife implementation "com.jakewharton:butterknife:10.2.0" implementation 'androidx.legacy:legacy-support-v4:1.0.0' annotationProcessor "com.jakewharton:butterknife-compiler:10.2.0" implementation "com.google.android.material:material:1.0.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' }

三.我在使用中遇到的问题#

3.1 网络权限忘记授予#

  • 解决措施:加上权限即可
Copy
<uses-permission android:name="android.permission.INTERNET" />

3.2 ButterKnife框架版本问题#

使用ButterKnife框架的时候

当是androidX的时候,需要implementation 10.2.0版本的ButterKnife

Copy
//引入ButterKnife implementation "com.jakewharton:butterknife:10.2.0" implementation 'androidx.legacy:legacy-support-v4:1.0.0' annotationProcessor "com.jakewharton:butterknife-compiler:10.2.0"

当是android 28等其他版本的时候,可以导入8.4.0版本的ButterKnife(导入10.2.0版本会出错)

Copy
implementation 'com.jakewharton:butterknife:8.4.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'

3.3 ButterKnife需要Java 1.8以上的支持#

Copy
compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }

3.4 Fragment中点击事件失效的问题#

  • 点击事件失效发生的场景:Fragment中初始化控件没有用ButterKnife框架

解决措施如下:

A:方法一:

  • 将控件的初始化放在onCreateView
  • 将控件的点击事件的代码放在onActivityCreated

B:方法二:

  • Fragment中使用ButterKnife框架

如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

本文参考链接:

posted @   许朋友爱玩  阅读(1118)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示
CONTENTS