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 等性能检测工具,来检测具体函数耗时工夫,而后来对其做具体的优化。
- 我的项目不及时须要的代码通过异步加载。
- 将对一些使用率不高的初始化,做懒加载。
- 将对一些耗时工作通过开启一个 IntentService 来解决。
- 还通过 redex 重排列 class 文件,将启动阶段须要用到的文件在 APK 文件中排布在一起,尽可能的利用 Linux 文件系统的 pagecache 机制,用起码的磁盘 IO 次数,读取尽可能多的启动阶段须要的文件,缩小 IO 开销,从而达到晋升启动性能的目标。
-
通过抖音公布的文章通晓在 5.0 低版本能够做
MultiDex
优化,在第一次启动的时候,间接加载没有通过 OPT 优化的原始 DEX,先使得 APP 可能失常启动。而后在后盾启动一个独自过程,缓缓地做完 DEX 的 OPT 工作,尽可能防止影响到前台 APP 的失常应用。Ps:1. 面试官这里会感觉你对启动优化的确理解的不错,有肯定的启动优化教训。
- 在第五点面试官会感觉你比拟关注该圈子的动静,发现好的解决方案,并能用在本人我的项目上。这一点是加分项!
-
Application 启动完之后,AMS 会找出前台栈顶待启动的 Activity , 最初也是通过 AIDL 告诉 ActivityThread#H 来进行对 Activity 的实例化并顺次执行生命周期
onCreate
、onStart
、onRemuse
函数,那么这里因为 onCreate 生命周期中如果调用了setContentView
函数,底层就会通过将 XML2View 那么这个过程必定是耗时的。所以要精简 XML 布局代码,尽可能的应用ViewStub
、include
、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 信号来的时候这一帧还没画完,造成掉帧。
既然咱们晓得了卡顿的根本原因,那么咱们就能够监控卡顿,从而能够对卡顿优化做到极致。咱们能够从上面四个方面来监控应用程序卡顿:
-
基于 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 来监控
-
-
基于开源框架 BlockCanary 来监控
-
基于开源框架 rabbit-client 来监控
怎么防止卡顿:
肯定要防止在主线程中做耗时工作,总结一下 Android 中主线程的场景:
-
UI 生命周期的管制
-
零碎事件的解决
-
音讯解决
-
界面布局
-
界面绘制
-
界面刷新
-
-
…
还有一个最重要的就是防止内存抖动,不要在短时间内频繁的内存调配和开释。
基于这几点去说卡顿必定是没有问题的。
-
3. 缩小内存占用
能够从如下几个方面去开展阐明:
- AutoBoxing(主动装箱): 能用小的坚定不必大的。
- 内存复用
- 应用最优的数据类型
- 枚举类型: 应用注解枚举限度替换 Enum
-
图片内存优化(这里能够从 Glide 等开源框架去说下它们是怎么设计的)
- 抉择适合的位图格局
- bitmap 内存复用,压缩
- 图片的多级缓存
- 根本数据类型如果不必批改的倡议全副写成 static final, 因为 它不须要进行初始化工作,间接打包到 dex 就能够间接应用,并不会在 类 中进行申请内存
- 字符串拼接别用 +=,应用 StringBuffer 或 StringBuilder
- 不要在 onMeause, onLayout, onDraw 中去刷新 UI
- 尽量应用 C++ 代码转换 YUV 格局,别用 Java 代码转换 RGB 等格局,真的很占用内存
-
4. 缩小程序异样
缩小程序异样那么咱们能够从稳定性和 Crash 来别离阐明。
这个咱们将在第四点会具体的介绍程序的稳定性和 Crash。
如果说出这些,再理论开发中举例说明一下怎么解决的应该是没有问题的。
3、你在我的项目中有没有遇见卡顿问题?是怎么排查卡顿?又是怎么优化的?
程序员:
有遇见, 比方在主线程中做耗时操作、频繁的创建对象和销毁对象导致 GC 回收频繁、布局的层级多等。
面试官:
嗯,那具体说说是怎么优化的。
程序员:
这里咱们还是能够从显示原理和优化倡议来开展阐明,参考如下:
- 显示原理:
-
绘制原理:
-
刷新原理:
View 的 requestLayout 和 ViewRootImpl##setView 最终都会调用 ViewRootImpl 的 requestLayout 办法,而后通过 scheduleTraversals 办法向 Choreographer 提交一个绘制工作,而后再通过 DisplayEventReceiver 向底层申请 vsync 垂直同步信号,当 vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往音讯队列 post 一个异步工作,最终是 ViewRootImpl 去执行绘制工作,最初调用 performTraversals 办法,实现绘制。
具体流程能够参考上面流程图:
-
卡顿的根本原因:
从刷新原理来看卡顿的基本原理是有两个中央会造成掉帧:
一个是主线程有其它耗时操作,导致 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 来监控
- 基于开源框架 BlockCanary 来监控
- 基于开源框架 rabbit-client 来监控
-
-
怎么能够进步程序运行晦涩
1. 布局优化:
1.1 布局优化剖析工具:
1.2 优化计划:
-
晋升动画性能
- 尽量别用补间动画,改为属性动画,因为通过性能监控发现补间动画重绘十分频繁
- 应用硬件加速进步渲染速度,实现平滑的动画成果。
-
怎么防止卡顿:
肯定要防止在主线程中做耗时工作,总结一下 Android 中主线程的场景:
- UI 生命周期的管制
- 零碎事件的解决
- 音讯解决
- 界面布局
- 界面绘制
- 界面刷新
- …
基于这几点去说卡顿必定是没有问题的。
4、怎么保障 APP 的稳固运行?
程序员:
保障程序的稳固咱们能够从内存、代码品质、Crash、ANR、后盾存活等知识点来开展优化。
面试官:
那你具体说说你是怎么做的?
程序员:
1. 内存
能够从第二点内存优化来阐明
2. 代码品质
- 团队之前互相代码审查,保障了代码的品质,也能够学习到了其它共事码代码的思维。
- 应用 Link 扫描代码,查看是否有缺点性。
- Crash
- 通过实现 Thread.UncaughtExceptionHandler 接口来全局监控异样状态,产生 Crash 及时上传日志给后盾,并且及时通过插件包修复。
- Native 线上通过 Bugly 框架实时监控程序异样情况,线下局域网应用 Google 开源的 breakpad 框架。产生异样就收集日志上传服务器(这里要留神的是日志上传的性能问题,前面省电模块会阐明)
- ANR
- 后盾存活
面试官:
嗯,你对知识点把握的挺好。
说完这些,这一关也算是过了。
5、说说你在我的项目中网络优化?
程序员:
有,这一点其实能够通过 OKHTTP 连接池和 Http 缓存来说一下(当然这里不会再开展剖析 OKHTTP 源码了)
面试官:
那你具体说一下吧
程序员
说了这些之后,再说一下你以后应用网络框架它们做了哪些优化比方 OKHTTP(Socket 连接池、Http 缓存、责任链)、Retrofit(动静代理)。说了这些个别这关也算是过了。
6、你在我的项目中有用过哪些存储形式? 对它们的性能有过优化吗?
程序员:
次要用过 sp,File,SQLite 存储形式。其中对 sp 和 sqlite 做了优化。
面试官:
那你说说都做了哪些优化?
程序员:
这一块如果你应用过其它第三方的数据库,能够说说它们的原理和它们存取的形式。
7、你在我的项目中有做过自定义 View 吗?有对它做过什么优化?
程序员:
有做过。比方反复绘制,还有大图长图有过优化。
面试官:
那具体说一说
程序员:
最初也是联合实在场景具体说一个。
8、你们我的项目的耗电量怎么样? 有做过优化吗?
程序员:
在没有优化之前继续工作 30 分钟的耗电量是 8%, 优化后是 4%。
面试官:
那你说一说你是怎么优化的。
程序员:
因为咱们产品是一款社交通信的软件,有音视频通话、GPS 定位上报、长连贯的场景,所以优化起来的确有点艰难。不过最初也还是优化了一半的电量上来。次要做了如下优化:
说出这些之后,在联合我的项目一个实在的优化点来阐明一下。
9、有做过日志优化吗?
程序员:
有优化,在之前没有思考任何性能的状况下,我是间接有 log 就写入文件,只管我开了线程池去写文件,只有软件在运行那么就会频繁的使 CPU 进行工作。这也间接的导致了耗电。
面试官:
那你具体说一下,最初怎么解决这个问题的?
程序员:
开展下面这些点阐明之后,面试官个别不会尴尬你。
10、你们 APK 有多大?有做过 APK 体积相干的优化吗?
程序员:
有过优化,在没有优化之前我的项目的包体积大小是 80M, 优化之后是 50M.
面试官:
说一说是怎么优化的
程序员:
基于这几点优化计划,个别都能解决 APK 体积问题。最初再把本人我的项目 APK 体积优化步骤联合下面点说一下就行。
总结
其实性能优化点都是非亲非故的,比方卡顿会波及内存、显示,启动也会波及 APK dex 的影响。所以说性能优化不仅仅是单方面的优化,肯定要把握最根本的优化计划,能力更加深入探讨性能原理问题。
在这里也建设大家多看风行开源框架源码,比方 Glide (内存方面), OKhttp (网络连接方面) 优化的真的很极致。到这里性能优化方面的常识也就说完了,下来肯定好好去消化。
相干视频:
【2021 最新版】Android studio 装置教程 +Android(安卓)零基础教程视频(适宜 Android 0 根底,Android 初学入门)_哔哩哔哩_bilibili
Android 高级 UI 性能优化——FlowLayout 流式布局我的项目实战长_哔哩哔哩_bilibili
Android 高级 UI 性能优化——View 的 Measure 原理利用与 xml 解析过程原理解说_哔哩哔哩_bilibili
Android 高级 UI 性能优化——LayoutInflater.inflate 函数意义与参数阐明_哔哩哔哩_bilibili
Android 高级 UI 性能优化——ViewPager 嵌套 Fragment UI 模式性能优化_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/6844904105438134286,如有侵权,请分割删除。