GITHUB
https://blog.51cto.com/6342127/2307514
说明
这篇文章是将好久以来看过的文章,包括本身写的一些测试代码的总结.属于笔记的性质,没有面面俱到,一些本身相对熟悉的点可能会略过.<br>
最开始看到的性能优化的文章,就是胡凯的优化典范系列,后来又陆续看过一些人写的,我的以为anly_jun和胡凯的质量最好.<br>
文章大的框架也是先把优化典范过一遍,记录我的认为重要的点,而后是anly_jun的系列,将以前未覆盖的补充进去,也包括HenCoder的一些课程相关内容.<br>
固然除了上面几位,还有不少其余大神的文章,时间久了也记不太清,在此一并谢过.javascript
笔记内容引用来源
1.Android性能优化之渲染篇
1.VSYNC
- 帧率:GPU在1秒内绘制操做的帧数.如60fps.
- 咱们一般都会提到60fps与16ms,这是由于人眼与大脑之间的协做没法感知超过60fps的画面更新.
- 开发app的性能目标就是保持60fps,这意味着每一帧只有16ms=1000/60的时间来处理全部的任务
- 刷新率:屏幕在1秒内刷新屏幕的次数.如60Hz,每16ms刷新1次屏幕.
- GPU获取图形数据进行渲染,而后屏幕将渲染后的内容展现在屏幕上.
- 大多数手机屏幕的刷新率是60Hz,若是GPU渲染1帧的时间低于1000/60=16ms,那么在屏幕刷新时候都有最新帧可显示.若是GPU渲染某1帧 f 的时间超过16ms,在屏幕刷新时候,f并无被GPU渲染完成则没法展现,屏幕只能继续展现f的上1帧的内容.这就是掉帧,形成了UI界面的卡顿.
<br>
下面展现了帧率正常和帧率低于刷新率(掉帧)的情形
2.GPU渲染:GPU渲染依赖2个组件:CPU和GPU
- CPU负责Measure,Layout,Record,Execute操做.
- GPU负责Rasterization(栅格化)操做.
- Resterization栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操做.它把组件拆分到不一样的像素上进行显示.这是一个很费时的操做.
- CPU负责把UI组件计算成Polygons(多边形),Texture(纹理),而后交给GPU进行栅格化渲染.
- 为了App流畅,咱们须要确保在16ms内完成全部CPU和GPU的工做.
3.过分绘制
Overdraw过分绘制是指屏幕上的某个像素在同一帧的时间内被绘制了屡次.过分绘制会大量浪费CPU及GPU资源/占用CPU和GPU的处理时间php
- 过分绘制的缘由
- UI布局存在大量重叠
- 非必须的背景重叠.
- 如Activity有背景,Layout又有背景,子View又有背景.仅仅移除非必要背景就能够显著提高性能.
- 子View在onDraw中存在重叠部分绘制的状况,好比Bitmap重叠绘制
4.如何提高渲染性能
- 移除XML布局文件中非必要的Background
- 保持布局扁平化,尽可能避免布局嵌套
- 在任什么时候候都避免调用requestLayout(),调用requestLayout会致使该layout的全部父节点都发生从新layout的操做
-
在自定义View的onDraw中避免过分绘制.
<br>代码实例:html<br>效果图:java
2.Android性能优化以内存篇
1.Android虚拟机的 分代堆内存/Generational Heap Memory模型
- 和JVM不一样:Android的堆内存多了1个永久代/Permanent Generation.
- 和JVM相似:
- 新建立的对象存储在新生代/Young Generation
- GC所占用的时间和它是哪个Generation有关,Young Generation的每次GC操做时间是最短的,Old Generation其次,Permanent Generation最长
- 不管哪一代,触发GC后,全部非垃圾回收线程暂停,GC结束后全部线程恢复执行
- 若是短期内进行过多GC,屡次暂停线程进行垃圾回收的累积时间就会增大.占用过多的帧间隔时间/16ms,致使CPU和GPU用于计算渲染的时间不足,致使卡顿/掉帧.
2.内存泄漏和内存溢出
内存泄漏就是无用对象占据的内存空间没有及时释放,致使内存空间浪费的状况.memory leak.
<br>
内存溢出是App为1个对象申请内存空间,内存空间不足的状况.out of memory.
<br>
内存泄漏数量足够大,就会引发内存溢出.或者说内存泄漏是内存溢出的缘由之一.python
3.Android性能优化典范-第2季
1.提高动画性能
- Bitmap的缩放,旋转,裁剪比较耗性能.例如在一个圆形的钟表图上,咱们把时钟的指针抠出来当作单独的图片进行旋转会比旋转一张完整的圆形图性能好.
- 尽可能减小每次重绘的元素能够极大提高性能.能够把复杂的View拆分会更小的View进行组合,在须要刷新界面时候仅对指定View进行重绘.
- 假如钟表界面上有不少组件,能够把这些组件作拆分,背景图片单独拎出来设置为一个独立的View,经过setLayerType()方法使得这个View强制用Hardware来进行渲染.至于界面上哪些元素须要作拆分,他们各自的更新频率是多少,须要有针对性的单独讨论
2.对象池
- 假如钟表界面上有不少组件,能够把这些组件作拆分,背景图片单独拎出来设置为一个独立的View,经过setLayerType()方法使得这个View强制用Hardware来进行渲染.至于界面上哪些元素须要作拆分,他们各自的更新频率是多少,须要有针对性的单独讨论
- 短期内大量对象被建立而后很快被销毁,会屡次触发Android虚拟机在Young generation进行GC,使用AS查看内存曲线,会看到内存曲线剧烈起伏,称为"内存抖动".
- GC会暂停其余线程,短期屡次GC/内存抖动会引发CPU和GPU在16ms内没法完成当前帧的渲染,引发界面卡顿.
- 避免内存抖动,可使用对象池
-
实例android
3.for index,for simple,iterator三种遍历性能比较
- 不要用for index去遍历链表,由于LinkedList在get任何一个位置的数据的时候,都会把前面的数据走一遍.应该使用Iterator去遍历
- get(0),直接拿到0位的Node0的地址,拿到Node0里面的数据
- get(1),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,拿到Node1里面的数据
- get(2),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,从1位的Node1中找到下一个2位的Node2的地址,找到Node2,拿到Node2里面的数据
- Vector和ArrayList,使用for index遍历效率较高
4.Merge:经过Merge减小1个View层级
- 能够将merge当作1个ViewGroup v,若是v的类型和v的父控件的类型一致,那么v其实不必存在,由于白白增长了布局的深度.因此merge使用时必须保证merge中子控件所应该在的ViewGroup类型和merge所在的父控件类型一致.
- Merge的使用场景有2个:
- Activity的布局文件的根布局是FrameLayout,则将FrameLayout替换为merge
- 由于setContentView本质就是将布局文件inflate后加载到了id为android.id.content的FrameLayout上.
- merge做为根布局的布局文件经过include标签被引入其余布局文件中.这时候include所在的父控件,必须和merge所在的布局文件"本来根布局"一致.
- Activity的布局文件的根布局是FrameLayout,则将FrameLayout替换为merge
-
代码示例<br>
merge做为根布局的布局文件,用于Activity的setContentView:nginx<br>
merge做为根布局的布局文件,被include标签引入其余布局文件中:git
5.使用.9.png做为背景
- 典型场景是1个ImageView须要添加1个背景图做为边框.这样边框所在矩形的中间部分和实际显示的图片就好重叠发生Overdraw.
- 能够将背景图制做成.9.png.和前景图重叠部分设置为透明.Android的2D渲染器会优化.9.png的透明区域.
6.减小透明区域对性能的影响
- 不透明的View,显示它只须要渲染一次;若是View设置了alpha值,会至少须要渲染两次,性能很差
- 设置透明度setAlpha的时候,会把当前view绘制到offscreen buffer中,而后再显示出来.offscreen buffer是 一个临时缓冲区,把View放进来并作透明度的转化,而后显示到屏幕上,这个过程性能差,因此应该尽可能避免这个过程
- 如何避免使用offscreen buffer
- 对于不存在过分绘制的View,如没有背景的TextView,就能够直接设置文字颜色;ImageView设置图片透明度setImageAlpha;自定义View设置绘制时的paint的透明度
- 若是是自定义View,肯定不存在过分绘制,能够重写hasOverlappingRendering返回false便可.这样设置alpha时android会自动优化,避免使用offscreen buffer.
- 若是不是1,2两种状况,要设置View的透明度,则须要让GPU来渲染指定View,而后再设置透明度.
4.Android性能优化典范-第3季
1.避免使用枚举,用注解进行替代
- 枚举的问题
- 每一个枚举值都是1个对象,相比较Integer和String常量,枚举的内存开销至少是其2倍.
- 过多枚举会增长dex大小及其中的方法数量,增长App占用的空间及引起65536概率
-
如何替代枚举:使用注解github
- android.support.annotation中的@IntDef,@StringDef来包装Integer和String常量.
- 3个步骤
- 首先定义常量
- 而后自定义注解,设置取值范围就是刚刚定义的常量,并设置自定义注解的保留范围为源码时/SOURCE
- 位指定的属性及方法添加自定义注解.
-
代码实例算法
5.Android内存优化之OOM
如何避免OOM:
- 减少对象的内存占用
- 内存对象复用防止重建
- 避免内存泄漏
- 内存使用策略优化
1.减少对象的内存占用
- 避免使用枚举,用注解替代
- 减少建立的Bitmap的内存,使用合适的缩放比例及解码格式
- inSampleSize:缩放比例
- decode format:解码格式
- 如今不少图片资源的URL均可以添加图片尺寸做为参数.在经过网络获取图片时选择合适的尺寸,减少网络流量消耗,并减少生成的Bitmap的大小.
2.内存对象的重复利用
- 对象池技术:减小频繁建立和销毁对象带来的成本,实现对象的缓存和复用
- 尽可能使用Android系统内置资源,可下降APK大小,在必定程度下降内存开销
- ConvertView的复用
- LRU的机制实现Bitmap的缓存(图片加载框架的必备机制)
- 在for循环中,用StringBuilder代替String实现字符串拼接
3.避免内存泄漏
- 在App中使用leakcanary检测内存泄漏:leakcanary
-
Activity的内存泄漏
-
Handler引发Activity内存泄漏
- 缘由:Handler做为Activity的1个非静态内部类实例,持有Activity实例的引用.若Activity退出后Handler依然有待接收的Message,这时候发生GC,Message-Handler-Activity的引用链致使Activity没法被回收.
-
2种解决方法
- 在onDestroy调用Handler.removeCallbacksAndMessages(null)移除该Handler关联的全部Message及Runnable.再发生GC,Message已经不存在,就能够顺利的回收Handler及Activity
-
自定义静态内部类继承Handler,静态内部类实例不持有外部Activity的引用.在自定义Handler中定义外部Activity的弱引用,只有弱引用关联的外部Activity实例未被回收的状况下才继续执行handleMessage.自定义Handler持有外部Activity的弱引用,发生GC时不耽误Activity被回收.
- 在避免内存泄漏的前提下,若是要求Activity退出就不执行后续动做,用方法1.若是要求后续动做在GC发生前继续执行,使用方法2
-
- Context:尽可能使用Application Context而不是Activity Context,避免不经意的内存泄漏
- 资源对象要及时关闭
4.内存使用策略优化
- 图片选择合适的文件夹进行存放
- hdpi/xhdpi/xxhdpi等等不一样dpi的文件夹下的图片在不一样的设备上会通过scale的处理。例如咱们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。须要注意到在这种状况下,内存占用是会显著提升的。对于不但愿被拉伸的图片,须要放到assets或者nodpi的目录下
- 谨慎使用依赖注入框架.依赖注入框架会扫描代码,须要大量的内存空间映射代码.
- 混淆能够减小没必要要的代码,类,方法等.下降映射代码所需的内存空间
- onLowMemory()与onTrimMemory():没想到应该怎么用
- onLowMemory
- 当全部的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调.在这种状况下,须要尽快释放当前应用的非必须的内存资源,从而确保系统可以继续稳定运行
- onTrimMemory(int level)
- 当系统内存达到某些条件的时候,全部正在运行的应用都会收到这个回调,同时在这个回调里面会传递如下的参数,表明不一样的内存使用状况,收到onTrimMemory()回调的时候,须要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面能够提升系统的总体运行流畅度,另外也能够避免本身被系统判断为优先须要杀掉的应用
- onLowMemory
6.Android开发最佳实践
1.注意对隐式Intent的运行时检查保护
- 相似打开相机等隐式Intent,不必定可以在全部的Android设备上都正常运行.
- 例如系统相机应用被关闭或者不存在相机应用,或者某些权限被关闭均可能致使抛出ActivityNotFoundException的异常.
- 预防这个问题的最佳解决方案是在发出这个隐式Intent以前调用resolveActivity作检查
- 代码实例
2.Android 6.0的权限
3.MD新控件的使用:Toolbar替代ActionBar,AppBarLayout,Navigation Drawer, DrawerLayout, NavigationView等
7.Android性能优化典范-第4季
1.网络数据的缓存.okHttp,Picasso都支持网络缓存
okHttp Picasso
<br>
MVP架构实现的Github客户端(4-加入网络缓存)
2.代码混淆
2.1.AS中生成keystore.jks应用于APK打包
<br>
-
1:生成keystore.jks
<br> - 2:查看.jks文件的SHA1安全码
<br>
在AS的Terminal中输入:
<br>
keytool -list -v -keystore C:\Users\Administrator\Desktop\key.jks
<br>
keytool -list -v -keystore .jks文件详细路径
<br>
回车后,输入密钥库口令/就是.jks的密码,输入过程不可见,输入完毕回车便可!
<br>
2.2.proguard-rules关键字及部分通配符含义
<table align="center">
<tr>
<td>关键字</td>
<td>描述</td>
</tr>
<tr>
<td>keep</td>
<td>保留类和类中的成员,防止它们被混淆或移除</td>
</tr>
<tr>
<td>keepnames</td>
<td>保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除</td>
</tr>
<tr>
<td>keepclasseswithmembers</td>
<td>保留类和类中的成员,防止它们被混淆或移除,前提是指名的类中的成员必须存在,若是不存在则仍是会混淆</td>
</tr>
<tr>
<td>keepclasseswithmembernames</td>
<td>保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是指名的类中的成员必须存在,若是不存在则仍是会混淆</td>
</tr>
<tr>
<td>keepclassmembers</td>
<td>只保留类中的成员,防止它们被混淆或移除</td>
</tr>
<tr>
<td>keepclassmembernames</td>
<td>只保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除</td>
</tr>
</table>
<br>
<table align="center">
<tr>
<td>通配符</td>
<td>描述</td>
</tr>
<tr>
<td>< field ></td>
<td>匹配类中的全部字段</td>
</tr>
<tr>
<td>< method ></td>
<td>匹配类中的全部方法</td>
</tr>
<tr>
<td>< init ></td>
<td>匹配类中的全部构造函数</td>
</tr>
<tr>
<td></td>
<td>
1.和字符串联合使用,表明任意长度的不包含包名分隔符(.)的字符串:<br>
a.b.c.MainActivity: a...MainActivity能够匹配;a.就匹配不上;<br>
2.*单独使用,就能够匹配全部东西
</td>
</tr>
<tr>
<td></td>
<td>
匹配任意长度字符串,包含包名分隔符(.)<br>
a.b.能够匹配a.b包下全部内容,包括子包
</td>
</tr>
<tr>
<td></td>
<td>
匹配任意参数类型.好比:<br>
void set(**)就能匹配任意传入的参数类型;<br>
get*()就能匹配任意返回值的类型
</td>
</tr>
<tr>
<td>…</td>
<td>
匹配任意长度的任意类型参数.好比:<br>
void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)
</td>
</tr>
</table>
<br>
- keep 完整类名{*;}, 能够对指定类进行彻底保留,不混淆类名,变量名,方法名.<br>
- 在App中,咱们会定义不少实体bean.每每涉及到bean实例和json字符串间互相转换.部分json库会经过反射调用bean的set和get方法.于是实体bean的set,get方法不能被混淆,或者说咱们本身写的方法,若是会被第三方库或其余地方经过反射调用,则指定方法要keep避免混淆.
- 咱们本身写的使用了反射功能的类,必须keep
- 若是咱们要保留继承了指定类的子类,或者实现了指定接口的类
2.3.proguard-rules.pro通用模板
2.4.混淆jar包
<br>
郭霖大神博客有介绍,本身没试过
2.5.几条实用的Proguard rules
<br>
在上面提供的通用模板上继续添加下面几行:
- repackageclasses:除了keep的类,会把咱们本身写的全部类以及所使用到的各类第三方库代码通通移动到咱们指定的单个包下.
- 好比一些比较敏感的被keep的类在包a.b.min下,咱们可使用 -repackageclasses a.b.min,这样就有成千上万的被混淆的类和未被混淆的敏感的类在a.b.min下面,正常人根本就找不到关键类.尤为是keep的类也只是保留关键方法,名字也被混淆过.
- -obfuscationdictionary,-classobfuscationdictionary和-packageobfuscationdictionary分别指定变量/方法名,类名,包名混淆后的字符串集.
- 默认咱们的代码命名会被混淆成字母组合,使用这些配置能够用乱码或中文内容进行命名.中文命名能够破坏部分反编译软件的正常工做,乱码则极大加大了查看代码的难度.
- dict.txt:须要放到和app模块的proguard-rules.pro同级目录.dict.txt具体内容能够本身写,参考开源项目:一种生成阅读极其困难的proguard字典的算法
- -assumenosideeffects class android.util.Log是在编译成 APK 以前把日志代码所有删掉.
- Androidstudio 混淆去掉日志 assumenosideeffects 不起做用
- 由于默认状况下,使用的是proguard-android.txt的混淆规则.proguard-android.txt中包含-dontoptimize.-dontoptimize致使日志语句不会被优化掉.因此咱们提供的模板不包含-dontoptimize这一句.
- Androidstudio 混淆去掉日志 assumenosideeffects 不起做用
2.6.字符串硬编码
- 对于反编译者来讲,最简单的入手点就是字符串搜索.硬编码留在代码里的字符串值都会在反编译过程当中被原样恢复,不要使用硬编码.
- 若是必定要使用硬编码
- 新建1个存储硬编码的常量类,静态存放字符串常量,即便找到了常量类,反编译者很难搜索到哪里用了这些字符串.
- 常量类中的静态常量字符串,用名称做为真正内容,而值用难以理解的编码表示.
2.7.res资源混淆及多渠道打包
<br>
简单讲,使用腾讯的2个gradle插件来实现res资源混淆及多渠道打包.
<br>
res资源混淆:AndResGuard
<br>
多渠道打包:VasDolly
<br>
多渠道打包原理+VasDolly和其余多渠道打包方案对比
<br><br>
具体流程:
<br>
AndResGuard使用了chaychan的方法,单首创建gradle文件
<br>
-
项目根目录下build.gradle中,添加插件的依赖,具体以下
-
在app目录下单首创建gradle文件and_res_guard.gradle.内容以下
-
模块app下的build.gradle文件添加依赖,具体以下
-
首先使用AndResGuard实现资源混淆,再使用VasDolly实现多渠道打包
-
在Gradle界面中,找到app模块下andresguard的task.
- 若是想打debug包,则执行resguardDebug指令;
- 若是想打release包,则执行resguardRelease指令.
- 此处咱们双击执行resguardRelease指令,在app目录下的/build/output/apk/release/AndResGuard_{apk_name}/ 文件夹中找到混淆后的Apk,其中app-release_aligned_signed.apk为进行混淆并签名过的apk.
- 咱们查看app-release_aligned_signed.apk,res文件夹改名为r,里面的目录名称以及xml文件已经被混淆.
- 将app-release_aligned_signed.apk放到app模块下,在Gradle界面中,找到app模块下channel的task,执行reBuildChannel指令.
- 双击执行reBuildChannel指令,几秒钟就生成了20个经过app-release_aligned_signed.apk的多渠道apk.
- 经过helper类库中的ChannelReaderUtil类读取渠道信息
- 双击执行reBuildChannel指令,几秒钟就生成了20个经过app-release_aligned_signed.apk的多渠道apk.
-
3.APK瘦身
4.更高效的数据序列化:只是看看从没用过,Protocal Buffers,Nano-Proto-Buffers,FlatBuffers
5.数据呈现的顺序以及结构会对序列化以后的空间产生不小的影响
-
gzip
- gzip概念:HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术
- 通常对纯文本内容可压缩到原大小的40%
- 减小文件大小有两个明显的好处,一是能够减小存储空间,二是经过网络传输文件时,能够减小传输的时间
-
okHttp对gzip的支持
- okHttp支持gzip自动解压缩,不须要设置Accept-Encoding为gzip
- 开发者手动设置Accept-Encoding,okHttp不负责解压缩
- 开发者没有设置Accept-Encoding时,则自动添加Accept-Encoding: gzip,自动添加的request,response支持自动解压
- 自动解压时移除Content-Length,因此上层Java代码想要contentLength时为-1
- 自动解压时移除 Content-Encoding
- 自动解压时,若是是分块传输编码,Transfer-Encoding: chunked不受影响
-
咱们在向服务器提交大量数据的时候,但愿对post的数据进行gzip压缩,须要使用自定义拦截器
- okHttp支持gzip自动解压缩,不须要设置Accept-Encoding为gzip
- gzip概念:HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术
-
改变数据结构,就是将原始集合按照集合中对象的属性进行拆分,变成多个属性集合的形式.
- 改变数据结构后JSON字符串长度有明显下降
- 使用GZIP对JSON字符串进行压缩,在原始集合中元素数据重复率逐渐变大的状况下,GZIP压缩后的原始JSON字符串长度/GZIP压缩后的改变数据结构的JSON字符串会明显大于1.
-
代码及测试结果以下,结果仅供参考(ZipUtils是网上荡的,且没有使用网上用过的Base64Decoder,Base64Encoder)
8.Android性能优化典范-第5季
多线程大部份内容源自凯哥的课程,我的以为比优化典范写得清晰得多
1.线程
- 线程就是代码线性执行,执行完毕就结束的一条线.UI线程不会结束是由于其初始化完毕后会执行死循环,因此永远不会执行完毕.
- 如何简单建立新线程:
- 两种方式建立新线程性能无差异,使用Runnable实例适用于但愿Runnable复用的情形
- 经常使用的建立线程池2种方式
- Executors.newCachedThreadPool():通常状况下使用newCachedThreadPool便可.
- Executors.newFixedThreadPool(int number):短时批量处理/好比要并行处理多张图片,能够直接建立包含图片精确数量的线程的线程池并行处理.
- 《阿里巴巴Java开发手册》规定:
- 线程池不容许使用 Executors 去建立,而是经过 ThreadPoolExecutor 的方式.这样
的处理方式让写的同窗更加明确线程池的运行规则,规避资源耗尽的风险 - 看Android中Executors源码.Executors.newCachedThreadPool/newScheduledThreadPool容许的建立线程数量为 Integer.MAX_VALUE,可能会建立大量的线程,从而致使 OOM.而newFixedThreadPool,newSingleThreadExecutor不会存在这种风险.
- 线程池不容许使用 Executors 去建立,而是经过 ThreadPoolExecutor 的方式.这样
- [如何正确建立ThreadPoolExecutor:有点麻烦,晚点详述]()
- ExecutorService的shutdown和shutdownNow
- shutdown:在调用shutdown以前ExecutorService中已经启动的线程,在调用shutdown后,线程若是执行未结束会继续执行完毕并结束,但不会再启动新的线程执行新任务.
- shutdownNow:首先中止启动新的线程执行新任务;并尝试结束全部正在执行的线程,正在执行的线程可能被终止也可能会继续执行完成.
-
如何正确建立ThreadPoolExecutor<br>
3.1:ThreadPoolExecutor构造参数- int corePoolSize:该线程池中核心线程最大数量.默认状况下,即便核心线程处于空闲状态也不会被销毁.除非经过allowCoreThreadTimeOut(true),则核心线程在空闲时间达到keepAliveTime时会被销毁<br>
- int maximumPoolSize:该线程池中线程最大数量<br>
- long keepAliveTime:该线程池中非核心线程被销毁前最大空闲时间,时间单位由unit决定.默认状况下核心线程即便空闲也不会被销毁,在调用allowCoreThreadTimeOut(true)后,该销毁时间设置也适用于核心线程<br>
- TimeUnit unit:keepAliveTime/被销毁前最大空闲时间的单位<br>
- BlockingQueue<Runnable> workQueue:该线程池中的任务队列.维护着等待被执行的Runnable对象.BlockingQueue有几种类型,下面会详述<br>
- ThreadFactory threadFactory:建立新线程的工厂.通常状况使用Executors.defaultThreadFactory()便可.固然也能够自定义.<br>
- RejectedExecutionHandler handler:拒绝策略.当须要建立的线程数量达到maximumPoolSize而且等待执行的Runnable数量超过了任务队列的容量,该如何处理.<br>
3.2:当1个任务被放进线程池,ThreadPoolExecutor具体执行策略以下:<br>
- 若是线程数量没有达到corePoolSize,有核心线程空闲则核心线程直接执行,没有空闲则直接新建核心线程执行任务;
- 若是线程数量已经达到corePoolSize,且核心线程无空闲,则将任务添加到等待队列;
- 若是等待队列已满,则新建非核心线程执行该任务;
- 若是等待队列已满且总线程数量已达到maximumPoolSize,则会交由RejectedExecutionHandler handler处理.<br>
3.3:阻塞队列/BlockingQueue<Runnable> workQueue<br>
- BlockingQueue有以下几种:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
- SynchronousQueue:SynchronousQueue的容量是0,不存储任何Runnable实例.新任务到来会直接尝试交给线程执行,如全部线程都在忙就建立新线程执行该任务.
- LinkedBlockingQueue:默认状况下没有容量限制的队列.
- ArrayBlockingQueue:一个有容量限制的队列.
- DelayQueue:一个没有容量限制的队列.队列中的元素必须实现了Delayed接口.元素在队列中的排序按照当前时间的延迟值,延迟最小/最先要被执行的任务排在队列头部,依次排序.延迟时间到达后执行指定任务.
- PriorityBlockingQueue:一个没有容量限制的队列.队列中元素必须实现了Comparable接口.队列中元素排序依赖元素的天然排序/compareTo的比较结果.
- 各类BlockingQueue的问题<br>
1.SynchronousQueue缺点:由于不具有存储元素的能力,于是当任务很频繁时候,为了防止线程数量超标,咱们每每设置maximumPoolSize是Integer.MAX_VALUE,建立过多线程会致使OOM.《阿里巴巴Java开发手册》中强调不能使用Executors直接建立线程池,就是对应Android源码中newCachedThreadPool和newScheduledThreadPool,本质上就是建立了maximumPoolSize为Integer.MAX_VALUE的ThreadPoolExecutor.<br>
2.LinkedBlockingQueue由于没有容量限制,因此咱们使用LinkedBlockingQueue建立ThreadPoolExecutor,设置maximumPoolSize是无心义的,若是线程数量已经达到corePoolSize,且核心线程都在忙,那么新来的任务会一直被添加到队列中.只要核心线程无空闲则一直得不到被执行机会.<br>
3.DelayQueue和PriorityBlockingQueue也具备一样的问题.因此corePoolSize必须设置合理,不然会致使超出核心线程数量的任务一直得不到机会被执行.这两类队列分别适用于定时及优先级明确的任务.<br>
3.4:RejectedExecutionHandler handler/拒绝策略有4种<br>
1.hreadPoolExecutor.AbortPolicy:丢弃任务,并抛出RejectedExecutionException异常.ThreadPoolExecutor默认就是使用AbortPolicy.<br>
2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不会抛出异常.<br>
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃排在队列头部的任务,不抛出异常,并尝试从新执行任务.<br>
4.ThreadPoolExecutor.CallerRunsPolicy:丢弃任务,但不抛出异常,并将该任务交给调用此ThreadPoolExecutor的线程执行. - synchronized 的本质
- 保证synchronized方法或者代码块内部资源/数据的互斥访问
- 即同一时间,由同一个Monitor监视的代码,最多只有1个线程在访问
- 保证线程之间对监视资源的数据同步.
- 任何线程在获取Monitor后,会第一时间将共享内存中的数据复制到本身的缓存中;
- 任何线程在释放Monitor后,会第一时间将缓存中的数据复制到共享内存中
- 保证synchronized方法或者代码块内部资源/数据的互斥访问
-
volatile
- 保证被volatile修饰的成员的操做具备原子性和同步性.至关于简化版的synchronized
- 原子性就是线程间互斥访问
- 同步性就是线程之间对监视资源的数据同步
-
volatile生效范围:基本类型的直接复制赋值 + 引用类型的直接赋值
- volatile型变量自增操做的隐患
- volatile类型变量每次在读取的时候,会越过线程的工做内存,直接从主存中读取,也就不会产生脏读
- ++自增操做,在Java对应的汇编指令有三条
- 从主存读取变量值到cpu寄存器
- 寄存器里的值+1
- 寄存器的值写回主存
- 若是N个线程同时执行到了第1步,那么最终变量会损失(N-1).第二步第三步只有一个线程是执行成功.
- 对变量的写操做不依赖于当前值,才能用volatile修饰.
- volatile型变量自增操做的隐患
- 保证被volatile修饰的成员的操做具备原子性和同步性.至关于简化版的synchronized
- 针对num++这类复合类的操做,可使用java并发包中的原子操做类原子操做类:AtomicInteger AtomicBoolean等来保证其原子性.
2.线程间交互
-
一个线程终结另外一个线程
-
Thread.stop不要用:
- 由于线程在运行过程当中随时有可能会被暂停切换到其余线程,stop的效果至关于切换到其余线程继续执行且之后不再会切换回来.咱们执行A.stop的时候,彻底没法预知A的run方法已经执行了多少,执行百分比彻底不可控.
private static void t2(){
Thread t = new Thread(){br/>@Override
public void run() {
for(int i=0;i<1000000;i++){
System.out.println(""+i);
}
}
};
t.start();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.stop();
}
-
-
Thread.interrupt:仅仅设置当前线程为被中断状态.在运行的线程依然会继续运行.
- Thread.isInterrupted:获取当前线程是否被中断
- Thread.interrupted():若是线程A调用了Thread.interrupted()
- 若是A以前已经被中断,调用Thread.interrupted()返回false,A已经不是被中断状态
- 若是A以前不是被中断状态,调用Thread.interrupted()返回true,A变成被中断状态.
-
单纯调用A.interrupt是无效果的,interrupt须要和isInterrupted联合使用
- 用于咱们但愿线程处于被中断状态时结束运行的场景.
- interrupt和stop比较的优势:stop后,线程直接结束,咱们彻底没法控制当前执行到哪里;<br>
interrupt后线程默认会继续执行,咱们经过isInterrupted来获取被中断状态,只有被中断且知足咱们指定条件才return,能够精确控制线程的执行百分比.
......
799999
800000
Process finished with exit code 0 -
InterruptedException:
- 若是线程A在sleep过程当中被其余线程调用A.interrupt(),会触发InterruptedException.
- 若是调用A.interrupt()时候,A并不在sleep状态,后面再调用A.sleep,也会当即抛出InterruptedException.
老子被叫醒了:睡了493ms
Process finished with exit code 0
-
线程等待:wait,notifyAll,notify
- wait,notifyAll,notify是属于Object的方法.用于线程等待的场景,需用Monitor进行调用
- wait:
- 当1个线程A持有Monitor M.
- 此时调用M.wait,A会释放M并处于等待状态.并记录A在当前代码执行的位置Position.
- notify:
- 当调用M.notify(),就会唤醒1个由于调用M.wait()而处于等待状态的线程
- 若是有A,B,C--多个线程都是由于调用M.wait()而处于等待状态,不必定哪一个会被唤醒并尝试获取M
- notifyAll:
- 当调用M.notifyAll(),全部由于调用M.wait()而处于等待状态的线程都被唤醒,一块儿竞争尝试获取M
- 调用notify/notifyAll被唤醒并获取到M的线程A,会接着以前的代码执行位置Position继续执行下去
线程:Thread-2 尝试printStr时间:1539247468146
线程:Thread-1 尝试printStr时间:1539247468944
setStr时间:1539247469944
线程:Thread-1 printStr时间:1539247469944
str:老子设置一下
线程:Thread-2 printStr时间:1539247469944
str:老子设置一下
3.Executor、 AsyncTask、 HandlerThead、 IntentService 如何选择
- HandlerThead就不要用,HandlerThead设计目的就是为了主界面死循环刷新界面,无其余应用场景.
- 能用线程池就用线程池,由于最简单.
- 涉及后台线程推送任务到UI线程,可使用Handler或AsyncTask
- Service:就是为了作后台任务,不要UI界面,须要持续存活.有复杂的须要长期存活/等待的场景使用Service.
- IntentService:属于Service.当咱们须要使用Service,且须要后台代码执行完毕后该Service自动被销毁,使用IntentService.
4.AsyncTask的内存泄漏
- GC Roots:由堆外指向堆内的引用,包括:
- Java方法栈帧中的局部变量
- 已加载类的静态变量
- native代码的引用
- 运行中的Java线程
- AsyncTask内存泄漏本质:正在运行的线程/AsyncTask 在虚拟机中属于GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的对象不能被回收.
- 因此AsyncTask和其余线程工具同样,只要是使用线程,都有可能发生内存泄漏,都要及时关闭,AsyncTask并不比其余工具更差.
5.RxJava.
讲的太多了这里推荐1个专题RxJava2.x<br>
下面记录一下本身不太熟的几点<br>
- RxJava总体结构:
- 链的最上游:生产者Observable
- 链的最下游:观察者Observer
- 链的中间多个节点:双重角色.便是上一节点的观察者Observer,也是下一节点的生产者Observable.
- Scheduler切换线程的原理:源码跟踪下去,实质是经过Excutor实现了线程切换.
6.Android M对Profile GPU Rendering工具的更新
- Swap Buffers:CPU等待GPU处理的时间
- Command Issur:OpenGL渲染Display List所须要的时间
- Sync&Upload:一般表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减小该段区域的执行时间,咱们能够减小屏幕上的图片数量或者是缩小图片自己的大小
- Draw:测量绘制Display List的时间
- Measure & Layout:这里表示的是布局的onMeasure与onLayout所花费的时间.一旦时间过长,就须要仔细检查本身的布局是否是存在严重的性能问题
- Animation:表示的是计算执行动画所须要花费的时间.包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦这里的执行时间过长,就须要检查是否是使用了非官方的动画工具或者是检查动画执行的过程当中是否是触发了读写操做等等
- Input Handling:表示的是系统处理输入事件所耗费的时间,粗略等于对于的事件处理方法所执行的时间.一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操做
- Misc/Vsync Delay:若是稍加注意,咱们能够在开发应用的Log日志里面看到这样一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。这意味着咱们在主线程执行了太多的任务,致使UI渲染跟不上vSync的信号而出现掉帧的状况
9.Android性能优化典范-第6季
1.启动闪屏
- 当点击桌面图标启动APP的时候,App会出现短暂的白屏,一直到第一个Activity的页面的渲染加载完毕
-
为了消除白屏,咱们能够为App入口Activity单独设置theme.
- 在单独设置的theme中设置android:background属性为App的品牌宣传图片背景.
-
在代码执行到入口Activity的onCreate的时候设置为程序正常的主题.
AndroidManifest.xmlandroid:icon="@mipmap/ic_launcher"<br "="" rel="nofollow">br/><application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"br/>android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"br/>android:theme="@style/AppTheme">
<activity android:name=".MainActivity"br/>android:theme="@style/ThemeSplash">//为入口Activity单独指定theme
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</manifest>public class MainActivity extends AppCompatActivity {br/>@Override
protected void onCreate(Bundle savedInstanceState) {
//在代码执行到入口Activity时候设置入口Activity为默认主题
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_all = findViewById(R.id.tv_all);
tv_local = findViewById(R.id.tv_local);
//注册全局广播
registerReceiver(globalReceiver,new IntentFilter("global"));
//注册本地广播
LocalBroadcastManager.getInstance(this).registerReceiver(localBroadReceiver,new IntentFilter("localBroadCast"));
}
}
- 若是是只有1张图片,放在mipmap-nodpi,或mipmap-xxxhdpi下
- 全部的大背景图片,统一放在mipmap-nodpi目录,用一套1080P素材能够解决大部分手机适配问题,不用每一个资源目录下放一套素材
- 通过试验,不论ImageView宽高是不是wrap_content,只要图片所在文件夹和当前设备分辨率不匹配,都会涉及到放大或压缩,占用的内存都会相应的变化.尤为对于大图,放在低分辨率文件夹下直接OOM.
<br>具体缘由:<br>
郭霖:Android drawable微技巧,你所不知道的drawable的那些细节当咱们使用资源id来去引用一张图片时,Android会使用一些规则来去帮咱们匹配最适合的图片。什么叫最适合的图片?好比个人手机屏幕密度是xxhdpi,那么drawable-xxhdpi文件夹下的图片就是最适合的图片。所以,当我引用android_logo这张图时,若是drawable-xxhdpi文件夹下有这张图就会优先被使用,在这种状况下,图片是不会被缩放的。可是,若是drawable-xxhdpi文件夹下没有这张图时, 系统就会自动去其它文件夹下找这张图了,优先会去更高密度的文件夹下找这张图片,咱们当前的场景就是drawable-xxxhdpi文件夹,而后发现这里也没有android_logo这张图,接下来会尝试再找更高密度的文件夹,发现没有更高密度的了,这个时候会去drawable-nodpi文件夹找这张图,发现也没有,那么就会去更低密度的文件夹下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。
整体匹配规则就是这样,那么好比说如今终于在drawable-mdpi文件夹下面找到android_logo这张图了,可是系统会认为你这张图是专门为低密度的设备所设计的,若是直接将这张图在当前的高密度设备上使用就有可能会出现像素太低的状况,因而系统自动帮咱们作了这样一个放大操做。
那么一样的道理,若是系统是在drawable-xxxhdpi文件夹下面找到这张图的话,它会认为这张图是为更高密度的设备所设计的,若是直接将这张图在当前设备上使用就有可能会出现像素太高的状况,因而会自动帮咱们作一个缩小的操做
3.尽可能复用已经存在的图片.<br>
好比一张图片O已经存在,若是有View的背景就是O旋转事后的样子,能够直接用O建立RotateDrawable.而后将设置给View使用.<br>
注意:RotateDrawable已经重写了其onLevelChange方法,因此必定要设置level才会生效<br>
实例:
4.开启混淆和资源压缩:在app模块下的的build.gradle中
5.对于简单/规则纹理的图片,使用VectorDrawable来替代多个分辨率图片.VectorDrawable 有不少注意事项,后面单独一篇文章总结
10.网络优化
网络优化主要有几个方面:下降网络请求数量,下降单次请求响应的数据量,在弱网环境下将非必要网络请求延缓至网络环境好的时候.
1.下降网络请求数量:获取一样的数据,屡次网络请求会增长电量消耗,且屡次请求整体上将消耗服务端更多的时间及资源<br>
- 接口Api设计要合理.能够将多个接口合并,屡次请求显示1个界面,改造后1个接口便可提供完整数据.
- 根据具体场景实时性需求,在App中加入网络缓存,在实时性有效区间避免重复请求:主要包括网络框架和图片加载框架的缓存.
2.下降单次请求的数据量
- 网络接口Api在设计时候,去除多余的请求参数及响应数据.
- 网络请求及响应数据的传输开启GZIP压缩,下降传输数据量.
- okHttp对gzip的支持前面已记录
- Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON执行序列化.
- Protocal Buffers网上有使用的方法,相对GSON有点繁琐.若是对网络传输量很敏感,能够考虑使用.其余几种方案的文章很少.
- 网络请求图片,添加图片宽高参数,避免下载过大图片增长流量消耗.
3.弱网环境优化这块没有经验,直接看anly_jun的文章
App优化之网络优化<br>
文章中提到:用户点赞操做, 能够直接给出界面的点同意功的反馈, 使用JobScheduler在网络状况较好的时候打包请求.
11.电量优化
12.JobScheduler,AlarmManager和WakeLock
JobScheduler在网络优化中出现过,WakeLock涉及电量优化,AlarmManager和WakeLock有类似,但侧重点不一样.
- WakeLock:好比一段关键逻辑T已经在执行,执行未完成Android系统就进入休眠,会致使T执行中断.WakeLock目的就在于阻止Android系统进入休眠状态,保证T得以继续执行.
- 休眠过程当中自定义的Timer、Handler、Thread、Service等都会暂停
- AlarmManager:Android系统自带的定时器,能够将处于休眠状态的Android系统唤醒
- 保证Android系统在休眠状态下被及时唤醒,执行 定时/延时/轮询任务
- JobScheduler:JobScheduler目的在于将当下不紧急的任务延迟到后面更合适的某个时间来执行.咱们能够控制这些任务在什么条件下被执行.
- JobScheduler能够节约Android设备当下网络,电量,CPU等资源.在指定资源充裕状况下再执行"不紧要"的任务.
JobScheduler:<br>
Android Jobscheduler使用<br>
Android开发笔记(一百四十三)任务调度JobScheduler<br>
WakeLock:<br>
Android WakeLock详解<br>
Android PowerManager.WakeLock使用小结<br>
Android的PowerManager和PowerManager.WakeLock用法简析<br>
AlarmManager和WakeLock使用:<br>
后台任务 - 保持设备唤醒状态
13.性能检测工具
1.Android Studio 3.2以后,Android Device Monitor已经被移除.Android Device Monitor原先包含的工具由新的方案替代.Android Device Monitor
- DDMS:由Android Profiler代替.能够进行CPU,内存,网络分析.
- TraceView:能够经过Debug类在代码中调用Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()来记录二者之间全部线程及线程中方法的耗时,生成.trace文件.经过abd命令能够将trace文件导出到电脑,经过CPU profiler分析.
- Systrace:能够经过命令行生成html文件,经过Chrome浏览器进行分析.
- 生成html文件已实现.但文件怎么分析暂未掌握,看了网上一些文章说实话仍是没搞懂
- Hierarchy Viewer:由Layout Inspector代替.但当前版本的Layout Inspector不能查看每一个View具体的onMeasure,onLayout,onDraw耗时,功能是不足的.咱们可以使用系统提供的Window.OnFrameMetricsAvailableListener来计算指定View的onLayout及onDraw耗时.
- Network Traffic tool:由Network Profiler代替.
2.其中Android Profiler如何使用,直接看官网便可.Profile your app performance.
3.TraceView
- TraceView能够直接经过CPU profiler中点击Record按钮后,任意时间后点击Stop按钮.便可生成trace文件.并可将.trace文件导出.
- TraceView也能够经过Debug类在代码中精确控制要统计哪一个区间代码/线程的CPU耗时.
<br>这种用法是 anly_jun大神文章里学到的代码执行完毕,会在Android设备中生成JetApp.trace文件.经过Device File Explorer,找到sdcard/Android/data/app包名/files/JetApp.trace<br>
在JetApp.trace上点击右键->Copy Path,将trace文件路径复制下来.<br>
Windows下cmd打开命令行,执行 adb pull 路径,便可trace文件导出到电脑. - trace文件分析很简单.咱们能够看到每一个线程及线程中每一个方法调用消耗的时间.
4.Layout Inspector很简单,在App运行后,点击Tools->Layout Inspector便可.
下面只看Window.OnFrameMetricsAvailableListener怎么用.
从Android 7.0 (API level 24)开始,Android引入Window.OnFrameMetricsAvailableList接口用于提供每一帧绘制各阶段的耗时,数据源与GPU Profile相同.
-
在Activity中使用OnFrameMetricsAvailableListener:
- 经过调用this.getWindow().addOnFrameMetricsAvailableListener(@NonNull OnFrameMetricsAvailableListener listener,Handler handler)来添加监听.
- 经过调用this.getWindow().removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener)取消监听.
-
解析ConstraintLayout的性能优点中引用了Google使用OnFrameMetricsAvailableListener的例子android-constraint-layout-performance.其中Activity是Kotlin写的,尝试将代码转为java,解决掉报错后运行.
- 在Application中使用OnFrameMetricsAvailableListener,则能够统一设置,不须要每一个Activity单独设置,推荐使用开源项目ActivityFrameMetrics
- 在Application的onCreate中设置单帧渲染总时间超过W毫秒和E毫秒,会在Logcat中打印警告和错误的Log信息.
- Application设置完成运行App,出现单帧渲染总耗时超过指定时间,便可看到Logcat中的信息.
5.Systrace:经过命令行生成html文件,经过Chrome浏览器进行分析
- 首先电脑要安装python,这里有几个坑:<br>
- Python要安装2.7x版本,不能安装最新的3.x.
- 好比本身电脑中systrace文件夹路径是:C:\Users\你的用户名\AppData\Local\Android\Sdk\platform-tools\systrace,若是咱们安装的是3.x版本,在这个路径下执行python systrace.py *** 命令会报错,提示你应该安装2.7
- Python安装时候,要记得勾选"Add python.exe to Path".
- 这时候直接执行python systrace.py ***命令仍是会报错:ImportError: No module named win32com
- Python要安装2.7x版本,不能安装最新的3.x.
- 生成html及如何分析