android fragment
- 我们会经常被推荐使用fragment,但是当我们看到一个activity中啥也没做,就只是添加了一个fragment时,我们可能会疑惑,这样子我还不如一个activity就搞定了,为何还要用fragment
- 但是当你面临一个新需求,现在要把两个activity的界面组合在一起,比如平板由于界面比较大,完全可以在一屏显示两个activity的界面,此时你如果之前是按照fragment的写法,那么面对这个需求你能很轻松就实现了,但如果你之前只按照activity的写法,你能怎么做,重新写一个activity来组合之前两个activity的代码吗?
- 现在你应该知道fragment的重要性了吧
Activity和Fragment的生命周期
onResume
- fragment的onResume并不是指fragment对用户可见时就会触发,这个onResume只是同它attach的Activity的onResume一样的,因此你别指望用onResume来作为fragment是否对用户可见的判断
popBackStack
- 当我们调用addToBackStack时,fragment就会入栈,那么怎么将fragment出栈呢
- 这就需要用到popBackStack,在没有任何参数的情况下popBackStack会把栈顶出栈
- 当然我们也可以通过名字指定从哪里开始出栈,而这个名字就是addToBackStack时设置的那个名字,一般我们都以类名作为名字
- popBackStack并不会立即执行,如果你想立马执行,可以使用popBackStackImmediate或executePendingTransactions
- popBackStack还有个flag的参数设置,这个设为0的话,表示只出栈其之上的,它自身还留在栈中,如果是POP_BACK_STACK_INCLUSIVE的话,则表示将其自身以及自身之上的全都出栈
问答
fragment和activity如何相互调用
- 在fragment中可以通过**getActivity()**方法获得其附着的Activity的引用,然后就可以调用Activity的相关方法了,如
View listView = getActivity().findViewById(R.id.list);
- 也可以在onAttach时来实例自定义接口,如
public static class FragmentA extends ListFragment {
OnArticleSelectedListener listener;
...
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
listener = (OnArticleSelectedListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener");
}
}
...
}
- 在Activity中可以通过FragmentManager的 findFragmentById() 或 findFragmentByTag() 或 popBackStack() 或 addOnBackStackChangedListener() 来管理fragment
调用remove()或replace()方法时,原先的fragment会被销毁吗
- 会不会销毁得看你的fragment在事务中有没有调用addToBackStack(),如果没有调用,则原先的fragment会被销毁(即执行到onDetach),否则只会执行到onDestroyView
add()和replace()有什么区别
- 可以这么理解,add就像是图层叠加,如果你的fragment背景是透明的,当你add多个fragment,你会看到画面叠加在一起
- replace就相当于把前面已经有的图层移走,因此即使你replace的fragment背景是透明的,也看不到前面的图层的画面
- add()方法不会销毁原先的fragment,而replace()则有可能会销毁原先的fragment,为什么说有可能呢,这是因为销不销毁得看原先的fragment在replace()的事务中有没有调用addToBackStack(),有的话那就不会销毁
怎么知道堆栈中还有多少个fragment
- 可以通过getSupportFragmentManager().getBackStackEntryCount()或者getSupportFragmentManager().getFragments().size()获得
Activity中的fragment什么时候会被销毁
- 当Activity被销毁时,则附着于此Activity的fragment都会被销毁,值得注意的是Activity先于fragment被销毁
addToBackStack()的作用是什么
- 在add或remove或replace片段(fragment)时,如果调用了addToBackStack方法,则当按物理返回键时会执行回退动作,例如依次加入了a,b,c,则返回时会回到b,再回到a再退出
如何在fragment之间传递数据
- 推荐使用ViewModel在fragment之间共享数据,其他方式可以参考官网说法
- 在 Fragment 之间传递数据 | Android 开发者 | Android Developers
fragment中的getContext和getActivity有何区别
- 在androidx.fragment.app.Fragment中,
@Nullable
final public FragmentActivity getActivity() {
return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}
@Nullable
public Context getContext() {
return mHost == null ? null : mHost.getContext();
}
- 从代码中我们可以看到getContext和getActivity都和mHost有关,因此只要mHost为null,getContext和getActivity也就为null了,而mHost是在attach时被赋值的,在detach时被置为null的,因此只要fragment生命周期执行到了onDetach,我们就不能再使用getContext和getActivity了,否则就会产生空指针异常
- 上面我们已经知道getContext和getActivity都和mHost有关,那么mHost是个什么类型的对象呢
- 翻看源码可以知道mHost是FragmentHostCallback的对象实例,于是转向查看FragmentHostCallback的构造函数
- 在androidx.fragment.app.FragmentHostCallback中,
public FragmentHostCallback(@NonNull Context context, @NonNull Handler handler,
int windowAnimations) {
this(context instanceof Activity ? (Activity) context : null, context, handler,
windowAnimations);
}
FragmentHostCallback(@NonNull FragmentActivity activity) {
this(activity, activity /*context*/, new Handler(), 0 /*windowAnimations*/);
}
FragmentHostCallback(@Nullable Activity activity, @NonNull Context context,
@NonNull Handler handler, int windowAnimations) {
mActivity = activity;
mContext = Preconditions.checkNotNull(context, "context == null");
mHandler = Preconditions.checkNotNull(handler, "handler == null");
mWindowAnimations = windowAnimations;
}
- 我们可以从构造函数中得知,其实getContext和getActivity都是同一个对象,当你打印日志的时候也能发现他们都是同一个对象
- 而且在onAttach回调时getContext和getActivity就不为null了,可以正常使用了
销毁时是Activity先销毁还是Fragment先销毁
- 如果fragment没有加入回退栈中,则当我们跟踪生命周期会发现Activity先执行了onDestroy,然后才轮到Fragment的onDestroyView
- 也就是可能跟我们想的不太一样,Activity先被销毁了,附加在此Activity上的Fragment才被销毁
当屏幕旋转时,Fragment被实例多次,该怎么解决
- 当屏幕旋转时,我们会发现fragment被创建了两个,再旋转回来时,此时fragment已经有3个了,也就是旋转n次,就会有n+1个fragment,
- 这种情况不管是add和replace都会出现,不同的是add会导致画面叠加,所以你看灰色文字会变深,而replace则不会导致画面叠加,灰色文字还是原来的灰色
- 而且这种情况跟是否有addToBackStack没关系,只要屏幕旋转,fragment实例对象就会增加一个
- 当你屏幕旋转两次时,你会在日志中发现总共创建了6个fragment实例对象
- 造成这种现象的原因是系统由于配置变化(如屏幕旋转)或内存不足杀掉原先的Activity再重启Activity时会还原原先的fragment,如果在Activity的onCreate中没有判断这种重启的情况,则会产生多个fragment实例,一个是系统自动恢复产生的,一个是onCreate中再次创建的
- 知道原因后可以在onCreate中通过findFragmentByTag来判断fragment是否存在,存在则不要重新创建了,例如
if (savedInstanceState != null) {
Fragment fragment = mFragmentManager.findFragmentByTag(AFragment.class.getSimpleName());
if (fragment != null) {
mFragmentManager.beginTransaction()
.show(fragment)//这里直接show即可,如果用add则会抛异常,因为之前已经add过了,同一个实例不能add两次
.commit();
}
} else {
mFragmentManager.beginTransaction()
.replace(R.id.frag_container, AFragment.newInstance("", ""), AFragment.class.getSimpleName())
.commit();
}
- 另外在fragment中的onCreate方法中可以调用 setRetainInstance(true)来保留fragment,这样配置发生变化时fragment还能保持原来那个实例
class RetainFragment extends Fragment {
private static final String TAG = "RetainFragment";
public RetainFragment() {}
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new RetainFragment();
fm.beginTransaction().add(fragment, TAG).commit();
}
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
如何获得前台fragment
/**
* 获得当前栈顶fragment,只适用于addToBackStack是指定的name和add/replace时指定的tag一致的情况
*
* @return
*/
public Fragment getTopFragment() {
int n = fragmentManager.getBackStackEntryCount();
if (n > 0) {
FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(n - 1);
return fragmentManager.findFragmentByTag(entry.getName());
}
return null;
}
有时发现fragment的内容会顶到状态栏去
- 遇到这种情况在根布局加上android:fitsSystemWindows=“true” 即可
其他
- 有时你可能遇到如下错误:
Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]
- 这是因为你重载了了fragment的构造方法,但是在一些情况下,如屏幕翻转时,fragment被重新创建,就可能会造成数据丢失
- 因此参数传递推荐使用Fragment.setArguments