美文网首页
Android---MVP学习之路(三)

Android---MVP学习之路(三)

作者: HPD_黄霹雳 | 来源:发表于2018-01-30 15:14 被阅读0次

    通过 Andriod---MVP学习之路(二),我们通过attachview和detachView很好解决了空指针问题。现在还有一个问题,如果手机屏幕旋转等,触发了activity的重启,该怎么处理Presenter呢?

    有如下几种方法:

    通过onSaveInstanceState恢复数据

    通过onSaveInstanceState保存界面和presenter的数据。横竖屏切换后通锅onCreated中的onSaveInstanceState恢复数据

    将Presenter保存在一个地方,再次onCreate时还原

    很多实现方法

    最佳方案,通过Loader延长Presenter生命周期

    什么是Loader?它有什么用?

    我们都知道,当手机状态发生改变比如旋转时,Activity会重新启动。Loader是Android框架中提供的在手机状态改变时不会被销毁的工具。Loader的生命周期是是由系统控制的,只有在向Loader请求数据的Activity/Fragment被永久销毁时才会被清除,所以也不需要自己写代码来清空它。

    一般Loader是用来在后台加载数据的,而且是用它的子类CursorLoader或AsyncTaskLoader,尤其是CursorLoader,直接就绑定了Content Provider和数据库。当然如果写个类继承Loader基类的话也不需要开启后台线程。
    听起来好厉害。但这和Presenter有什么关系?

    就像刚才说的一样,关键问题就是在哪里存储Presenter以及什么时候销毁它们。而我们刚刚就看到了Loader的强大之处:由安卓系统框架提供,有单独生命周期,会被自动回收且不必在后台运行。

    所以思考一下需求以及Loader的功能,我们可以让Loader作为Presenter的提供者,而不需要担心手机状态改变。

    将同步的Loader作为存放Presenter的缓存。

    这里的重点就在于同步使用Loader时,我们可以知道在生命周期的哪个阶段Presenter被创建了并且可以工作了。甚至是在Activity/Fragment可见之前。

    要注意的是,Activity和Fragment在何时传递Presenter对象这个问题上是有区别的。对于任何一个Activity实例,只需要在调用super.onStart()之后就可以使用Presenter了,但对于Fragment实例来说,首次创建时可以在super.onStart()之后传入,但在Fragment被重新create时,就必须要在super.onResume()之后传入了。所以在Fragment中,只需要在onResume()方法执行后将Presenter传入就行了。

    使这种方法可行的另外一个要点就是系统对Loader的处理方式,每一个Activity/Fragment都有一个LoaderManager,而且只有这个LoaderManager可以管理与Activity/Fragment相关联的Loader,这就使得相同的Fragment与其Presenter可以同时存在多个实例。

    上代码,继上篇文章中的项目代码,进行改造。

    先看以下步骤完成后的项目目录:


    图片.png
    步骤1:创建PresenterLoader
    public class PresenterLoader<T extends BaseMVPPresenterImp> extends Loader<T> {
    
        public static String TAG = "PresenterLoader";
        private final PresenterFactory<T> factory;
        private T presenter;
    
    
        public PresenterLoader(Context context, PresenterFactory factory) {
            super(context);
            this.factory = factory;
        }
    
        @Override
        protected void onStartLoading() {
            Log.i(TAG, "hpdhpd11 PresenterLoader onStartLoading");
            if (presenter != null) {
                // 如果已经有Presenter,就直接返回,其实不用这个方法也行,因为返回的是同一个对象
                //deliverResult(presenter)不会触发onLoadFinished。
                deliverResult(presenter);
                return;
            }
            // 如果没有,需要执行onForceLoad
            forceLoad();
        }
    
        @Override
        protected void onForceLoad() {
            Log.i(TAG, "hpdhpd11 PresenterLoader onForceLoad");
            // 通过工厂来实例化Presenter
            presenter = factory.create();
            // 返回Presenter
            deliverResult(presenter);
        }
    
        @Override
        protected void onReset() {
            Log.i(TAG, "hpdhpd11 PresenterLoader onReset");
            presenter = null;
        }
    }
    

    onStartLoading():会在Activity的onStart()调用之后被系统调用来获取一个Loader实例。在这里先判断是否已经有Presenter对象了还是需要创建。

    onForceLoad():在调用forceLoad()方法后自动调用,我们在这个方法中创建Presenter并返回它。

    deliverResult():会将Presenter传递给Activity/Fragment。

    onReset():会在Loader被销毁之前调用,我们可以在这里告知Presenter以终止某些操作或进行清理工作。

    PresenterFactory:这个接口可以隐藏创建Presenter所需要的参数。通过这个接口我们可以调用各种构造器,这样可以避免写一堆PresenterLoader的子类来返回不同类型的Presenter。
    这个接口形式上大概就是这样:

    public interface PresenterFactory<T extends BaseMVPPresenterImp> {
        T create();
    }
    
    在Activity/Fragment中如何实现呢?

    现在我们已经实现了Loader,下面要将Loader和Activity/Fragment连接起来,刚才说过,这个连接点就是LoaderManager。我们需要调用FragmentActivity的getSupportLoaderManager()或Fragment的getLoaderManager()方法来获得LoaderManager实例并调用其initLoader()方法。Google建议在Activity的onCreate()与Fragment的onActivityCreated()中调用此方法。

    当调用initLoader()方法时要传入一个id,只需要保证在一个Activity/Fragment内单一即可,不需要全局单一。这个id就是用来识别Loader的。还可以选择传入一个Bundle,但在这个例子中不需要。还要穿入一个LoaderCallbacks实例。

    刚才说过,不要再Fragment的onCreate()方法中调用initLoader()方法,要在onActivityCreated()中调用它,不然就会遇到不同Fragment共享一个Loader的问题。

    如果你发现在手机状态改变时onLoadFinished()会被调用两次,不妨参考stackoverflow下的这个问题。Presenter在被传入Activity后的逻辑可能会使这个成为一个问题,此时可以尝试在Fragment的onResume()方法中调用initLoader()方法,或者在onActivityCreate()方法中存一个flag以防多次获取Presenter。

    当我们调用了initLoader()后Loader和Activity/Fragment的生命周期就绑定了:执行onStart()方法时会调用onStartLoading(),执行onStop()方法时会调用onStopLoading()。但onReset()方法只会在Activity/Fragment被销毁或主动调用destroyLoader()时被调用。

    LoaderManager有一个restartLoader()方法可以强制重新加载。不过除非我们需要重新创建Presenter,不然不需要调用这个方法。

    通过LoaderCallbacks获取Presenter

    LoaderCallbacks是Activity/Fragment和Loader之间的桥梁。共有三个回调方法:

    onCreateLoader():在这里构造Loader实例。

    onLoadFinished():Loader在这里传入数据,在这个例子中,也就是Presenter。

    onLoadReset():在这里清除对于数据的引用。

    修改BaseMVPActivity

    public class BaseMVPActivity<P extends BaseMVPPresenterImp<V>, V extends BaseMVPViewInterface> extends HPDBaseActivity implements BaseMVPViewInterface, LoaderManager.LoaderCallbacks<P> {
    
        public final static int BASE_LOADER_ID = 100;
        protected ProgressDialog progressDialog;
        protected P presenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.i(TAG, "hpdhpd11 BaseMVPActivity onCreate");
            progressDialog = new ProgressDialog(this);
            getSupportLoaderManager().initLoader(BASE_LOADER_ID, null, this);
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            Log.i(TAG, "hpdhpd11 BaseMVPActivity onStart");
            if (presenter != null) {
                presenter.attachView((V) this);
            }
        }
    
        @Override
        protected void onDestroy() {
            Log.i(TAG, "hpdhpd11 BaseMVPActivity onDestroy");
            if (presenter != null) {
                presenter.detachView();
            }
            super.onDestroy();
        }
    
        @Override
        public void showLoading() {
    
            progressDialog.setMessage("showLoading");
            if (!progressDialog.isShowing()) {
                progressDialog.show();
            }
        }
    
        @Override
        public void hideLoading() {
    
            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }
        }
    
        @Override
        public void showFailError(String message) {
            if (TextUtils.isEmpty(message)) {
                message = "发生错误";
            }
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
        }
    
    
        @Override
        public Loader<P> onCreateLoader(int id, Bundle args) {
    
            return null;
        }
    
        @Override
        public void onLoadFinished(Loader<P> loader, P data) {
            Log.i(TAG, "hpdhpd11 BaseMVPActivity onLoadFinished");
            presenter = data;
        }
    
        @Override
        public void onLoaderReset(Loader<P> loader) {
            Log.i(TAG, "hpdhpd11 BaseMVPActivity onLoaderReset");
            presenter = null;
        }
    }
    

    修改LoginActivity

    public class LoginActivity extends BaseMVPActivity<LoginPresenter, LoginViewInterface> implements LoginViewInterface {
    
        private EditText etName, etPassword;
        private Button btnLogin, btnClear;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
            Log.i(TAG, "hpdhpd11 LoginActivity onCreate");
            initView();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Log.i(TAG, "hpdhpd11 LoginActivity onDestroy");
        }
    
        @Override
        public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
            return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
                @Override
                public LoginPresenter create() {
                    Log.i(TAG, "hpdhpd11 LoginActivity onCreateLoader");
                    return new LoginPresenter();
                }
            });
        }
    
    
        private void initView() {
    
            etName = findViewById(R.id.et_name);
            etPassword = findViewById(R.id.et_password);
            btnLogin = findViewById(R.id.btn_login);
            btnClear = findViewById(R.id.btn_clear);
    
            btnLogin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    presenter.login();
                }
            });
    
            btnClear.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    presenter.clear();
                }
            });
    
        }
    
        @Override
        public String getUserName() {
            return etName.getText().toString().trim();
        }
    
        @Override
        public String getUserPassword() {
            return etPassword.getText().toString().trim();
        }
    
        @Override
        public void clearName() {
            etName.setText("");
        }
    
        @Override
        public void clearPassword() {
            etPassword.setText("");
        }
    
        @Override
        public void go2OtherActivity() {
            Toast.makeText(this, "go2OtherActivity", Toast.LENGTH_SHORT).show();
        }
    
    }
    

    我们再看看打印的Log,看看Log是怎么走的?
    进入这个LoginActivity后:可以看到,onLoadFinished之后,也就是presenter已经是绑定了。然后activity的onStart执行了, 就可以交互了

    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onCreate
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreate
    com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onStartLoading
    com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onForceLoad
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreateLoader
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoadFinished
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart
    
    

    当我们关闭屏幕后,再打开APP,看看Log是怎么走的?
    可以看到,执行了PresenterLoader的onStartLoading方法,就是去检查presenter是否存在,不存在则创建,存在则返回,因为发返回的是同一个对象,deliverResult(presenter)不会触发onLoadFinished。

    com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onStartLoading
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart
    

    当我们横竖屏切换时,看看Log是怎么走的?
    可以看到,横竖屏切换后,activity会重启,getSupportLoaderManager().initLoader(BASE_LOADER_ID, null, this);执行后,因为已经存在这个对应ID的loader,会直接执行onLoadFinished(),返回这个presenter.

    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onDestroy
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onDestroy
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onCreate
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onCreate
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoadFinished
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onStart
    
    

    当我们推出这个activity后,看看Log是怎么走的?
    可以看到:activity后,会执行onLoaderReset和onReset方法,我们可以在这个方法里面释放presenter。

    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onDestroy
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 BaseMVPActivity onLoaderReset
    com.example.hpd.hpdmvpdemo I/PresenterLoader: hpdhpd11 PresenterLoader onReset
    com.example.hpd.hpdmvpdemo I/.login.LoginActivity: hpdhpd11 LoginActivity onDestroy
    
    

    我们之后写Activity的时候,可以直接使用继承BaseMVPActivity,并指定泛型。如:

    public class LoginActivity extends BaseMVPActivity<LoginPresenter, LoginViewInterface> implements LoginViewInterface 
    

    并且实现onCreateLoader方法,如:

      @Override
        public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
            return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
                @Override
                public LoginPresenter create() {
                    Log.i(TAG, "hpdhpd11 LoginActivity onCreateLoader");
                    return new LoginPresenter();
                }
            });
        }
    

    就不用再去管presenter的状态。

    参考文章:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0314/4050.html

    相关文章

      网友评论

          本文标题:Android---MVP学习之路(三)

          本文链接:https://www.haomeiwen.com/subject/aupqzxtx.html