Fragment最佳实践

最佳实践

创建

使用静态方法newInstance()方法来实例化Fragment,避免创建Fragment黑盒问题,协同开发(Activity也提供startActivity方法,提供启动参数)。
另外,使用setArguments来进行参数传递,可以避免在横竖屏切换时,Fragment自动调用自身无参构造函数,导致数据丢失引发崩溃。

1
2
3
4
5
6
7
public static SomeFragment newInstance(String params) {
Bundle args = new Bundle();
args.putString(PARAMS, params);
SomeFragment fragment = new SomeFragment();
fragment.setArguments(args);
return fragment;
}

TIPS
类似可以在每个Activity提供startActivity方法,过滤传入参数。

数据保存/恢复

区分Fragment和View的状态保存,不在Fragment中存储View的状态
每个自定义View需要对自身状态进行负责,在视图销毁之前存储自身状态。一旦Fragment从后退栈中返回,它的View会被销毁,并重新创建。
Fragment没有restore方法,所以其保存和恢复的方法分别是onSaveInstanceStateonActivityCreated。Fragment在onSaveInstanceState的时候,调用FragmentManagersaveAllState方法,将Fragment数据存储在Parcelable变量中(outState.putParcelable(FRAGMENTS_TAG, p)FRAGMENTS_TAG = "android:support:fragments";),会自动搜集每个View(有实现保存/恢复内部方法)的状态,restore(没有onRestoreInstanceState方法,用onActivityCreated方法 - 或者其他有savedInstanceState变量方法)的时候,会自动将这些数据以id对应还原到相应view。其成员变量会直接销毁,所以需要手动保存/恢复它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SomeView extends View {
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// save view state
return bundle;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
// restore view state
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SomeFragment extends Fragment {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// save state
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// restore state
}
}

键盘监听

目前测试较好方式是在rootView进行键盘监听(设置其focustouchtrue)。

1
2
3
rootView.setFocusable(true);
rootView.setFocusableOnTouchMode(true);
rootView.setOnKeyListener((view, keyCode, event) -> {});

Fragment Or Activity?

旧项目有全部用单Activity的先例,遇到蛮多问题,相对也解决了很多问题,也对Fragment的一些坑进行研究,包括FragmentManager的一些bug等。
目前不建议采用单Activity来做项目,会导致复杂度上升,内存上升等。目前一个页面用Activity,里面采用Fragment来达到复用/多屏幕适配目的。
可以参考目前已经在多个项目实践过的开源项目:AndroidKnife/Tiger-Pro: Architecture and Libraries used in Tiger Application like MVP etc.

常见错误

Fragment视图重叠

Fragment生命周期开始于add,结束于remove
此问题原因很简单,在add或者replace时候,调用含有TAG的方法,之后再addreplace,之前会被替换掉,也就不会有多个相同fragment。
但是从复用角度来说,这是一个资源的浪费,可以在onActivityCreated方法中判断savedInstanceState是否为空,不为空则查找fragment,若有此实例,则不进行创建,直接复用。

其他方案:
1 重写Activity的onSaveInstanceState方法,将其置空(或将"android:support:fragments"置空)。此方式将其他的View数据也统一不保存,不是很合理。
2 曲线救国,给Fragment根View设置背景,拦截点击事件(backgroundclickable属性)。此方式虽然有两个Fragment同时存在,但是由于有了背景及点击事件拦截,此时就避免了事件穿透及视觉差。此方式可以解决视觉上差异,但是却仍然有两个实例,而且初始化网络/业务会执行两次。
3 用replace替代show/hide方法。replace会先移除Fragment,之后再添加此Fragment,所以不会出现重叠。

总结

1 每个View应内部实现状态的保存和恢复,即onSaveInstanceStateonRestoreInstanceState,且如果需要恢复需要设置android:id属性。
2 分开处理Fragment和View的状态。
3 ETC。

Ref

Android: fragments overlapping issue - Stack Overflow
android - Overlapping hidden fragments after application gets killed and restored - Stack Overflow
serialization - When using an android bundle, why does a serialised stack deserialise as an ArrayList? - Stack Overflow
The Real Best Practices to Save/Restore Activity’s and Fragment’s state. (StatedFragment is now deprecated) :: The Cheese Factory
保存/恢复Activity和Fragment状态的最佳实践(译) - Code杂货铺 - 知乎专栏