mOnStartListener = new OnStartListener(this);
// 这个实例持有了ViewDataBinging的实例,虽然是弱引用。
}
lifecycleOwner.getLifecycle().addObserver(mOnStartListener);
// 问题出现在这里,如果这个lifecycle是fragment的,View被销毁了,里面不会进行反注册。
}
for (WeakListener<?> weakListener : mLocalFieldObservers) {
if (weakListener != null) {
weakListener.setLifecycleOwner(lifecycleOwner);
}
}
}
正确的做法是需要给这个 ViewDataBinding 设置 viewLifecycleOwner.
binding.lifecycleOwner = viewLifecycleOwner
多说一句啊,这个问题是如何被发现的呢?我们有一套检查框架逻辑的代码,对于这个问题,我们会在 fragment 的 onStop 函数里面检查有多少实例在监听 fragment 的生命周期,我们发现,这个数字会一直涨,这个问题就暴露了,适当的时候,和大家分享一下这套框架。
3. Glide 自我管理的生命周期值得信赖吗?
不值得信赖了
Glide 是一个非常流行的图片加载框架,不得不说,Glide 的缓存这一块的设计非常的优秀,功能强大,可扩展强。还有它的生命周期的自我管理,通过创建一个 fragment 在当前的页面,通过这个 fragment 的生命周期,实现在 onStart 的时候进行图片加载,在 onStop 的时候,把还没有执行或者没有执行完的任务缓存下来,以便在 onStart 的再执行,当然是在没有 onDestory 的情况下。
一切都很完美,直到遇到了 Navigation。
glide.with(fragment).load(url).into(imageview).
呵呵,上面的这段,在 Navigation 的架构下,如果 Fragment 还在,但是执行了 onDestroyView,imageview 需要被销毁。这个情况下,如果图片加载任务没执行完,任务就会被缓存下来了。这个任务还有对需要被销毁的 imageview 有强引用,导致这个 imageview 销毁不了,从而内存泄漏。
如何 100%的重现这个问题呢,有个简单的方法,让大家可以验证一下这个问题。给这个任务,加一个图片的 transformation,这个 transformation 什么也不干,就是 sleep 3 秒钟,在这个 3 秒中之内,跳转到另一个页面。这会导致当前页面进行 View 的 destory,但是 fragment 并不会 destory,因为这个任务还没执行完,这个任务就会被 Glide 缓存,具体会被缓存位置为 RequestManager->RequestTracker->pendingRequests。
如何来解决这个问题呢?这个没有现成的解决方法,在 Glide 的官网有提类似的问题,但是 Glide 维护者听起来还没有意识到这个问题,没有后续的计划。当然,我们需要来解决这个问题,不然我们的代码就会存在这一点瑕疵了。
解决的方法:自己来管理 Glide 的生命周期,不要通过那个看不见的 fragment 的生命周期,因为那是靠不住的。我们自己写了一个 RequestManager,通过传入的 fragment 的 viewLifecycleOwner 来进行管理。使用也很方便,在调用的时候如下即可。
KGlide.with(fragment).load(url).into(imageview).
源码精简了一下,贴在这里,请指正。
import com.bumptech.glide.manager.Lifecycle as GlideLifecycle
class KGlide {
companion object {
private val lifecycleMap = ArrayMap<LifecycleOwner, RequestManager>()
@MainThread
fun with(fragment: Fragment): RequestManager {
Util.assertMainThread()
val lifecycleOwner = fragment.viewLifecycleOwner
if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) {
throw IllegalStateException(“View is already destroyed.”)
}
if (lifecycleMap[lifecycleOwner] == null) {
val appContext = fragment.requireContext().applicationContext
lifecycleMap[lifecycleOwner] = RequestManager(
Glide.get(appContext),
KLifecycle(lifecycleOwner.lifecycle),
KEmptyRequestManagerTreeNode(), appContext
)
}
return lifecycleMap[lifecycleOwner]!!
}
}
class KEmptyRequestManagerTreeNode : RequestManagerTreeNode {
override fun getDescendants(): Set {
return emptySet()
}
}
class KLifecycle(private val lifecycle: Lifecycle) : GlideLifecycle {
private val lifecycleListeners =
Collections.newSetFromMap(WeakHashMap<LifecycleListener, Boolean>())
private val lifecycleObserver = object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
val listeners = Util.getSnapshot(lifecycleListeners)
for (listener in listeners) {
listener.onStart()
}
}
override fun onStop(owner: LifecycleOwner) {
val listeners = Util.getSnapshot(lifecycleListeners)
for (listener in listeners) {
listener.onStop()
}
}
override fun onDestroy(owner: LifecycleOwner) {
val listeners = Util.getSnapshot(lifecycleListeners)
for (listener in listeners) {
listener.onDestroy()
}
lifecycleMap.remove(owner)
lifecycleListeners.clear()
lifecycle.removeObserver(this)
}
}
init {
lifecycle.addObserver(lifecycleObserver)
}
override fun addListener(listener: LifecycleListener) {
lifecycleListeners.add(listener)
when (lifecycle.currentState) {
Lifecycle.State.STARTED, Lifecycle.State.RESUMED -> listener.onStart()
Lifecycle.State.DESTROYED -> listener.onDestroy()
else -> listener.onStop()
}
}
override fun removeListener(listener: LifecycleListener) {
lifecycleListeners.remove(listener)
}
}
}
4. Android 组件的生命周期自我管理值得信任吗?
不值得,信任需要我们对 Android 生命周期的管理细节足够的了解。没有足够的了解,哪里来的信任,也就是盲目的信任。
我们在 Android 官方文档里面应该看到过 LiveData 的介绍,下面摘录一段。
Livedata is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.
然后还向我们说明 Livedata 的不会导致内存泄漏。
This is especially useful for activities and fragments because they can safely observe LiveData objects and not worry about leaks—activities and fragments are instantly unsubscribed when their lifecycles are destroyed.
写的很清楚,言之昭昭啊。如果你相信了官方文档的介绍,就 too young,too simple 了。LiveData 未必会在 lifecycleOwner 销毁的时候进行反注册,内存泄漏还是会发生。我们看一段 LiveData 会产生内存泄漏的代码。
class HomeFragment : Fragment() {
private val model: NavigationViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.home_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.getTextValue().observe(viewLifecycleOwner){
view.findViewById(R.id.text).text = it
}
if (isXXX()) {
findNavController().navigate(R.id.next_action)
}
}
}
当你进入某个页面,发现需要导航到另一个页面,这个时候就需要很小心。如果像上面这样的写法,就会导致内存泄漏。
这个 Case 里,在 Fragment.onViewCreated()的模板方法,监听了一个 LiveData,这会导致这个 LiveData 持有外面对象的引用。理想情况下,这个 LivaData 会在 LifecycleOwner 在 onDestory 的时候进行反注册,但是在一些情况下,这个反注册就不会进行。
如上代码的情况下,如果这个页面马上跳到 next_action 的页面,之前订阅的 LiveData 就不会进行反注册。原因出在当跳出这个页面的时候,页面还处于生命周期的状态 INITIALIZED,但是反注册的条件是这个页面的生命周期状态至少是 CREATED.
void performDestroyView() {
mChildFragmentManager.dispatchDestroyView();
if (mView != null && mViewLifecycleOwner.getLifecycle().getCurrentState()
.isAtLeast(Lifecycle.State.CREATED)) {
mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
…
}
其实 Android 的生命周期管理还是值得信任的,前提是我们得彻底搞清楚状态流转的细节。
5. 当 ViewPager2 遇到 Navigation
ViewPager 是在应用开发的过程中,高频的用到的组件。Android 的官网有对基本的使用有详细的介绍。
一直都很美好,直到遇到 Navigation。
让我们来看官方例子里面 ViewPager2 的 Adapter 的类的声明。
class DemoCollectionAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 100
override fun createFragment(position: Int): Fragment {
// Return a NEW fragment instance in createFragment(int)
val fragment = DemoObjectFragment()
fragment.arguments = Bundle().apply {
// Our object is just an integer 😛
putInt(ARG_OBJECT, position + 1)
}
return fragment
}
}
不避讳的说,我们实际项目中的代码,也犯了同样的问题。不是说官网的写法有问题,而是在 Navigation 的框架下,才会导致的内存泄漏问题。这个泄漏是如何发生的呢?我们来看一下 FragmentStateAdapter 的构造函数。
/**
* @param fragment if the {@link ViewPager2} lives directly in a {@link Fragment} subclass.
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentActivity)
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentManager, Lifecycle)
*/
public FragmentStateAdapter(@NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
/**
* @param fragmentManager of {@link ViewPager2}'s host
* @param lifecycle of {@link ViewPager2}'s host
* @see FragmentStateAdapter#FragmentStateAdapter(FragmentActivity)
* @see FragmentStateAdapter#FragmentStateAdapter(Fragment)
*/
public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Lifecycle lifecycle) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}
可以看到 FragmentStateAdapter 最终会走两个参数的构造函数。第一个是 fragmentManager of ViewPager2’s Host,第二个参数是 lifecycle of ViewPager2’s host。如果你看懂了之前的问题,你就会知道这个问题出在哪里了。在 Navigation 下面,fragment 和 view 的生命周期是不一致的,如果我们在 FragmentStateAdapter 的构造函数中,只传入 fragment 的实例的话,第二个参数 lifecycle 用的是第一个参数 fragment 的 lifecycle。但是很显然,viewpager2’s host 的 lifecycleOwner 是 fragment 的 viewlifecycleOwner,而不是其本身。
具体导致的问题是,在 ViewPager2 实例被销毁的时候,对应的 FragmentStateAdapter 并不会被销毁,因为如果只传一个参数的话,使用的是 Fragment 的生命周期,只有在 fragment 退出的时候,才会被销毁。
这里多说一句啊,FragmentStateAdapter 实例不能被设置到多个 ViewPager2 的对象,所以当 ViewPager2 被重建的时候,这个 Adapter 不能被重用。
这些问题其实很难被发现,LeakCanary 也不能发现。幸好我们有个工具,在每个需要被检查的类的构造函数里面进行记录,然后在类的 finalize 方法对这个记录进行处理,如果发现某个类一直被构造,但是不执行 finalize 方法,这个类就需要被好好关照了。
6. ViewPager2 设置 Adapter 导致的 Fragment 重建问题
先来看以下的代码片段:
Line1:val viewPager2: ViewPager2 = …
Line2:val adapter: FragmentStateAdapter = …
Line3:viewPager2.adapter = adapter
Line4:model.getContentList.observe(viewLifecycleOwner) {
Line5: adapter.data = it
Line6: adapter.notifyDataSetChanged()
Line7:}
大家应该看不出来这段代码的问题所在的吧,这个是非常常规的写法。当然这段代码在非 Navigation 的架构下面是没有问题的。但是如果在 Navigation 的架构下,就会有比较严重的问题了。
说明一下问题出现的场景,如果用户先进入这个页面,执行上面代码,viewpager 正常显示。然后注意,重要的步骤来了,在这个页面上,导航到另外一个页面。那当前的这个页面会执行 fragment 的 onStop,注意并不会执行 onDestory。但是会执行 onDestoryView,也就是说 viewPager 将会被销毁,但是 fragment 被保留了。
那如果重新回到这个页面会发生什么事情呢,之前 onStop 的 fragment 会执行 onStart,包括 Adatper 里面生成的 fragment 也会进行重建,并创建 View。
出人意料的事情发生了,Adatper 里面的 fragment 在重建完成之后,立刻又被销毁掉了,这里的销毁是真正的销毁,执行了 onDestory 方法。然一个新的 fragment 被重新创建出来,这就是 fragment 重建问题。是什么导致了这个问题呢?
具体执行销毁 Fragment 的代码如下,在 FragmentStateAdapter 的 gcFragments 的方法。
void gcFragments() {
if (!mHasStaleFragments || shouldDelayFragmentTransactions()) {
return;
}
// Remove Fragments for items that are no longer part of the data-set
Set toRemove = new ArraySet<>();
for (int ix = 0; ix < mFragments.size(); ix++) {
long itemId = mFragments.keyAt(ix);
if (!containsItem(itemId)) {
toRemove.add(itemId);
mItemIdToViewHolder.remove(itemId); // in case they’re still bound
}
}
// Remove Fragments that are not bound anywhere – pending a grace period
if (!mIsInGracePeriod) {
mHasStaleFragments = false; // we’ve executed all GC checks
for (int ix = 0; ix < mFragments.size(); ix++) {
long itemId = mFragments.keyAt(ix);
if (!isFragmentViewBound(itemId)) {
toRemove.add(itemId);
}
}
}
for (Long itemId : toRemove) {
removeFragment(itemId);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
尾声
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2021字节跳动Android面试历年真题解析》
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
[外链图片转存中…(img-RKbjKZyj-1713720988158)]
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
[外链图片转存中…(img-JXXk4Oba-1713720988159)]
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-OUcBO10b-1713720988160)]
《2017-2021字节跳动Android面试历年真题解析》
[外链图片转存中…(img-DNzJAlCv-1713720988160)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!