2024年安卓最新Android 开发面试备战春招之“你做过那些性能优化?”,面试被打

更多Android高级工程师进阶学习资料

进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  1. 知道了 attachBaseContextonCreate 在应用中最先启动,那么我们就可以通过 TreceView 等性能检测工具,来检测具体函数耗时时间,然后来对其做具体的优化。
  • 项目不及时需要的代码通过异步加载。

  • 将对一些使用率不高的初始化,做懒加载。

  • 将对一些耗时任务通过开启一个 IntentService来处理。

  • 还通过 redex 重排列 class 文件,将启动阶段需要用到的文件在 APK 文件中排布在一起,尽可能的利用 Linux 文件系统的 pagecache 机制,用最少的磁盘 IO 次数,读取尽可能多的启动阶段需要的文件,减少 IO 开销,从而达到提升启动性能的目的。

  • 通过抖音发布的文章知晓在 5.0 低版本可以做 MultiDex 优化,在第一次启动的时候,直接加载没有经过 OPT 优化的原始 DEX,先使得 APP 能够正常启动。然后在后台启动一个单独进程,慢慢地做完 DEX 的 OPT 工作,尽可能避免影响到前台 APP 的正常使用。

Ps:

  1. 面试官这里会觉得你对启动优化确实了解的不错,有一定的启动优化经验。
  1. 在第五点面试官会觉得你比较关注该圈子的动态,发现好的解决方案,并能用在自己项目上。这一点是加分项!
  1. Application 启动完之后,AMS 会找出前台栈顶待启动的 Activity , 最后也是通过 AIDL 通知 ActivityThread#H 来进行对 Activity 的实例化并依次执行生命周期 onCreateonStartonRemuse 函数,那么这里由于 onCreate 生命周期中如果调用了 setContentView 函数,底层就会通过将 XML2View 那么这个过程肯定是耗时的。所以要精简 XML 布局代码,尽可能的使用 ViewStubincludemerge 标签来优化布局。接着在 onResume 声明周期中会请求 JNI 接收 Vsync (垂直同步刷新的信号) 请求,16ms 之后如果接收到了刷新的消息,那么就会对 DecorView 进行 onMeasure->onLayout->onDraw 绘制。最后才是将 Activity 的根布局 DecorView 添加到 Window 并交于 SurfaceFlinger 显示。

所以这一步除了要精简 XML 布局,还有对自定义 View 的测量,布局,绘制等函数不能有耗时和导致 GC 的操作。最后也可以通过 TreaceView 工具来检测这三个声明周期耗时时间,从而进一步优化,达到极限。

这一步给面试官的感觉你对整个 Activity 的启动和 View 的绘制还有刷新机制都有深入的研究,那么此刻你肯定给面试官留了一个好印象,说明你平时对这些源码级别的研究比较广泛,透彻。

总结:

最后我基于以上的优化减少了 50% 启动时间。

面试官:

嗯,研究的挺深的,源码平时不少看吧。

程序员:

到这里,我知道这一关算是过了!

2、有做过相关的内存优化吗?


程序员:

有做过,目前的项目内存优化还是挺多的,要不我先说一下优化内存有什么好处吧?咱们不能盲目的去优化!

有的时候对于自己熟悉的领域,一定要主动出击,自己主导这场面试。

面试官:

可以。

Ps:这里大多数面试官会同意你的请求,除非遇见装B的。

程序员:

好处:

  1. 减少 OOM ,可以提高程序的稳定性。

  2. 减少卡顿,提高应用流畅性。

  3. 减少内存占用,提高应用后台存活性。

  4. 减少程序异常,降低应用 Crash 率, 提高稳定性。

那么我基于这四点,我的程序做了如下优化:

  • 1.减少 OOM

在应用开发阶段我比较喜欢用 LeakCanary 这款性能检测工具,好处是它能实时的告诉我具体哪个类发现了内存泄漏(如果你对 LeakCanary 的原理了解的话,可以说一说它是怎么检测的)。

还有我们要明白为什么应用程序会发送 OOM ,又该怎么去避免它?

发生 OOM 的场景是当申请 1M 的内存空间时,如果你要往该内存空间存入 2M 的数据,那么此时就会发生 OOM。

在应用程序中我们不仅要避免直接导致 OOM 的场景还要避免间接导致 OOM 的场景。间接的话也就是要避免内存泄漏的场景。

内存泄漏的场景是这个对象不再使用时,应用完整的执行最后的生命周期,但是由于某些原因,对象虽然已经不再使用,仍然会在内存中存在而导致 GC 不会去回收它,这就意味着发生了内存泄漏。(这里可以介绍下 GC 回收机制,回收算法,知识点尽量往外扩展而不脱离本题)

最后在说一下在实际开发中避免内存泄漏的场景:

  1. 资源型对象未关闭: Cursor,File

  2. 注册对象未销毁: 广播,回调监听

  3. 类的静态变量持有大数据对象

  4. 非静态内部类的静态实例

  5. Handler 临时性内存泄漏: 使用静态 + 弱引用,退出即销毁

  6. 容器中的对象没清理造成的内存泄漏

  7. WebView: 使用单独进程

其实这些都是基础,把它记下就行了。记得多了在实际开发中就有印象了。

  • 2.减少卡顿

怎么减少卡顿? 那么我们可以从 2 个原理方面来探讨卡顿的根本原因,第一个原理方面是绘制原理,另一个就是刷新原理。

  1. 绘制原理:

  1. 刷新原理:

View 的 requestLayout 和 ViewRootImpl##setView 最终都会调用 ViewRootImpl 的 requestLayout 方法,然后通过 scheduleTraversals 方法向 Choreographer 提交一个绘制任务,然后再通过 DisplayEventReceiver 向底层请求 vsync 垂直同步信号,当 vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往消息队列 post 一个异步任务,最终是 ViewRootImpl 去执行绘制任务,最后调用 performTraversals 方法,完成绘制。

详细流程可以参考下面流程图:

卡顿的根本原因:

  • 从刷新原理来看卡顿的根本原理是有两个地方会造成掉帧:

一个是主线程有其它耗时操作,导致doFrame 没有机会在 vsync 信号发出之后 16 毫秒内调用;

还有一个就是当前doFrame方法耗时,绘制太久,下一个 vsync 信号来的时候这一帧还没画完,造成掉帧。

既然我们知道了卡顿的根本原因,那么我们就可以监控卡顿,从而可以对卡顿优化做到极致。我们可以从下面四个方面来监控应用程序卡顿:

  1. 基于 Looper 的 Printer 分发消息的时间差值来判断是否卡顿。

//1. 开启监听

Looper.myLooper().setMessageLogging(new

LogPrinter(Log.DEBUG, “ActivityThread”));

//2. 只要分发消息那么就会在之前和之后分别打印消息

public static void loop() {

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);

}

final MessageQueue queue = me.mQueue;

for (;😉 {

Message msg = queue.next(); // might block

//分发之前打印

final Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

try {

//分发消息

msg.target.dispatchMessage(msg);

//分发之后打印

if (logging != null) {

logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

}

}

}

  1. 基于 Choreographer 回调函数 postFrameCallback 来监控

  1. 基于开源框架 BlockCanary 来监控

  2. 基于开源框架 rabbit-client 来监控

怎么避免卡顿:

一定要避免在主线程中做耗时任务,总结一下 Android 中主线程的场景:

  1. UI 生命周期的控制

  2. 系统事件的处理

  3. 消息处理

  4. 界面布局

  5. 界面绘制

  6. 界面刷新

还有一个最重要的就是避免内存抖动,不要在短时间内频繁的内存分配和释放。

基于这几点去说卡顿肯定是没有问题的。

  • 3.减少内存占用

可以从如下几个方面去展开说明:

  1. AutoBoxing(自动装箱): 能用小的坚决不用大的。

  2. 内存复用

  3. 使用最优的数据类型

  4. 枚举类型: 使用注解枚举限制替换 Enum

  5. 图片内存优化(这里可以从 Glide 等开源框架去说下它们是怎么设计的)

  • 选择合适的位图格式

  • bitmap 内存复用,压缩

  • 图片的多级缓存

  1. 基本数据类型如果不用修改的建议全部写成 static final,因为 它不需要进行初始化工作,直接打包到 dex 就可以直接使用,并不会在 类 中进行申请内存

  2. 字符串拼接别用 +=,使用 StringBuffer 或 StringBuilder

  3. 不要在 onMeause, onLayout, onDraw 中去刷新 UI

  4. 尽量使用 C++ 代码转换 YUV 格式,别用 Java 代码转换 RGB 等格式,真的很占用内存

  • 4.减少程序异常

减少程序异常那么我们可以从稳定性和 Crash 来分别说明。

这个我们将在第四点会详细的介绍程序的稳定性和 Crash 。

如果说出这些,再实际开发中举例说明一下怎么解决的应该是没有问题的。

3、你在项目中有没有遇见卡顿问题?是怎么排查卡顿?又是怎么优化的?


程序员:

有遇见, 比如在主线程中做耗时操作、频繁的创建对象和销毁对象导致 GC 回收频繁、布局的层级多等。

面试官:

嗯,那具体说说是怎么优化的。

程序员:

这里我们还是可以从显示原理和优化建议来展开说明,参考如下:

  1. 显示原理:
  • 绘制原理:

  • 刷新原理:

View 的 requestLayout 和 ViewRootImpl##setView 最终都会调用 ViewRootImpl 的 requestLayout 方法,然后通过 scheduleTraversals 方法向 Choreographer 提交一个绘制任务,然后再通过 DisplayEventReceiver 向底层请求 vsync 垂直同步信号,当 vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往消息队列 post 一个异步任务,最终是 ViewRootImpl 去执行绘制任务,最后调用 performTraversals 方法,完成绘制。

详细流程可以参考下面流程图:

  1. 卡顿的根本原因:

从刷新原理来看卡顿的根本原理是有两个地方会造成掉帧:

一个是主线程有其它耗时操作,导致doFrame 没有机会在 vsync 信号发出之后 16 毫秒内调用;

还有一个就是当前 doFrame 方法耗时,绘制太久,下一个 vsync 信号来的时候这一帧还没画完,造成掉帧。

既然我们知道了卡顿的根本原因,那么我们就可以监控卡顿,从而可以对卡顿优化做到极致。我们可以从下面四个方面来监控应用程序卡顿:

  • 基于 Looper 的 Printer 分发消息的时间差值来判断是否卡顿。

//1. 开启监听

Looper.myLooper().setMessageLogging(new

LogPrinter(Log.DEBUG, “ActivityThread”));

//2. 只要分发消息那么就会在之前和之后分别打印消息

public static void loop() {

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);

}

final MessageQueue queue = me.mQueue;

for (;😉 {

Message msg = queue.next(); // might block

//分发之前打印

final Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

try {

//分发消息

msg.target.dispatchMessage(msg);

//分发之后打印

if (logging != null) {

logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

}

}

}

  • 基于 Choreographer 回调函数 postFrameCallback 来监控

  1. 怎么可以提高程序运行流畅

1.布局优化:

1.1 布局优化分析工具:

1.2 优化方案:

  1. 提升动画性能

总结

Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

上面分享的字节跳动公司2021年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

【Android高级架构视频学习资源】

Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

上面分享的字节跳动公司2021年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
[外链图片转存中…(img-eF3cSeXa-1715774012612)]

【Android高级架构视频学习资源】

Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言面试八股文是指在春季招聘中常见的C语言相关的面试题目和知识点。下面是一份常见的C语言面试八股文,供您参考: 1. C语言的基本数据类型有哪些? C语言的基本数据类型包括整型、浮点型、字符型和指针型。 2. 请介绍一下C语言中的变量和常量。 变量是用来存储数据的内存位置,可以通过变量名来访问和修改其值。常量是指在程序执行过程中不会改变的值。 3. 什么是数组?请介绍一下C语言中的数组。 数组是一种存储相同类型数据的集合,通过索引来访问数组中的元素。在C语言中,数组的大小在定义时就需要确定,并且数组的下标从0开始。 4. 请介绍一下C语言中的指针。 指针是一个变量,其值为另一个变量的地址。通过指针可以直接访问和修改内存中的数据。使用指针可以提高程序的效率和灵活性。 5. 请介绍一下C语言中的函数。 函数是一段完成特定任务的代码块,可以通过函数名来调用执行。函数可以接收参数并返回一个值,也可以不接收参数或不返回值。 6. 请介绍一下C语言中的流程控制语句。 C语言中的流程控制语句包括条件语句(if-else语句、switch语句)、循环语句(for循环、while循环、do-while循环)和跳转语句(break语句、continue语句、goto语句)。 7. 请介绍一下C语言中的结构体。 结构体是一种自定义的数据类型,可以包含多个不同类型的成员变量。通过结构体可以将多个相关的数据组织在一起。 8. 请介绍一下C语言中的文件操作。 C语言中的文件操作主要包括打开文件、读写文件和关闭文件。可以使用标准库函数来进行文件操作,如fopen、fread、fwrite、fclose等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值