从不一样的角度去实践布局优化

共 8788字,需浏览 18分钟

 ·

2021-05-26 10:34

前言

对于布局优化,常见的方式有:

  1. 减少布局嵌套层数;
  2. 减少过度绘制;
  3. 界面复用 & 进行按需加载。

除此以外,「本文将从不一样的角度去实践布局优化」,主要包括:

  1. 渐进式加载
  2. 异步加载
  3. 采用声明式 UI

方式1:渐进式加载

定义

一部分、一部分地去加载,即当前帧加载完成后,再去加载下一帧。

优点

  1. 减缓同一时刻,加载 View 带来的压力;
  2. 适配成本低,在中低端机上面效果明显。

缺点

依旧需要在主线程读取 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开发方式可能将会是主流。


总结

本文从不一样的角度去实践布局优化,此处给出一些使用 & 学习的建议:

  1. 渐进式加载:在 声明式UI还未完全推广前,推荐使用这种方案;
  2. 异步加载:效果显著,但适配成本高,建议应用于复杂View;
  3. 声明式 UI:原生UI布局未来的使用趋势,建议列入长期学习计划中。

「Carson每天带你学习一个Android知识点」,长按扫描关注公众号。同时,期待您精彩文章的投稿:真诚邀请您来分享

最后福利:学习资料赠送

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

    点击“在看”就能升职 & 加薪水哦!
浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐