目前网上的mvp框架大多存在以下问题:
1,Presenter持有View的引用,容易导致出现内存泄漏
MvpPresenter mvpPresenter = new MvpPresenter(this); // 不推荐这样写,持有activty引用,容易出现内存泄露
2,如果每一个Activity都对应一个Model和Presenter的话,试想一下,如果有几百个Activity的话那么是不是也要创建很多个Model和Presenter,关键是很多地方都是重复的可以通用的。所以,创建基类就显得很有必要了,具体查看本例写法,优不优雅看过之后您评论。
// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.4.0'
// Retrofit和jxjava关联
compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
// Retrofit使用Gson转换
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
// RxJava
compile 'io.reactivex.rxjava2:rxjava:2.1.12'
// RxAndroid
compile 'io.reactivex.rxjava2:rxandroid:2.0.2'
1,RetrofitHelper
主要用于Retrofit的初始化:
// 声明Retrofit对象
private Retrofit mRetrofit = null;
OkHttpClient client = new OkHttpClient();
// 由于该对象会被频繁调用,采用单例模式,下面是一种线程安全模式的单例写法,构造方法已被private修饰
private volatile static RetrofitHelper instance = null;
public static RetrofitHelper getInstance(){
if (instance == null){
synchronized (RetrofitHelper.class) {
if(instance == null){
instance = new RetrofitHelper();
}
}
}
return instance;
}
private RetrofitHelper(){
initRetrofit();
}
/**
* 初始化 retrofit
*/
private void initRetrofit() {
mRetrofit = new Retrofit.Builder()
.baseUrl(UrlConstant.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
public RetrofitService getServer(){
return mRetrofit.create(RetrofitService.class);
}
其中getServer方法就是为了获取RetrofitService接口类的实例化。然后定义了一个静态方法getInstance用于获取自身RetrofitHelper的实例化,并且只会实例化一次。
2,RetrofitService
public interface RetrofitService {
@GET("book/search")
Observable<Book> getSearchBooks(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
3,DataManager
/**
* 该类用来管理RetrofitService中对应的各种API接口
* 当做Retrofit和presenter中的桥梁,Activity就不用直接和retrofit打交道了
*/
public class DataManager {
private RetrofitService mRetrofitService;
private DataManager(){
this.mRetrofitService = RetrofitHelper.getInstance().getServer();
}
//由于该对象会被频繁调用,采用单例模式,下面是一种线程安全模式的单例写法,构造方法已被private修饰
private volatile static DataManager instance;
public static DataManager getInstance() {
if (instance == null) {
synchronized (DataManager.class) {
if (instance == null) {
instance = new DataManager();
}
}
}
return instance;
}
// 将retrofit的业务方法映射到DataManager中,以后统一用该类来调用业务方法
// 以后在RetrofitService中增加业务方法的时候,相应的这里也要添加一个方法,建议方法名字相一致
public Observable<Book> getSearchBooks(String name, String tag, int start, int count){
return mRetrofitService.getSearchBooks(name,tag,start,count);
}
}
这个类其实就是为了让你更方便的调用RetrofitService 中定义的方法,可以看到,在它的构造方法中,我们得到了RetrofitService 的实例化,然后定义了一个和RetrofitService 中同名的方法,里面其实就是调用RetrofitService 中的这个方法。这样,把RetrofitService 中定义的方法都封装到DataManager 中,以后无论在哪个要调用方法时直接在DataManager 中调用就可以了,而不是重复建立RetrofitService 的实例化,再调用其中的方法。
4,其中bean下放我们请求的实体类,这里就是Book用插件,GsonFormat动态生成,此处因为省略
public class Book {
private int count;
private int start;
private int total;
private List<BooksBean> books;
// ...省略getter和setter方法以及各自的toString方法
}
5,创建抽象类BasePresenter,代码如下:
public abstract class BasePresenter<V extends BaseView> {
//将所有正在处理的Subscription都添加到CompositeSubscription中。统一退出的时候注销观察
private CompositeDisposable mCompositeDisposable;
private V baseView;
public void attachView(V baseView) {
this.baseView = baseView;
}
/**
* 解绑View,该方法在BaseMvpActivity类中被调用
*/
public void detachView(){
baseView = null;
// 在界面退出等需要解绑观察者的情况下调用此方法统一解绑,防止Rx造成的内存泄漏
if (mCompositeDisposable != null) {
mCompositeDisposable.dispose();
}
}
/**
* 获取View
* @return
*/
public V getMvpView(){
return baseView;
}
/**
* 将Disposable添加,在每次网络访问之前初始化时进行添加操作
*
* @param subscription
*/
public void addDisposable(Disposable subscription) {
//csb 如果解绑了的话添加 sb 需要新的实例否则绑定时无效的
if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) {
mCompositeDisposable = new CompositeDisposable();
}
mCompositeDisposable.add(subscription);
}
}
6,创建接口BaseView,代码如下:
/**
* 定义通用的接口方法
*/
public interface BaseView {
// 显示进度框
void showProgressDialog();
// 关闭进度框
void hideProgressDialog();
// 出错信息的回调
void onError(String result);
}
7,创建BaseActivity,代码如下:
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
initPresenter();
//初始化控件,一般在BaseActivity中通过ButterKnife来绑定,所以该方法内部一般我们初始化界面相关的操作
initViews();
//获取数据
getDataFromServer();
}
// 设置布局
protected abstract int getLayoutId();
// 初始化界面
protected abstract void initViews();
// 获取数据
protected void getDataFromServer(){}
// 实例化presenter
protected void initPresenter(){}
}
8,创建BaseMvpActivity,代码如下:
public abstract class BaseMvpActivity<V extends BaseView, P extends BasePresenter<V>> extends BaseActivity {
private P presenter;
@Override
protected void initPresenter() {
//实例化Presenter
presenter = createPresenter();
//绑定
if (presenter != null){
presenter.attachView((V) this);
}
}
// 初始化Presenter
protected abstract P createPresenter();
protected P getPresenter(){
return presenter;
}
@Override
protected void onDestroy() {
//解绑
if (presenter != null){
presenter.detachView();
}
super.onDestroy();
}
}
9,UrlConstant类代码如下:
public class UrlConstant {
public static final String BASE_URL = "https://api.douban.com/v2/";
}
经过以上的步骤针对该baseurl的mvp框架就已经搭建完毕了,如果使用其他网络框架如OkGo等,前三步可以直接跳过不写,因为前三步主要是针对Retrofit网络请求过程的封装,接下来我们来实现请求接口数据,获取服务器端返回的数据。
1,根据具体的业务需求创建BaseView的接口,此处为BookView:
/**
* 根据具体的业务需求,定义特有的方法,该接口为所有的view公用
* 不同的activity定义不同的View,这些View有个共同点,那就是都继承自BaseView
*/
public interface BookView extends BaseView {
// 当前页面比较简单仅仅是获取接口数据进行展示,
// 业务比较复杂的时候,可能一个页面需要不同的接口得到不同的数据类型
void onSuccess(Book mBook);
//获取到广告位数据
// void loadBannerSuccess(BannerBean bean);
}
2,根据具体的业务需求创建BasePresenter的实现类,此处为BookPresenter:
public class BookPresenter extends BasePresenter<BookView> {
private DataManager dataManager;
private Book mBook;
public BookPresenter() {
dataManager = DataManager.getInstance();
}
public void getSearchBooks(String name,String tag,int start,int count){
if(getMvpView() != null){
// 进行网络请求
dataManager.getSearchBooks(name,tag,start,count)
// doOnSubscribe用于在call之前执行一些初始化操作
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
//请求加入管理,统一管理订阅,防止内存泄露
addDisposable(disposable);
// 显示进度提示
getMvpView().showProgressDialog();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Book>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull Book book) {
mBook = book;
}
// 此处注意:onComplete和onError只会调用其中一个,不可能同时被触发
@Override
public void onError(@NonNull Throwable e) {
// 在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出
e.printStackTrace();
getMvpView().onError("请求失败!!");
getMvpView().hideProgressDialog();
}
@Override
public void onComplete() {
// onComplete方法和onError方法是互斥的,
// RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。
if (mBook != null){
getMvpView().onSuccess(mBook);
}
// 隐藏进度
getMvpView().hideProgressDialog();
}
});
}
}
}
3,接下来就是BookActivity的实现了,注意此处继承的是BaseMvpActivity,布局文件也很简单一个textview和一个button,点击button将获取到的数据显示在textView上即可:
public class BookActivity extends BaseMvpActivity<BookView,BookPresenter> implements BookView {
private ProgressDialog progressDialog;
private Button button;
private TextView text;
@Override
protected int getLayoutId() {
return R.layout.activity_book;
}
// 获取presenter实例
@Override
protected BookPresenter createPresenter() {
return new BookPresenter();
}
// 初始化界面
@Override
protected void initViews() {
button = (Button) findViewById(R.id.button);
text = (TextView) findViewById(R.id.text);
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在拼命加载中...");
}
// 按钮的点击事件:请求接口,获取网络数据
public void getData(View view){
// 调用presenter层的方法执行网络请求
getPresenter().getSearchBooks("天涯明月刀", null, 0, 1);
}
@Override
public void onSuccess(Book mBook) {
// 将获取到的数据的信息显示在TextView组件上
text.setText(mBook.toString());
}
// 或者通过设置progress组件的VISIBLE和GONE来控制进度提示的显示和隐藏,此处是使用对话框提示
@Override
public void showProgressDialog() {
if(!progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void hideProgressDialog() {
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void onError(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}