7、WebView: 使用单独进程
其实这些都是基础,把它记下就行了。记得多了在实际开发中就有印象了。
- 2.减少卡顿
怎么减少卡顿? 那么我们可以从 2 个原理方面来探讨卡顿的根本原因,第一个原理方面是绘制原理,另一个就是刷新原理。
1、绘制原理:
2、刷新原理:
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);
}
}
}
2、基于 Choreographer 回调函数 postFrameCallback 来监控
3、基于开源框架 BlockCanary 来监控
4、基于开源框架 rabbit-client 来监控
怎么避免卡顿:
一定要避免在主线程中做耗时任务,总结一下 Android 中主线程的场景:
1、UI 生命周期的控制
2、系统事件的处理
3、消息处理
4、界面布局
5、界面绘制
6、界面刷新
7、…
还有一个最重要的就是避免内存抖动,不要在短时间内频繁的内存分配和释放。
基于这几点去说卡顿肯定是没有问题的。
- 3、减少内存占用
可以从如下几个方面去展开说明:
1、AutoBoxing(自动装箱): 能用小的坚决不用大的。
2、内存复用
3、使用最优的数据类型
4、枚举类型: 使用注解枚举限制替换 Enum
5、图片内存优化(这里可以从 Glide 等开源框架去说下它们是怎么设计的)
- 选择合适的位图格式
- bitmap 内存复用,压缩
- 图片的多级缓存
6、基本数据类型如果不用修改的建议全部写成 static final,因为 它不需要进行初始化工作,直接打包到 dex 就可以直接使用,并不会在 类 中进行申请内存
7、字符串拼接别用 +=,使用 StringBuffer 或 StringBuilder
8、不要在 onMeause, onLayout, onDraw 中去刷新 UI
9、尽量使用 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 来监控
-
基于开源框架 BlockCanary 来监控
-
基于开源框架 rabbit-client 来监控
2、怎么可以提高程序运行流畅
1、布局优化:
1.1 布局优化分析工具:
1.2 优化方案:
3、提升动画性能
-
尽量别用补间动画,改为属性动画,因为通过性能监控发现补间动画重绘非常频繁
-
使用硬件加速提高渲染速度,实现平滑的动画效果。
4、怎么避免卡顿:
一定要避免在主线程中做耗时任务,总结一下 Android 中主线程的场景:
- UI 生命周期的控制
- 系统事件的处理
- 消息处理
- 界面布局
- 界面绘制
- 界面刷新
- …
基于这几点去说卡顿肯定是没有问题的。
4、怎么保证 APP 的稳定运行?
程序员:
保证程序的稳定我们可以从内存、代码质量、Crash、ANR、后台存活等知识点来展开优化。
面试官:
那你具体说说你是怎么做的?
程序员:
1、内存
可以从第二点内存优化来说明
2、代码质量
1、团队之前相互代码审查,保证了代码的质量,也可以学习到了其它同事码代码的思想。
2、使用 Link 扫描代码,查看是否有缺陷性。
3、Crash
1、通过实现 Thread.UncaughtExceptionHandler 接口来全局监控异常状态,发生 Crash 及时上传日志给后台,并且及时通过插件包修复。
2、Native 线上通过 Bugly 框架实时监控程序异常状况,线下局域网使用 Google 开源的 breakpad 框架。发生异常就搜集日志上传服务器(这里要注意的是日志上传的性能问题,后面省电模块会说明)
4、ANR
5、后台存活
面试官:
嗯,你对知识点掌握的挺好。
说完这些,这一关也算是过了。
5、说说你在项目中网络优化?
程序员:
有,这一点其实可以通过 OKHTTP 连接池和 Http 缓存来说一下(当然这里不会再展开分析 OKHTTP 源码了)
面试官:
那你具体说一下吧
程序员
说了这些之后,再说一下你当前使用网络框架它们做了哪些优化比如 OKHTTP(Socket 连接池、Http缓存、责任链)、Retrofit(动态代理)。说了这些一般这关也算是过了。
6、你在项目中有用过哪些存储方式? 对它们的性能有过优化吗?
程序员:
主要用过 sp,File,SQLite 存储方式。其中对 sp 和 sqlite 做了优化。
面试官:
那你说说都做了哪些优化?
程序员:
这一块如果你使用过其它第三方的数据库,可以说说它们的原理和它们存取的方式。
7、你在项目中有做过自定义 View 吗?有对它做过什么优化?
程序员:
有做过。比如重复绘制,还有大图长图有过优化。
面试官:
那具体说一说
最后
都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。
技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;
我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 PDF(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言
高级UI与自定义view;
自定义view,Android开发的基本功。
性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。
NDK开发;
未来的方向,高薪必会。
前沿技术;
组件化,热升级,热修复,框架设计
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多
当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。
不出半年,你就能看出变化!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
了系统的高级进阶教程,会比自己碎片化学习效果强太多
当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。
不出半年,你就能看出变化!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!