Android MVP高级

2017-01-13 14:56:49来源:http://www.jianshu.com/p/d5828ea38b3c作者:仙鬼人点击

第七城市

上一篇:Android MVP进阶


前面一篇文章,我们讲了Android MVP的进阶应用,解决了一个View空指针异常隐患,但是这样就完美了么?
No!还是有不足之处的
Activity会在很多情况下被系统重启:当用户旋转屏幕、在后台时内存不足、改变语言设置、attache 一个外部显示器等。
这种情况下Presenter该怎么处理呢?一般有如下几种方案:


就让Presenter一起挂掉吧

在这种方案下当Activity/Fragment挂掉时Presenter也一起挂掉了。此时如果想要保存一些状态信息的话就要借助onSaveInstanceState()方法了,需要在View的实现类中保存状态并在重新onCreate()时利用状态信息重新实例化Presenter,或者直接把保存下来的instance Bundle直接传给Presenter以便Presenter处理。如果是纯数据的话这样也没什么不好的,但如果要保存一些后台线程的引用的话就很难办了。


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

我见到的最多的就是这个方案,不过具体的实现方法千差万别。
最简单最naive的实现方式,就是在Activity/Fragment中保留对Presenter的静态引用。这样我们可以再手机旋转时保留Presenter对象,但整个Applicaiton中只能有一个同样的Activity/Fragment-Presenter实例组合。但当一个ViewPager中有多个相同的Fragment时,这种方法就行不通了。


另一个方式就是调用Fragment的setRetainInstance(true)方法。

设置Fragment的这个属性可以保证Fragment不会被destroy,这样Presenter就随之被保留。但这种方式仍有局限,对一个子Fragment或是Activity这样就不起作用。


最后一种方式就是使用单例缓存机制并通过标识符来存储一个Presenter类的不同实例。

为了保留Presenter,Activity/Fragment需要在onSaveInstanceState()中传递Presenter实例的标识符。这里的问题是如何实现这种逻辑以及何时将Presenter从单例缓存中移除。


上述方案或这或那都有那么些毛病,那么有没有最佳方案呢?当然有,有请我们强大的Loader登场,铛铛铛铛~~
下面内容摘自:
译文:通过Loader延长Presenter生命周期
原文:Presenter surviving orientation changes with Loaders


什么是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可以同时存在多个实例。


贴代码:


public class PresenterLoader<T extends Presenter> extends Loader<T> {
private final PresenterFactory<T> factory;
private T presenter;
public PresenterLoader(Context context, PresenterFactory factory) {
super(context);
this.factory = factory;
}
@Override
protected void onStartLoading() {
if (presenter != null) {
deliverResult(presenter);
return;
}
forceLoad();
}
@Override
protected void onForceLoad() {
presenter = factory.create();
deliverResult(presenter);
}
@Override
protected void 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 Presenter> {
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():在这里清除对于数据的引用。


引文结束,代码贴自己的:
这里跟引文不一样,我们把Presenter LoaderCallbacks的实现都放在我们上篇文章中的BaseActivity里(当然同理可加一个BaseFragment)


public class BaseActivity<P extends Presenter<V>, V extends MvpView> extends AppCompatActivity implements MvpView, LoaderManager.LoaderCallbacks<P> {
public final static int BASE_ACTIVITY_LOADER_ID = 100;
protected ProgressDialog progressDialog;
protected P presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressDialog = new ProgressDialog(this);
getSupportLoaderManager().initLoader(BASE_ACTIVITY_LOADER_ID, null, this);
}
@Override
protected void onStart() {
super.onStart();
if (presenter != null) {
presenter.attachView((V) this);
}
}
@Override
protected void onDestroy() {
if (presenter != null) {
presenter.detachView();
}
super.onDestroy();
}
/**
* 显示loading对话框
*
* @param msg
*/
@Override
public void showLoading(String msg) {
progressDialog.setMessage(msg);
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
/**
* 隐藏loading对话框
*/
@Override
public void hideLoading() {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
/**
* 显示错误信息
*
* @param errorMsg
*/
@Override
public void showError(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
@Override
public Loader<P> onCreateLoader(int id, Bundle args) {
// TODO: 子类要实现此方法创建Loader
return null;
}
@Override
public void onLoadFinished(Loader<P> loader, P data) {
presenter = data;
}
@Override
public void onLoaderReset(Loader<P> loader) {
presenter = null;
}
}

这段代码的要点有:


在Activity/Fragment实例中通过一个唯一的ID初始化Loader。


getSupportLoaderManager().initLoader(LOADER_ID, null, this);
onStart()方法执行时会创建Loader或重新连接到一个已经存在的Loader。在这里我们在onLoadFinished()里创建并传递Presenter对象。由于这些代码都是同步的,所以当onStart()方法执行完后Presenter也可以正常工作了。


另外注意一点,引文原文作者的代码里把detachView放在onStop里,这是不合适的,当你调用startActivityForResult的时候,当前Activity会调onStop,Presenter与view断开连接,等从下一个页面返回的时候如果带着数据,并要在onActivityResult里执行业务操作的时候,问题就出现了,这个时候你的Activity的onStart还没有调呢!onActivityResult在onStart之前调@@@,于是Presenter与view还没有建立连接,然后就抛出了MvpViewNotAttachedException这个异常,解决办法就是把detachView放到onDestory里。


我们再看BaseActivity里:


    @Override
public Loader<P> onCreateLoader(int id, Bundle args) {
// TODO: 子类要实现此方法创建Loader
return null;
}

onCreateLoader返回空,这个要在子类中实现,那么就看看子类中怎么实现,改造我们的LoginActivity


public class LoginActivity extends BaseActivity<LoginPresenter, ILoginView> implements ILoginView, View.OnClickListener {
private EditText username;
private EditText password;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
username = (EditText) findViewById(R.id.username);
password = (EditText) findViewById(R.id.password);
findViewById(R.id.login).setOnClickListener(this);
}
@Override
public Loader<LoginPresenter> onCreateLoader(int id, Bundle args) {
return new PresenterLoader(this, new PresenterFactory<LoginPresenter>() {
@Override
public LoginPresenter create() {
return new LoginPresenter(new UserModel());
}
});
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.login:
presenter.login();
break;
}
}
/**
* 从UI中获取用户输入的用户名
*
* @return
*/
@Override
public String getUsername() {
return username.getText().toString().trim();
}
/**
* 从UI中获取用户输入的密码
*
* @return
*/
@Override
public String getPassword() {
return password.getText().toString().trim();
}
/**
* 显示结果
*
* @param result
*/
@Override
public void showResult(String result) {
Toast.makeText(LoginActivity.this, result, Toast.LENGTH_SHORT).show();
}
}

看到没有,我们在这里实现了了onCreateLoader,在这里产生LoginPresenter
哈哈,整个LoginActivity又变干净了有木有,大部份Presenter,Loaders都放在了BaseActivity中。LoginActivity里只要实现onCreateLoader就行了
Android MVP 高级篇到此结束,代码在这里


下一篇:Android MVP扩展




第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台