MVP模式, 开源库mosby的使用及代码分析 (2)

mosby还支持ViewGroup作为View, 它提供了MvpFrameLayout, MvpLinearLayout和MvpRelativeLayout以供继承, Delegate的实现类是ViewGroupMvpDelegateImpl, 用到的生命周期主要是onAttachedToWindow()和#onDetachedFromWindow().

Presenter中调用View的方法

MvpBasePresenter的实现没有什么特殊的, 主要是存了一个View的WeakReference. 新版中推荐使用ifViewAttached(ViewAction<V>)方法来把判断和执行一次性做了. 原来的isViewAttached()和getView()已经标记为deprecated了.
关于这样做的原因, 在这里有讨论: https://github.com/sockeqwe/mosby/issues/233.

屏幕旋转时的状态保存

mosby是处理了屏幕旋转时的状态保存的, 可以看到初始化ActivityMvpDelegateImpl时默认第三个参数是true, 即屏幕旋转时保存状态.
具体做法是通过PresenterManager把presenter保存起来.
保存的时候传了activity和一个生成的viewId:

private P createViewIdAndCreatePresenter() { P presenter = delegateCallback.createPresenter(); if (presenter == null) { throw new NullPointerException( "Presenter returned from createPresenter() is null. Activity is " + activity); } if (keepPresenterInstance) { mosbyViewId = UUID.randomUUID().toString(); PresenterManager.putPresenter(activity, mosbyViewId, presenter); } return presenter; }

恢复状态的时候需要把之前存的Presenter拿出来还是用activity的实例和viewId:

@Nullable public static <P> P getPresenter(@NonNull Activity activity, @NonNull String viewId) { if (activity == null) { throw new NullPointerException("Activity is null"); } if (viewId == null) { throw new NullPointerException("View id is null"); } ActivityScopedCache scopedCache = getActivityScope(activity); return scopedCache == null ? null : (P) scopedCache.getPresenter(viewId); }

其中viewId是通过bundle保存和恢复出来的:

@Override public void onSaveInstanceState(Bundle outState) { if (keepPresenterInstance && outState != null) { outState.putString(KEY_MOSBY_VIEW_ID, mosbyViewId); if (DEBUG) { Log.d(DEBUG_TAG, "Saving MosbyViewId into Bundle. ViewId: " + mosbyViewId + " for view " + getMvpView()); } } }

那么问题来了:

1.既然我们已经有了一个viewId作为key, 为什么还需要activity来作为查询条件?

2.如果真的需要这个条件, 那么屏幕旋转以后activity都重建了, 如何通过新的activity实例获得之前的Presenter呢?

首先我是在代码中找到了第二个问题的答案, 即两个不同的activity是如何关联起来的:

static final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (savedInstanceState != null) { String activityId = savedInstanceState.getString(KEY_ACTIVITY_ID); if (activityId != null) { // After a screen orientation change we map the newly created Activity to the same // Activity ID as the previous activity has had (before screen orientation change) activityIdMap.put(activity, activityId); } } } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { // Save the activityId into bundle so that the other String activityId = activityIdMap.get(activity); if (activityId != null) { outState.putString(KEY_ACTIVITY_ID, activityId); } } ... @Override public void onActivityDestroyed(Activity activity) { if (!activity.isChangingConfigurations()) { // Activity will be destroyed permanently, so reset the cache String activityId = activityIdMap.get(activity); if (activityId != null) { ActivityScopedCache scopedCache = activityScopedCacheMap.get(activityId); if (scopedCache != null) { scopedCache.clear(); activityScopedCacheMap.remove(activityId); } // No Activity Scoped cache available, so unregister if (activityScopedCacheMap.isEmpty()) { // All Mosby related activities are destroyed, so we can remove the activity lifecylce listener activity.getApplication() .unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks); if (DEBUG) { Log.d(DEBUG_TAG, "Unregistering ActivityLifecycleCallbacks"); } } } } activityIdMap.remove(activity); } };

通过Bundle存取传递一个activityId, 新创建的activity实例和旧的activity实例就有相同的id. 这个关系存储在Map<Activity, String> activityIdMap里.
这样在新的activity中通过map查询到activityId之后, 在Map<String, ActivityScopedCache> activityScopedCacheMap中再通过activityId查到了ActivityScopedCache对象, 再用viewId作为key查询到presenter.

看了onActivityDestroyed()部分的代码之后也终于明白了第一个问题的答案, 即这样做的原因, 如果只用viewId, 我们是解决了存放和查询, 但是没有解决释放的问题.
因为我们的需求只是在屏幕旋转的情况下保存presenter的实例, 我们仍然需要在activity真的销毁的时候释放对presenter实例的保存.
这里用了activity.isChangingConfigurations()的条件来区分activity是真的要销毁, 还是为了屏幕旋转要销毁.

PS: 说到状态保存和恢复, 之前的一篇博客写得很详细, 可以参考一下: Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复

其他

Mosby还支持LCE(Loading-Content-Error)和ViewState, 为开发者省去更多套路化的代码, 还有处理屏幕旋转之后的状态恢复.
有空的时候再写一篇扒一扒吧.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppyjx.html