Android的性能优化⾯试题
1、 你对 APP 的启动有过研究吗? 有做过相关的启动优化吗?
程序员:
之前做热修复的时候研究过 Application 的启动原理。项⽬中也做过⼀些启动优化。
⾯试官:
哦,你之前研究过热修复? (这个时候有可能就会深⼊的问问热修复的原理,这⾥咱们就不讨论热修复原理) 那你说说对启动⽅⾯都做了哪些优化?
程序员:
我发现程序在冷启动的时候,会有 1s 左右的⽩屏闪现,低版本是⿊屏的现象,在这期间我通过翻阅系统主题源码,发现了系统 AppTheme 设置了⼀个 windowBackground ,由此推断就是这个属性捣的⿁,开始我是通过设置 windowIsTranslucent 透明属性,发现虽然没有了⽩屏,但是中间还是有⼀⼩段不可见,这个⽤户体验还是不好的。最后我观察了市⾯上⼤部分的 Android 软件在冷启动的时候都会有⼀个 Splash 的⼴告页,同时在增加⼀个倒数的计时器,最后才进⼊到登录页⾯或者主页⾯。我最后也是这样
做的,原因是这样做的好处可以让⽤户先基于⼴告对本APP 有⼀个基本认识,⽽且在倒数的时候也预留给咱们⼀些对插件和⼀些必须或者耗时的初始化做⼀些准备。
Ps:这⾥会让⾯试官感觉你是⼀个注重⽤户体验的
通过翻阅 Application 启动的源码,当我们点击桌⾯图标进⼊我们软件应⽤的时候,会由 AMS 通过 Socket 给 Zygote 发送⼀个 fork ⼦进程的消息,当 Zygote fork ⼦进程完成之后会通过反射启动 ActivityThread##main 函数,最后⼜由 AMS 通过 aidl 告诉 ActivityThread##H 来反射启动创建Application 实例,并且依次执⾏ attachBaseContext 、onCreate ⽣命周期,由此可见我们不能在这 2 个⽣命周期⾥做主线程耗时操作。
Ps: 这⾥会让⾯试官感觉你对 App 应⽤的启动流程研究的⽐较深,有过真实的翻阅底层源码,⽽并不是背诵答案。
知道了 attachBaseContext 、onCreate 在应⽤中最先启动,那么我们就可以通过 TreceView 等性能检测⼯具,来检测具体函数耗时时间,然后来对其做具体的优化。
Ps:1、⾯试官这⾥会觉得你对启动优化确实了解的不错,有⼀定的启动优化经验。
2、在第五点⾯试官会觉得你⽐较关注该圈⼦的动态,发现好的解决⽅案,并能⽤在⾃⼰项⽬上。这⼀
点是加分项!
项⽬不及时需要的代码通过异步加载。
将对⼀些使⽤率不⾼的初始化,做懒加载。
将对⼀些耗时任务通过开启⼀个 IntentService来处理。
还通过 redex 重排列 class ⽂件,将启动阶段需要⽤到的⽂件在 APK ⽂件中排布在⼀起,尽可能的利⽤ Linux ⽂件系统的 pagecache 机制,⽤最少的磁盘 IO 次数,读取尽可能多的启动阶段需要的⽂件,减少 IO 开销,从⽽达到提升启动性能的⽬的。
通过抖⾳发布的⽂章知晓在 5.0 低版本可以做 MultiDex 优化,在第⼀次启动的时候,直接加载没有经过 OPT 优化的原始 DEX,先使得 APP 能够正常启动。然后在后台启动⼀个单独进程,慢慢地做完 DEX 的 OPT ⼯作,尽可能避免影响到前台 APP 的正常使⽤。
Application 启动完之后,AMS 会出前台栈顶待启动的 Activity , 最后也是通过 AIDL 通知 ActivityThread#H 来进⾏对 Activity 的实例化并依次执⾏⽣命周期 onCreate、onStart、onRemuse 函数,那么这⾥由于 onCreate ⽣命周期中如果调⽤了 setContentView 函数,底层就会通过将 XML2View 那么这个过程肯定是耗时的。所以要精简 XML 布局代码,尽可能的使⽤ ViewStub、inclu
de 、merge 标签来优化布局。接着在 onResume 声明周期中会请求 JNI 接收 Vsync (垂直同步刷新的信号) 请求,16ms 之后如果接收到了刷新的消息,那么就会对DecorView 进⾏ onMeasure->onLayout->onDraw 绘制。最后才是将 Activity 的根布局 DecorView 添加到 Window 并交于SurfaceFlinger 显⽰。所以这⼀步除了要精简 XML 布局,还有对⾃定义 View 的测量,布局,绘制等函数不能有耗时和导致 GC 的操作。最后也可以通过 TreaceView ⼯具来检测这三个声明周期耗时时间,从⽽进⼀步优化,达到极限。
这⼀步给⾯试官的感觉你对整个 Activity 的启动和 View 的绘制还有刷新机制都有深⼊的研究,那么此刻你肯定给⾯试官留了⼀个好印象,说明你平时对这些源码级别的研究⽐较⼴泛,透彻。
总结:
最后我基于以上的优化减少了 50% 启动时间。
⾯试官:
嗯,研究的挺深的,源码平时不少看吧。
程序员:
到这⾥,我知道这⼀关算是过了!
2、有做过相关的内存优化吗?
程序员:
有做过,⽬前的项⽬内存优化还是挺多的,要不我先说⼀下优化内存有什么好处吧?咱们不能盲⽬的去优化!
有的时候对于⾃⼰熟悉的领域,⼀定要主动出击,⾃⼰主导这场⾯试。
⾯试官:
可以。
Ps:这⾥⼤多数⾯试官会同意你的请求,除⾮遇见装B的。
程序员:
好处:
减少 OOM ,可以提⾼程序的稳定性。
减少卡顿,提⾼应⽤流畅性。
减少内存占⽤,提⾼应⽤后台存活性。
减少程序异常,降低应⽤ Crash 率, 提⾼稳定性。
那么我基于这四点,我的程序做了如下优化:
1.减少 OOM
在应⽤开发阶段我⽐较喜欢⽤ LeakCanary 这款性能检测⼯具,好处是它能实时的告诉我具体哪个类发现了内存泄漏(如果你对 LeakCanary 的原理了解的话,可以说⼀说它是怎么检测的)。
还有我们要明⽩为什么应⽤程序会发送 OOM ,⼜该怎么去避免它?
发⽣ OOM 的场景是当申请 1M 的内存空间时,如果你要往该内存空间存⼊ 2M 的数据,那么此时就会发⽣ OOM。
在应⽤程序中我们不仅要避免直接导致 OOM 的场景还要避免间接导致 OOM 的场景。间接的话也就是要避免内存泄漏的场景。
内存泄漏的场景是这个对象不再使⽤时,应⽤完整的执⾏最后的⽣命周期,但是由于某些原因,对象
虽然已经不再使⽤,仍然会在内存中存在⽽导致 GC 不会去回收它,这就意味着发⽣了内存泄漏。(这⾥可以介绍下 GC 回收机制,回收算法,知识点尽量往外扩展⽽不脱离本题)
最后在说⼀下在实际开发中避免内存泄漏的场景:
资源型对象未关闭: Cursor,File
注册对象未销毁: ⼴播,回调监听
类的静态变量持有⼤数据对象
⾮静态内部类的静态实例
Handler 临时性内存泄漏: 使⽤静态 + 弱引⽤,退出即销毁
容器中的对象没清理造成的内存泄漏
WebView: 使⽤单独进程
其实这些都是基础,把它记下就⾏了。记得多了在实际开发中就有印象了。
2.减少卡顿
怎么减少卡顿? 那么我们可以从 2 个原理⽅⾯来探讨卡顿的根本原因,第⼀个原理⽅⾯是绘制原理,另⼀个就是刷新原理。
绘制原理:
刷新原理:
View 的 requestLayout 和 ViewRootImpl##setView 最终都会调⽤ ViewRootImpl 的 requestLayout ⽅
法,然后通过scheduleTraversals ⽅法向 Choreographer 提交⼀个绘制任务,然后再通过 DisplayEventReceiver 向底层请求 vsync 垂直同步信号,当vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往消息队列 post ⼀个异步任务,最终是 ViewRootImpl 去执⾏绘制任务,最后调⽤ performTraversals ⽅法,完成绘制。
详细流程可以参考下⾯流程图:
卡顿的根本原因:
从刷新原理来看卡顿的根本原理是有两个地⽅会造成掉帧:
⼀个是主线程有其它耗时操作,导致doFrame 没有机会在 vsync 信号发出之后 16 毫秒内调⽤;
还有⼀个就是当前doFrame⽅法耗时,绘制太久,下⼀个 vsync 信号来的时候这⼀帧还没画完,造成掉帧。
制作android软件流程
既然我们知道了卡顿的根本原因,那么我们就可以监控卡顿,从⽽可以对卡顿优化做到极致。我们可以从下⾯四个⽅⾯来监控应⽤程序卡顿:
基于 Looper 的 Printer 分发消息的时间差值来判断是否卡顿。
//1. 开启监听  Looper().setMessageLogging(newLogPrinter(Log.DEBUG,"ActivityThread"));//2. 只要分发消息那么就会在之前和之后分别打印消息publicstaticvoidloop(){finalLooperme=myLooper();if(me==null){thrownewRuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}finalMessageQueuequeue=me.mQueue;...for(;;){();// //分发之前打印finalPrinterlogging=me.mLogging;if(loggin
g!=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 来监控
基于开源框架 BlockCanary 来监控
基于开源框架 rabbit-client 来监控
怎么避免卡顿:
⼀定要避免在主线程中做耗时任务,总结⼀下 Android 中主线程的场景:
UI ⽣命周期的控制
系统事件的处理
消息处理
界⾯布局
界⾯绘制
界⾯刷新
...
还有⼀个最重要的就是避免内存抖动,不要在短时间内频繁的内存分配和释放。
基于这⼏点去说卡顿肯定是没有问题的。
3.减少内存占⽤
可以从如下⼏个⽅⾯去展开说明:
AutoBoxing(⾃动装箱): 能⽤⼩的坚决不⽤⼤的。
内存复⽤
使⽤最优的数据类型
枚举类型: 使⽤注解枚举限制替换 Enum
图⽚内存优化(这⾥可以从 Glide 等开源框架去说下它们是怎么设计的)
选择合适的位图格式
bitmap 内存复⽤,压缩
图⽚的多级缓存
基本数据类型如果不⽤修改的建议全部写成 static final,因为 它不需要进⾏初始化⼯作,直接打包到 dex 就可以直接使⽤,并不会在 类 中进⾏申请内存
字符串拼接别⽤ +=,使⽤ StringBuffer 或 StringBuilder
不要在 onMeause, onLayout, onDraw 中去刷新 UI
尽量使⽤ C++ 代码转换 YUV 格式,别⽤ Java 代码转换 RGB 等格式,真的很占⽤内存
4.减少程序异常

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。