安卓项目实战之:最实用的Retrofit2+RxJava2+MVP框架搭建,避免各种内存泄漏

工程目录结构

安卓项目实战之:最实用的Retrofit2+RxJava2+MVP框架搭建,避免各种内存泄漏_第1张图片
目前网上的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();
    }
}

你可能感兴趣的:(Android,安卓项目实战系列)