问题描述:
在使用mvp等模式进行开发时,有时会使用单Activity嵌套单Fragment进行业务解耦,往往会在Activity onCreated的时候,直接通过add或者replace直接添加子Fragment,这时候就会发现,在系统资源紧张的情况下,或者在后台运算过于频繁的时候,易出现卡顿,黑屏/白屏的问题,具体现象是整个屏幕完全黑掉或者白掉,几秒后,界面才显示出来,并且通过修改theme并不能解决这个黑白屏问题。
问题原因:
问题的核心就是在于以下代码:
supportFragmentManager
.beginTransaction()
.replace(resId, fragment)
.commitAllowingStateLoss()
不要迷信啥add代替replace会降低性能要求之类的,如果你的Activity只是单个Fragment填充的话,那add代替replace就基本没有意义了,所以再咋修改这里的乱七八糟的方案都不好使。
问题的核心点在于,单Fragment做的ui过于复杂了(view树过深或者自定义View在初始化的时候做了太多乱七八糟的操作了),add或者replace的时候会调Fragment的生命周期拿view,这时候就会因性能问题把屏幕卡死(具体是那块代码或者深入原因导致的我还没研究清楚,希望有大神能提点一下),所以,核心是让这个方法返回的界面尽可能简单点,渲染快一点
@MainThread
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (mContentLayoutId != 0) {
return inflater.inflate(mContentLayoutId, container, false);
}
return null;
}
解决方案
最好的解决方案就是写ui的时候注意点,尽量降低View树的层级,或者是多Fragment嵌套,尽量使View绘制的不那么麻烦,或者干脆直接在Activity里码代码,放弃这个倒霉催的架构。但是对于大多数已经出现这个问题的小伙伴们来说,往往都是这个界面进过多次迭代之后,代码莫名其妙的垒到这么多,忽然就开始黑屏了,这时候再去往外摘,显然是个有心无力的大工程。
像上边说的这种情况,解决思路有两种,核心是使用ViewStub进行延迟加载:
- 把整个界面全部使用ViewStub包裹,然后在onCreateView的时候,用协程也好,RxJava也好,做个延迟,绕过
onCreateView这个时机,如:
override fun initView(parent: View?) {
//延迟初始化,防止replace fragment 导致黑屏
viewLifecycleOwner.lifecycleScope
.launch(Dispatchers.Main) {
delay(100)
//先注册监听,要不不好使
vs_content.setOnInflateListener { _, _ ->
//在这才是真正的view初始化完成,开始写逻辑,要不拿的view空指针
}
vs_content.inflate()
}
}
看不懂kotlin和协程的小伙伴可以先去学习一下,为了更直观,放弃了所有的封装,我已经写成最基础的使用方式了。。。vs_content就是ViewStub,initView是我封装了一层的代码,实际就是在onViewCreated()中调用的,当然,也可以在onCreateView中调用,这都看不懂的自行面壁。。。
- 第二种就比较复杂了,我就不上代码了,简单说就是根据需求,用多个ViewStub分功能区分,把界面拆成多个ViewStub分别维护,比如不同接口调用之后生成数据再填充的数据,在接口返回之后再去使用ViewStub的inflate方法,或者根据id单独inflate,这就涉及到ViewStub的使用了,这里就不做具体分析了。大家可以移步去看下其他大神们对应对应的文档。
总之,简单一句话,就是onCreateView的时候,尽量把当时就加载的View树整的越简单越好,这样做的目的就是防止在add或者replace占用过多的资源,导致App卡顿造成黑屏,同时ViewStub分功能加载也可以把一瞬间的性能要求分散到多点去,对整个App的开发都是有好处的。