最近我在Droidcon Paris举办了一场技术讲座,我讲述了Square公司在使用Android fragments时遇到的问题,以及其他人如何避免使用fragments。
在2011年,基于以下原因我们决定在项目中使用fragments:
-
在那个时候,我们还没有支持平板设备-但是我们知道最终将会支持的,Fragments有助于构建响应式UI;
-
Fragments是view controllers,它们包含可测试的,解耦的业务逻辑块;
-
Fragments API提供了返回堆栈管理功能(即把activity堆栈的行为映射到单独一个activity中);
-
由于fragments是构建在views之上的,而views很容易实现动画效果,因此fragments在屏幕切换时具有更好的控制;
-
Google推荐使用fragments,而我们想要我们的代码标准化;
自从2011年以来,我们为Square找到了更好的选择。
关于fragments你所不知道的
复杂的生命周期
Android中,Context是一个上帝对象(god object),而Activity是具有附加生命周期的context。具有生命周期的上帝对象?有点讽刺的意味。Fragments不是上帝对象,但它们为了弥补这一点,实现了及其复杂的生命周期。
Steve Pomeroy为Fragments复杂的生命周期制作了一张图表看起来并不可爱:
上面Fragments的生命周期使得开发者很难弄清楚在每个回调处要做什么,这些回调是同步的还是异步的?顺序如何?
难以调试
当你的app出现bug,你使用调试器并一步一步执行代码以便了解到底发生了什么,这通常能很好地工作,直到你遇到了FragmentManagerImpl:它是地雷。
下面这段代码很难跟踪和调试,这使得很难正确的修复app中的bug:
switch (f.mState) {
case Fragment.INITIALIZING:
if (f.mSavedFragmentState != null) {
f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG);
f.mTarget = getFragment(f.mSavedFragmentState,
FragmentManagerImpl.TARGET_STATE_TAG);
if (f.mTarget != null) {
f.mTargetRequestCode = f.mSavedFragmentState.getInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
}
f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
if (!f.mUserVisibleHint) {
f.mDeferStart = true;
if (newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
}
}
// ...
}
如果你曾经遇到屏幕旋转时旧的unattached的fragment重新创建,那么你应该知道我在谈论什么(不要让我从嵌套fragments讲起)。
正如Coding Horror所说,根据法律要求我需要附上这个动画的链接。
经过多年深入的分析,我得到的结论是WTFs/min = 2^fragment的个数。
View controllers?没这么快
由于fragments创建,绑定和配置views,它们包含了大量的视图相关的代码。这实际上意味着业务逻辑没有和视图代码解耦-这使得很难针对fragments编写单元测试。
Fragment事务
Fragment事务使得你可以执行一系列fragment操作,不幸的是,提交事务是异步的,而且是附加在主线程handler队列尾部的。当你的app接收到多个点击事件或者配置发生变化时,将处于不可知的状态。
class BackStackRecord extends FragmentTransaction {
int commitInternal(boolean allowStateLoss) {
if (mCommitted)
throw new IllegalStateException("commit already called");
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
}
Fragment创建魔法
Fragment实例可以由你或者fragment manager创建。下面代码似乎很合理:
DialogFragment dialogFragment = new DialogFragment() {
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { ... }
};
dialogFragment.show(fragmentManager, tag);
然而,当恢复activity实例的状态时,fragment manager可能会尝试通过反射机制重新创建这个fragment类的实例。由于这是一个匿名内部类,它的构造函数有一个隐藏的参数,持有外部类的引用。
android.support.v4.app.Fragment$InstantiationException:
Unable to instantiate fragment com.squareup.MyActivity$1:
make sure class name exists, is public, and has an empty
constructor that is public
Fragments的经验教训
尽管存在缺点,fragments教给我们宝贵的教训,让我们在编写app的时候可以重用:
-
单Activity界面:没有必要为每个界面使用一个activity。我们可以分割我们的app为解耦的组件然后根据需要进行组合。这使得动画和生命周期变得简单。我们可以把组件代码分割成视图代码和控制器代码。
-
返回栈不是activity特性的概念;我们可以在一个activity中实现返回栈。
-
没有必要使用新的API;我们所需要的一切都是早就存在的:activities,views和layout inflaters。
-