花门楼前见秋草,岂能贫贱相看老。这篇文章主要讲述Android性能优化全方面解析相关的知识,希望能为你提供帮助。
目的 公司的新需求终于解决完了,
离测试和发布还有段时间,
第一次体验了下没需求没bug的感觉,
真是舒爽~然后翻了翻有什么可以学的。无意翻到了android后期发展的五大趋势。一、性能优化。二、高级UI。三、JNI/NDK开发。四、架构师。五、RN开发。这也许将会是我的进阶趋势。早已知道在瓶颈期的我,
似乎看到了突破的希望的。
其实,
关注我的或者在群里的小伙伴也知道,
UI那块我问题不大。但是高级UI就有难度了。我们先不管他,
一个一个来。先从性能优化来。其实我是拒绝写这篇文章的。为什么?
性能优化的分类很多,
一个分类写一篇感觉篇幅量很小,
结合在一起写有感觉很大。而我目前打算整体的整理一下。
那么我们先分析下性能优化有那几个方面:
一、内存优化。二、UI优化(
布局优化和绘制优化)
。三、速度的优化(
线程优化/网络优化)
。四、电量优化。五、启动优化。应该就这些了。那么这只是五大方面,
里面还结合了各种细节方面的。不急,
我们下面一个个的介绍。
内存优化 关于性能优化我们可以不知道其他的,
但一定要知道内存优化。因为内存泄漏可以Android的常客。那么什么是内存泄漏呢?
内存不在GC的掌控范围之内了。那么java的GC内存回收机制是什么?
某对象不在有任何引用的时候才会进行回收。那么GC回收机制的原理是什么?
又或者说可以作为GC Root引用点的是啥?
或许有人听不懂我在讲啥。我们先来看张图。
文章图片
当我们向上寻找, 一直寻找到GC Root的时候, 此对象不会进行回收, 例如, 一个Activity。那么如果我们向上寻找, 直到找到GC Root对象的时候, 就说明它是不可以回收的, 例如, 我定义了一个int a; 但是这个数据, 我整个页面或者说整个项目都没有用到, 则这个对象会被GC掉。
GC的引用点
- java栈中引用的对象
- 方法静态引用的对象
- 方法常量引用的对象
- Native中JNI引用的对象
- Thread——“活着的”线程
那么我们如何判断一个对象是一个垃圾对象, 可以讲他进行回收呢? 举了小例子教你们如何区分:
一般在学校吃饭,
我们有两种情况,
第一:
吃完饭就直接走人,
碗筷留给阿姨来收拾处理。
第二:
吃完之后把碗筷放到收盘处直接进行回收。
但我们是个有素质的人,
一般采用第二种情况,
但根据想法,
我们更倾向于第一种。
那么一般在饭店或者KFC中,
都是第一种情况。
那么此时,
问题来了,
如果我已经吃完饭,
然后我并没有离开饭店,
做在位置上和朋友吹吹牛逼,
谈谈理想,
聊聊人生。
那么桌上那一堆碗筷是收还是不收?
讲道理是不能收的。虽然实际也是不能收的。因为顾客是上帝~~~
So, 我们如何判断一个对象是一个可回收的垃圾对象呢? 这是我们的一个主观的判断。但是有种情况我们是必须要考虑到的, 没错, 就是内存过多无法释放的时候, 会直接导致OOM。整个项目boom炸了。什么鬼? outofmemory。没错就是它。
内存溢出
分析原因 我们需要分析内存溢出的原因, 我们先来看一张图:
文章图片
内存泄漏一般导致应用卡顿, 极端情况会导致项目boom。Boom的原因是因为超过内存的阈值。
原因主要有两方面:
- 代码存在泄漏, 内存无法及时释放导致oom( 这个我们后面说)
- 一些逻辑消耗了大量内存, 无法及时释放或者超过导致oom
我们来看下下面两张图:
文章图片
文章图片
对比两张图, 我们可以在第一张的情况出现了oom情况, 我们通过log打印发现, 处理的好像没什么问题, 换句话说, 如果我不放那0.8M的图片。然后继续不停的操作同样会出现OOM, 然而我们就蒙了。没什么图片加载怎么就这么崩掉了。
如何查看 首先, 我们确定我们项目或者某几个类里面是否存在内存溢出的问题。我们可以通过如下方法:
- Android–> System Information–> MemoryUsage查看Object里面是否有没有被释放的Views和Activity
- 命令行模式: adb shell dumpsys meminfo 包名 -d
文章图片
我们找到Object。看看我们内存的消耗情况。
文章图片
随便这么一看, 尼玛蛋, 1300左右的view和一个Activity。还有3个context。可怕。。可以理解为一个Activity里面使用了将近1300个view。。。想都不敢想。。。
我们可以通过看Memory Monitor工具。 检查一个一个的动作。( 比如Activity的跳转) 。反复多次执行某一个操作, 不断的通过这个工具查看内存的大概变化情况。 前后两个内存变化增加了不少。
我们可以更仔细的查找泄漏的位置, 在AS里面使用 Heap SnapShot工具( 堆栈快照) 。如图所示:
文章图片
我们点击后, 他会进行一段时间的监控, 然后会生成一个文件。我们点击我们package tree view。
文章图片
我们找到自己项目的包名。然后进行进一步的分析。首先看一下2个列表的列名到底指的什么。
文章图片
实例化对象的详细信息:
文章图片
我们来随便的看一下内存中的数量:
文章图片
这还是我们刚进手机, 一个bean就被调用了这么多次。简直可怕。这个我们可以通过内存分析工具解决的。
内存分析工具 性能优化工具:
- Heap SnapShot工具
- Heap Viewer工具
- LeakCanary工具
- MAT工具
- TraceView工具( Device Monitor)
- MemoryAnalyzer
- GT Home
- iTest
因为我没有这些工具, 无法进行演示。
- 我们尽量不要使用Activity的上下文,
而是使用application的上下文,
因为application的生命周期长,
进程退出时才会被销毁。所以,
单例模式是最容易造成内存溢出的原本所在,
因为单例模式的生命周期的应该和application的生命周期一样长,
而不是和Activity的相同。
- Animation也会导致内存溢出,
为什么?
因为我们是通过view来进行演示的,
导致view被Activity持有,
而Activity又持有view。最后因为Activity无法释放,
导致内存泄漏。解决方法是在Activity的ondestory(
)
方法中调用Animation.cancle(
)
进行停止,
当然一些简单的动画我们可以通过自定义view来解决。至少我现在已经很少使用Animation了。没有一个动画是自定义view解决不了的。如何有,
那就是两个~~~。
- 人为在UI线程中做轻微耗时操作,
导致UI线程卡顿;
- 布局Layout过于复杂,
无法在16ms内完成渲染;
- 同一时间动画执行的次数过多,
导致CPU或GPU负载过重;
- View过度绘制,
导致某些像素在同一帧时间内被绘制多次,
从而使CPU或GPU负载过重;
- View频繁的触发measure、layout,
导致measure、layout累计耗时过多及整个View频繁的重新渲染;
- 内存频繁触发GC过多(
同一帧中频繁创建内存)
,
导致暂时阻塞渲染操作;
- 冗余资源及逻辑等导致加载和执行缓慢;
- 臭名昭著的ANR;
布局优化
GPU绘制 我们对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置-> 开发者选项-> 调试GPU过度绘制( 不同设备可能位置或者叫法不同) 中打开调试后可以看见如下图( 对settings当前界面过度绘制进行分析) :
文章图片
文章图片
这图看着太乱, 我们来一张简洁明了的图:
文章图片
我们的目标就是尽量减少红色Overdraw, 看到更多的蓝色区域。
可以发现, 开启后在我们想要调试的应用界面中可以看到各种颜色的区域, 具体含义如下:
文章图片
Overdraw有时候是因为你的UI布局存在大量重叠的部分, 还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景, 然后里面的Layout又有自己的背景, 同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片, 这就能够减少大量的红色Overdraw区域, 增加蓝色区域的占比。这一措施能够显著提升程序性能。
如果布局中既能采用RealtiveLayout和LinearLayout, 那么直接使用LinearLayout, 因为Relativelayout的布局比较复杂, 绘制的时候需要花费更多的CPU时间。如果需要多个LinearLayout或者Framelayout嵌套, 那么可采用Relativelayout。因为多层嵌套导致布局的绘制有大部分是重复的, 这会减少程序的性能。
GPU呈现模式分析 我们依旧打开设置–> 开发者选项–> GPU呈现模式分析–> 在屏幕上显示为条形图, 如图所示:
文章图片
文章图片
当然, 也可以在执行完UI滑动操作后在命令行输入如下命令查看命令行打印的GPU渲染数据( 分析依据: Draw + Process + Execute = 完整的显示一帧时间 < 16ms) :
adb shell dumpsys gfxinfo [应用包名]
随着界面的刷新, 界面上会以实时柱状图来显示每帧的渲染时间, 柱状图越高表示渲染时间越长, 每个柱状图偏上都有一根代表16ms基准的绿色横线, 每一条竖着的柱状线都包含三部分( 蓝色代表测量绘制Display List的时间, 红色代表OpenGL渲染Display List所需要的时间, 黄色代表CPU等待GPU处理的时间) , 只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题( 个别超出基准线其实也不算啥问题的) 。就简单的看下我们公司项目刚启动的时候:
文章图片
突然就有那么一种想吐槽的感觉…..我记得之前我做了瘦身的优化, 但是要让我做性能优化, 我觉得应该没那么简单……..
代码优化
Android Studio和IntellJ idead都有自带的代码检查工具。打开Analyze-> Run Inspection by Name… –> unused resource 点击开始检测, 等待一下后会发现如下结果:
文章图片
我们还可以这样, 将鼠标放在代码区点击右键-> Analyze-> Inspect Code–> 界面选择你要检测的模块-> 点击确认开始检测, 等待一下后会发现如下结果:
文章图片
当然, 我这只是截取了少一部分, 我们看下下面那个提示: @ param v tag description is missing 。意味着v的类型缺少了, 要么补上介绍, 要么直接删除。
上面那两种方法是最容易找到代码缺陷以及无用代码的地方。所以尽情的入坑去填坑把~~~
绘制优化
那么什么是绘制优化? 绘制优化主要是指View的Ondraw方法需要避免执行大量的操作。我将分为了2个方面。
- ondraw方法不需要创建新的局部对象, 这是因为ondraw方法是实时执行的, 这样会产品大量的临时对象, 导致占用了更多内存, 并且使系统不断的GC。降低了执行效率。
- Ondraw方法不需要执行耗时操作, 在ondraw方法里少使用循环, 因为循环会占用CPU的时间。导致绘制不流畅, 卡顿等等。Google官方指出, view的绘制帧率稳定在60dps, 这要求每帧的绘制时间不超过16ms( 1000/60)。虽然很难保证, 但我们需要尽可能的降低。
网络优化 线程是我们项目中不可缺少的重要部分, 因为我们大多数数据都是从网络获取的。So, 线程这个是必备用品。
我们依旧可以通过Memory下面的Net进行网络的监听:
文章图片
ANR问题
相信这个问题在座的各种没少遇到过, 那么什么是ANR? application not responding。应用程序无响应。那么一般什么时候会出现ANR。Android官方规定: activity如果5s内无响应事件( 屏幕触摸事件或者键盘输入事件) 。BroadcastReceiver如果在10s内无法处理完成。Service如果20s内无法处理完成。这三种情况会导致ANR。用张简洁的图来介绍把。看起来方便~~
文章图片
线程优化
上面说的三种导致ANR的情况, 绝大多数就是因为线程阻塞导致的。那么我们应该如何处理呢? Android系统为我们提供了若干组工具类来解决此问题。
- Asynctask:
为UI线程与工作线程之间进行快速处理的切换提供一种简单便捷的机制。适用于当下立即需要启动,
但是异步执行的生命周期短暂的场景。
- HandlerThread:
为某些回调方法或者等待某些执行任务的执行设置一个专属的线程,
并提供线程任务的调度机制。
- ThreadPool:
把任务分解成不同的单元,
分发到各个不同的线程上,
进行同时并发处理。
- IntentService:
适合执行由Ui触发的后台任务。并可以把这些任务执行的情况通过一定的机制反馈给UI。
一般多线程的情况我们可以通过Asynctask处理。( 这玩意我真没怎么用过- -) 我前面有说过annotation。这是google官方推出的注解。比bufferknife强大很多。这个可以快捷方便的处理多线程而且不会导致线程阻塞, 而且你也可以控制线程的顺序, 例如我要执行完线程A后, 根据线程A的某个参数来执行线程B。以此类推…..
至于线程池么, 最多的还是要说道图片加载了~~。图片加载用三方就行了~想看详细介绍, 我前面有说, 当然除了这个还有下载操作。这就和IntentService有关联了。一般下载我很少涉及到。。用过几次android原生的downloadmanager。。感觉略坑。
KO网络优化
现在讲网络优化的重点了…重点..重点…, 一般用到网最最最主要的是什么? 时间! ! 速度! ! 成功率! ! , 时间! ! 速度! ! 成功率! ! , 时间! ! 速度! ! 成功率! ! 重要的事说三遍哈。
图片处理 这已经不是第一次在此文提到图片了。可见图片的重要性! !
- 使用WebP格式; 同样的照片, 采用WebP格式可大幅节省流量, 相对于JPG格式的图片, 流量能节省将近 25% 到 35 %; 相对于 PNG 格式的图片, 流量可以节省将近80%。最重要的是使用WebP之后图片质量也没有改变。So, 去和后台的小伙伴们商量吧~~~
- 使用缩略图, 我在前面写图片加载有说过, 就是控制他的inside和option。然后进行图片缩放。压缩? 讲道理….我并不知道网络图片怎么压缩, but, 我会缩放啊~~反正也不会失真。啦啦啦~咬我啊?
在某些情况, 我们尽量少使用GPS定位, 如果条件允许, 尽可能使用网络定位。
下载、上传, 我们尽可能使用断点, 说个简单的, 我在公司, 准备下一个500M的游戏, 但是下到200M的时候我下班了, 此时没有了无线网, 我们可以回家后用无线继续下载。So, 断点续传, 断点下载也是我们的必修课~, 所以我前面单独提了一篇断点续传的文章。
刷新数据时, 尽可能使用局部刷新, 而不是全局刷新, 第一、界面会闪屏一下, 网差的界面直接白屏一段时间也不是不可能。第二、流量的使用! ! 我又要拿我们公司项目搞事情了。一个闪屏的缓存60+ M。。。没错, 就是60+ M。简直可怕, 我清个3、5次缓存, 在打开个3、5次。好了, 2分钟时间, 我一个月流量就没了。。。So, 我前面提到的网络缓存很重要, 至于会不会加在项目中, 我还是要看了在说- - 一个不小心, 整个项目炸了都有可能。。。
启动优化 【Android性能优化全方面解析】众所周知, 一个好的产品, 除了功能强大, 好的性能也必不可少。有调查显示, 近50%的受访者因为apk太大而拒绝使用, 近40%的受访者会因为APP性能差而卸载, 性能也是造成APP用户沮丧的头号原因。
安卓应用的启动方式分为三种: 冷启动、暖启动、热启动, 不同的启动方式决定了应用UI对用户可见所需要花费的时间长短。顾名思义, 冷启动消耗的时间最长。基于冷启动方式的优化工作也是最考验产品用户体验的地方。谈及优化之前, 我们先看看这三种启动方式的应用场景, 以及启动过程中系统都做了些什么工作。
冷启动
为什么说冷启动是耗时最长的。冷启动是在启动应用前, 系统没有获取到当前app的activity、Service等等。例如, 第一次启动app。又或者说杀死进程后第一次启动。那么对比其他两种方式。冷启动自然是耗时最久的。
应用发生冷启动时, 系统一定会执行下面的三个任务:
- 开始加载并启动应用
- 应用启动后, 显示一个空白的启动窗口( 启动闪屏页)
- 创建应用信息
- application的初始化
- 启动UI线程
- 创建Activity
- 导入视图( inflate view)
- 计算视图大小( onmesure view)
- 得到视图排版( onlayout view)
- 绘制视图( ondraw view)
暖启动
当应用中的 Activities 被销毁, 但在内存中常驻时, 应用的启动方式就会变为暖启动。相比冷启动, 暖启动过程减少了对象初始化、布局加载等工作, 启动时间更短。但启动时, 系统依然会展示闪屏页, 直到第一个 Activity 的内容呈现为止。
热启动
相比暖启动, 热启动时应用做的工作更少, 启动时间更短。热启动产生的场景很多, 常见如: 用户使用返回键退出应用, 然后马上又重新启动应用。
如何优化
我们先对比下三种启动的时间对比:
冷启动:
文章图片
暖启动 :
文章图片
热启动:
文章图片
我们可以看到三者的明显的差距, 一个冷启动将近一分钟, 反正我是不想看, 每次跑项目都好慢~那么我们应该怎么做? 看到有些人介绍说改变项目的theme。把它改成launcher的theme。但我觉得, 这种做测试的确没问题。但是一般项目都会有闪屏页。然后从闪屏跳转到首页。我们可以按照大多数的项目来改善。怎么说的, 我们可以看到一般项目都有倒计时显示。也就是说倒计时结束就自动进入首页。或者可以直接跳过进入首页。也就是说我们可以通过此方法来进行, 也就是说只要他倒计时结束, 不管请求是否全部获取完我们都直接进入首页。我们可以在闪屏页进行一些必要的加载, 例如用户信息, 定位等等, 那么至于其他的, 我们可以进入主页进行预加载。就和热更新一样, 在用户不知情的情况下, 默默的更新bug。So, 对于一些网络请求, 例如广告之类的。我们可以通过此方法进行预加载。
我们还可以这样, 闪屏页我们把他当作一个fragment嵌套在MainActivity中, 那么我们可以在进入闪屏时直接预加载主页的view。倒计时我们把闪屏页remove掉直接显示首页。
通过上面的介绍, 我们对启动优化有了一定的了解, 其实总结的话很简单。就是减少耗时操作, 总结如下:
- 主线程中涉及到Shareperference能否在非UI线程执行。
- Application的创建过程中尽量少的进行耗时操作。
- 减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作。
耗电概念
其实大多数开发者对电量优化的重视程度极低, 其实提到性能优化想到的就是内存优化, 但我们不能忽视其他的优化, 电量优化其实还是必要的, 例如爱奇艺、优酷等等的视频播放器以及音乐播放器。众所周知, 音乐和视频其实是耗电量最大的。如果用户一旦发现我们的应用非常耗电, 不好意思, 他们大多会选择卸载来解决此类问题。为此, 我们需要进行优化。
如何优化
其实我们把上面那四种优化解决了, 就是最好的电量优化。So, 对于电量优化, 我在此提一些建议:
- 需要进行网络请求时, 我们需先判断网络当前的状态。
- 在多网络请求的情况下, 最好进行批量处理, 尽量避免频繁的间隔网络请求。
- 在同时有wifi和移动数据的情况下, 我们应该直接屏幕移动数据的网络请求, 只有当wifi断开时在调用, 因为, wifi请求的耗电量远比移动数据的耗电量低的低。
- 后台任务要尽可能少的唤醒CPU。( 比方说, 锁屏时, QQ的消息提示行就是唤醒了CPU。但是它的提示只有在你打开锁屏或者进行充电时才会进行提示。)
文章参考 《Android艺术探索》
Android应用开发性能优化完全分析
性能优化典范
双十二技术哥
google官方优化视频
推荐阅读
- android入门 — AlertDialog对话框
- Missing &#39;name&#39; key attribute on element activity at AndroidMan
- Android开发艺术探索——第八章(理解Window和WindowManager)
- Android相机实时自动对焦的完美实现
- 捷速PDF编辑工具更改PDF页面尺寸的办法_其它办公
- 迅速搜索excel公式中的出错的办法介绍_Excel专区
- word文档对指定文字进行格式替换的办法_Word专区
- word文档打开文件出错的处理办法_Word专区
- 设置Word首字母大写的办法介绍_Word专区