众所周知在Android开发中Fragment的生命周期非常复杂,复杂得甚至让Square公司提出了我为什么主张反对使用Android Fragment转而提倡使用自定义View组合替代Fragment。但是没办法公司项目还是使用了很多Fragment嵌套。遇到问题还是需要自己去处理的。
这里以Fragment的状态保存和恢复(即重建)来讨论一些关于Fragment的生命周期问题。
不知道各位有没有写过下面,类似的代码。
public class TabActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
SectionsPagerAdapter mSectionsPagerAdapter =
new SectionsPagerAdapter(getSupportFragmentManager());
ViewPager mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
}
//交互的接口定义
public interface OnInteractionListener {
void action(String action);
}
public static class PlaceholderFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "section_number";
public PlaceholderFragment() {
}
private OnInteractionListener listener;
public void setListener(OnInteractionListener listener) {
this.listener = listener;
}
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_tab, container, false);
TextView textView = (TextView) rootView.findViewById(R.id.section_label);
textView.setText(
getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
//使用接口 和Activity交互
listener.action("fragment response view is build");
return rootView;
}
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* 在get方法中 返回Fragment实例 并设置接口回调
* @param position
* @return
*/
@Override public Fragment getItem(int position) {
Logger.d("position = "+position);
PlaceholderFragment fragment = PlaceholderFragment.newInstance(position + 1);
//注入接口实例 打印输出
fragment.setListener(new OnInteractionListener() {
@Override public void action(String action) {
Logger.d(action);
}
});
return fragment;
}
@Override public int getCount() {
return 3;
}
@Override public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "SECTION 1";
case 1:
return "SECTION 2";
case 2:
return "SECTION 3";
}
return null;
}
}
}
基本思路是使用ViewPager显示Fragment,使用FragmentPagerAdapter管理Fragment,并在创建的地方加入外部接口实例注入过程。
这段代码看起来没什么问题,跑起来也没有问题。但是有很大隐患。
首先代码是使用Android Studio自动生成的,操作如下。我在原有基础上加上了Fragment和Activity的通过接口交互的操作(有注释的部分代码)。
如果按上一篇提到的开发者选项->开启不保留活动
测试切换后的Activity恢复重建。然后程序就崩溃了!!,原因竟然是NullPointerException空指针,因为交互的接口实例为空。
因为接口实例的注入在FragmentPagerAdapter
的getItem
中完成,我们发现问题的入口就是FragmentPagerAdapter。
FragmentPagerAdapter:只贴出相关源码
public abstract class FragmentPagerAdapter extends PagerAdapter {
private final FragmentManager mFragmentManager;
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
}
从源码中发现instantiateItem
中调用getItem(int
position)是有条件的。只有当上一步尝试在FragmentManager
抽象类中查找不到特定的Fragment时才会调用,去子类中获取Fragment实例。
而FragmentManager
是由外部注入的,注入的是抽象定义没有实现代码,且findFragmentByTag
方法是怎么样根据name去查找Fragment的。
我们得到以下两个问题:
1:FragmentManager的实例是什么?
2:findFragmentByTag查找的是什么?
查看源码我们来到FragmentActivity,它的主要功能就是对嵌套在他内部的Fragment进行管理。
FragmentActivity:
public class FragmentActivity {
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
//FragmentActivity对内部状态的保存操作
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
}
}
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
*/
public FragmentManager getSupportFragmentManager() {
return mFragments.getSupportFragmentManager();
}
由以上这些代码,我们可以知道对于Fragment的控制,FragmentActivity其实是通过FragmentController
实现的
FragmentController
public class FragmentController {
/**
* Returns a {@link FragmentManager} for this controller.
*/
public FragmentManager getSupportFragmentManager() {
return mHost.getFragmentManagerImpl();
}
}
通过这行代码我们最终来到了目的地
FragmentManagerImpl:它是FragmentManager抽象类的具体实现类
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
ArrayList mActive;
ArrayList mAdded;
@Override
public Fragment findFragmentByTag(String tag) {
if (mAdded != null && tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
if (mActive != null && tag != null) {
// Now for any known fragment.
for (int i=mActive.size()-1; i>=0; i--) {
Fragment f = mActive.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
return null;
}
}
findFragmentByTag:是从内部持有的Fragment集合中根据tag名称查找的。
我们通过源码回答了前面提出的两个问题,但是还是没有弄清楚崩溃的原因。
但是我们发现ArrayList
内部变量。根据对备忘录模式的理解,肯定存在对该变量的备忘录封装操作。
回到前面的部分FragmentActivity对内部状态的保存操作
我们看看FragmentManagerImpl关于备忘录模式的实现,篇幅有限只看保存操作。
FragmentManagerImpl:
Parcelable saveAllState() {
//省略其他操作 只看关键代码
// First collect all active fragments.
int N = mActive.size();
FragmentState[] active = new FragmentState[N];
for (int i=0; i
Fragment的备忘录对象实现:
final class FragmentState implements Parcelable {
final String mClassName;
final int mIndex;
final boolean mFromLayout;
final int mFragmentId;
final int mContainerId;
final String mTag;
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
final boolean mHidden;
Bundle mSavedFragmentState;
Fragment mInstance;
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
mHidden = frag.mHidden;
}
}
FragmentManager的备忘录实现:
final class FragmentManagerState implements Parcelable {
FragmentState[] mActive;
int[] mAdded;
BackStackState[] mBackStack;
public FragmentManagerState() {
}
}
所以基于以上的Fragment的备忘录模式的实现,Android系统能够保证当FragmentActivity被销毁后,重新返回时的重建。恢复到离开时的状态。
在看了这么多源码之后,重新返回思考刚才的重建后崩溃NPE问题,分析情景如下:
onCreate(Bundle savedInstanceState)
方法中Bundle有被保存下来的数据,FragmetManager被恢复成之前的状态,Fragment容器中有内容。findFragmentByTag
得到Fragment实例。知道了问题发生的原因,解决方案的思路就很清晰,因为数据的重建恢复完全由源码完成,我们所能做的就是配合源码的执行,在适当的生命周期添加适当的代码。
配合Fragment的生命周期,改变外部对象的注入方式,比如这样
Activity实现接口
public class TabActivity extends AppCompatActivity implements OnInteractionListener {
}
Fragment从生命周期中获取外部对象
public static class PlaceholderFragment extends Fragment {
@Override public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnInteractionListener){
listener= (OnInteractionListener) context;
}
}
优点:可以很简单的实现解决问题,代码量比较少。
缺点:Fragmen和Activity的接口实现方式隐式绑定,不容易理解。且对于ViewPager这样的,还需要再添加代码集中控制Fragment集合。
根据FragmentPagerAdapter的源码,重新定义创建和绑定过程
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Created by LiCola on 2017/6/5. 按照FragmentActivity和FragmentPagerAdapter
* 对子Fragment的生命周期和重建的顺序特性抽象的父类,
* 建议项目中所有的有关FragmentPagerAdapter 都直接继承该抽象类,或按照该思路管理Fragment
*/
public abstract class FragmentPagerRebuildAdapter extends FragmentPagerAdapter {
private final T PLACE_FRAGMENT = null;
protected final int pageSize;
protected List fragments;
public FragmentPagerRebuildAdapter(FragmentManager fm, int pageSize) {
super(fm);
this.pageSize = pageSize;
fragments = loadPlaceFragment(pageSize);
}
/**
* 根据位置参数创建并返回一个Fragment实例 该方法FragmentActivity在新建Fragment时调用,销毁后重建时不会调用
*
* @param position 位置参数
* @return 创建好的Fragment实例
*/
protected abstract T createFragment(int position);
/**
* 操作某个Fragment,设置或绑定操作或数据 该方法,新建或重建都调用
*
* @param fragment 对某个位置的Fragment
* @param position 某个位置的位置参数
*/
protected abstract void bindFragment(T fragment, int position);
private void unbindFragment(T fragment, int position) {
}
public List getFragmentList() {
return fragments;
}
/**
* 根据传入位置 得到Fragment
*/
@Nullable
public T getFragmentByPosition(int position) {
if (fragments == null || fragments.size() == 0 || position >= fragments.size()) {
return null;
}
return fragments.get(position);
}
/**
* 得到ViewPager当前页的Fragment
*/
@Nullable
public T getFragmentByCurrentItem(ViewPager viewPager) {
if (viewPager == null) {
return null;
}
return getFragmentByPosition(viewPager.getCurrentItem());
}
/**
* 获取实例方式 该方法的position可能会乱序输入,所以使用set方式
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
bindFragment((T) object, position);
fragments.set(position, (T) object);
return object;
}
@Override
public Fragment getItem(int position) {
Fragment fragment = createFragment(position);
if (fragment == null) {
throw new UnsupportedOperationException("createFragment(position="
+ position
+ " 没有返回Fragment实例),检查代码确保createFragment方法覆盖所有position");
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
unbindFragment((T) object, position);
}
@Override
public int getCount() {
return pageSize;
}
/**
* 初始化List集合,并使用占位对象填充,否则无法使用ArrayList的set直接填充指定位置的数据
* @param pageSize
* @return
*/
private List loadPlaceFragment(int pageSize) {
if (pageSize <= 0) {
throw new IllegalArgumentException("FragmentPagerRebuildAdapter pageSize<=0");
}
ArrayList placeList = new ArrayList<>(pageSize);
for (int i = 0; i < pageSize; i++) {
placeList.add(PLACE_FRAGMENT);
}
return placeList;
}
}
优点:逻辑清晰,子类只需要根据提示实现抽象方法,且提供了容器控制。
缺点:需要确定Fragment数量,不能改变数量,对现有代码修改比较大。
水平有限,错误之处还望大家多多指正