Fragment 诞生之初就被定义为一个小型 Activity,因此它代理了 Activity 的许多能力(例如 startActivityForResult 等),职责不够单一。随着 Jetpack 各种新组件的出现,Fragment 的很多职责被有效地进行了分担,其本身也可以更更好地聚焦在对 UI 的划分而管理上面,以前的一些 API 也可以退出历史舞台了。本文就盘点一下 Fragment 那些被废弃的 API。
原文链接:https://juejin.cn/post/7117070402676850702#heading-8
本文的介绍基于 Fragment 版本 1.4.0
以前, Fragment 的构造函数不允许携带参数,因为某些场景中 Fragment 会由系统自动创建,例如基于 XML 创建 Fragment、Activity 被杀死后的恢复重建等等。此时,系统通过调用 instantiate
来创建 Fragment,instantiate 通过反射调用 Fragment 无参的构造函数。
现在 Fragment 的构造函数允许携带参数了,我们可以通过自定义 FragmentFactory
,调用 Fragment 的任意构造函数,而系统通过调用 FragmentFactory 来创建 Fragment。
我们可以自定义 FragmentFactory,并重写它的 instantiate 方法来创建 Fragment:
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)
}
}
我们将 FragmentFactory 设置给 FragmentManger,之后系统就可以在各种场景中使用工厂创建 Fragment 了。
//Activityoverride fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = myFragmentFactory super.onCreate(savedInstanceState)
}
注意 FragmentFactory 的设置必须在 super.onCreate 之前,因为当 Activity 进入重建路径时,会在 super.onCreate 中使用到它。
关于 FragmentFactory 的更多介绍:juejin.cn/post/698904…
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) 是 API23 之后新增的 API,前身是 onAttach(Activity)
,它也是为了去掉与 Activity 的耦合而被废弃和取代。
关于 Fragment 生命周期的更多内容:juejin.cn/post/700697…
当系统发生横竖屏旋转等 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()
}
}
}
}
关于 ViewModel 初始化时机的相关内容,请参考:juejin.cn/post/699721…
Fragment 经常配合 ViewPager 使用以满足多 Tab 页场景的需求。默认情况下屏幕外部的 Fragment 会跟随显示中的 Fragment 一同被加载,这会影响初始页面的显示速度。setUserVisibleHint
是以前我们常用的“懒加载”实现方案:当 ViewPager 中的 Fragment 进/出屏幕时,FragmentPagerAdapter
会对其调用 setUserVisibleHint,传入 true/false,通知其是否可见:
@Overridepublic 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@Overridepublic 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;
}
关于 setMaxLifecycle 的工作原理:juejin.cn/post/696163…
以前,我们在 Fragment 可以通过 startActivityForResult/onActivityResult
启动 Activity 并获取返回的结果,这本质是调用了 Activity 的同名方法。随着 Activity Result API
的启用,startActivityForResult/onActivityResult 已经在 Activity 以及 Fragment 中被废弃。相对于 onActivityResult 的结果返回方式,Activity Result API 避免了对 requestCode 的依赖,以更加直观的方式获得 Activity 返回结果。
基本使用步骤如下图:
首先,我们创建一个 ActivityResultContract
,这里定义了跨 Activity 通信的输入输出协议,系统预置了一系列 ActivityResultContracts.XXXX
可直接使用。然后,我们使用 registerForActivityResult
注册我们的 Contract 和对应的 Callback,Callback 中我们可以获取 Activity 的返回结果。代码如上
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) }}
}
registerForActivityResult 会返回一个 ActivityResultLauncher
句柄,我们使用它启动 Activity,如下:
val intent = Intent(this, TargetActivity::class.java)
launcher.launch(intent)
最后我们在目标 Activity 中调用 setResult
返回结果即可:
//TargetActivity.ktclass TargetActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
setResult(Activity.RESULT_OK, Intent().putExtra("my-data", "data"))
finish()
}
}
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/getTargetFragment
原本用于 Fragment 之间的通信,例如从 FragmentA 跳转到 FragmentB ,在 B 中发送结果返回给 A:
// 向 FragmentB 设置 targetFragmentFragmentB fragment = new FragmentB();
fragment.setTargetFragment(FragmentA.this, AppConstant.REQ_CODE_SECOND_FRAGMENT);
//切换至 FragmentBtransaction.replace(R.id.fragment_container, fragment).commit();// FragmentB 中获取 FragmentA 并进行回调Fragment fragment = getTargetFragment();
fragment.onActivityResult(AppConstant.REQ_CODE_SECOND_FRAGMENT, Activity.RESULT_OK, inte
如上,代码非常简单,但是这样的通信无法感应生命周期,即使 FragmentA 处于后台也会在 onActivityResult
响应回调。目前 TargetFragment 相关 API 已经被废弃,取而代之的是根为合理的 Fragment Result API
。
假设需要在 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)
}
当从 FragmentB 返回结果时:
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 返回到前台时,才会收到结果。
Fragment 是帮助我们组织和管理 UI 的重要组件,即使在 Compose 时代也具有使用价值,因此谷歌官方一直致力于对它的 API 的优化,希望他更加易用和便于测试。这些已废弃的 API 在未来的版本中将会彻底删除,所以如果你还在使用着他们,应该尽快予以替换。
官方也提供了工具帮助我们发现对于过期 API 的使用,Fragment-1.4.0 之后,我们可以通过全局设置严格模式策略,发现项目中的问题:
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()
}
}
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/gmPezMzhsSgb_gg4y99uyg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。