Fragment 这些 API 已废弃,您还在使用吗?| 开发者说·DTalk
The following article is from AndroidPub Author fundroid
Fragment 诞生之初就被定义为一个小型 Activity,因此它代理了 Activity 的许多能力 (例如 startActivityForResult 等),职责不够单一。随着 Jetpack 各种新组件的出现,Fragment 的很多职责被有效地进行了分担,其本身也可以更好地聚焦在对 UI 的划分和管理上面,早设计的一些 API 也可以退出历史舞台了。本文就盘点一下 Fragment 那些被废弃的 API。
instantiate
class MyFragmentFactory(private val arg: Any) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val clazz = loadFragmentClass(classLoader, className)
if (clazz == MyFragment::class.java) {
return MyFragment(arg)
}
return super.instantiate(classLoader, className)
}
}
//Activity
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = myFragmentFactory
super.onCreate(savedInstanceState)
}
注意 FragmentFactory 的设置必须在 super.onCreate 之前,因为当 Activity 进入重建路径时,会在 super.onCreate 中使用到它。
onActivityCreated
Fragment 早期设计中与 Activity 耦合较多,例如在生命周期方面上除了代理了 Activity 标准生命周期回调以外,还增加了 onActivityCreated 用来观察与 Activity 的绑定关系,onActivityCreated 被认为是 onStart 之前最后一个阶段,此时 Fragment 的 View Hierarchy 已经与 Activity 绑定,因此常用来在这里完成一些基于 View 的初始化工作。
现在,官方正在逐渐去掉 Fragment 与 Activity 之间的耦合,一个更加独立的 Fragment 更利于复用和测试,因此 onActivityCreated 被废除,取而代之的是在 onViewCreated 中处理与 View 相关的初始化逻辑,与 View 无关的初始化可以前置到 onCreate。但要注意 onViewCreated 回调的时间点,Fragment 的 View 还没加入 Activity View 的 Hierarchy。
如果我们实在需要获得 Activity 的 onCreate 事件通知,可以通过在 onAttach(Context) 中通过 LifecycleObserver 来获取:
override fun onAttach(context: Context) {
super.onAttach(context)
requireActivity().lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
//...
}
})
}
onAttach(Context) 是 API 23 之后新增的 API,前身是 onAttach(Activity),它也是为了去掉与 Activity 的耦合而被废弃和取代。
setRetainInstance
当系统发生横竖屏旋转等 ConfigurationChanged 时,伴随 Activity 的重新 onCreate,Fragment 也会重新创建。setRetainInstance(true) 可以保持 ConfigurationChanged 之后的 Fragment 实例不变。因为有这个特性,以前我们经常会借助 setRetainInstance 来保存 Fragment 甚至 Activity 的状态。
但是使用 setRetainInstance 保存状态存在隐患,如果 Fragment 持有了对 Activity View 的引用则会造成泄露或者异常,所以我们仅保存与 View 无关的状态即可,不应该保存整个 Fragment 实例,所以 setRetainInstance/getRetainInstance 被废弃,取而代之的是推荐使用 ViewModel 保存状态。
对于 ViewModel 的基操想必大家都很熟悉就不赘述了。这里只提醒一点,既然 ViewModel 可以在 ConfigurationChanged 之后保持状态,那么 ViewModel 的初始化只需进行一次即可。不少人会像下面这样初始化 ViewModel:
class DetailTaskFragment : Fragment(R.layout.fragment_detailed_task){
private val viewModel : DetailTaskViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//订阅 ViewModel
viewMode.uiState.observe(viewLifecycleOwner) {
//update ui
}
//请求数据
viewModel.fetchTaskData(requireArguments().getInt(TASK_ID))
}
}
在 onViewCreated 中使用 fetchTaskData 请求数据,当横竖屏旋转造成 Fragment 重建时,虽然我们可以从 ViewModel 中获取最新数据,但是仍然会执行一次多余的 fetchTaskData。因此更合理的 ViewModel 初始化时机应该是在其内部的 init 中进行,代码如下:
class TasksViewModel: ViewModel() {
private val _tasks = MutableLiveData<List<Task>>()
val tasks: LiveData<List<Task>> = _uiState
init {
viewModelScope.launch {
_tasks.value = withContext(Dispatchers.IO){
TasksRepository.fetchTasks()
}
}
}
}
setUserVisibleHint
Fragment 经常配合 ViewPager 使用以满足多 Tab 页场景的需求。默认情况下屏幕外部的 Fragment 会跟随显示中的 Fragment 一同被加载,这会影响初始页面的显示速度。setUserVisibleHint 是以前我们常用的 "懒加载" 实现方案: 当 ViewPager 中的 Fragment 进/出屏幕时,FragmentPagerAdapter 会对其调用 setUserVisibleHint,传入 true/false,通知其是否可见:
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
onVisible(); //自定义回调: 进入屏幕
} else {
onInVisible();//离开屏幕
}
}
如上,通过重写 setUserVisibleHint 我们可以在 onVisible/onInVisible 中获知 Fragment 显示的时机,便于实现懒加载。但是这种做法有缺陷,首先,您需要为 Fragment 增加基类来定义 onVisible/onInvisible,其次,新增的这两个方法跟原生的生命周期回调交织在一起,增加了代码复杂度和出错的概率。幸好现在我们有了新的 "懒加载" 解决方案: FragmentTransaction#setMaxLifecycle: setMaxLifecycle 可以将屏幕外尚未显示的 Fragment 的最大的生命周期的状态限制在 Started。
当 Fragment 真正进入屏幕后再推进到 Resumed,此时 onResume 才会响应。借助 setMaxLifecycle 我们仅依靠原生回调即可实现懒加载,而且还避免了额外基类的引入。
如果您使用的是 ViewPager2,其对应的 FragmentStateAdapter 已经默认支持了 setMaxLifecycle。对于传统的 ViewPager,启动 setMaxLifecycle 的方法也很简单,FragmentPagerAdapter 的构造方法新增了一个 behavior 参数,只要在此处传值为 FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 即可,在 instantiateItem 方法中,会根据 behavior 为创建的 Fragment 设置 setMaxLifecycle。
// FragmentPagerAdpater.java
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
...
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
// mBehaviour为1的时候走新逻辑if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
// 初始化item时将其生命周期限制为STARTED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
// 兼容旧版逻辑
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
onActivityResult
val launcher : ActivityResultLauncher =
registerForActivityResult(
//使用预置的 Contract:StartActivityForResult
ActivityResultContracts.StartActivityForResult()) {
activityResult ->
// 获取 Activity 返回的 ActivityResult
Log.d("TargetActivity", activityResult.toString())
// D/TargetActivity: ActivityResult{resultCode=RESULT_OK, data=Intent { (has extras) }}
}
val intent = Intent(this, TargetActivity::class.java)
launcher.launch(intent)
最后我们在目标 Activity 中调用 setResult 返回结果即可:
//TargetActivity.kt
class TargetActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResult(Activity.RESULT_OK, Intent().putExtra("my-data", "data"))
finish()
}
}
requestPermissions
requestPermissions/onRequestPermissionsResult 底层也是基于 startActivityForResult/onActivityResult 实现的,因此同样被废弃了,升级为 Result API 的方式。
ActivityResultContracts 预置了申请权限相关的 Contract:
request_permission.setOnClickListener {
requestPermission.launch(permission.BLUETOOTH)
}
request_multiple_permission.setOnClickListener {
requestMultiplePermissions.launch(
arrayOf(
permission.BLUETOOTH,
permission.NFC,
permission.ACCESS_FINE_LOCATION
)
)
}
// 申请单一权限
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
// Do something if permission grantedif (isGranted) toast("Permission is granted")
else toast("Permission is denied")
}
// 一次申请多权限
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
// Do something if some permissions granted or denied
permissions.entries.forEach {
// Do checking here
}
}
setTargetFragment
setTargetFragment/getTargetFragment 原本用于 Fragment 之间的通信,例如从 FragmentA 跳转到 FragmentB,在 B 中发送结果返回给 A:
// 向 FragmentB 设置 targetFragment
FragmentB fragment = new FragmentB();
fragment.setTargetFragment(FragmentA.this, AppConstant.REQ_CODE_SECOND_FRAGMENT);
//切换至 FragmentB
transaction.replace(R.id.fragment_container, fragment).commit();
// FragmentB 中获取 FragmentA 并进行回调
Fragment fragment = getTargetFragment();
fragment.onActivityResult(AppConstant.REQ_CODE_SECOND_FRAGMENT, Activity.RESULT_OK, inte
假设需要在 FragmentA 监听 FragmentB 返回的数据,首先在 FragmentA 设置监听:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setFragmentResultListener 是 fragment-ktx 提供的扩展函数
setFragmentResultListener("requestKey") { requestKey, bundle ->
// 监听key为“requestKey”的结果, 并通过bundle获取
val result = bundle.getString("bundleKey")
// ...
}
}
// setFragmentResultListener 是Fragment的扩展函数,内部调用 FragmentManger 的同名方法
public fun Fragment.setFragmentResultListener(
requestKey: String,
listener: ((requestKey: String, bundle: Bundle) -> Unit)
) {
parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}
val result = "result"
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
//setFragmentResult 也是 Fragment 的扩展函数,其内部调用 FragmentManger 的同名方法
public fun Fragment.setFragmentResult(requestKey: String, result: Bundle) {
parentFragmentManager.setFragmentResult(requestKey, result)
}
FragmentA 通过 Key 向 FragmentManager 注册 ResultListener,FragmentB 返回 result 时,FM 通过 Key 将结果回调给 FragmentA,而且最重要的是 Result API 是生命周期可感知的,listener.onFragmentResult 在 Lifecycle.Event.ON_START 的时候才调用,也就是说只有当 FragmentA 返回到前台时,才会收到结果。
最后
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
FragmentStrictMode.defaultPolicy =
FragmentStrictMode.Policy.Builder()
.detectFragmentTagUsage() //setTargetFragment的使用
.detectRetainInstanceUsage()//setRetainInstance的使用
.detectSetUserVisibleHint()//setUserVisibleHint的使用
.detectTargetFragmentUsage()//setTargetFragment的使用
.apply {
if (BuildConfig.DEBUG) {
// Debug 模式下崩溃
penaltyDeath()
} else {
// Release 模式下上报
penaltyListener {
FirebaseCrashlytics.getInstance().recordException(it)
}
}
}
.build()
}
}
https://developer.android.google.cn/guide/fragments/debugging?hl=zh-cn#strictmode
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向