精华内容
下载资源
问答
  • 今天给大家把我压箱底的一些BATJ等一线大厂面试资料献出来,主要为Android 基础与底层原理技术点要求。先声明这些都是我在一个安卓高级技术社群里收集的,也整理了相应的答案方便大家有个参考,如果有侵犯泄露等问题...

    今天给大家把我压箱底的一些BATJ等一线大厂面试资料献出来,主要为Android 基础与底层原理技术点要求。先声明这些都是我在一个安卓高级技术社群里收集的,也整理了相应的答案方便大家有个参考,如果有侵犯泄露等问题请私我解决谢谢。

    请看面试题;

    1. 数据库的操作类型有哪些,如何导入外部数据库?(腾讯)

    读懂题目。如果碰到问题比较模糊的时候可以适当问问面试官。配合面试官来面试:面试是一个相互了解的过程,要充分利用面试的题目和时间把自己的能力和技术展现出来,面试官能够看到你的真实技术。

    1) 使用数据库的方式有哪些?

    • openOrCreateDatabase(String path);
    • 继承SqliteOpenHelper类对数据库及其版本进行管理(onCreate,onUpgrade)

    当在程序当中调用这个类的方法getWritableDatabase()或者getReadableDatabase();的时候才会打开数据库。如果当时没有数据库文件的时候,系统就会自动生成一个数据库。

    2) 操作的类型:增删改查CRUD

    • 直接操作SQL语句:SQliteDatabase.execSQL(sql);

    • 面向对象的操作方式:SQLiteDatabase.insert(table, nullColumnHack, ContentValues);

    如何导入外部数据库?

    • 一般外部数据库文件可能放在SD卡或者res/raw或者assets目录下面。

    • 写一个DBManager的类来管理,数据库文件搬家,先把数据库文件复制到”/data/data/包名/databases/”目录下面,然后通过db.openOrCreateDatabase(db文件),打开数据库使用。

    我上一个项目就是这么做的,由于app上架之前就有一些初始数据需要内置,也会碰到数据的升级等问题,我是这么做的…… 同时我碰到最有意思的问题就是关于数据库并发操作的问题,比如:多线程操作数据库的时候,我采取的是封装使用互斥锁来解决……

    1. 是否使用过本地广播,和全局广播有什么差别?(抖音)

    引入本地广播的机制是为了解决安全性的问题:

    1) 正在发送的广播不会脱离应用程序,比用担心app的数据泄露;
    2) 其他的程序无法发送到我的应用程序内部,不担心安全漏洞。(比如:如何做一个杀不死的服务---监听火的app 比如微信、友盟、极光的广播,来启动自己。)
    3) 发送本地广播比发送全局的广播高效。(全局广播要维护的广播集合表 效率更低。全局广播,意味着可以跨进程,就需要底层的支持。)

    本地广播不能用静态注册。----静态注册:可以做到程序停止后还能监听。

    使用:

    (1) 注册
    LocalBroadcastManager.getInstance(this).registerReceiver(new XXXBroadCastReceiver(), new IntentFilter(action));
    (2) 取消注册:
    LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)

    3. 是否使用过 IntentService,作用是什么, AIDL 解决了什么问题? (小米)

    4. Activity、 Window、 View 三者的差别, fragment 的特点?(360)

    5. 描述一次网络请求的流程(新浪)

    6. Handler、 Thread 和 HandlerThread 的差别(小米)

    7. 低版本 SDK 实现高版本 api(小米)

    8. launch mode 应用场景(百度、小米、乐视)

    9. touch 事件传递流程(小米)

    10. view 绘制流程(百度)

    11. 什么情况导致内存泄漏(美团)

    12. ANR 定位和修正

    13. 什么情况导致 oom(乐视、美团)

    14. Android Service 与 Activity 之间通信的几种方式

    15. Android 各个版本 API 的区别

    16.如何保证一个后台服务不被杀死,比较省电的方式是什么?(百度)

    17. Requestlayout, onlayout, onDraw, DrawChild 区别与联系(猎豹)

    18. invalidate()和 postInvalidate() 的区别及使用(百度)

    19. Android 动画框架实现原理(腾讯)

    20. Android 为每个应用程序分配的内存大小是多少?(美团)

    看具体的手机平台,常见的有:64M/32M等

    21. LinearLayout 对比 RelativeLayout(百度)

    22. 优化自定义 view(百度、乐视、小米)

    1)减少在onDraw里面大量计算和对象创建和大量内存分配。
    2)应该尽量少用invalidate()次数。
    3)view里面耗时的操作layout。减少requestLayout()避免让UI系统重新遍历整棵树。Mearsure。
    4)如果你有一个很复杂的布局,不如将这个复杂的布局直接使用你自己的写的ViewGroup来实现。减少了一个树的层次关系 全部都是自己测量和layout,达到优化的目的。(Facebook就经常这么干)

    24. ContentProvider(乐视)

    提示:跨进程通信。进程之间进行数据交互共享。;源码来一剁。

    25.fragment 生命周期

    26. volley 解析(美团、乐视)

    27. Android Glide 源码解析

    28. Android 属性动画特性(乐视、小米)
    ..............

    文章篇幅不够,没有全部贴上答案详解;

    对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

    这里附上上述的技术体系图相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

    由于篇幅有限,这里以图片的形式给大家展示一小部分。

    详细整理在石墨文档可以见;

    Android架构视频+BAT面试专题PDF+学习笔记​

    网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

    天道酬勤,只要你想,大厂offer并不是遥不可及!

    希望本篇文章能为你带来帮助,如果有问题,请在评论区留言

    展开全文
  • 20200831抖音前端校招一面视频1小时20分钟(我人傻了!!!!!!)1.看你在重构一个项目,你讲讲你重构的心得?你如何保证你重构后的功能逻辑是完整的?你如何让以后来维护你重构的代码的人能清晰明了的知道你写了...

    20200831 抖音前端校招一面视频1小时20分钟

    (我人傻了!!!!!!)

    1.看你在重构一个项目,你讲讲你重构的心得?你如何保证你重构后的功能逻辑是完整的?你如何让以后来维护你重构的代码的人能清晰明了的知道你写了什么?

    2.进程和线程的区别?同一进程不同线程的哪些数据段是共享的?

    3.http响应header中的cache-control有哪些常规值?

    4.http中get/post在请求参数上有什么区别?get参数的长度有限制吗?文件上传一般是post请求,那么在body里我们用什么类型的数据?

    5.编程题:给定形如的 URL,将其转换成com.toutiao.www的形式,要求必须原地操作。(就只能在给定的string上进行操作,不能新建变量来缓存操作结果,不能用split、splice、slice等会新建一个缓存变量的东东。反正这里我一点头绪都没有。)

    6.编程题:

    Semantic Versioning 是一个前端通用的版本规范。

    格式为“{MAJOR}.{MINOR}.{PATCH}-{alpha|beta|rc}.{number}”,

    要求实现 compare(a, b) 方法,比较 a, b 两个版本大小,

    当 a > b 是返回 1;

    当 a = b 是返回 0;

    当 a 

    其中,rc > beta > alpha,major > minor > patch;

    例子,1.2.3 

    7.CSS的盒子模型简单介绍一下;padding和margin设置百分比是基于什么计算的?

    8.布局题:CSS三栏布局实现,左右固宽200px,中间自适应, 高度相等。当top没有设置的时候,left和right元素的高度是基于什么去计算的?

    9.this判断题,写出输出结果。

    var length = 10;

    function fn() {

    return this.length+1;

    }

    var obj = {

    length: 5,

    test1: function() {

    return fn();

    }

    };

    obj.test2=fn;

    //下面代码输出是什么

    console.log(obj.test1())

    console.log(fn()===obj.test2())

    10.DOM操作题

    给一个元素Element,找出这个元素的全部Input子元素。

    function findAllInputElement (element) {

    }

    11.介绍一下事件循环。给出下题输出结果。

    console.log('begin')

    setTimeout(() => {

    console.log('setTimeout 1')

    Promise.resolve().then(() => {

    console.log('promise 1')

    setTimeout(() => {

    console.log('setTimeout2 between promise1&2')

    })

    }).then(() => {

    console.log('promise 2')

    })

    }, 0) console.log('end')

    12.怎么判断一个对象是空对象?

    13.useState为什么在调第二个函数的时候就能触发页面渲染?

    14.webpack的打包工具都有哪些,怎么配置?

    15.面试官总结:对于校招生毕业生来讲,在经验方面肯定是不太够的,那么我们去考察一个毕业生是否合格,就肯定会从基础上面去问,webpack、node、react的实现等,就比如,useState的原理。问这些的原因就是想看看你个人的潜力,想看看你个人能在未来两年或者多年后能走到多远。再说react,会用大家都会用,但你有没有去了解过怎么实现的?对于个人来讲,每个阶段有每个阶段的考察任务,对于你现阶段,就是看你基础,其他的都是你后面的事情,所以你要掌握好自己的节奏。

    (别问,问就是凉了。面试官说得很对,对于校招生来讲,基础才是最重要的,在这次面试中,我忘了很多基础的东西(实习的锅...),但是对很多技术上的东西也没有达到一个深度,就这有点得不偿失,害,现在只能抽时间疯狂补基础了。o(╥﹏╥)o)

    展开全文
  • 今天要和大家分享的是关于抖音 Android 性能优化的内存优化,希望对大家的学习和工作有所启发和帮助。 正文 内存作为计算机程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻...

    前言

    字节跳动技术团队是一个追求完美的团队,他们在技术上的思考常常会给我们带来很多启发。

    今天要和大家分享的是关于抖音 Android 性能优化的内存优化,希望对大家的学习和工作有所启发和帮助。

    正文

    内存作为计算机程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。抖音作为一款用户使用广泛的产品,需要在各种机器资源上保持优秀的流畅性和稳定性,内存优化是必须要重视的环节。

    本文从抖音 Java OOM 内存优化的治理实践出发,尝试给大家分享一下抖音团队关于 Java 内存优化中的一些思考,包括工具建设、优化方法论。

    抖音 Java OOM 背景

    在未对抖音内存进行专项治理之前我们梳理了一下整体内存指标的绝对值和相对崩溃,发现占比都很高。另外,内存相关指标在去年春节活动时又再次激增达到历史新高,所以整体来看内存问题相当严峻,必须要对其进行专项治理。抖音这边通过前期归因、工具建设以及投入一个双月的内存专项治理将整体 Java OOM 优化了百分之 80。

    Java OOM Top 堆栈归因

    在对抖音的 Java 内存优化治理之前我们先根据平台上报的堆栈异常对当前的 OOM 进行归因,主要分为下面几类:

    图 1. OOM 分类

    其中 pthread_create 问题占到了总比例大约在百分之 50,Java 堆内存超限为百分之 40 多,剩下是少量的 fd 数量超限。其中 pthread_create 和 fd 数量不足均为 native 内存限制导致的 Java 层崩溃,我们对这部分的内存问题也做了针对性优化,主要包括:

    • 线程收敛、监控

    • 线程栈泄漏自动修复

    • FD 泄漏监控

    • 虚拟内存监控、优化

    • 抖音 64 位专项

    治理之后 pthread_create 问题降低到了 0.02‰以下,这方面的治理实践会在下一篇抖音 Native 内存治理实践中详细介绍,大家敬请期待。本文重点介绍 Java 堆内存治理。

    堆内存治理思路

    从 Java 堆内存超限的分类来看,主要有两类问题:

    1. 堆内存单次分配过大多次分配累计过大

    触发这类问题的原因有数据异常导致单次内存分配过大超限,也有一些是 StringBuilder 拼接累计大小过大导致等等。这类问题的解决思路比较简单,问题就在当前的堆栈。

    2. 堆内存累积分配触顶

    这类问题的问题堆栈会比较分散,在任何内存分配的场景上都有可能会被触发,那些高频的内存分配节点发生的概率会更高,比如 Bitmap 分配内存。这类 OOM 的根本原因是内存累积占用过多,而当前的堆栈只是压死骆驼的最后一根稻草,并不是问题的根本所在。所以这类问题我们需要分析整体的内存分配情况,从中找到不合理的内存使用(比如内存泄露、大对象、过多小对象、大图等)。

    工具建设

    工具思路

    工欲善其事,必先利其器。从上面的内存治理思路看,工具需要主要解决的问题是分析整体的内存分配情况,发现不合理的内存使用(比如内存泄露、大对象、过多小对象等)。

    我们从线下和线上两个维度来建设工具:

    线下

    线下工具是最先考虑的,在研发和测试的时候能够提前发现内存泄漏问题。业界的主流工具也是这个思路,比如 Android Studio Memory Profiler、LeakCanary、Memory Analyzer (MAT)。

    我们基于 LeakCanary 核心库在线下设计了一套自动分析上报内存泄露的工具,主要流程如下:

    图 2.线下自动分析流程

    抖音在运行了一段线下的内存泄漏工具之后,发现了线下工具的各种弊端:

    1. 检测出来的内存泄漏过多,并且也没有比较好的优先级排序,研发消费不过来,历史问题就一直堆积。另外也很难和业务研发沟通问题解决的收益,大家针对解决线下的内存泄漏问题的 ROI(投入产出比)比较难对齐。

    2. 线下场景能跑到的场景有限,很难把所有用户场景穷尽。抖音用户基数很大,我们经常遇到一些线上的 OOM 激增问题,因为缺少线上数据而无从查起。

    3. Android 端的 HPORF 的获取依赖原生的 Debug.dumpHporf,dump 过程会挂起主线程导致明显卡顿,线下使用体验较差,经常会有研发反馈影响测试。

    4. LeakCanary 基于 Shark 分析引擎分析,分析速度较慢,通常在 5 分钟以上才能分析完成,分析过程会影响进程内存占用。

    5. 分析结果较为单一,仅仅只能分析出 Fragment、Activity 内存泄露,像大对象、过多小对象问题导致的内存 OOM 无法分析。

    线上

    正是由于上述一些弊端,抖音最早的线下工具和治理流程并没有起到什么太大作用,我们不得不重新审视一下,工具建设的重心从线下转成了线上。线上工具的核心思路是:在发生 OOM 或者内存触顶等触发条件下,dump 内存的 HPROF 文件,对 HPROF 文件进行分析,分析出内存泄漏、大对象、小对象、图片问题并按照泄露链路自动归因,将大数据问题按照用户发生次数、泄露大小、总大小等纬度排序,推进业务研发按照优先级顺序来建立消费流程。为此我们研发了一套基于 HPORF 分析的线下、线上闭环的自动化分析工具 Liko(寓意 ko 内存 Leak 问题)。

    Liko 介绍

    Liko 整体架构

    图 3. Liko 架构图

    整体架构由客户端、Server 端和核心分析引擎三部分构成。

    • 客户端

    在客户端完成 HPROF 数据采集和分析(针对端上分析模式),这里线上和线下策略不同。

    线上:主要在 OOM 和内存触顶时通过用户无感知 dump 来获取 HPROF 文件,当 App 退出到后台且内存充足的情况进行分析,为了尽量减少对 App 运行时影响,主要通过裁剪 HPROF 回传进行分析,减轻服务器压力,对部分比例用户采用端上分析作为 Backup。

    线下:dump 策略配置较为激进,在 OOM、内存触顶、内存激增、监测 Activity、Fragment 泄漏数量达到一定阈值多种场景下触发 dump,并实时在端上分析上传至后台并在本地自动生成 html 报表,帮助研发提前发现可能存在的内存问题。

    • Server 端

    Server 端根据线上回传的大数据完成链路聚合、还原、分配,并根据用户发生次数、泄露大小、总大小等纬度促进研发测消费,对于回传分析模式则会另外进行 HPORF 分析。

    • 分析引擎

    基于 MAT 分析引擎完成内存泄露、大对象、小对象、图片等自动归因,同时支持在线下自动生成 Html 报表。

    Liko 流程图

    图 4. Liko 流程图

    整体流程分为

    1. Hprof 收集

    2. 分析时机

    3. 分析策略

    Hprof 收集

    收集过程我们设置了多种策略可以自由组合,主要有 OOM、内存触顶、内存激增、监测 Activity、Fragment 泄漏数量达到一定阈值时触发,线下线上策略配置不同。

    为了解决 dump 挂起进程问题,我们采用了子进程 dump+fileObsever 的方式完成 dump 采集和监听。

    在 fork 子进程之前先 Suspend 获取主进程中的线程拷贝,通过 fork 系统调用创建子进程让子进程拥有父进程的拷贝,然后 fork 出的子进程中调用 Hprof 的 DumpHeap 函数即可完成把耗时的 dump 操作在放在子进程。由于 suspendresume 是系统函数,我们这里通过自研的 native hook 工具对 libart.so hook 获取系统调用。由于写入是在子进程完成的,我们通过 Android 提供的 fileObsever 文件写入进行监控获取 dump 完成时机。

    图 5.子进程 dump 流程图

    Hprof 分析时机

    为了达到分析过程对于用户无感,我们在线上、线下配置了不同的分析时机策略,线下在 dump 分析完成后根据内存状态主动触发分析,线上当用户下次冷启退出应用后台且内存充足的情况下触发分析。

    分析策略

    分析策略我们提供了两种,一种在 Android 客户端分析,一种回传至 Server 端分析,均通过 MAT 分析引擎进行分析。

    端上分析
    分析引擎

    端上分析引擎的性能很重要,这里我们主要对比了 LeakCanary 的分析引擎 Shark 和 Haha 库的 MAT。

    图 6. Shark VS MAT

    我们在相同客户端环境对 160M 的 HPROF 多次分析对比发现 MAT 分析速度明显优于 Shark,另外针对 MAT 分析后仍持有统治者树占用内存我们也做了主动释放,对比性能收益后采用基于 MAT 库的分析引擎进行分析,对内存泄漏引用链路自动归并、大对象小对象引用链自动分析、大图线下自动还原线上过滤无用链路,分析结果如下:

    内存泄漏

    图 7. 内存泄漏链路

    对泄漏的 Activity 的引用链进行了聚合分析,方便一次性解决该 Activity 的泄漏链释放内存。

    大对象

    图 8. 大对象链路

    大对象不止分析了引用链路,还递归分析了内部 top 持有对象(InRefrenrece)的 RetainedSize。

    小对象

    图 9. 小对象链路

    小对象我们对 top 的外部持有对象(OutRefrenrece)进行聚合得到占有小对象最多的链路。

    图片

    图 10. 图片链路

    图片我们过滤了图片库等无效引用且对 Android 8.0 以下的大图在线下进行了还原。

    回传分析

    为了最大限度的节省用户流量且规避隐私风险,我们通过自研 HPROF 裁剪工具 Tailor 在 dump 过程对 HPROF 进行了裁剪。

    裁剪过程

    图 11. Tailor 裁剪流程

    去除了无用信息

    • 跳过 header

    • 分 tag 裁剪

      • 裁剪无用信息:char[]; byte[]; timestamp; stack trace serial number; class serial number;
      • 压缩数据信息

    同时对数据进行 zlib 压缩,在 server 端数据还原,整体裁剪效果:180M—>50M---->13M

    优化实践

    内存泄漏

    除了通过后台根据 GCROOT+引用链自动分配研发跟进解决我们常见的内存泄漏外,我们还对系统导致一些内存泄漏进行了分析和修复。

    系统异步 UI 泄漏

    根据上传聚合的引用链我们发现在 Android 6.0 以下有一个 HandlerThread 作为 GCROOT 持有大量 Activity 导致内存泄漏,根据引用发现这些泄漏的 Activity 都被一个 Runnable(这里是 Runnable 是一个系统事件 SendViewStateChangedAccessibilityEvent)持有,这些 Runnable 被添加到一个 RunQueuel 中,这个队列本身被 TheadLocal 持有。

    图 12. HandlerThread 泄露链路

    我们从 SendViewStateChangedAccessibilityEvent 入手对源码进行了分析发现它在 notifyViewAccessibilityStateChangedIfNeeded 中被抛出,系统的大量 view 都会在自身的一些 UI 方法(eg: setChecked)中触发改函数。

    SendViewStateChangedAccessibilityEventrunOrPost 方法会走到我们常用的 View 的 postDelay 方法中,这个方法在当 view 还未被 attched 到根 view 的时候会加入到一个 runQueue 中。

    这个 runQueue 会在主线程下一次的 performTraversals() 中消费掉。

    如果这个 runQueue 不在主线程那就没有消费的机会。

    根据上面的分析发现造成这种内存泄漏需要满足一些条件:

    1. view 调用了 postDelay 方法 (这里是 notifyViewAccessisbilityStateChangeIfNeeded 触发)

    2. view 处于 detached 状态

    3. 上述过程是在非主线程里面操作的,ThreadLocal 非 UIThread,持有的 runQueue 不会走 performTraversals 消费掉。

    抖音这边大量使用了异步 UI 框架来优化渲染性能,框架内部由一个 HandlerThread 驱动,完全符合上述条件。针对该问题,我们通过反射获取非主线程的 ThreadLocal,在每次异步渲染完主动清理内部的 RunQueue。

    图 13. 反射清理流程

    另外,Google 在 6.0 上也修复了 notifyViewAccessisbilityStateChangeIfNeeded 的判断不严谨问题。

    内存泄漏兜底

    大量的内存泄漏,如果我们都靠推进研发解决,经常会出现生产大于消费的情况,针对这些未被消费的内存泄漏我们在客户端做了监控和止损,将 onDestory 的 Activity 添加到 WeakRerefrence 中,延迟 60s 监控是否回收,未回收则主动释放泄漏的 Activity 持有的 ViewTree 的背景图和 ImageView 图片。

    大对象

    主要对三种类型的大对象进行优化

    • 全局缓存:针对全局缓存我们按需释放和降级了不需要的缓存,尽量使用弱引用代替强引用关系,比如针对频繁泄漏的 EventBus 我们将内部的订阅者关系改为弱引用解决了大量的 EventBus 泄漏。

    • 系统大对象:系统大对象如 PreloadDrawableJarFile 我们通过源码分析确定主动释放并不干扰原有逻辑,在启动完成或在内存触顶时主动反射释放。

    • 动画:用原生动画代替了内存占用较大的帧动画,并对 Lottie 动画泄漏做了手动释放。

    图 14. 大对象优化点

    小对象

    小对象优化我们集中在字段优化、业务优化、缓存优化三个纬度,不同的纬度有不同的优化策略。

    图 15. 小对象优化思路

    通用类优化

    在抖音的业务中,视频是最核心且通用的 Model,抖音业务层的数据存储分散在各个业务维护了各自视频的 Model,Model 本身由于聚合了各个业务需要的属性很多导致单个实例内存占用就不低,随着用户使用过程实例增长内存占用越来越大。对 Model 本身我们可以从属性优化和拆分这两种思路来优化。

    • 字段优化:针对一次性的属性字段,在使用完之后即使清理掉缓存,比如在视频 Model 内部存在一个 Json 对象,在反序列完成之后 Json 对象就没有使用价值了,可以及时清理。

    • 类拆分:针对通用 Model 冗杂过多的业务属性,尝试对 Model 本身进行治理,将各个业务线需要用到的属性进行梳理,将 Model 拆分成多个业务 Model 和一个通用 Model,采用组合的方式让各个业务线最小化依赖自己的业务 Model,减少大杂烩 Model 不必要的内存浪费。

    业务优化

    • 按需加载:抖音这边 IM 会全局保存会话,App 启动时会一次性 Load 所有会话,当用户的会话过多时相应全局占用的内存就会较大,为了解决该问题,会话列表分两次加载,首次只加载一定数量到内存,需要时再加载全部。

    • 内存缓存限制或清理:首页推荐列表的每一次 Loadmore 操作,都不会清理之前缓存起来的视频对象,导致用户长时间停留在推荐 Feed 时,缓存起来的视频对象过多会导致内存方面的压力。在通过实验验证不会对业务产生负面影响情况下对首页的缓存进行了一定数量的限制来减小内存压力。

    缓存优化

    上面提到的视频 Model,抖音最早使用 Manager 来管理通用的视频实例。Manager 使用 HashMap 存储了所有的视频对象,最初的方案里面没有对内存大小进行限制且没有清除逻辑,随着使用时间的增加而不断膨胀,最终出现 OOM 异常。为了解决视频 Model 无限膨胀的问题设计了一套缓存框架主要流程如下:

    图 16. 视频缓存框架

    使用 LRU 缓存机制来缓存视频对象。在内存中缓存最近使用的 100 个视频对象,当视频对象从内存缓存中移除时,将其缓存至磁盘中。在获取视频对象时,首先从内存中获取,若内存中没有缓存该对象,则从磁盘缓存中获取。在退出 App 时,清除 Manager 的磁盘缓存,避免磁盘空间占用不断增长。

    图片

    关于图片优化,我们主要从图片库的管理和图片本身优化两个方面思考。同时对不合理的图片使用也做了兜底和监控。

    图片库

    针对应用内图片的使用状况对图片库设置了合理的缓存,同时在应用 or 系统内存吃紧的情况下主动释放图片缓存。

    图片自身优化

    我们知道图片内存大小公式 = 图片分辨率 * 每个像素点的大小。

    图片分辨率我们通过设置合理的采样来减少不必要的像素浪费。

    //开启采样
    ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
        .setDownsampleEnabled(true)
        .build();
    Fresco.initialize(context, config);
    
    //请求图片时,传入resize的大小,一般直接取View的宽高
    ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
        .setResizeOptions(new ResizeOptions(50, 50))
        .build();mSimpleDraweeView.setController(
        Fresco.newDraweeControllerBuilder()
            .setOldController(mSimpleDraweeView.getController())
            .setImageRequest(request)
            .build());
    

    而单个像素大小,我们通过替换系统 drawable 默认色彩通道,将部分没有透明通道的图片格式由 ARGB_8888 替换为 RGB565,在图片质量上的损失几乎肉眼不可见,而在内存上可以直接节省一半。

    图片兜底

    针对因 activity、fragment 泄漏导致的图片泄漏,我们在 onDetachedFromWindow 时机进行了监控和兜底,具体流程如下:

    图 17. 图片兜底流程

    图片监控

    关于对不合理的大图 or 图片使用我们在字节码层面进行了拦截和监控,在原生 Bitmap or 图片库创建时机记录图片信息,对不合理的大图进行上报;另外在 ImageView 的设置过程中针对 Bitmap 远超过 view 本身超过大小的场景也进行了记录和上报。

    图 18. 图片字节码监控方案

    更多思考

    是不是解决了 OOM 内存问题就告一段落了呢?作为一只追求极致的团队,我们除了解决静态的内存占用外也自研了 Kenzo(Memory Insight)工具尝试解决动态内存分配造成的 GC 卡顿。

    Kenzo 原理

    Kenzo 采用 JVMTI 完成对内存监控工作,JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口。JVMTI 开发时,应用建立一个 Agent 使用 JVMTI,可以使用 JVMTI 函数,设置回调函数,并从 Java 虚拟机中得到当前的运行态信息,并作出自己的业务判断。

    图 19. Agent 时序图

    Jvmti SetEventCallbacks 方法可以设置目标虚拟机内部事件回调,可以根据 jvmtiCapabilities 支持的能力和我们关注的事件来定义需要 hook 的事件。

    Kenzo 采用 Jvmti 完成如下事件回调:

    • 类加载准备事件 -> 监控类加载

      • ClassPrepare:某个类的准备阶段完成。
    • GC -> 监控 GC 事件与时间

      • GarbageCollectionStart:GC 启动时。
      • GarbageCollectionFinish:GC 结束后。
    • 对象事件 -> 监控内存分配

      • ObjectFree:GC 释放一个对象时。
      • VMObjectAlloc:虚拟机分配一个对象的时候。

    框架设计

    Kenzo 整体分位两个部分:

    生产端

    • 采集内存数据

    • 以 sdk 形式集成到宿主 App

    消费端

    • 处理生产端的数据

    • 输入 Kenzo 监控的内存数据

    • 输出可视化报表

    图 20. kenzo 框架

    生产端主要以 Java 进行 API 调用,C++完成底层检测逻辑,通过 JNI 完成底层逻辑控制。

    消费端主要以 Python 完成数据的解析、视图合成,以 HTML 完成页面内容展示。

    工作流

    图 21. kenzo 框架

    可视化展示

    图 22. kenzo 聚合展示

    启动阶段内存归因

    基于动态内存监控我们对最为核心的启动场景的内存分配进行了归因分析,优化了一些头部的内存节点分配:

    图 23.启动阶段内存节点归因

    另外我们也发现启动阶段存在大量的字符串拼接操作,虽然编译器已经优化成了 StringBuider append,但是深入 StringBuider 源码分析仍在存在大量的动态扩容动作(System.copy),为了优化高频场景触发动态扩容的性能损耗,在 StringBuilder 在 append()的时候,不直接往 char[]里塞东西,而是先拿一个 String[]把它们都存起来,到了最后才把所有 String 的 length 加起来,构造一个合理长度的 StringBuilder。通过使用编译时字节码替换的方式,替换所有 StringBuilder 的 append 方法使用自定义实现,优化后首次安装首页 Feed 滑动 1min 的 FPS 提升 1 帧/S,非首次安装启动,滑动 1min 的 FPS 提升 0.6 帧/S。

    加入我们

    我们是负责抖音客户端基础技术能力研发和前沿技术探索的客户端团队,我们专注于性能、架构、稳定性、研发工具、编译构建等方向的深耕,保障超大规模团队的研发效率和工程质量,将 6 亿人使用的抖音打造成极致用户体验的产品。

    如果你对技术充满热情,欢迎加入抖音基础技术团队,让我们共建亿级全球化 App。目前我们在上海、北京、杭州、深圳均有招聘需求,内推可以联系邮箱:tech@bytedance.com;邮件标题:姓名 - 工作年限 - 抖音 - 基础技术 - Android / iOS

    更多分享

    本文在开源项目:https://github.com/xieyuliang/Tecent-Android-Archiecture(点击此处蓝色字体可查看)
    中已收录
    ,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

    这次就分享到这里吧,下篇见

    西瓜视频稳定性治理体系建设一:Tailor 原理及实践

    基于有限状态机与消息队列的三方支付系统补单实践

    UME - 丰富的Flutter调试工具

    一例 Go 编译器代码优化 bug 定位和修复解析


    作者:字节跳动技术团队
    原文地址:https://juejin.cn/post/6908517174667804680

    展开全文
  • 写在前面的话 性能优化这个知识点是很多大厂面试中都会问到的问题,尤其是想要面试抖音的...正在准备抖音Android面试的朋友记得点赞收藏哦,希望本文对大家的学习和工作有帮助。 原文地址:新一代全能型性能分析

    写在前面的话

    性能优化这个知识点是很多大厂面试中都会问到的问题,尤其是想要面试抖音的Android岗的朋友。

    用户交互响应的耗时,作为 Android 用户日常感知最深的一项性能指标,在日常开发中有着非常重要的意义。而抖音 Android 基础技术团队为打造极致的交互响应体验,一直在致力于极致性能的探索,其中就包括如何打造极致的耗时检测工具。

    今天要和大家分享的是抖音内部人员的技术总结。正在准备抖音Android岗面试的朋友记得点赞收藏哦,希望本文对大家的学习和工作有帮助。

    原文地址:新一代全能型性能分析工具 Rhea

    推荐阅读:重磅首发!昨晚最新爆出的“移动开发性能优化笔记”,GitHub已标星8K,看完我爱了!

    概述

    俗话说,工欲善其事,必先利其器,我们要做好性能优化,首要是要能够发现性能的问题,这就需要有靠谱的工具来帮助我们做性能分析。市面上主流的性能分析工具有:Systrace、TraceView、Android Studio 的 CPU Profiler。相信做性能优化的同学对这些工具应该都是非常熟悉了,抖音最早也是用 Systrace 作为主要的分析工具,在优化前期也发挥了比较大的作用。

    随着抖音的性能优化来到了深水区,我们需要发现并解决更细粒度、更多维度的性能问题,我们会关注几毫秒的耗时,关注线上一些低端机用户遇到的锁阻塞和 IO 等待问题。而市面上这些主流的性能分析工具因其使用的局限性和较大的性能损耗,已经无法满足抖音性能优化的需求。为了能够百尺竿头更进一步,我们需要开发更加灵活、精细化以及多元的信息和工具来辅助我们进行高效的优化工作。

    在这样的背景之下,抖音 Android 基础技术团队开发了 Rhea( [ˈriːə] 瑞亚,寓意时光女神)跟踪器(Tracer),其是一种通过静态代码插桩技术自动添加 Trace,用来分析 APP 运行时耗时的性能分析工具,意思是要做一个功能全面、追求效率、大家都喜欢的女神,也符合我们工具的核心设计原则。Rhea 跟踪器获取 Trace 不仅要性能损耗低,还要能脱离 PC 端工具在 App 侧直接抓取,跟踪更多常规函数耗时的同时还要可以跟踪系统调用,如:锁信息、I/O 耗时、Binder IPC 以及更多其他信息。最后,还提供转换脚本工具,用于将原始跟踪文件生成可视化报告,便于用户分析性能问题。

    优势对比

    Rhea 当前因其无侵入、高性能、信息全等优势已在字节多个 APP 上落地使用,效果明显,已多次帮助大家快速发现性能问题,其包含的信息包括不限层级的应用层函数、IO、锁、Binder、CPU 调度等耗时信息等,其部分效果如下所示:

    相对于其他 Android 性能排查工具,其具体优势表现为:

    当前,Systrace 只能监控特定系统信息,监控应用层的耗时则需要手动打点;TraceView 性能跟采样率关系密切,采样过于频繁性能开销巨大,采样过低又难以精准发现问题函数;Nanoscope 虽然几乎没有性能损耗,但每次都需定制 ROM 刷机,使用成本非常高,并且这些工具都只支持 debugable 的应用程序线下分析,这些工具在针对 APP 性能优化都有不甚完美之处,而 Rhea 是一个集大成者,融合了各工具优势并弥补了相关缺陷。

    架构演进之路

    第一阶段:基于 Systrace 补充函数耗时 Trace

    Systrace 是 Android 性能调试优化的常用工具,它可以收集进程的活动信息,如函数调用耗时、锁等;也可以收集内核信息,如 CPU 调度、IO 活动、Binder 调用信息等;这些信息会统一时间轴,在 Chrome 浏览器中显示出来,方便工程师性能调试、优化卡顿等工作。因此,抖音早期性能优化首选 Systrace 作为主要工具,其大致流程如下:

    1. 功能改造

    Systrace 工具只能监控特定系统调用的耗时情况,它不支持应用程序代码的耗时分析,所以在使用时有一些局限性。原生 Systrace 需要开发者在方法的起止位置手动加入 Trace.beginSectionTrace.endSection 方法对,这个过程就变成了开发者预判耗时位置,然后在手动加入监控函数对,通过不断重复添加监控点、打包、运行、采集数据,从而一步步完成耗时方法定位,这也使得 Systrace 的使用成本变得极高。

    为了提高 Systrace 的易用性,我们开发了 Rhea 1.0 对 Systrace 功能进行了改造,加入了自动插桩机制:通过字节码插桩自动完成 Trace.beginSectionTrace.endSection 方法对的插入,并且通过运行时限制方法层级的方式,来有效控制因引入监控带来的性能损耗。

    • 插桩类及桩方法伪代码:
    class Tracer{
        method_stack = list()
        max_size = 6
     
        methodIn(method_id, method_name){
            if(method_stack.size()<=max_size){
                method_stack.push(method_id)
                Trace.beginSection(method_name)
            }
        }
     
        methodOut(){
            if(method_stack.size>0){
                method_stack.pop()
                Trace.end()
            }
        }
    }
    
    • 被插桩方法:
    method1(){
        Tracer.methodIn(1,method1)
         ...
        Tracer.methodOut()
    }
    

    输出数据如下所示,指定层级内所有方法即可按照预期展示在输出 html 中:

    2. 方法 Did not finished 问题

    在使用改造后的 systrace 时,我们时常会遇到如下问题:

    分析发现,主要原因在于方法在运行期执行中被中断,例如:方法执行过程中发生异常后,被其调用者方法捕获,发生异常方法的 Systracer.o 方法未被调用。如图:test 方法中的 error 方法执行时出现 arr[2]的数组越界,导致 test 方法中的插桩方法 SysTracer.o(13L)未调用,异常被 onCreate 中的 catch 块捕获,从而导致 test 的插桩方法没有被成对调用,最终导致了 test 外层所有的方法调用都无法正确闭合。(注意:本小结提到的桩方法,即 SysTracer 相关方法,均是通过字节码插桩自动插入

    解决办法,在外层所有异常捕获的位置,额外插入桩方法,重新这种异常调用链下的桩方法不成对问题。如下图:

    3. 依然存在的问题

    • 性能问题

    随着 Rhea 1.0 功能的深入使用,在带来极大便利的同时,功能本身的不足也逐渐暴露出来。在采集数据过程中,其本身的性能损耗会导致在一些实际性能优化过程中会带偏方向。经我们严格测试,其性能损耗有 11.5%左右,如下所示:

    在实际使用过程中发现,在开启 Systrace 之后,对应 Sleep 耗时占比在极端情况下会超过 40%以上。一方面是 APP 锁带来的 Sleep 耗时。例如,在抖音启动路径上 SharedPreference 优化过程中,在开启 Rhea 1.0 的 Systrace 功能后,发现 SP 调用存在明显的锁耗时,当时针对 SP 进行了一番锁的优化后,上线发现效果并不明显,后续经过一系列排查,发现锁的耗时是由于开启 Systrace 功能后导致。另一方面是 IO 带来的 Uninterrupt Sleep 耗时。例如,我们在一次性能优化过程中看到了很多__fdget_pos 操作,对_fdget_pos 操作相对 Uninterruptible Sleep 的占比统计了下,至少占了 Uninterruptible Sleep 总耗时的 60%左右。

    我们花了比较长时间,额外加了很多 IO 的信息,最终定位原因是在开启 Systrace 后,由于所有线程的 trace 都会写入同一个文件,所有线程会同步竞争内核态的文件 pos 锁导致。工具本身的性能问题误导了我们的排查方向,同时也暴露了在排查这种 IO Wait 问题的时候由于 IO 信息不全导致排查效率不高的问题。

    • 限制层级导致的调用缺失

    Systrace 的原理决定了当我们在应用层插入更多函数插桩以定位应用层耗时问题的时候,会导致非常严重的性能问题,所以我们在线下使用该工具会通过限制插桩层级的方式以减少运行时性能损耗。但是层级的限制使得超过既定层级的函数调用数据缺失,在分析调用层级较深的函数耗时的时候,无法定位到准确的耗时点。

    • 使用场景限制

    由于 Systrace 在采集数据的过程中,需要依赖 PC,对于一些需要脱离 PC 采集数据的场景,Systrace 就无法满足需求了。比如我们产品运营同学经常会在线下场景实地测试抖音的使用性能,例如地铁、餐馆、咖啡厅等,这些实际使用场景下的性能数据,systrace 就无法支持到了。

    • 低端机无法正常使用

    我们在针对低端机进行耗时优化时,发现诸如三星、oppo 等一些早期的低端机型,systrace 也不能支持其数据抓取。

    针对以上问题,我们对工具进行了深入的探索和优化。于是,工具的开发进入了第二阶段。

    第二阶段:高性能全场景的 Trace 抓取工具

    1. 功能升级

    为了弥补第一阶段功能短板,进一步提高性能,同时能满足更多使用场景,我们找到了新的解决方案:在 Java 层,通过记录方法首末位置时间戳、所在线程等信息,过滤出大于指定耗时阈值的函数后,将数据异步记录到文件。数据采集结束后,将输出文件转换成指定格式后,便可通过 SDK 提供的 Systrace 工具转化成方便查看的 Html 格式,从而实现和 Systrace 相同的可视化效果。

    2. 实现原理

    Rhea 2.0 如何采集数据并生成和 Systrace 相同可视化效果的 html 呢?SDK 中 Systrace 工具的–from-file 命令可将原始的.trace 格式数据转成 html 格式,分析.trace 数据内部格式:

    多次尝试后得出结论,可被 SDK Systrace 工具解析的 .trace 文件需满足如下格式:

    格式说明:

    • :线程名,若为主线程,可指定为包名。

    • :线程 ID。

    • <B|E>:标记该条记录为方法开始(B)还是结束(E)。

    • :所在进程 ID。

    • :方法标记,字符长度不可超过 127。

    由此可知,Mtrace 采集的数据至少需要包含以上内容。

    以下则是对应 Trace 格式:

    depth,methodID,inTime,outTime,threadName,threadID
    
    

    相较于 Rhea 1.0 的 Systrace,Rhea 2.0 的 Method Trace 性能损耗有了显著的降低,性能损耗也由 11.5%下降至 3%,效果如下所示:

    [图片上传失败…(image-2374ec-1613733813728)]

    3. 最佳实践

    • 功能使用

    MTrace 相较于 Systrace,提供了更丰富的线下功能,其中包括:解决针对真实用户点对点的卡顿耗时问题反馈功能,解决产品、运营、QA 同学外出走查场景的问题反馈功能。

    总之,不管你在哪,性能反馈都一触即达!如图为完整操作流程。

    • 线上案例

    一个真实的案例:抖音灰度版本线上用户反馈卡顿,通过 MTrace 功能包实现远程卡顿问题分析排查!以下则是通过用户配合回传的真实卡顿数据,经过解析即可发现耗时调用点:

    4. 存在的问题

    由于这个阶段采集的只有 Java 方法层的数据,在抖音启动 IO 耗时优化工作中,Method Trace 无法提供哪些函数进行了 IO 操作,以及 IO 操作读取/写入了哪些文件,给优化工作带来了较大的难度。另外在一些复杂场景中,Method Trace 只记录函数执行时长,但是不能准确定位是由于多线程同步等锁或者系统 IO 导致的执行时间变长。

    针对上面的问题,我们意识到一套优秀的 Trace 工具还需要融合更多的系统事件,于是工具进入了第三阶段的打磨。

    第三阶段:动态一体化 Trace 工具规划

    Rhea 1.0 和 2.0 在抖音早期的性能优化工作中成绩显著,但随着优化工作的深入同时也暴漏诸多局限与不便。

    一方面,使用常规的 Systrace 工具做性能优化,本身有诸多局限性。一是 Trace 信息少,在默认情况下,只包含系统预置的耗时打点信息,并不足以支持常规的耗时分析需要在 App 侧手动调用 Trace.beginSectionTrace.endSection 方法才能获取更多函数耗时信息,为避免影响线上包大小,使用完以后又需手动移除,一上一下事倍而功半。

    二是 Systrace 本身性能损耗大,特别是应用通过插桩的方式对业务代码进行大量的打点时,极端情况性能损耗会超过 50%。三是 Systrace 完全依赖 PC 端工具抓取,不够灵活。尤其是需要能够稳定复现性能问题的场景,对于一些特定区域或者特定用户群体才能复现的问题无法获直接高效的取到有效信息,依赖研发或者测试走查,甚至用户反馈的部分概率问题即使走查也无法通过 Systrace 获取到对应的信息,从而导致优化效率低。

    另一方面,通过简单定制 Trace 的获取函数耗时相较于 Systrace,虽然有显著的性能提升,和更高的灵活性,但数据只包含基本的耗时信息,在部分复杂场景(如持有锁引起的耗时),数据仍存在局限。

    如上工具都均已无法完全满足抖音的启动、首刷以及低端机等核心场景的性能优化工作,我们需要重新设计和规划功能更加强大的动态一体化 Trace 工具来辅助分析性能。

    1. 该工具要非常灵活,可以不依赖 PC 端的抓取脚本,同时支持线上线下,能够在应用任何想要抓取数据的时候运行,作为一个平台性工具,Rhea 还需要支持动态扩展,支持多种场景的配置和动态开关,可以将任意需要的信息进行采集。

    2. 该工具抓取的 Trace 信息要全面,能够采集和追踪包括 ATrace 插桩、等锁信息、I/O 信息以及 Binder 耗时等在内的多种信息。

    3. 要支持可视化,统一的格式进行输出和格式化,最终以兼容 Systrace 的结果进行前端展示和使用,尽量不要改变使用者习惯。

    4. 性能损耗要低,以免带偏性能优化方向。

    因此,我们重新设计了新一代 Trace 分析工具:

    整体上,App 通过集成 Rhea SDK 在打包时不限层级插入函数耗时桩方法,在运行时插入 IO、Binder、Lock 等相关 Trace 信息,支持动态配置,统一 Trace 格式为 atrace,同时支持获取系统级别的 Linux ftrace、Android Framework atrace 和 App 插入的 atrace 信息,能够不依赖 PC 抓取,最终提供可视化显示。具体实现如下:

    一、不依赖 PC 抓取 Trace

    为了实现不依赖 PC 抓取 Trace,我们有必要先了解下 Android atrace 的实现机制。首先,是 atrace 包括的数据源包括:

    其中,用户空间的数据包括了应用层的自定义 Trace、系统层的 gfx 渲染相关 Trace、系统层打的锁相关的 Trace 信息等,其最终都是通过调用 Android SDK 提供的Trace.beginSection或者 ATRACE_BEGIN 记录到同一个文件点/sys/kernel/debug/tracing/trace_marker 中的。此节点允许用户层写入字符串,ftrace 会记录该写入操作时的时间戳,当用户在上层调用不同函数时,写入不同的调用信息,比如函数进入和退出分别写入,那么 ftrace 就可以记录跟踪函数的运行时间。atrace 在处理用户层的多种 trace 类别时,只是激活不同的 TAG,如选择了 Graphics,则激活 ATRACE_TAG_GRAPHICS,将渲染事件记录到 trace_marker。

    而内核空间的数据主要是一些补充的分析数据 freq、sched、binder 等,常用的比如 CPU 调度的相关信息包括:

    • CPU 频率变化情况

    • 任务执行情况

    • 大小核的调度情况

    • CPU Boost 调度情况

    这些信息是 App 可以通过直接读取/sys/devices/system/cpu 节点下相关信息获得,而另外一部分标识线程状态的信息则只能通过系统或者 adb 才能获取,且这些信息不是统一的一个节点控制,其需要激活各自对应的事件节点,让 ftrace 记录下不同事件的 tracepoint。内核在运行时,根据节点的使能状态,会往 ftrace 缓冲中打点记录事件。例如,激活线程调度状态信息记录,需要激活类似如下相关节点:

    events/sched/sched_switch/enable
    events/sched/sched_wakeup/enable
    

    激活后,则可以获取到线程调度状态相关的信息,比如:

    • Running: 线程在正常执行代码逻辑

    • Runnable: 可执行状态,等待调度,如果长时间调度不到,说明 CPU 繁忙

    • Sleeping: 休眠,一般是在等待事件驱动

    • Uninterruptible Sleep: 不可中断的休眠,需要看 Args 的描述来确定当时的状态

    • Uninterruptible Sleep - Block I/O: IO 阻塞

    最终,上述两大类事件记录都汇集到内核态的同一缓冲中,PC 端上 Systrace 工具脚本是通过指定抓取 trace 的类别等参数,然后触发手机端的/system/bin/atrace 开启对应文件节点的信息,接着 atrace 会读取 ftrace 的缓存,生成只包含 ftrace 信息的 atrace_raw 信息,最终通过脚本转换成可视化 HTML 文件。大致流程如下:

    因此,我们基于 Android atrace 的实现原理,我们同步参考了 Facebook 的 profilo 用于在 APP 侧直接获取 atrace 的方案,实现了不依赖 PC 抓取 Trace 的方法。

    我们通过 dlopen 获取 libcutils.so 对应句柄,通过对应 symbol 从中找到 atrace_enabled_tags 和 atrace_marker_fd 对应指针,从而设置 atrace_enabled_tags 用以打开 atrace 开关,具体实现如下:

     
      std::string lib_name("libcutils.so");
      std::string enabled_tags_sym("atrace_enabled_tags");
      std::string marker_fd_sym("atrace_marker_fd");
     
      if (sdk < 18) {
        lib_name = "libutils.so";
        // android::Tracer::sEnabledTags
        enabled_tags_sym = "_ZN7android6Tracer12sEnabledTagsE";
        // android::Tracer::sTraceFD
        marker_fd_sym = "_ZN7android6Tracer8sTraceFDE";
      }
     
      if (sdk < 21) {
        handle = dlopen(lib_name.c_str(), RTLD_LOCAL);
      } else {
        handle = dlopen(nullptr, RTLD_GLOBAL);
      }
      // safe check the handle
      if (handle == nullptr) {
        ALOGE("atrace_handle is null");
        return false;
      }
     
      atrace_enabled_tags_ = reinterpret_cast<std::atomic<uint64_t> *>(
              dlsym(handle, enabled_tags_sym.c_str()));
      if (atrace_enabled_tags_ == nullptr) {
        ALOGE("atrace_enabled_tags not defined");
        goto fail;
      }
     
      atrace_marker_fd_ = reinterpret_cast<int*>(
          dlsym(handle, marker_fd_sym.c_str()));
    

    接下来,我们通过 hook libcutils 动态库中的 write、write_chk 方法通过判定 atrace_marker_fd 来将对应 atrace 信息拦截下来转储到到本地或上传到云端分析。实现如下所示:

    ssize_t proxy_write_chk(int fd, const void* buf, size_t count, size_t buf_size) {
      BYTEHOOK_STACK_SCOPE();
      if (Atrace::Get().IsAtrace(fd, count)) {
        Atrace::Get().LogTrace(buf, count);
        return count;
      }
     
      ATRACE_BEGIN_VALUE("__write_chk:", FileInfo(fd, count).c_str());
     
      size_t ret = BYTEHOOK_CALL_PREV(proxy_write_chk, fd, buf, count, buf_size);
     
      ATRACE_END();
     
      return ret;
    }
    

    二、提供更加全面 Trace 信息

    1. 锁耗时

    Java 层的锁,无论是同步方法还是同步块,最终都会走到虚拟机的 MonitorEnter 和 MonitorExit,在 MonitorEnter 中实现了多种锁状态的切换,包括从无锁到轻锁,轻锁中的偏向和重入,出现竞争并超过自旋的次数之后升级成重锁分配 monitor 对象,其中 art 现在的自旋不是真的自旋,而是用 sched_yield 主动让出 CPU 等待下次调度。

    而我们需要首先关注的就是出现锁竞争升级成重锁后的等待耗时信息,这个信息从 Android 6.x 开始会通过 ATrace 的方式输出到 trace_marker 中。

    但是想要轻锁的信息还需要做一些额外的工作,因为是否输出轻锁的 ATrace 信息除了 ATRACE_ENABLE 条件之外,还有另外一个 systrace_lock_logging 的开关变量控制,这个变量是虚拟机中一个全局变量的成员,这个成员变量的值正常情况下是由虚拟机启动的时候确定,默认是 false,可以通过启动虚拟机的时候传递-verbose:sys-locks 参数来打开,但是作为普通应用我们没有办法通过这种方式来打开,所以需要用非常规手段在运行时动态打开:

    1. 首先确认从 Android7.x 开始,这个结构的大小、成员顺序是否有发生变化;

    2. 如果没有变化,则可以自己定义一个相同的结构,因为里面都是原始的 bool 类型变量,不会引入其他依赖;

    3. 如果有变化,但是向前兼容,我们想要访问的成员位置没有变化,只是往后追加了成员,也同样可以自己定义相同的结构;

    4. 通过 dlsym 找到虚拟机的全局符号 gLogVerbosity

    5. 将其类型转换为预先定义的结构体类型;

    6. 访问 systrace_lock_logging 成员并赋值为 true;

    7. 轻锁的 ATrace 信息即可正常输出;

    std::string lib_name("libart.so");
    // art::gLogVerbosity
    std::string log_verbosity_sym("_ZN3art13gLogVerbosityE");
     
    void *handle = nullptr;
    handle = npth_dlopen_full(lib_name.c_str());
    if (handle == nullptr) {
      ALOGE("libart handle is null");
      return false;
    }
     
    log_verbosity_ = reinterpret_cast<LogVerbosity*>(
        npth_dlsym(handle, log_verbosity_sym.c_str()));
    if (log_verbosity_ == nullptr) {
      ALOGE("gLogVerbosity not defined");
      npth_dlclose(handle);
      return false;
    }
     
    npth_dlclose(handle);
    

    2. IO 耗时

    在做抖音在启动路径上性能优化时,我们统计了冷启动的耗时,其中占比最长的是进程处于 D 状态(不可中断睡眠态,Uninterruptible Sleep ,通常我们用 PS 查看进程状态显示 D,因此俗称 D 状态)的时间,这部分耗时占比占总启动耗时的 40%左右,进程为什么会被置于 D 状态呢?处于 uninterruptible sleep 状态的进程通常是在等待 IO,比如磁盘 IO,其他外设 IO,正是因为得不到 IO 的响应,进程才进入了 uninterruptible sleep 状态,所以要想使进程从 uninterruptible sleep 状态恢复,就得使进程等待的 IO 恢复。类似如下:

    但我们在使用 Systrace 进行优化时仅能得到如上内核态的调用状态,却无法得知具体的 IO 操作是什么。因此,我们专门设计了一套获取 IO 耗时信息的方案,其包括用户空间和内核空间两部分。

    一是在用户空间,为了采集到需要的 I/O 耗时信息,我们通过 Hook I/O 操作时标准的关键函数族,包括 open,write,read,fsync,fdatasync 等,插入对应的 trace 埋点用于统计对应的 IO 耗时。以 fsync 为例:

    int proxy_fsync(int fd) {
      BYTEHOOK_STACK_SCOPE();
      ATRACE_BEGIN_VALUE("fsync:", FileInfo(fd).c_str());
     
      int ret = BYTEHOOK_CALL_PREV(proxy_fsync, fd);
     
      ATRACE_END();
      return ret;
    }
    

    二是在内核空间,除了可由 systrace 或 atrace 直接支持启用的功能之外,ftrace 还提供了其他功能,并且包含一些对调试性能问题至关重要的高级功能(这些功能需要 root 访问权限,通常可能也需要新的内核)。因此,我们基于此添加了显示定制 IO 信息等功能。在线下模式,我们开启了/sys/kernel/debug/tracing/events/android_fs 节点下 ftrace 信息,用于收集 IO 相关的信息,

    这时候,我们追本溯源,先找到 Systrace 之母,Google Android 和 Chrome 团队的所有开源项目 Catapult 。正是 Catapult 生成了 Systrace 及其解析器的工具,在 Catapult 中,采用 javascript 实现了一个跨平台的 trace 解析工具,我们在此基础上开发了 Rhea 工具脚本将转换成 systrace 可显示化的格式,用于快速诊断发现 IO 性能瓶颈。

    例如,我们线上监控发现我们某个 View 方法调用 setText 方法会导致 ANR,线下通过 Systrace 抓取 Trace 如下:

    此时,看到主线程处于 D 状态,却束手无策,而通过我们的 Rhea 工具,获取 Trace 如下:

    我们很容易就定位到此时是由于读取对应字体带来的 IO 耗时导致的问题。

    3. Binder 耗时

    在抖音启动性能性能优化过程中,我们通常还会遇到 Sleep 带来的耗时问题,这部分耗时通常占据总耗时的 30%左右,处在这种睡眠状态,进程通常是在等锁或是 Binder 调用耗时导致,通常在线下,我们可以通过开启 tracing/events/binder 节点获取到,但是在线上由于权限问题我们很难获取到这部分信息。因此,我们通过 Hook libbinder.so 对应的 android_os_BinderProxy_transact 方法来统计对应 binder 调用耗时。

    if (TraceProvider::Get().isEnableBinder()) {
      // static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,jint code, jobject dataObj, jobject replyObj, jint flags)
      bytehook_stub_t stub = bytehook_hook_single(
          "libbinder.so",
          NULL,
          "_ZN7android14IPCThreadState8transactEijRKNS_6ParcelEPS1_j",
          reinterpret_cast<void*>(proxy_transact),
          NULL,
          NULL);
      stubs.push_back(stub);
    }
    

    之后,统计对应 binder 耗时,如果耗时超过指定阈值,则将对应堆栈打印出来用于辅助分析 Sleep 耗时问题。

    static void log_binder(int64_t start, int64_t end, int64_t flags) {
      JNIEnv *env = context.env;
      env->CallStaticVoidMethod(context.javaRef, context.logBinder, start, end, flags);
    }
     
    status_t proxy_transact(void *pIPCThreadState, int32_t handle, uint32_t code,
                      const void *data, void *reply, uint32_t flags) {
      // todo: add more informations
      nsecs_t start = systemTime();
      status_t status = BYTEHOOK_CALL_PREV(proxy_transact, pIPCThreadState, handle, code, data, reply,
                                  flags);
      nsecs_t end = systemTime();
      nsecs_t cost_us = ns2us(end - start);
      if (is_main_thread() && cost_us > 10000) {
        log_binder(ns2us(start), ns2us(end), flags);
        nsecs_t end_ = systemTime();
      }
     
      return status;
    }
    

    trace 效果如图所示:

    4. 支持后续增加更多数据源

    当然,仅仅支持上述这些信息不可能完全覆盖我们性能优化过程中未来还可能遇到的其他问题,因此,我们支持了动态配置的功能,后续仅需要在现有框架下,简单添加对应配置项及其功能即可快速方便收集到我们所需要的信息。

    enum TraceConfigKey {
      kIO = 0,
      kBinder,
      kThinLock,
      kStopTraceUnhook,
      kLockStack,
     
      kKeyEnd,
    };
    

    5. 不限层级插桩获取函数耗时

    限制插桩的层级固然可以提升运行时性能,但是限制层级后面临两个问题:

    • 函数调用数据采集不全面;

    • 难以定位深层的耗时调用;

    因此在用户态,为了获取 App 更多的 Trace 信息,便于性能优化。我们采用不限制层级的插桩方案。开发了在编译阶段不限制层级插桩的插件,通过静态代码插桩方式,在 App 调用方法的起始和结束位置分别插入 Trace.beginSectionTrace.endSection 。效果如下:

    三、优化降低性能损耗

    1. 插桩性能优化

    在插桩阶段, 我们做了如下优化:

    • 支持自定义插桩作用域, 减少 Trace 对于其他无关模块的运行损耗;

    • 针对 Trace 数据出现不闭合的问题, 对 catch 代码块进行全插桩;

    • 针对高频调用函数, 可以选择性的添加到黑名单中, 提升运行时性能;

    • 为支持生产环境使用,我们采用在 proguard 后进行插桩,由于函数内联等优化, 相较于混淆前插桩插桩数量可以减少 2.6%。对于线上模式,直接插入方法 ID,收集 Trace 后需在主机端或服务端对方法 id 重新映射成方法名,但又考虑到线下用户的易用性,在线下模式打包阶段直接插入方法名;

    • 在编译阶段通过分析字节码信息,过滤掉不耗时函数的插桩。

    2. 优化 App 侧启停 Trace 性能

    由于 App 侧抓取 Trace 的实现要依赖于 hook,我们参考了 Facebook Profilo 的实现,但其实现存在动态库过大、启停 Trace 耗时问题,因此我们进一步优化了 App 本地获取 atrace 依赖的动态库大小和性能。如下所示:

    3. 优化 Trace 写入性能

    由于在 App 方法中插入大量 Trace 信息,在开启 atrace 后,所有线程会将所有的 trace 都写入到 trace_marker 文件,会带来 IO 损耗剧增,会掩盖真实性能问题,原因是所有线程都在短时间向 trace_marker 文件进行写入操作,同时竞争内核态 pos 锁,导致获取到的 trace 文件无法真实反映性能问题,如下图所示:

    因此,我们将原本直接写入内核态文件的 Trace 在用户态进行拦截,缓存起来,再以异步 IO 的方式转储。既避免了大量用户态与内核态切换带来的上下文损耗,又避免了直接 IO 带来的 IO 损耗。效果如下所示:

    四、可视化

    由于我们将用户态 atrace 和内核态 ftrace 分别存储在对应空间下的 ringbuffer 中,原生的 systrace 只能分别进行可视化,因此我们开发了统一整合 trace 的脚本工具,将多个 trace 信息将成为单个的 html 文件,当浏览 trace 信息时,可在 Chrome(chrome://tracing 访问)中可视化显示。

    未来规划

    目前,Rhea 对 Native 的支持还不够全;性能优化还不够极致,特别在用于分析卡顿问题时需要定位几毫秒甚至更细粒度耗时的情况下,性能损耗仍然会有些偏大,在一定程度上会带偏优化方向;目前 Trace 工具更多的还是在线下使用,由于插桩过多影响了包大小,使得我们线上部分只能对小规模的用户群体定向打开,没法全量上线定位线上大规模用户的性能问题。未来我们会重点解决如上问题,将 Trace 工具打造到极致。

    小结

    目前新一代 Trace 分析工具 Rhea 其主要优势如下:

    1、使用灵活,不依赖 PC 抓取脚本,同时支持线上线下多种模式和配置开关;

    2、支持采集和追踪包括不限层级 ATrace 函数耗时插桩、等锁信息、I/O 信息以及 Binder 耗时等在内的多种信息;

    3、兼容性高,支持 API 16~30 全机型的 trace 抓取;

    4、零侵入代码,通过 gradle 完成插件全部配置,无任何代码直接调用。

    性能优化专项进阶

    性能优化是现在市场上的热门技术,针对这个点,有大佬还进行了专门的资料搜集整理,大家对这个知识点感兴趣的可以看一下下面的这些点。

    文章篇幅过长,大家可以直接去这里获取:重磅首发!昨晚最新爆出的“移动开发性能优化笔记”,GitHub已标星8K,看完我爱了!

    资料详情

    第一章 设计思想与代码质量优化

    • 六大原则(单一职责原则、里氏替换原则、依赖倒转原则、接口隔离原则……)
    • 设计模式:结构型模式(桥接模式、适配器模式、装饰器模式、代理模式、门面(外观)模式……),创建型模式(建造者模式、单例模式、抽象工厂模式、工厂方法模式……)
    • 数据结构(数组、栈、队列、链表、树……)
    • 算法(排序算法、查找算法……)

    第二章 程序性能优化

    • 启动速度与执行效率优化(冷启动和热启动解析、APP 启动黑白屏解决办法、APP 卡顿问题分析及解决方案、启动速度与执行效率优化之 StrictMode……)
    • 布局检测与优化(布局层级优化、过度渲染……)
    • 内存优化(内存抖动和内存泄漏、内存大户,Bitmap 内存优化、Profile 内存监测工具、Mat 大对象与泄漏检测、耗电优化、网络传输与数据存储优化网络传输与数据存储优化、APK 大小优化、屏幕适配……)
    • 耗电优化(Doze&Standby、Battery Historian、JobScheduler、WorkManager、)
    • 网络传输与数据存储优化(google 序列化工具 protobuf、7z 极限压缩……)
    • APK 大小优化(APK 瘦身、微信资源混淆原理……)

    • 屏幕适配(进行适配的原理、屏幕分辨率限定符与 smallestWidth 限定符适配原理、为什么选择 smallestWidth 限定符适配、怎么适配其他 module、常见问题处理……)
    • OOM 问题原理解析(adj 内存管理机制、JVM 内存回收机制与 GC 算法解析、生命周期相关问题总结、Bitmap 压缩方案总结……)
    • ANR 问题解析(AMS 系统时间调节原理、程序等待原理分析、ANR 问题解决方案……)
    • Crash 监控方案(Java 层监控方案、Nativie 层监控方案……)

    第三章 开发效率优化

    • 分布式版本控制系统 Git(企业高效持续集成平台场景介绍、GIT 分布式版本控制系统、GIT 分支管理……)
    • 自动化构建系统 Gradle:
      Gradle 与 Android 插件(gradle 与 android gradle 插件的关系、Gradle Transform API 的基本使用……),
      Gradle Transform API 的基本使用(什么是 Transform、Transform 的使用场景、Transform API 学习、输入的类型……)
      自定义插件开发(Gradle 插件简介、开始准备、实践、自定义 Gradle 插件、buildSrc 模块方式……)
      插件实战(多渠道打包、发版自动钉钉……)

    第四章 APP 性能优化实践

    • 启动速度(应用启动的一般流程、冷启动和热启动、启动速度的测量、启动窗口优化、线程优化、系统调度优化、GC 优化、IO 优化、资源重排、主页布局优化、类加载优化、选择合适的启动框架、减少 Activity 的跳转层次、厂商优化、后台保活……)

    • 流畅度(性能问题分析的一些工具和套路、通过性能数据数据分析、Android 平台性能导致的性能案例、Android App 自身导致的性能问题、低内存的数据特征和行为特征、应用宝、讯飞输入法无障碍服务导致的整机卡顿分析、字节跳动:今日头条图文详情页秒开实践……)
    • 抖音在 APK 包大小资源优化的实践(图片压缩、webp 无侵入式兼容、多 DPI 优化、重复资源合并、shrinkResource 严格模式、资源混淆(兼容 aab 模式)、ARSC 瘦身……)

    • 优酷响应式布局技术全解析(优酷APP响应式布局技术概述、优酷APP响应式布局Android落地、在分发场景的落地、在消费场景的落地、优酷APP响应式布局之测试方案……)
    • 网络优化(手机淘宝在网络的链路优化、百度 APP 在网络深度优化的实践……)
    • 手机淘宝双十一性能优化项目揭秘(一秒法则的实现、启动时间和页面帧率提升 20%、Android 手机内存节省50%……)
    • 高德 APP 全链路源码依赖分析(高德 APP 平台架构、基础实现原理、项目架构、应用场景及实现原理……)
    • 彻底干掉OOM的实战经验分享(排查内存泄漏、兜底策略、内存峰值太高、特大图排查优化……)
    • 微信 Android终端内存优化实践(Activity 泄露检测、Bitmap 分配及回收追踪、Native 内存泄漏检测、线程监控、内存监控……)

    总结

    如果你也想提升自己的性能优化技术,我觉得这份笔记你必定不能错过。有需要的朋友,我愿意免费分享给你们,戳下面蓝色字体即可跳转免费领取通道!

    https://github.com/xieyuliang/Android-P7-share/blob/master/Android%E5%BC%80%E5%8F%91%E8%BF%98%E4%

    展开全文
  • 本文从抖音 Java OOM 内存优化的治理实践出发,尝试给大家分享一下抖音团队关于 Java 内存优化中的一些思考,包括工具建设、优化方法论。 抖音 Java OOM 背景 在未对抖音内存进行专项治理之前我们梳理了一下整体...
  • 抖音首页右滑可进入“个人中心”页面,对于首页日活上亿的APP来说,这个页面的pv理论上应该不会太小。但是某些时候在此页面会出现滑动冲突的小问题,不太利于用户体验,通过反复的把玩测试,找到了必现的操作,作为...
  • 距离毕业之期不到半年,学校也在催促我们早点出校实习,年前通过一名学长内推去了字节跳动的抖音面试。12 月 31 号投的简历,1 月 6 号收到 HR 电话。隔这么长时间还是挺害怕的,以为简历都被筛掉了。 下面是我在...
  • 抖音android面经,成功获取offer

    千次阅读 2020-06-19 17:04:18
    3.说到了管道,让我说一下在Android的时候会用到管道吗 4.Java两个整型相加怎么知道有没有溢出 5.Java怎么停止线程 6.假如有4个线程同步开始,其中第4个线程要等前面三个线程执行完进行些统计操作,要怎么操作呢...
  • 抖音作为一款用户使用广泛的产品,需要在各种机器资源上保持优秀的流畅性和稳定性,内存优化是必须要重视的环节。 本文从抖音 Java OOM 内存优化的治理实践出发,尝试给大家分享一下抖音团队关于 Java 内存优化中的...
  • 一面过后面试官让我等5分钟继续二面。一面面经传送门 开始面试,首先惯例自我介绍,项目介绍 1.平时是怎么学习的 看书和读源码,看书多一点,在碎片时间会看一些博客和技术号的文章 2.看过哪些书 (参考一面...
  • 面试官:工作的话我们这一块负责抖音的开发,等你来了看谁带你吧。知识与技能你可以看看Android这方面的知识,自己做个小APP试试手。 我:谢谢您,还想请您对我的面试做个点评,给我一些改进与学习的建议 面试...
  • 面试官:每个部门做的工作都不同,比如我是做抖音的,并且工作不固定,你负责的工作只有来了才能确定。现在倒不需要准备什么,首先要保持对Android的兴趣,然后多动手,发现错误改正错误,只有改正错误才是真正的...
  • 原标题:阿里钉钉,字节抖音 Android 面经分享!微信改了推动机制,真爱请星标本公号公众号回复加入BATcoder技术群BAT原文链接:https://cloud.tencent.com/developer/article/1604727前段时间,在网上看到一篇面经...
  • 文末会给大家分享下我整理的Android面试专题及答案其中大部分都是大企业面试常问的面试题,可以对照这查漏补缺,当然了,这里所列的肯定不可能覆盖全部方式,不过对大家找工作肯定是有帮助! 本月飞机到达上海,到...
  • 提前批投递了字节头条的客户端,挂在了终面,正式批又投了抖音的客户端,简历通过后约了大概一周后面试。(从哪里跌倒就从哪里爬起来~) 一面(~45min) 1、介绍项目,讲述一下实习的工作内容和感想(~10min) ...
  • 找工作还是需要大家不要紧张,有我们干这一行的接触人本来就不多 难免看到面试官会紧张,主要是因为怕面试官问的问题到不上来,那时候不要着急 ,答不上了的千万不然胡扯一些,直接就给面试官说这块我还没接触到,...
  • 给文章留个小赞,就可以免费领取啦~ 戳我领取:3000页Android开发者架构师核心知识笔记 《960全网最全Android开发笔记》 《379页Android开发面试宝典》 包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、...
  • 今天要和大家分享的是关于抖音 Android 性能优化的内存优化,希望对大家的学习和工作有所启发和帮助。 正文 内存作为计算机程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻...
  • 今日份收到一个粉丝分享来得字节抖音客户端的实习面经,他自己说这次没有面试成功的话,可能还在经历迷茫的找工作。机遇巧合通过学长的内推,前往字节跳动进行了面试。 我就废话不多说了,直接进入主题给大家分享...
  • 刚好闲下来,顺便收集了一些关于爱奇艺,字节跳动,抖音面试题目。也整理好了答案,希望对即将面试和跳槽的小伙伴有所帮助 一.2019爱奇艺秋招Android 1.使用堆排序方法排序(45,78,57,25,41,89),初始堆为...
  • 接触这一行也有很久了,从开始的实习到带团队,中间接触过很多人,前不久身边刚好有人去面试了阿里,抖音等这些公司还成功的面试上了,现在来分享一下面试前需要准备的知识点 很多人去面试之前,不知道会问到那些...
  • 文末会给大家分享下我整理的Android面试专题及答案其中大部分都是大企业面试常问的面试题,可以对照这查漏补缺,当然了,这里所列的肯定不可能覆盖全部方式,不过对大家找工作肯定是有帮助! 本月飞机到达上海,到...
  • 如题,这是一篇关于Android面试-性能优化最常问的面试题、面试点的文章。这也是“Android-面试官”系列的第二篇文章。对Java模块比较薄弱的小伙伴可以去看一下我的上一篇文章: Android-面试官:这些Java知识点我必...
  • 我21届秋招面试过快手、美团点评、百度、网易有道,最后上岸抖音Android客户端(base北京)。面试期间在牛客看了大量面经,面试时也有意记录下了面试题集。近期正值校招开启,特地整理出来回馈牛友~ 【先说结论】 ...
  • 以前一直想写一篇总结 Android 开发经验的文章,估计当时的我还达不到某种水平,所以思路跟不上,下笔又捉襟见肘。近日,思路较为明朗,于是重新操起键盘开始码字一番。先声明一下哈,本人不是大厂的程序猿。去年...
  • 2021新的一年,开启新的征程,回顾2020,真是太“南”了。...我认识很多优秀的 Android 工程师,他们丝毫不焦虑,因为他们知道清晰地知道自己的薄弱处和强项。 和他们的交流中,我总结了 3 点经验,分享

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,574
精华内容 1,029
关键字:

抖音android面试