Fragment基础
Fragment 生命周期
Fragment 生命周期简单版
- onAttach():Fragment 和 Activity 相关联时调用。可以通过该方法获取 Activity 引用,还可以通过 getArguments() 获取参数。
- onCreate():Fragment 被创建时调用
onActivityCreated():当 Activity 完成 onCreate() 时调用 - onStart():当 Fragment 可见时调用。
- onResume():当 Fragment 可见且可交互时调用
- onPause():当 Fragment 不可交互但可见时调用。
- onStop():当 Fragment 不可见时调用。
- onDestroyView():当 Fragment 的 UI 从视图结构中移除时调用。
- onDestroy():销毁 Fragment 时调用。
- onDetach():当 Fragment 和 Activity 解除关联时调用。
Fragment 的整个生命周期一直在这 6 个状态中流转,调用对应的生命周期方法然后进入下一个状态,如下图:
Fragment 与 Activity 生命周期
Fragment 的生命周期与 Activity 的生命周期密切相关 Activity 管理 Fragment 生命周期的方式是在 Activity 的生命周期方法中调用 FragmentManager 的对应方法,通过 FragmentManager 将现有的 Fragment 迁移至下一个状态,同时触发相应的生命周期函数。
可以看到 Fragment 比 Activity 多了几个额外的生命周期回调方法:
onAttach(Activity)
当 Fragment 与 Activity 发生关联时调用。
onCreateView(LayoutInflater, ViewGroup,Bundle)
创建该 Fragment 的视图
onActivityCreated(Bundle)
当 Activity 的 onCreate 方法返回时调用
onDestoryView()
与 onCreateView 想对应,当该 Fragment 的视图被移除时调用
onDetach()
与 onAttach 相对应,当 Fragment 与 Activity 关联被取消时调用
注意:除了 onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,
创建和重建过程: Activity 生命周期优先于 Fragment,onCreate/onStart/onResume, Fragment 在 Activity 之后
暂停和销毁过程: Fragment 生命周期优先于 Activity,onDestroy/onStop/onPause Fragment 在 Activity 之前
使用 add & replace 两种方式的生命周期变化
第一个 MyFragment 使用 Add 进容器。然后分别使用 Add 和 Replace 添加 My2Fragment。然后观察 MyFragment 和 My2Fragment 各自生命周期变化。
先看 Add transaction.add(R.id.container,fragment_2) 方式输出信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2021-08-07 11:07:13.310 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onAttach
2021-08-07 11:07:13.311 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onCreate
2021-08-07 11:07:13.318 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onViewCreated
2021-08-07 11:07:13.318 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onActivityCreated
2021-08-07 11:07:13.318 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onViewStateRestored
2021-08-07 11:07:13.318 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onStart
2021-08-07 11:07:13.324 7510-7510/com.example.myfirstproject D/MyFragment-TEST: onResume
2021-08-07 11:07:20.033 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onAttach
2021-08-07 11:07:20.034 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onCreate
2021-08-07 11:07:20.053 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onViewCreated
2021-08-07 11:07:20.053 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onActivityCreated
2021-08-07 11:07:20.053 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onViewStateRestored
2021-08-07 11:07:20.053 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onStart
2021-08-07 11:07:20.053 7510-7510/com.example.myfirstproject D/My2Fragment-TEST: onResume
可以看出使用 Add 添加 Fragment2,Fragment1 和 Fragment2 互不影响。
再看看 Relace transaction.replace(R.id.container, fragment_2) 方式输出信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2021-08-07 11:09:09.712 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onAttach
2021-08-07 11:09:09.713 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onCreate
2021-08-07 11:09:09.723 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onViewCreated
2021-08-07 11:09:09.723 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onActivityCreated
2021-08-07 11:09:09.723 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onViewStateRestored
2021-08-07 11:09:09.724 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onStart
2021-08-07 11:09:09.730 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onResume
2021-08-07 11:09:22.321 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onAttach
2021-08-07 11:09:22.321 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onCreate
2021-08-07 11:09:22.324 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onPause
2021-08-07 11:09:22.324 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onStop
2021-08-07 11:09:22.325 7806-7806/com.example.myfirstproject D/MyFragment-TEST: onDestroyView
2021-08-07 11:09:22.346 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onViewCreated
2021-08-07 11:09:22.346 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onActivityCreated
2021-08-07 11:09:22.346 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onViewStateRestored
2021-08-07 11:09:22.346 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onStart
2021-08-07 11:09:22.347 7806-7806/com.example.myfirstproject D/My2Fragment-TEST: onResume
上面需要注意的地方我已经用空行分隔开了,注意看使用 Replace 添加 Fragment2,Fragment1 走了销毁 onDestroyView 的生命周期。
也就是:replace 相比如 add,replace 之前的 fragment 是走的销毁生命周期。
Fragment 与 Activity 交互生命周期 网络图
Fragment 的 add(),remove(),show(),hide(),replace(),attach(),detach() 生命周期表现
代码托管:
http://git.oschina.net/zengfansheng/FragmentDemo
1、show() hide() (保存Fragment实例和View状态)
show()
显示之前隐藏的 Fragmenthide()
隐藏当前的 Fragment,仅仅是设置为不可见,并不会销毁;如果之前的 Fragment 中有 EditText,再次 show 时,数据还在。
进行 show() 和 hide(),
Fragment的实例(数据)和View
都在,仅仅是 Fragment 的 view 的显示和隐藏,生命周期没有变化
添加 Fragment1
添加 Fragment2,Fragment1 生命周期没有变化,只是 Fragment1 的 View 隐藏了
note
加上回退栈时,如果回退栈有多个 Fragment,那么可能会出现重影,监听回退栈退出情况?
2、attach()、detach() (保存Fragment实例,不保存View状态)
attach()
重建 UI 视图,附件到 UI 上并显示detach()
将 View 从 UI 中移除,和 remove() 不同,此时 Fragment 的状态依然由 FragmentManager 维护
detach() 不会走生命周期的 onDetach() 方法;detach() 后数据会丢失;此时还是由 FragmentManager 维护状态
先击 Fragment1,输入数据;再点击 Fragment2,输入数据
再点击 Fragment1,Fragment1 的数据已经丢失,没有走 onDetach() 方法
3、add() remove() (Fragment实例和View状态都不保存)
add()
往 Activity 中添加一个 Fragmentremove()
从 Activity 中移除一个 Fragment
note:
如果被移除的 Fragment 没有添加到回退栈,这个 Fragment 实例将会被销毁,会走 onDetach() 方法; 如果加入到了回退栈中,那么只会走到 onDestroyView() 方法,不会走 onDetach() 方法。
进行 remove(),
Fragment的实例和View
都不存在,会走 onDetach() 方法
先击 Fragment1,输入数据;再点击 Fragment2,输入数据
再点击 Fragment1,Fragment1 数据已经丢失,且走了 onDetach() 方法
4、replace() (先remove再add)
每次都会 new fragment;使用另外一个 Fragment 替换当前的,实际上就是 remove() 然后 add() 的合体
- 1)会销毁 Fragment 重新走生命周期;但如何加上 FramentTransition. addToBackStack(), 不会销毁 fragment1,只会 onDestroyView,Fragment 的实例数据还是会保存的,只会销毁 view 状态
- 2)创建 fragment2; 会造成 GC 不断回收对象以及创建对象。 还会造成什么问题?fragment 销毁和创建会浪费资源 — 内存、数据清空重新加载了。 推荐不使用 replace,使用 hide 和 show
先击 Fragment1,输入数据;再点击 Fragment2,输入数据
再点击 Fragment1,Fragment1 数据已经丢失
note
注意加上回退栈,同 add/remove
5、小结
- 既销毁 Fragmet 实例又销毁 Fragment 视图
会走 onDestroyView() 和 onDetach() 方法
- remove()
- replace() 其实就是先 remove 再 add
- 只销毁 Fragment 视图
只走 onDestroyView(),不走 onDetach() 方法
* detach()
- 既不销毁 Fragment 实例也不销毁 Fragment 视图,仅仅只是隐藏 Fragment 的 View
生命周期方法不变
1
2
* hide()
* show()
- 在 FragmentA 中的 EditText 填了一些数据,当切换到 FragmentB 时,如果希望会到 A 还能看到数据,则适合你的就是 hide 和 show;也就是说,希望保留用户操作的面板,你可以使用 hide 和 show,当然了不要使劲在那 new 实例,进行下非 null 判断。
- 我不希望保留用户操作,你可以使用 remove(),然后 add();或者使用 replace() 这个和 remove,add 是相同的效果
- remove 和 detach 有一点细微的区别,在不考虑回退栈的情况下,remove 会销毁整个 Fragment 实例,而 detach 则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前 Activity 一直存在,那么在不希望保留用户操作的时候,你可以优先使用 detach
核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public void f1(View v) {
FragmentTransaction fragmentTransaction = manager.beginTransaction();
Fragment f1 = manager.findFragmentByTag("f1");
fragmentTransaction.replace(R.id.framelayout, new Fragment1Fg());
fragmentTransaction.addToBackStack("hacket");
fragmentTransaction.commit();
Log.d(TAG, "f1: replace() addToBackStack()");
// if (f1 != null) {
// fragmentTransaction.show(f1);
// Log.d(TAG, "f1: not null,show()");
// // fragmentTransaction.attach(f1);
// // Log.d(TAG, "f1: not null,attach()");
// } else {
// f1 = new Fragment1Fg();
// fragmentTransaction.add(R.id.framelayout, f1, "f1");
// Log.d(TAG, "f1: is null,add()");
// }
//
// if (manager.findFragmentByTag("f2") != null) {
// Fragment f2 = manager.findFragmentByTag("f2");
// // fragmentTransaction.hide(f2); // hide
// fragmentTransaction.remove(f2); // remove
// // fragmentTransaction.detach(f2); // detach
// Log.d(TAG, "f1: remove f2");
// }
//
// if (manager.findFragmentByTag("f3") != null) {
// Fragment f3 = manager.findFragmentByTag("f3");
// // fragmentTransaction.hide(f3);
// fragmentTransaction.remove(f3);
// // fragmentTransaction.detach(f3);
// Log.d(TAG, "f1: remove f3");
// }
int commit = fragmentTransaction.commit();
Log.d(TAG, "f1: commit:" + commit);
// int i = fragmentTransaction.commitAllowingStateLoss();
}
Ref
Fragment 相关 API 的理解
FragmentActivity
宿主 Activity
FragmentManager
FragmentActivity 的 FragmentManager 是处理 FragmentTransaction 的而不是处理 Fragment,如下面的一个 FragmentTransaction 既包含 add 操作又包含 replace 操作。
- Fragment findFragmentById(@IdRes int id) 适用于在静态 fragment,不常用
- Fragment findFragmentByTag(String tag) 适用于动态加载的 Fragment,常用
- FragmentTransaction beginTransaction() 开启事务
- void popBackStack() 将 Fragment 从后台堆栈中弹出 (模拟用户按下 BACK 命令)
FragmentTransaction
保存 Fragment 操作的原子性,事务
- BackStack 内部的一个 Transaction 可以包含一个或多个和 Fragment 相关的操作。
1
2
3
4
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(restId, fragmentA);
ft.replace(fragmentB);
ft.commit();
- FragmentTransitions 默认并不会主动加入到 backstack 中,除非开发者调用了 addToBackStack(String tag) 方法。参数 ‘tag’ 将作为本次加入 BackStack 的 Transaction 的标志。
1
2
3
4
5
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(resId, fragmentA);
ft.replace(resId, fragmentB);
ft.addToBackstack("tag");
ft.commit();
- addToBackstack(name) 添加这个 fragment transaction 到回退栈
- setTransition(int transit) 应用动画
- commit() 调用 commit() 后,事务并不会马上执行。它会在 activity 的 UI 线程(其实就是主线程)中等待直到线程能执行的时候才执行(废话)。如果必要,你可以在 UI 线程中调用 executePendingTransactions() 方法来立即执行事务。但一般不需这样做,除非有其它线程在等待事务的执行。
- commitAllowingStateLoss()
警告:你只能在 activity 处于可保存状态的状态时,比如 running 中,onPause() 方法和 onStop() 方法中提交事务,否则会引发异常。这是因为 fragment 的状态会丢失。如果要在可能丢失状态的情况下提交事务,请使用 commitAllowingStateLoss()。
setReorderingAllowed (boolean)
setRetainInstance(boolean)
在 Activity 重新创建的时候可以不完全销毁 Fragment , 以便 Fragment 可以恢复。
- 一般在 Fragment 的 onCreate() 方法中调用 setRetainInstance(boolean) 最佳。
- 设置为 true 后 , Fragment 恢复会跳过 onAttach()→onCreate() 和 onDestroy()→onDetach() , 所以不要在 onCreate() 放初始化逻辑。
关于 Fragment,我们发现 setRetainInstance
方法经常被用到,那么这个方法的作用是什么呢?我们看看官方的解释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Control whether a fragment instance is retained across Activity
* re-creation (such as from a configuration change). This can only
* be used with fragments not in the back stack. If set, the fragment
* lifecycle will be slightly different when an activity is recreated:
* <ul>
* <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
* will be, because the fragment is being detached from its current activity).
* <li> {@link #onCreate(Bundle)} will not be called since the fragment
* is not being re-created.
* <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
* still be called.
* </ul>
*/
结合方法名以及方法的解释,可以知道一旦我们设置 setRetainInstance(true)
,意味着在 Activity 重绘时,我们的 Fragment 不会被重复绘制,也就是它会被 “ 保留 “。为了验证其作用,我们发现在设置为 true
状态时,旋转屏幕,Fragment 依然是之前的 Fragment。而如果将它设置为默认的 false,那么旋转屏幕时 Fragment 会被销毁,然后重新创建出另外一个 fragment 实例。并且如官方所说,如果 Fragment 不重复创建,意味着 Fragment 的 onCreate
和 onDestroy
方法不会被重复调用。所以在旋转屏 Fragment 中,我们经常会设置 setRetainInstance(true)
,这样旋转时 Fragment 不需要重新创建。
如果你的 App 恰好可以不做转屏,那么你可以很省事的在 Manifest 文件中添加标注,强制所有页面使用竖屏/横屏。如果你的页面不幸的需要支持横竖屏切换,那么你在预估工作量或者给客户报价时一定要考虑到。虽然加入转屏支持不会导致工作量翻倍,但是却有可能引起许多问题。尤其当页面有很多业务逻辑,有状态值的时候。所以我们在项目开发过程中,应该知道什么时候需要考虑状态保存,当状态保存出现问题时,应该怎么解决之。
Fragment 中的 onActivityForResult()
一层 Fragment
不要用 getActivity().startActivityForResult() 方法将无法获取到相关的返回数据。
要使用 Fragment 自身提供的一个方 fragment.startActivityForResult() 或者 this.startActivityForResult() 方法跳转到一个新的 Activity,然后在 Fragment 中调用 fragment.onActivityResult() 就可以处理返回的相关数据。
嵌套 Fragment,如 ViewPager
1
2
3
4
5
6
7
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
fragment.onActivityResult(requestCode,resultCode,data);
}
}
finish() 要在 setResult 后面
Fragment commit 几种方式
commit()
【异步提交】不会立即执行,commit() 后,在主线程中异步调用;不要在 activity save state 之后保存,否则会出现异常
返回值:如果调用了 addToBackStack(),返回 entry;否则返回一个负数
commitAllowingStateLoss()
【异步提交】允许 activiy save state 之后提交 transaction,
commitNow()
【同步提交】
这种方式比 commit 后,再调用 executePendingTransactions() 更好
1
2
commit()
executePendingTransactions()
这种方式不能被加入到 back stack 中去,如果之前调用了 addToBackStack,会抛出异常 IllegalStateException
也只能在 activity save state 之前调用,否则会抛异常
executePendingTransactions()
只能主线程调用,会将所有 pending 的操作提交,如果你不加入到 back stack,并且只有一个 transition,考虑用 commitNow
,可以避免其他 pending 影响
要保证 postponeEnterTransition()
被调用了
Fragment onKeyDown 处理
Fragment
1
2
3
interface IKeyDown {
fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean
}
在 BaseActivity 的 onKeyDown 或者,分发给 Fragment
1
2
3
4
5
6
7
8
9
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean ret = false;
ret = activityParseOnkey(keyCode);
if (!ret) {
ret = mCurFragment.onKeyDown(event); //这里的mCurFragment是我们前的Fragment
}
return ret;
}
Fragment 实现 IKeyDown 接口
DialogFragment
1
2
3
4
5
6
7
8
9
this.getDialog().setOnKeyListener(new OnKeyListener()
{
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event){
if (keyCode == KeyEvent.KEYCODE_SEARCH)
return true; // pretend we've processed it
else
return false; // pass on to be processed as normal
}
});
return false 和 return true 是是否允许事件下传,return true 是中断事件
Fragment 回退栈
介绍
类似与 Android 系统为 Activity 维护一个任务栈,我们也可以通过 Activity 维护一个回退栈来保存每次 Fragment 事务发生的变化。如果你将 Fragment 任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的 Fragment。一旦 Fragment 完全从后退栈中弹出,用户再次点击后退键,则退出当前 Activity。
如何添加到回退栈
如何添加一个 Fragment 事务到回退栈:
1
FragmentTransaction.addToBackStack(String)
核心代码:
1
2
3
4
5
6
7
FragmentTransaction fragmentTransaction = manager.beginTransaction();
Fragment f1 = manager.findFragmentByTag("f1");
fragmentTransaction.replace(R.id.framelayout, new Fragment1Fg());
fragmentTransaction.addToBackStack("hacket");
fragmentTransaction.commit();
Log.d(TAG, "f1: replace() addToBackStack()");
先击 Fragment1,输入数据;再点击 Fragment2,输入数据
再点击 Fragment1,发现使用 replace(),不会走 onDestroy() 和 onDetach(),说明实例不会被销毁。
注意:使用 android.app.Fragment 添加到回退栈似乎不行,要用 support v4 里面的 Fragment。
Activity 和 Fragment 通信
- 接口方式
- ViewModel 方式
- 使用观察者模式解决单 Activity 与多个 Fragment 通信
https://alphagao.com/2017/03/15/using-observer-pattern-deal-event-between-activity-and-fragments/
Fragment 懒加载
Fragment 常见版本
可以弄个上次加载的时间,来处理时间间隔不够时不加载数据
setUserVisibleHint(ViewPager+Fragment 用)
androidx1.1.0 已置为过期。该方法用于告诉系统,这个 Fragment 的 UI 是否是可见的;在 FragmentPagerAdapter 和 FragmentStatePagerAdapter 中调用,也就是配合 ViewPager 来使用的
Note: 该方法可能在 fragment lifecycle 外调用,无法保证顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class BdLazyFragment extends Fragment {
/**
* 在这里实现Fragment数据的缓加载.
* @param isVisibleToUser true表用户可见,false表不可见
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && getView() != null) {
lazyLoadData();
} else if (getView() != null && !isVisibleToUser) {
onUserInvisible();
}
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = onCreateFragmentView(inflater, container, savedInstanceState);
if (view != null && getUserVisibleHint()) {
lazyLoadData();
}
return view;
}
/**
* 懒加载数据,并在此绑定View数据
*/
protected abstract void lazyLoadData();
protected void onUserInvisible() {
}
protected abstract View onCreateFragmentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
}
onHiddenChanged(add/show/hide 用)
- void onHiddenChanged(boolean hidden)
当 Fragment 隐藏的状态发生改变时,该函数将会被调用,如果当前 Fragment 隐藏, hidden 的值为 true, 反之为 false。最为重要的是 hidden 的值,可以通过调用 isHidden() 函数获取。
通过传入的参数值来判断当前 Fragment 是否对用户可见,只是 onHiddenChanged() 是在 add+show+hide 模式下使用,而 setUserVisibleHint 是在 ViewPager+Fragment 模式下使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class LazyFragment:Fragment(){
private var isLoaded = false //控制是否执行懒加载
override fun onResume() {
super.onResume()
judgeLazyInit()
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
isVisibleToUser = !hidden
judgeLazyInit()
}
private fun judgeLazyInit() {
if (!isLoaded && !isHidden) {
lazyInit()
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
//懒加载方法
abstract fun lazyInit()
}
复杂 Fragment 嵌套的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
abstract class LazyFragment : Fragment() {
/**
* 是否执行懒加载
*/
private var isLoaded = false
/**
* 当前Fragment是否对用户可见
*/
private var isVisibleToUser = false
/**
* 当使用ViewPager+Fragment形式会调用该方法时,setUserVisibleHint会优先Fragment生命周期函数调用,
* 所以这个时候就,会导致在setUserVisibleHint方法执行时就执行了懒加载,
* 而不是在onResume方法实际调用的时候执行懒加载。所以需要这个变量
*/
private var isCallResume = false
/**
* 是否调用了setUserVisibleHint方法。处理show+add+hide模式下,默认可见 Fragment 不调用
* onHiddenChanged 方法,进而不执行懒加载方法的问题。
*/
private var isCallUserVisibleHint = false
override fun onResume() {
super.onResume()
isCallResume = true
if (!isCallUserVisibleHint) isVisibleToUser = !isHidden
judgeLazyInit()
}
private fun judgeLazyInit() {
if (!isLoaded && isVisibleToUser && isCallResume) {
lazyInit()
LogUtils.d("lazyInit:!!!!!!!")
isLoaded = true
}
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
isVisibleToUser = !hidden
judgeLazyInit()
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
isVisibleToUser = false
isCallUserVisibleHint = false
isCallResume = false
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
this.isVisibleToUser = isVisibleToUser
isCallUserVisibleHint = true
judgeLazyInit()
}
abstract fun lazyInit()
}
AndroidX 懒加载
虽然之前的方案就能解决轻松的解决 Fragment 的懒加载,但这套方案有一个最大的弊端,就是不可见的 Fragment 执行了 onResume()
方法。onResume 方法设计的初衷,难道不是当前 Fragment 可以和用户进行交互吗?既不可见,又不能和用户进行交互,你执行 onResume 方法干嘛?
基于此问题,Google 在 Androidx1.1.0
在 FragmentTransaction
中增加了 setMaxLifecycle
方法来控制 Fragment 所能调用的最大的生命周期函数
ViewPager+Fragment 模式下的方案
在 FragmentPagerAdapter 与 FragmentStatePagerAdapter 新增了含有 behavior
字段的构造函数,behavior 可取值:
- 如果 behavior 的值为
BEHAVIOR_SET_USER_VISIBLE_HINT
,那么当 Fragment 对用户的可见状态发生改变时,setUserVisibleHint 方法会被调用。 - 如果 behavior 的值为
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
,那么当前选中的 Fragment 在Lifecycle.State#RESUMED
状态 ,其他不可见的 Fragment 会被限制在Lifecycle.State#STARTED
状态。
使用了 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
后,确实只有当前可见的 Fragment 调用了 onResume 方法。而导致产生这种改变的原因,是因为 FragmentPagerAdapter 在其 setPrimaryItem
方法中调用了 setMaxLifecycle 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
//如果当前的fragment不是当前选中并可见的Fragment,那么就会调用
// setMaxLifecycle 设置其最大生命周期为 Lifecycle.State.STARTED,即上个可见的Fragment不可见了
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
//对于当前可见的Fragment,则设置其最大生命周期为Lifecycle.State.RESUMED
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
只有实际可见的 Fragment 会调用 onResume 方法, 那是不是为我们提供了 ViewPager 下实现懒加载的新思路呢:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在AndroidX1.1.0及以上版本;配合`FragmentPagerAdapter`和`FragmentStatePagerAdapter`使用
abstract class AndroidXLazyFragment : LogFragment() {
private var isLoaded = false
override fun onResume() {
super.onResume()
if (!isLoaded && !isHidden) {
lazyInit()
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
abstract fun lazyInit()
}
add+show+hide 模式下的新方案
- 将需要显示的 Fragment ,在调用 add 或 show 方法后,
setMaxLifecycle(showFragment, Lifecycle.State.RESUMED)
- 将需要隐藏的 Fragment ,在调用 hide 方法后,
setMaxLifecycle(fragment, Lifecycle.State.STARTED)
- LazyFragment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class LazyFragment : Fragment() {
private var isLoaded = false
override fun onResume() {
super.onResume()
//增加了Fragment是否可见的判断
if (!isLoaded && !isHidden) {
lazyInit()
Log.d(TAG, "lazyInit:!!!!!!!")
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
abstract fun lazyInit()
}
- show/hide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 使用add+show+hide模式加载fragment
*
* 默认显示位置[showPosition]的Fragment,最大Lifecycle为Lifecycle.State.RESUMED
* 其他隐藏的Fragment,最大Lifecycle为Lifecycle.State.STARTED
*
*@param containerViewId 容器id
*@param showPosition fragments
*@param fragmentManager FragmentManager
*@param fragments 控制显示的Fragments
*/
private fun loadFragmentsTransaction(
@IdRes containerViewId: Int,
showPosition: Int,
fragmentManager: FragmentManager,
vararg fragments: Fragment
) {
if (fragments.isNotEmpty()) {
fragmentManager.beginTransaction().apply {
for (index in fragments.indices) {
val fragment = fragments[index]
add(containerViewId, fragment, fragment.javaClass.name)
if (showPosition == index) {
setMaxLifecycle(fragment, Lifecycle.State.RESUMED)
} else {
hide(fragment)
setMaxLifecycle(fragment, Lifecycle.State.STARTED)
}
}
}.commit()
} else {
throw IllegalStateException(
"fragments must not empty"
)
}
}
/** 显示需要显示的Fragment[showFragment],并设置其最大Lifecycle为Lifecycle.State.RESUMED。
* 同时隐藏其他Fragment,并设置最大Lifecycle为Lifecycle.State.STARTED
* @param fragmentManager
* @param showFragment
*/
private fun showHideFragmentTransaction(fragmentManager: FragmentManager, showFragment: Fragment) {
fragmentManager.beginTransaction().apply {
show(showFragment)
setMaxLifecycle(showFragment, Lifecycle.State.RESUMED)
//获取其中所有的fragment,其他的fragment进行隐藏
val fragments = fragmentManager.fragments
for (fragment in fragments) {
if (fragment != showFragment) {
hide(fragment)
setMaxLifecycle(fragment, Lifecycle.State.STARTED)
}
}
}.commit()
}
ViewPager2 懒加载
ViewPager2 本身就支持对实际可见的 Fragment 才调用 onResume 方法。关于 ViewPager2 的内部机制
无界面 Fragment 应用场景
无界面 Fragment 库
1、Glide
Glide.with() 方法中传入的是 Activity、FragmentActivity、v4 包下的 Fragment、还是 app 包下的 Fragment,最终的流程都是一样的,那就是会向当前的 Activity 当中添加一个隐藏的 Fragment。那么这里为什么要添加一个隐藏的 Fragment 呢?因为 Glide 需要知道加载的生命周期。很简单的一个道理,如果你在某个 Activity 上正在加载着一张图片,结果图片还没加载出来,Activity 就被用户关掉了,那么图片还应该继续加载吗?当然不应该。可是 Glide 并没有办法知道 Activity 的生命周期,于是 Glide 就使用了添加隐藏 Fragment 的这种小技巧,因为 Fragment 的生命周期和 Activity 是同步的,如果 Activity 被销毁了,Fragment 是可以监听到的,这样 Glide 就可以捕获这个事件并停止图片加载了
- RxPermissions
RxPermissions 也是这样处理的,它内部持有一个 Fragment,这个 Fragment 没有视图,只负责请求权限和返回结果,相当于一个桥梁的作用,我们通过 rxPermissions 发起 request 的时候,其实并不是 activity 去 request,而是通过这个 Fragment 去请求,然后在 Fragment 的 onRequestPermissionsResult 中把结果发送出来,如此来避开 activity 的 onRequestPermissionsResult 方法。
https://github.com/yanzhenjie/AndPermission
AvoidOnResult
系统 lifecycle
无界面 Fragment 应用场景
1、生命周期联动
- RxBus 自动取消订阅
- RxPermissions
2、消除 Activity 中的回调
- onActivityResult
- onRequestPermissionsResult
- 界面的动画自动关闭
- 无界面 Fragment 绑定数据 https://juejin.im/entry/5a51e8f2518825734978bd0c
- 如何避免使用 onActivityResult,以提高代码可读性 https://juejin.im/post/5a4611786fb9a0451a76b565
ActivityNoResult 的 Callback 实现
使用无界面 Fragment 消除 onActivityResult 方式,需要注意需要 FragmentTransition 立即执行,因为 commit 是异步的
1
2
3
4
fm.beginTransaction()
.add(fragment, HolderLifeFragment.FRAGMENT_TAG)
.commitAllowingStateLoss()
fm.executePendingTransactions()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// HolderLifeFragment
class HolderLifeFragment : Fragment() {
private val mCallbacks = HashMap<Int, AvoidOnResult.Callback>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
fun startForResult(intent: Intent, requestCode: Int, callback: AvoidOnResult.Callback) {
mCallbacks.put(requestCode, callback)
startActivityForResult(intent, requestCode)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mCallbacks.remove(requestCode)
?.onActivityResult(requestCode, resultCode, data)
}
companion object {
const val FRAGMENT_TAG = "DEFAULT_FRAGMENT_TAG"
fun of(): HolderLifeFragment {
return HolderLifeFragment()
}
}
}
// AvoidNoResult
class AvoidOnResult private constructor() {
private var holderLifeFragment: HolderLifeFragment? = null
private constructor(activity: FragmentActivity) : this() {
holderLifeFragment = createFragment(activity.supportFragmentManager)
}
private constructor (fragment: Fragment) : this() {
holderLifeFragment = createFragment(fragment.childFragmentManager)
}
private fun createFragment(fm: FragmentManager): HolderLifeFragment? {
var fragment = findFragment(fm, HolderLifeFragment.FRAGMENT_TAG)
if (fragment == null) {
fragment = HolderLifeFragment.of()
fm.beginTransaction()
.add(fragment, HolderLifeFragment.FRAGMENT_TAG)
.commitAllowingStateLoss()
fm.executePendingTransactions()
}
return fragment
}
private fun findFragment(fm: FragmentManager, tag: String): HolderLifeFragment? {
var holderFragment = fm.findFragmentByTag(tag)
if (holderFragment is HolderLifeFragment) {
return holderFragment
}
return null
}
fun startForResult(intent: Intent, requestCode: Int, callback: Callback) {
holderLifeFragment?.startForResult(intent, requestCode, callback)
}
interface Callback {
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
}
companion object {
fun of(act: FragmentActivity): AvoidOnResult {
return AvoidOnResult(act)
}
fun of(act: Fragment): AvoidOnResult {
return AvoidOnResult(act)
}
}
}
使用:
1
2
3
4
5
6
7
8
9
10
var intent = Intent(applicationContext, HolderFragmentActivity结果页::class.java)
intent.putExtra("from", "activity")
AvoidOnResult.of(this)
.startForResult(intent, 110, object : AvoidOnResult.Callback {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
var result = data?.getStringExtra("data")
ToastUtils.showLong("$resultCode,$result")
tv_activity_result.text = "$resultCode,$result"
}
})
Ref
- Androidx 下 Fragment 懒加载的新实现
https://juejin.im/post/5e232d01e51d455801624c06