引言
Fragment 诞生之初就被定义为一个小型 Activity,因此它代理了 Activity 的许多能力(例如 startActivityForResult 等),职责不够单一。随着 Jetpack 各种新组件的出现,Fragment 的很多职责被有效地进行了分担,其本身也可以更好地聚焦在对 UI 的划分和管理上面,早设计的一些 API 也可以退出历史舞台了。本文就盘点一下 Fragment 那些被废弃的 API。
本文的介绍基于 Fragment 版本 1.4.0
一、instantiate
以前, 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 中使用到它。
二、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) 是 API23 之后新增的 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,通知其是否可见:
@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;}
五、onActivityResult
以前,我们在 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
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 设置 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() }}
这里就还给大家分享一套完备的知识体系,整体参照了各家一线大厂高工岗位的招聘要求及岗位技能需求,并且每个具体的知识节点还附带有一整套体系化的学习资料、笔记进行辅助,帮助你把所学的知识点全部串联起来!
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取
部分内容展示如下
01.Android必备底层技术:
- Java序列化:Serializable原理、Parcelable接口原理、Json、XML
- 注解、泛型与反射:自定义注解、注解的使用、泛型擦除机制、泛型边界、Java方法与Arm指令、Method反射源码、invoke方法执行原理
- 虚拟机:JVM垃圾回收器机制、JVM内存分配策略、Android虚拟机与JVM底层区别、虚拟机底层Odex本地指令缓存机制、虚拟机如何分别加载class与object、虚拟机类加载模型
- 并发:Java线程本质讲解、线程原理、线程通信、UnSafe类、线程池
- 编译时技术:OOP面向切面之AspectJ、字节码手术刀JavaSSit实战、字节码插桩技术(ASM)实战
- 动态代理:动态代理实现原理、动态代理在虚拟机中运行时动态拼接Class字节码分析、ProxyGenerator生成字节码流程
- 高级数据结构与算法:HashMap源码、ArrayList源码、排序算法
- Java IO:Java IO体系、IO文件操作
02.Framework:
- Binder:Linux内存基础、Binder四层源码分析、Binder机制、Binder进程通信原理
- Handler:Loop消息泵机制、Message解析
- Zygote:init进程与Zygote进程、Zygote启动流程、Socket通信模式、APP启动过程
- AMS:ActivityThread源码分析、AMS与ActivityThread通信原理、Activity启动机制
- PMS:PMS源码、APK安装过程分析、PMS对安装包的解析原理
- WMS:PhoneWindow实例化流程、DecorView创建过程、ViewRootImpl渲染机制
03.Android常用组件:
- Activty:Activity管理栈与Activity的启动模式、Activity生命周期源码分析
- Fragment:Fragment生命周期深入详解、Fragment事务管理机制详解、性能优化相关方案
- Service:Service启动模式分析、Service管理与通信方案、Service生命周期底层详解
04.高级UI:
- UI绘制原理:setContentView()方法下到底做了什么、AppCompatActivity与Activity的区别、UI测量、布局、绘制的底层执行流程
- 插件换肤:LayoutInflater加载布局分析、Android资源的加载机制、Resource与AssetManager
- 事件分发机制原理:事件执行U形链与L形链、事件拦截原理
- 属性动画:VSYNC刷新机制、ObjectAnimator与ValueAnimator源码讲解、Android属性动画:插值器与估值器
- RecycleView:布局管理器LayoutManager详解、回收池设计思想、适配器模式原理
- 高阶贝塞尔曲线
05.Jetpack:
- Lifecycle:Lifecycle源码、Lifecycle高阶应用
- ViewModel:ViewModel源码、ViewModel应用技巧
- LiveData:LiveData源码
- Navigation:Navigation源码
- Room:Room源码、Room+LiveData监听数据库数据变更刷新页面原理
- WorkManager内核
- Pagging原理
- DataBinding:单向绑定、双向绑定、如何与RecyclerView的配合使用、底层原理
06.性能优化:
- 启动优化:系统启动原理、Trace工具分析启动卡顿、类重排机制、资源文件重排机制
- 内存优化
- UI渲染优化:UI层级规范及对UI加载的影响、UI卡顿原因及修复、UI绘制、布局、测量原因以及处理方案
- 卡顿优化:造成卡顿的原因分析、内存抖动与GC回收、回收算法
- 耗电优化
- 崩溃优化:项目崩溃异常捕获、优雅的异常处理方案、如何避免异常弹框
- 安全优化:APP加固实现(防反编译,dex加固)、https防抓包机制(数据传输加载,客户端服务器端双向加密校验)
- 网络优化:serializable原理、parcelable接口原理、http与https原理详解、protbuffer网络IO详解、gzip压缩方案
- 大图加载优化:Glide巨图加载机制原理分析、大图多级缓存实现方案
- 多线程并发优化
- 储存优化:Android文件系统-sdcard与内存存储、Shared Preference原理、MMAP内存映射
- 安装包优化:shrinkResources去除无用资源、合理设置多语言、webp实现图片瘦身、合理配置armable-v7的so库、Lint检查工具实践
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取
07.音视频:
- C/C++:数据类型、数组、内存布局、指针、函数、预处理器、结构体、共用体、容器、类型转换、异常、文件流操作、线程
- H.265/H.265:音视频格式封装原理、编码原理、视频流H264的组装原理切片NAL单元、视频流H264码流分析、切片与宏快,运动矢量、信源编码器、高频滤波、帧间拆分与帧内预测、CTU,PU TU编码结构、DSP芯片解码流程、MediaPlayer与DSP芯片交互机制、投屏架构、MediaProjection与MeidiaCodec交互机制、H265码流交换
- MediaCodec:dsp芯片、编解码器的生命周期、解码器中输入队列与解析队列设计思想、MediaCodec中平缓解码解析、MediaExtractor 多路复用、MediaMuxer合成器、MediaFormat格式
- 音视频剪辑:视频剪辑、音频剪辑、音频合成、音谱显示、视频倒放
- 音视频直播:硬编码、软编码、native实现rtmp推流、摄像头预览帧编码NV21转YUV、视频画面封装拼接Packet包、音频流数据拼接Packet包、RtmpDump实时同步发送音视频数据、MediaProjection、Medicodec编码H264码流、rtmp推流
- OpenGL与音视频解码:OpenGL绘制流程、矩阵、Opencv详解、人脸识别效果实现
- OpenGL特效:CPU与GPU运行机制详解、世界坐标,布局坐标,与FBO坐标系、图像镜像与旋转处理、人脸定位与关键点定位、大眼效果、贴纸效果、美颜效果
- FFmpeg万能播放器:FFmpeg结构体、声音播放原理、Surface的渲染、像素绘制原理与对齐机制、音视频同步原理、视频播放器整体架构
- Webrtc音视频通话:WebRtc服务端环境搭建与Webrtc编译、1v1视频通话实现方案、群聊视频通话实现思路、多对多视频会议实现、1V1音视频通话实现
08.开源框架原理:
- Okhttp
- Retrofit
- RxJava
- Glide
- Hilt
- Dagger2
- EventBus
- 组件化、插件化、热修复等
09.Gradle:
- Groovy语法
- Gradle Android插件配置
- Gradle实践等
10.kotlin:
- Kotlin语法
- 扩展使用
- 进阶使用
- 实践等
11.Flutter:
- Dart语法
- UI
- 进阶使用
- 优化
- 实践等
12.鸿蒙:
- Ability组件
- 分布式任务
- 事件总线
- 鸿蒙线程
- UI自定义控件等
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取
Android路漫漫,共勉!