public static final String HEIGHT = “height”;
}
当用户进入相册页后,就优先使用缓存展示相册信息,然后在子线程查询后通过 RecyclerView 中的 DiffUtil 异步计算 Diff 后进行刷新。
-
- Tab 懒加载和封面加载
抖音的相册页使用的 ViewPager,有三个 Tab,但是我们知道 ViewPager 会默认加载页面的左右两页的。通过源码发现,ViewPager 提供的 setOffscreenPageLimit
也并不能解决预加载的问题。所以通过重写 PagerAdapter 来实现 Tab 没有显示出来时候不加载任何内容。
public void setOffscreenPageLimit(int limit) {
// DEFAULT_OFFSCREEN_PAGES = 1
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
抖音封面加载使用的是 Fresco,封面加载如果有合适的媒体库的缩略图,就优先使用缩略。如果没有媒体库的缩略图的情况下,我们针对 Fresco 中的对图片和视频封面的加载进行了一系列的优化。
图片封面: 由于现在的照片普遍质量比较高,很多都达到了 3-5M,对于一些中低端机,每次对原图进行 resize 也是比较耗资源的,所以我们采用了空间换时间,加载每一个图片时,给符合要求的图片缓存了一个 resize 后的图片,从而让中低端机的封面加载的更快。
视频封面: Fresco 对视频抽帧使用的是 MediaMetadataRetriever,且并没有对视频封面抽帧做缓存,导致每一次视频封面都需要重新抽帧,所以视频封面相当的慢。针对这个我们可以通过两个方式进行优化:
-
替换抽帧方式,可用多媒体相关库进行抽帧
-
对抽帧出来的封面进行磁盘缓存。
public class LocalVideoThumbnailProducer implements Producer<CloseableReference> {
// 仅展示关键抽帧逻辑
@Override
protected CloseableReference getResult() throws Exception {
String path = getLocalFilePath(imageRequest);
Bitmap thumbnailBitmap = ThumbnailUtils.createVideoThumbnail(path, calculateKind(imageRequest));
return CloseableReference.of(new CloseableStaticBitmap(
thumbnailBitmap,
SimpleBitmapReleaser.getInstance(),
ImmutableQualityInfo.FULL_QUALITY, 0));
}
- 其它优化
除上面的优化以外,我们还做一系列的探索: 产品形态优化(如 ins 相册只有 9 张图);厂商合作优化(手机厂商有封面的缓存私有接口,获取封面的 Bitmap 非常快);多个 Tab 复用 RecycledViewPool;低端机切 Tab 时主动释放 释放 Fresco Request;AndroidQ 适配等一系列的优化,由于篇幅有限,就不赘述了。
优化后相册页的加载逻辑:
- 效果收益
线下测试:
测试条件:
-
vivoNEX
-
8k 张图、665 视频
优化前
优化后
线上数据:相册首帧 PCT50 -43%;投稿率、开拍率、人均上传投稿数提升显著;
3.2 iOS 组件性能架构优化
- 问题发现
近一年来创作链路代码组件化逐步落地和优化,已形成了一套兼顾扩展性和隔离性的组件化方案,但是由于组件的加载仍然是主线程串行执行,所有功能性组件的加载仍然在 page load 时机进行,这样就导致了首帧需要等待所有组件加载完成才能执行的问题,并且随着后续业务组件的添加,这个问题将愈发严重。
Page Ready:整体组件加载过程完成,页面处于可正常交互响应状态;组件加载通常执行如下操作:UI Load、缓存读取、发起网络请求、RX 信号绑定等。
图 3-1-1 首帧被组件加载阻塞
解决方案一:延迟加载组件
图 3-1-2 组件加载延迟到首帧之后
为了解决问题,我们第一个想到的办法就是将组件的加载操作延后到首帧之后执行,同时为了保证首屏 UI 的正常加载,我们将组件加载拆分为首屏 UI+耗时操作两个部分,效果也是立竿见影的,首帧的时长得到了大幅度的削减,并且后续新增组件也不会对首帧产生影响。但是新的问题又出现了,由于组件加载的延后 ,一部分跟业务相关的事件绑定操作(组件间通信通过事件传递)也同时被延后,这样整体 Page Ready 的时长被拉长,感知上用户进入页面后并不能立刻点击相关功能按钮,需要等待首帧的结束,线上核心功能的渗透率小幅下降,对于创作场景来说是不能接受的。
解决方案二:组件加载和首帧同时进行
图 3-1-3 首帧和组件加载同时进行
有了方案一的铺垫,我们只需要解决一个关键问题:组件的加载也不能够被首帧 delay。看起来我们得到了一个伪命题 —— 组件加载不能阻塞首帧、首帧也不能阻塞组件加载,因为两者都是主线程串行执行,肯定是要有先后关系的,所以根本没法做到。
等等,好像我们忽略了一个重要问题,组件加载是逐个进行的,其实我们只需要保证首帧不被全量组件的加载阻塞就可以,毕竟单一组件的加载还是很快的。iOS 端我们想到了通过 runloop 的机制解决这个问题,既然我们要拆分组件的加载时机,倒不如干脆让组件加载在每一次 runloop 的空闲时机去执行,这样当有首帧渲染任务到来时,就可以优先执行首帧渲染,待 runloop 再次空闲时去执行剩余的组件加载,实现互相不阻塞的效果。关键实现如下:
- (void)registerTransactionMainRunloopObserver {
AssertMainThread();
if (self.runLoopObserver) return;
__auto_type runLoopCallback = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
AssertMainThread();
[self loopTransaction];
};
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
kCFRunLoopExit); // before exiting a runloop run
self.runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, activities, YES, INT_MAX, runLoopCallback)
CFRunLoopAddObserver(runLoop, self.runLoopObserver, kCFRunLoopCommonModes);
}
-
收益
-
首帧时长:由于我们把耗时的组件加载操作剥离以及开拍的调用提前,最终实现了拍摄首帧 30~50%的优化幅度(分机型和分位数指标);
-
性能防劣化:随着组件性能架构优化的最终落地,我们对于首帧的管控更加的严格,新业务的迭代再也不会影响首帧的时长,占用我们大量人力去排查线上首帧劣化的场景也少了很多,真正实现了首帧的代码级防劣化。
3.3 小屏幕的极致画质体验
- 背景
手机屏幕小、计算资源有限、网络可靠性差(比如在地下室或者车站)、部分机型易发热的特点,使得移动设备很难在画质体验上与 PC 端相比较。如何在有限的条件下,尽量提升移动端画质体验,尤其是作为源头的视频投稿画质体验,是必须攻克的难题。为了解决这个问题,我们在硬件能力使用、编码参数调优、画质算法开发、产品策略的智能化上做了非常多的尝试,提出了一整套相对完整的解决方案。
- 解决思路
硬件能力、画质算法、产品策略是移动端提升画质的三个主要途径。
- 解决方案:硬件能力
硬件能力包括手机厂商为提升画质提供的基础能力,拍摄流程从前到后包括 Camera 能力、编解码能力、画质增强能力等。
-
Camera:防抖、自动曝光、夜景模式
-
编解码:H264、bytevc1 硬编硬解能力
-
画质增强:HDR
解决方案:画质算法
通过算法提升画质是一种实现无限可能的手段,包括超分、画质增强、降噪、插帧、调色等。
解决方案:产品策略
全屏拍摄、高清、分级策略下发、视频透传等策略,通过产品设计,最大程度利用了屏幕、Camera 采集、芯片、内存等硬件资源,通过软件策略的合理调度和精心设置,使得用户感官上的画质体验提升,主观上达到最满意的状态。
- 全屏拍摄:摄像头画面最大程度撑满屏幕 。
图 3-3-1 - 全屏拍摄 - 系统相机 VS APP 相机
- 高清:提升分辨率和码率是提升画质有效的途径。
图 3-3-2 - 分辨率\码率提升对比
- 分级策略下发:对不同能力的机型下发不同的策略。
- 视频透传:将用户相册中的文件高清文件不经过客户端转码,而直接上传,完整保留文件的高画质。
- 画质评测:对多种场景进行画质评测,调整策略达到主观最优。
- 分版本监控画质变化
- 收益
线上投稿/消费画质提升;高粉用户(万粉以上)投稿率提升非常显著,低粉用户投稿波动;相关投稿的消费时长提升明显。
四、监控和防劣化
日常数据采集和线上报警是我们监控线上性能和业务是否正常的重要手段,合理的报警阈值设置有助于我们及时发现线上问题,合理的采集和多维度分类有助于我们快速的定位问题并辅助分析解决。
举例来说,假如某省份的某 CDN 发生故障,或者某国家的网络异常,导致该节点的道具下发失败率&耗时增加,最终业务表现的可能是整体投稿率下跌。那么我们反推一下,在线上出现投稿率下跌的时候,我们应该依赖哪些业务和性能埋点,才能快速的定位到出问题的是哪个功能和哪个地区呢?这样反向思考的方式,也是构建埋点和监控体系的常见方法,这里就不展开去讲了。
相对优化而言,防劣化是很多 APP 研发流程中不够重视的一环,我们经常为了某些功能的快速上线和发版忽略了新代码带来的性能劣化,导致“有人填坑,有人挖坑”,“刚做了优化,又要做优化”。事实上,防劣化是和优化同等重要的工作。
针对章节一中的指标体系,我们建设了一套完整的防劣化体系,包括 P0 级别的 MR 防劣化,P1 级别的 Daily Check 和兜底的 Version 对比:
图 4 -1 防劣化体系
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿
2e05a14868a3f0fd6ac81d625c.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-ONerO9GN-1712004359000)]
最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿
[外链图片转存中…(img-H9qHP2tl-1712004359000)]