1 . APP启动相关优化
1. 黑白屏
首先谈到黑白屏对于有些人可能会一脸懵逼,这是啥! 咋出来的.黑白屏是在我们的APP启动时 会首先看到一个 黑色的页面或者白色的页面.故称之为黑白屏. 白屏出现的原因是我们在设置项目主题的时候,让我们主题继承了 Light类型的主题 例如:<style name="AppTheme" parent="Theme.AppCompat.Light">
这样设置项目主题 ,我们的APP 在启动时会显示白色页面,如果不写parent 会在老版本上显示黑屏,现在的版本都是透明显示了.
解决方式:
1.在自己的<style name="AppTheme" parent="Theme.AppCompat.Light">中加入windowsbackground
2.设置windowbackground为透明的 <item name="android:windowIsTranslucent">true</item>
上面两种方式会有个问题,所有的activity启动都会显示
3.单独做成一个主题
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/bg</item>
</style>
<style name="AppTheme.Launcher1">
<item name="android:windowBackground">@drawable/bg</item>
</style>
<style name="AppTheme.Launcher2">
<item name="android:windowBackground">@drawable/bg</item>
</style>
再在功能清单中的单独activity下设置
<activity
android:theme="@style/AppTheme.Launcher"
然后在程序中使用setTheme(R.style.AppTheme);
让APP中所有的activity还是使用以前的样式,这样做就只有启动时才使用自己的样式
4.下面这种方式在QQ中被用到 ,禁止加载Priview window并且设置启动背景是透明,但是要注意 如果启动耗时会感觉无响应一段时间再进入APP,使用之前保证进入应用的耗时最短
<item name="android:windowDisablePreview">true</item>
<item name="android:windowBackground">@null</item>
2. 启动时长优化
APP启动时长也就是从点击app图标到肉眼可看到页面这一过程,而且APP的绘制是单线程工作的,如果在这一部分中操作过于耗时,那就会出现点击时候长时间无响应. 所以我们要降低这段时间内的耗时操作. 我们可操作性的启动周期一般是在Application的Oncreate() 方法到Activity的onResume 之间.
如果想要统计代码的运行时长可以使用如下API
Debug.startMethodTracing(filePath);
中间为需要统计执行时间的代码
Debug.stopMethodTracing();
使用上面的api 可以得到中间代码块运行的时间,主要分析Application 和Activity的的OnCreate方法,分析出耗时的操作,合理去安排.调用时机 达到缩短启动时长
解决方式:
1.分析业务代码,开线程去加载实时性要求不高没有UI操作 对异步要求不高的逻辑,
2,懒加载 用到的时候栽加载,网络 数据库 配置文件.....
2. UI绘制优化
UI绘制过程中如果任务繁重,也会导致频繁GC 占用主线程资源引起页面卡顿,所以需要先了解Android的绘制原理,分析在绘制过程中可能出现的卡段的点进行分析,这里不再赘述绘制过程 我的上一篇文章 中会有介绍绘制流程
源码分析setContentView()到底做了什么,布局绘制是在Activity的哪个周期),所以要分析这块减少GPU工作量(绘制),减少CPU工作量(计算)
1.过渡绘制优化
减少GPU工作量,可以开启开发者模式的Profile GPU rendering/调试GPU过度绘制 查看页面的绘制层级,绘制层级分为 蓝色 绿色 粉色 红色依次增加
解决方式:
1.减少背景重复挥着
(1)去掉单个activity的主题设置的属性可以在setContentView之前getWindow().setBackgroundDrawable(null);
(2)去掉所有activity主题设置中的属性
直接在styles.xml中设置<item name="android:windowBackground">@null</item>
(3)xml布局中嵌套控件的时候 背景设置要合理 优先最外层设置
设置主题背景要自行根据业务需求合理设置,
2.自定义控件使用剪裁减少控件之间重合部分
3.不要在onDraw方法中去做耗时操作,将一些全局的对象提前创建好,尤其在绘制大量图片是 ,提前在init中将bitmap 准备好.
4.合理使用api
invalidate():不测量 不布局 只绘制
requestLayout(): 重新测量布局 不去绘制
2.布局优化
减少CPU工作量,可以使用Device Monitor窗口中Hierarchy view去查看绘制三大方法的性能,去着重分析局部代码
三个点也是代表着View的Measure, Layout和Draw。
绿: 表示该View的此项性能比该View Tree中超过50%的View都要快;例如,代表Measure的是绿点,意味着这个视图的测量时间快于树中的视图对象的50%。
黄: 表示该View的此项性能比该View Tree中超过50%的View都要慢;
红: 表示该View的此项性能是View Tree中最慢的;。
解决方式:
1. xml层级嵌套一定要低,布局扁平化 ,LinearLayout 渲染会测量一次如果设置权重会测量两次, 但是RelativeLayout 会测量两次,但是RelativeLayout可以降低控件的层级,选择的时候要合理分析使用. 另外谷歌新出的协调布局ConstrainLayout也可以降低布局层级,复杂布局使用这个控件性能优于传统控件, 一般的布局使用这个控件性能消耗高于传统控件
2. 合理选择排版容器
选用merge标签去合并,减少一层嵌套
不经常显示的控件使用Viewstub标签
经常使用的布局可以使用include 标签
LinearLayout中的分割线可使用divide 属性 RecyclerView 分割线使用Itemdecoration
占位布局使用Space 这个控件只是去测量(很少用)
3.内存优化
内存优化是一个大的点,也是我们最关注的,想要优化内存方面,就需要简单去了解下 ,java的虚拟机 GC回收器等方面的知识.
1.内存分析工具
top/procrank
meinfo
Procstats
DDMS
MAT
Finder-Activity
LeakCanary
LeakInspector
工具很多,掌握原理方法,工具随便找两个能用就行,用过MAT 比较容易定位问题出处
Android Profiler的使用
Run菜单下的profile...
在图型用户界面上选择要分析的一段内存,右键export出来
Allocations: 动态分配对象个数
Deallocation:解除分配的对象个数
Total count :对象的总数
Shallow Size:对象本身占用的内存大小
Retained Size:GC回收能收走的内存大小
Mat工具的使用
转换profile文件格式
sdk/platform-tools/hprof-conv.exe
转换命令 hprof-conv -z src dst
下载: https://www.eclipse.org/mat/downloads.php
打开软件 File菜单下Open Heap Dump... 打开转换好的文件
点击QQL按钮查找activity
select * from instanceof android.app.Activity
2.java虚拟机
Java虚拟机会有线程私有区 ,和共享数据区,本地栈\虚拟机栈\程序计数器都在线程私有区中,程序计数器是一个代码指示器记录代码执行的地址,本地栈就是我们平时说的栈区, 栈溢出的问题会发生在这里.
共享区又分为方法区和java堆区,这些地方容易引发OOM,方法去存放
字面量public satic final java常量,符号引用 类,接口全名,方法名.java堆区存放对象实例,这里也是最容易引发OOM的区域 .平时所做的优化主要集中在这一部分的.
3.GC 垃圾回收
1.引用计数法
对象创建赋值会计数加1 销毁置空会减一,早期使用的是这中方式, 这种方式的缺点是 如果出现对象的相互引用 计数器会出现不为0的情况,导致 对象不能及时回收
2.可达性算法
GC会扫描两次根对象 ,不可达的对象会首先标记,等待第二次回收之后才会对标记对象回收,所以可以在finalize 中去激活对象的引用.
3.引用类型
强引用: Object obj=new Object();
软引用:内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了
弱引用:第一次扫到了,就标记下来,第二次扫到直接回收
虚引用:用于跟踪GC的回收通知,产生一个回收的回调
4.内存抖动,内存溢出
内存抖动是 内存频繁的分配与回收,当分配速度大于回收速度时会产生内存溢出
内存的回收算法: 标记清除算法, 复制算法,标记压缩算法,分代收集算法(年轻代 老年代 持久代 使用的收集算法很多)
5.内存泄漏
无用的对象因引用问题长时间得不到回收,就是内存泄漏, 最终会引起内存溢出OOM
解决方式
1. 合理使用数据类型 ,不要使用比需求占用更多内存的数据类型
2.循环尽量使用foreach 少用iterator ,自动装箱尽量少用
3.数据结构与算法,(数组 链表 栈 树 图...) 少量数据使用sparse数组 ArrayMap 性能不如HashMap 但是省内存
4.少用枚举每一个枚举都是单例对象 可使用其他方式代替
public class SHAPE {
public static final int RECTANGLE=0;
public static final int TRIANGLE=1;
public static final int SQUARE=2;
public static final int CIRCLE=3;
@IntDef(flag=true,value={RECTANGLE,TRIANGLE,SQUARE,CIRCLE})
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Model{
}
private @Model int value=RECTANGLE;
public void setShape(@Model int value){
this.value=value;
}
@Model
public int getShape(){
return this.value;
}
}
5.static final 问题
static会由编译器调用clinit方法进行初始化
static final不需要进行初始化工作,打包在dex文件中可以直接调用,并不会在类初始化申请内存
所以基本数据类型的成员,可以全写成static final
6.字符串连接少用+ 使用stringbuffer(线程安全,速度慢) stringbuilder (线程不安全,速度快)
7.重复申请内存
同一个方法多次调用,如递归函数 ,直接在循环中new对象等
不要在onMeause() onLayout() onDraw() 中去刷新UI(requestLayout)
8.LRU算法 重用回收的对象
9.尽量使用IntentService
10.Activity组件泄漏
Activity的上下文不要随便传递
非静态内部类和匿名内部类持有Activity引用 只要在内部类中能使用到Activity的方法都会默认持有上下文,可以新建一个class 使用软引用来处理
静态修饰的控件,每个控件都会有Activity的上下文
单例模式
handler的postdelay()问题,在onDestory 中记得销毁可能引发内存泄漏的对象
4.图片相关
这里不再分析,现在图片的加载都在用三方工具. 也有使用哈夫曼编码对rgb的元数据进行变频压缩
1.bitmap 获取
public class ImageResize {
/**
* 缩放bitmap
* @param context
* @param id
* @param maxW
* @param maxH
* @return
*/
public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
Resources resources = context.getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
// 只解码出 outxxx参数 比如 宽、高 ,不会加载整个图片
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources,id,options);
//根据宽、高进行缩放
int w = options.outWidth;
int h = options.outHeight;
//设置缩放系数
options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
if (!hasAlpha){
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
options.inJustDecodeBounds = false;
//设置成能复用 图片复用设置下面两个方法
options.inMutable=true;
options.inBitmap=reusable;
return BitmapFactory.decodeResource(resources,id,options);
}
/**
* 计算缩放系数
* @param w
* @param h
* @param maxW
* @param maxH
* @return 缩放的系数
*/
private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) {
int inSampleSize = 1;
if (w > maxW && h > maxH){
inSampleSize = 2;
//循环 使宽、高小于 最大的宽、高
while (w /inSampleSize > maxW && h / inSampleSize > maxH){
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
5.包体积优化
1.将图片转换成webp 使用svg矢量图
webp 技术是从视屏解码演变过来的 ,通过像素宏 预测技术去记录色块的颜色,可以使用Androidstudio的 右键->convert to webp转成 webp图片 用法和普通图片一样 ,转成webp图片可以减少26%左右的图片大小,android 4.0 开始原生支持.但不支持透明度,4.2.1 才支持显示包含透明度的webp图片
[官方介绍:](<https://developers.google.com/speed/webp/docs/precompiled)
项目中的简单图标可以使用svg 矢量图.
2. 去除多语言
由于第三方库,如appcompat-v7的引入,库中包含了大量的国际化资源, 根据情况通过配置删除
android {
defaultConfig {
resConfigs "zh"
}
}
3.NDK架构设置
如果项目中包含第三方SDK或者自己使用了ndk, 如果不进行配置会打包全cpu架构的动态库进入apk。 对于真机,只需要保留一个armeabi(armeabi-v7a)就可以了,基本上armable的so也是兼容armable-v7的,armable-v7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。,x86的so库目前也只有模拟器在使用了
image4.移除无用资源(Lint检查)
image移除时要注意动态获取资源的方式,int indetifier =getResources().getIdentifier("img_bubble_receive", "drawable", ggetResources().getDrawable(indetifier); 动态获取资源id,未直接使用R.xx.xx 则这个id代表的资源会被认为没有使用过(类似不能混淆反射类)
5. Lint 检查
Lint 是Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码结构/质量问题,同时提供一些解决方案,而且这个过程不需要我们手写测试用。 代码迭代版本一多,很容易会遗留一些无用的代码、资源文件,我们可以使用 Lint 进行 清除
6. 开启混淆
Android代码混淆,是一种Android APP保护技术,用于保护APP不被破解和逆 向分析。ProGuard的三大作用
压缩 移除未被使用的类、属性、方法等,并且会在优化动作执行之后再次执行(因为优化后可能会再 次暴露一些未被使用的类和成员。
优化 优化字节码,并删除未使用的结构。
混淆 将类名、属性名、方法名混淆为难以读懂的字母
android {
buildTypes {
release {
minifyEnabled true
}
}
}
7.开启删除无用资源(与Lint不同)
shrinkResources = true
shrinkResources 用来开启压缩无用资源,也就是没有被引用的文件(经过实测是drawable,layout,实际并不是彻底删除,文件名,但是没有内容,等等),但是因为需要知道是否被引用所以需要配合mififyEnable使用,只有当两者都为true的时候才起到真正的删 除无效代码和无引用资源的目的与去除无用资源不同的是,比如某个java类没有用到,被混淆时删除了,而该类引入了layout资 源 。此时会将这个资源也压缩掉
android {
buildTypes {
release {
shrinkResources true
}
}
}
8.使用一套资源
根据分辨率的趋势建议只放720p的资源,其他分辨率的资源 与720 的相差不大. 但是要根据业务需求自行考虑放几套资源
9.使用tinypng有损压缩
android打包本身会对png进行无损压缩,所以使用像tinypng这样的有损压缩是有必要的。
重点是Tinypng使用智能有损压缩技术,以尽量少的失真换来图片大小的锐减,效果非常好,强烈推荐。
Tinypng的官方网站:http://tinypng.com/
10. 使用jpg格式
如果对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。
在启动页,活动页等之类的大图展示区采用jpg将是非常明智的选择。
11. 缩小大图
如果经过上述步骤之后,你的工程里面还有一些大图,考虑是否有必要维持这样的大尺寸,是否能适当的缩小。
事实上,由于设计师出图的原因,我们拿到的很多图片完全可以适当的缩小而对视觉影响是极小的。
12. 覆盖第三库里的大图
有些第三库里引用了一些大图但是实际上并不会被我们用到,就可以考虑用1x1的透明图片覆盖。
你可能会有点不舒服,因为你的drawable下竟然包含了一些莫名其妙的名称的1x1图片…
13. 使用AndResGuard 对资源进行混淆
[图片上传失败...(image-727ecb-1576806224438)]
14. 使用debugApi 编译
一些三方类库在开发环境需要使用 正式环境不需要刻意使用debugApi 只在debug环境下引入
image15.使用tint 着色器 使用shape 绘制背景
16.防止重复功能的类库引入
在开发中同样功能的库不要都引入 ,寻找最符合业务需求的最小的包去使用,如有必要刻意自行筛选出有用的功能模块自行去复制到项目中,精简业务代码,减少冗余代码.
17.其他方式
组件化 插件化 redex 其他三方的打包方式....
网友评论