Android 性能优化

参考文章:
https://juejin.im/post/6844904105438134286

https://blog.csdn.net/qq_16592085/article/details/106950590

https://www.cnblogs.com/guanxinjing/p/11522959.html

一、启动优化

1、程序在冷启动的时候,会有 1s 左右的白屏闪现,低版本是黑屏的现象,在这期间我通过翻阅系统主题源码,发现了系统 AppTheme 设置了一个 windowBackground ,由此推断就是这个属性捣的鬼,开始我是通过设置 windowIsTranslucent 透明属性,发现虽然没有了白屏,但是中间还是有一小段不可见,这个用户体验还是不好的。最后我观察了市面上大部分的 Android 软件在冷启动的时候都会有一个 Splash 的广告页,同时在增加一个倒数的计时器,最后才进入到登录页面或者主页面。我最后也是这样做的,原因是这样做的好处可以让用户先基于广告对本 APP 有一个基本认识,而且在倒数的时候也预留给咱们一些对插件和一些必须或者耗时的初始化做一些准备。

2、通过翻阅 Application 启动的源码,当我们点击桌面图标进入我们软件应用的时候,会由 AMS 通过 Socket 给 Zygote 发送一个 fork 子进程的消息,当 Zygote fork 子进程完成之后会通过反射启动 ActivityThread##main 函数,最后又由 AMS 通过 aidl 告诉 ActivityThread##H 来反射启动创建Application 实例,并且依次执行 attachBaseContext 、onCreate 生命周期,由此可见我们不能在这 2 个生命周期里做主线程耗时操作。

3、知道了 attachBaseContext 、onCreate 在应用中最先启动,那么我们就可以通过 TreceView 等性能检测工具,来检测具体函数耗时时间,然后来对其做具体的优化。

项目不及时需要的代码通过异步加载。将对一些使用率不高的初始化,做懒加载。将对一些耗时任务通过开启一个 IntentService来处理。还通过 redex 重排列 class 文件,将启动阶段需要用到的文件在 APK 文件中排布在一起,尽可能的利用 Linux 文件系统的 pagecache 机制,用最少的磁盘 IO 次数,读取尽可能多的启动阶段需要的文件,减少 IO 开销,从而达到提升启动性能的目的。通过抖音发布的文章知晓在 5.0 低版本可以做 MultiDex 优化,在第一次启动的时候,直接加载没有经过 OPT 优化的原始 DEX,先使得 APP 能够正常启动。然后在后台启动一个单独进程,慢慢地做完 DEX 的 OPT 工作,尽可能避免影响到前台 APP 的正常使用。

4、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 工具来检测这三个声明周期耗时时间,从而进一步优化,达到极限。

二、内存优化

1、减少 OOM ,提高程序的稳定性。

在应用开发阶段我比较喜欢用 LeakCanary 这款性能检测工具,好处是它能实时的告诉我具体哪个类发现了内存泄漏(如果你对 LeakCanary 的原理了解的话,可以说一说它是怎么检测的)。
还有我们要明白为什么应用程序会发送 OOM ,又该怎么去避免它?
发生 OOM 的场景是当申请 1M 的内存空间时,如果你要往该内存空间存入 2M 的数据,那么此时就会发生 OOM。
在应用程序中我们不仅要避免直接导致 OOM 的场景还要避免间接导致 OOM 的场景。间接的话也就是要避免内存泄漏的场景。
内存泄漏的场景是这个对象不再使用时,应用完整的执行最后的生命周期,但是由于某些原因,对象虽然已经不再使用,仍然会在内存中存在而导致 GC 不会去回收它,这就意味着发生了内存泄漏。(这里可以介绍下 GC 回收机制,回收算法,知识点尽量往外扩展而不脱离本题)
最后在说一下在实际开发中避免内存泄漏的场景:

1、资源型对象未关闭: Cursor,File
2、注册对象未销毁: 广播,回调监听
3、类的静态变量持有大数据对象
4、非静态内部类的静态实例
5、Handler 临时性内存泄漏: 使用静态 + 弱引用,退出即销毁
6、容器中的对象没清理造成的内存泄漏
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 等开源框架去说下它们是怎么设计的)

1、选择合适的位图格式
2、bitmap 内存复用,压缩
3、图片的多级缓存
6、基本数据类型如果不用修改的建议全部写成 static final,因为 它不需要进行初始化工作,直接打包到 dex 就可以直接使用,并不会在 类 中进行申请内存
7、字符串拼接别用 +=,使用 StringBuffer 或 StringBuilder
8、不要在 onMeause, onLayout, onDraw 中去刷新 UI
9、尽量使用 C++ 代码转换 YUV 格式,别用 Java 代码转换 RGB 等格式,真的很占用内存

4、减少程序异常,降低应用 Crash 率, 提高稳定性。

减少程序异常那么我们可以从稳定性和 Crash 来分别说明。

3、布局优化

1.1 布局优化分析工具:
在这里插入图片描述

1.2 优化方案:
在这里插入图片描述

4、你在项目中有没有遇见卡顿问题?是怎么排查卡顿?又是怎么优化的?

比如在主线程中做耗时操作、频繁的创建对象和销毁对象导致 GC 回收频繁、布局的层级多等。

1、显示原理:
绘制原理:
在这里插入图片描述
刷新原理:
View 的 requestLayout 和 ViewRootImpl##setView 最终都会调用 ViewRootImpl 的 requestLayout 方法,然后通过 scheduleTraversals 方法向 Choreographer 提交一个绘制任务,然后再通过 DisplayEventReceiver 向底层请求 vsync 垂直同步信号,当 vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往消息队列 post 一个异步任务,最终是 ViewRootImpl 去执行绘制任务,最后调用 performTraversals 方法,完成绘制。
详细流程可以参考下面流程图:

在这里插入图片描述
2、卡顿的根本原因:
从刷新原理来看卡顿的根本原理是有两个地方会造成掉帧:
一个是主线程有其它耗时操作,导致doFrame 没有机会在 vsync 信号发出之后 16 毫秒内调用;
还有一个就是当前 doFrame 方法耗时,绘制太久,下一个 vsync 信号来的时候这一帧还没画完,造成掉帧。
既然我们知道了卡顿的根本原因,那么我们就可以监控卡顿,从而可以对卡顿优化做到极致。我们可以从下面四个方面来监控应用程序卡顿:

1、基于 Looper 的 Printer 分发消息的时间差值来判断是否卡顿。
2、基于 Choreographer 回调函数 postFrameCallback 来监控。
3、基于开源框架 BlockCanary 来监控
4、基于开源框架 rabbit-client 来监控

5、动画优化

1、尽量别用补间动画,改为属性动画,因为通过性能监控发现补间动画重绘非常频繁
2、使用硬件加速提高渲染速度,实现平滑的动画效果。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值