从不一样的角度去实践布局优化
前言
对于布局优化,常见的方式有:
减少布局嵌套层数; 减少过度绘制; 界面复用 & 进行按需加载。
除此以外,「本文将从不一样的角度去实践布局优化」,主要包括:
渐进式加载 异步加载 采用声明式 UI
方式1:渐进式加载
定义
一部分、一部分地去加载,即当前帧加载完成后,再去加载下一帧。
优点
减缓同一时刻,加载 View 带来的压力; 适配成本低,在中低端机上面效果明显。
缺点
依旧需要在主线程读取 xml 文件。
具体使用
先加载核心View,再逐步加载其他View。一种较为极端的做法是:当加载 xml 文件时仅加载一个空白的 xml,布局全部使用 ViewStub 标签进行懒加载。
核心伪代码
private void start(){
loadA(){
loadB(){
loadC()
}
}
}
方式2:异步加载
定义
即在子线程创建 View。在实际应用中,一般是采用:预加载 View,即先启动子线程创建View,在需要使用时先去缓存里面查找 View 是否已经创建好了。
优点
大大减少 View的创建时间
缺点
需对预加载View的生命周期进行管理,即何时进行预加载及销毁,否则容易出现加载不及时(即预加载策略失效) & 内存泄漏问题。
具体使用
官方提供了异步加载View的工具类:AsyncLayoutInflater,但存在一个核心问题:异步加载的view只能通过callback回调才能获得。这个问题会导致开发者使用时对于UI变化十分不灵活。因此,下面对AsyncLayoutInflater进行优化。
优化方案
优化原理:将预加载的View都保存在一个Map里;需要使用时从Map取出。
/**
* 1. 保存预加载的View到Map里
**/
@UiThread
fun asyncInflate(context: Context,vararg items: AsyncInflateItem?) {
items.forEach { item ->
if (item == null || item.layoutResId == 0 || mInflateMap.containsKey(item.inflateKey) || item.isCancelled() || item.isInflating()) {
return
}
mInflateMap[item.inflateKey] = item
onAsyncInflateReady(item)
inflateWithThreadPool(context, item)
}
}
/**
* 2. 从Map取出预加载的View
* 原理:
* 1. 先从缓存结果里面拿 View,拿到了view直接返回
* 2. 没拿到view,但是子线程在inflate中,等待返回
* 3. 如果还没开始inflate,由UI线程进行inflate
* @param context
* @param layoutResId 需要拿的layoutId
* @param parent container
* @param inflateKey 每一个View会对应一个inflateKey,因为可能许多地方用的同一个 layout,但是需要inflate多个,用InflateKey进行区分
* @param inflater 外部传进来的inflater,外面如果有inflater,传进来,用来进行可能的SyncInflate,
* @return 最后inflate出来的view
*/
@UiThread
fun getInflatedView(
context: Context?,
layoutResId: Int,
parent: ViewGroup?,
inflateKey: String?,
inflater: LayoutInflater
): View {
if (!TextUtils.isEmpty(inflateKey) && mInflateMap.containsKey(inflateKey)) {
val item = mInflateMap[inflateKey]
val latch = mInflateLatchMap[inflateKey]
if (item != null) {
val resultView = item.inflatedView
if (resultView != null) {
//拿到了view直接返回
removeInflateKey(item)
replaceContextForView(resultView, context)
Log.i(TAG, "getInflatedView from cache: inflateKey is $inflateKey")
return resultView
}
if (item.isInflating() && latch != null) {
//没拿到view,但是在inflate中,等待返回
try {
latch.await()
} catch (e: InterruptedException) {
Log.e(TAG, e.message, e)
}
removeInflateKey(item)
if (resultView != null) {
Log.i(TAG, "getInflatedView from OtherThread: inflateKey is $inflateKey")
replaceContextForView(resultView, context)
return resultView
}
}
//如果还没开始inflate,则设置为false,UI线程进行inflate
item.setCancelled(true)
}
}
Log.i(TAG, "getInflatedView from UI: inflateKey is $inflateKey")
// 拿异步inflate的View失败,UI线程inflate
return inflater.inflate(layoutResId, parent, false)
}
方式3:声明式UI - Jetpack Compose
Jetpack Compose 是一个用于构建原生Android UI 的工具包,属于声明式的编程模型; 即你通过简单的描述UI外观,Compose既能渲染出对应的UI效果:当状态发生改变时,UI将自动更新。 兼容性好:基于Kotlin构建,因此可以与Java编程语言完全互操作,并且可以直接访问所有Android和Jetpack API。它与现有的UI工具包也是完全兼容的,因此你可以混合原来的View和现在新的View。
具体可参考文章:我们该如何优化 UI 构建?
声明式 UI 框架近年来飞速发展:
React 为声明式 UI 奠定了坚实基础 Flutter 的发布将声明式 UI 的思想成功带到移动端开发领域 Apple和Google 分别先后发布了自己的声明式UI框架SwiftUI 和 Jetpack Compose。
以后,原生UI布局,声明式的UI开发方式可能将会是主流。
总结
本文从不一样的角度去实践布局优化,此处给出一些使用 & 学习的建议:
渐进式加载:在 声明式UI还未完全推广前,推荐使用这种方案; 异步加载:效果显著,但适配成本高,建议应用于复杂View; 声明式 UI:原生UI布局未来的使用趋势,建议列入长期学习计划中。
「Carson每天带你学习一个Android知识点」,长按扫描关注公众号。同时,期待您精彩文章的投稿:真诚邀请您来分享
最后福利:学习资料赠送

福利:由本人亲自撰写 & 整理的「Android学习方法资料」 数量:10名 参与方式:「点击文章右下角”在看“ -> 回复截图到公众号 即可,我将从中随机抽取」 点击“在看”就能升职 & 加薪水哦!
评论