Android性能优化

一、性能优化分类

APP性能优化可以分为如下几方面

APP启动速度优化、内存优化、包体积优化、布局优化和稳定性优化。

二、APP启动速度优化

一:应用启动类型

应用启动类型分为三种:冷启动、热启动、温启动

(1)冷启动

简介:从点击应用图标到开始创建应用UI界面完全显示且用户可操作的全部过程。特点是耗时最多,是APP启动速度的衡量标准。

冷启动流程:Click Event -> IPC -> Process.start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl

点击应用,加载并启动APP,创建APP进程。接下来执行ActivityThread的main方法,在main方法中会执行Loop和Handler的创建,创建完成之后,就会执行到 bindApplication 方法,在这里使用了反射去创建 Application以及调用了 Application相关的生命周期,Application结束之后,便会执行Activity的生命周期,在Activity生命周期结束之后,最后,就会执行到 View的绘制。

进程的创建是系统行为,我们没办法优化,我们可以着手优化的点在Application创建,Avtivity创建,View绘制。

(2)热启动

应用从后台切换到前台。

(3)温启动

简介:温启动时由于app的进程仍然存在,只执行冷启动第二阶段流程

温启动常见场景:

1、用户双击返回键退出应用

2、app由于内存不足被回收

二:APP启动耗时检测

通过耗时检测,可以验证我们的优化方案是否有效和优化方案的效果。可以检测到具体的耗时任务,针对耗时任务进行优化。

(1)查看Logcat

在Android Studio Logcat中过滤关键字“Displayed”,可以看到对应的冷启动耗时日志。

(2)函数插桩

插桩:在目标程序代码中某些位置插入或修改成一些代码,从而在目标程序运行过程中获取某些程序状态并加以分析。简单来说就是在代码中插入代码。 那么函数插桩,便是在函数中插入或修改代码。

实现原理:编辑一个统计耗时的工具类。创建map:key为方法名,value为耗时统计对象(对象存储开始时间和结束时间等其他属性)。记录某个方法的结束时间-开始时间,把方法的耗时记录到本地。然后上传到服务器。在上传数据到服务器时建议根据用户ID的尾号来抽样上报。

特点:精确,可带到线上,但是代码有侵入性,修改成本高。

(3)✨AOP(Aspect Oriented Programming) 打点

面向切面编程,通过预编译和运行期动态代理实现程序功能统一维护的一种技术。

1、作用

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时大大提高了开发效率。

(4)✨启动速度分析工具*

TraceView和Systrace

三:应用启动优化方案

(1)启动白屏,设置启动背景图

对于启动应用白屏:我们可以设置启动背景图。

设置Activity的theme属性windowBackground,预先设置一个启动图片(layer-list实现)。避免了启动白屏和点击启动图标不响应的情况。

<style name="Splash" parent="AppCompat.FullScreen">
    <item name="android:windowBackground">
    @drawable/bg_splash</item>
</style>
<!--AppCompat FullScreen-->
<style name="AppCompat.FullScreen" parent="AppTheme">
    <item name="windowNoTitle">true</item>
    <item name="windowActionBar">false</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowIsTranslucent">false</item>
</style>

(2)异步初始化

对于可以异步初始化的任务:我们可以使用异步启动器在Application的onCreate方法中执行异步加载。子线程分担主线程任务,并行减少时间。

实现方案:异步启动器

1、任务Task化,启动逻辑抽象成Task(Task即对应一个个的初始化任务)。

2、根据所有任务依赖关系排序生成一个有向无环图:例如推送SDK初始化任务需要依赖于获取设备id的初始化任务,各个任务之间都可能存在依赖关系,所以将它们的依赖关系排序生成一个有向无环图能将并行效率最大化。

3、多线程按照排序后的优先级依次执行:例如必须先初始化获取设备id的初始化任务,才能去进行推送SDK的初始化任务。

原理:初始化多个list,主要存放所有task、和先执行的task。初始化map,存放依赖关系的task。然后对list进行排序,形成一个有向无环图。然后遍历,开启runnable,来执行task任务。

源码:https://github.com/zeshaoaaa/LaunchStarter

(3)延迟初始化

对于不能异步执行的,但不是必须在onCreate完成前执行的,我们可以利用延迟启动器进行加载。我们创建延迟任务队列,存放延迟的task。利用IdleHandler的特性,当CPU空闲时,mIdleHandler便会回调自身的queueIdle方法。在queueIdle回调的时候,从队列取出任务来延迟执行。https://shimo.im/docs/JQv9QhYYGtKXJqc3/read

(4)根据场景耗时分析

通过耗时分析,对代码进行针对性优化,同样也可以来验证我们优化的成果。通过过滤关键字“Displayed”,可以看到对应的冷启动耗时日志;通过函数插桩和AOP打点,统计耗时时间和上传耗时时间到服务器,分析线上启动耗时情况。通过分析工具TraceView,来查找单次执行最耗时的方法和执行次数最多的方法;根据APP版本迭代过程中,进行启动速度对比。如果有明显变慢,对版本差异对比,查找问题。

(5)数据缓存

根据业务进行数据缓存,把数据存到本地,启动的时候不需要加载网络。

三、内存优化

一、内存优化主要工作

(1)防止内存泄漏,内存泄漏会导致内存不足、GC频繁、OOM。

(2)防止内存抖动,内存抖动会导致页面卡顿,甚至造成OOM。

二、内存泄漏

https://blog.csdn.net/weixin_37292229/article/details/71405980

三、内存抖动

当频繁创建对象,导致内存频繁分配和回收导致内存不稳定,就会出现内存抖动,通常会导致页面卡顿,甚至造成 OOM。可以通过Android Studio 的 Profiler 工具查看内存抖动。

(1)内存抖动场景模拟

我们模拟一个内存抖动的场景:点击按钮使用 handler 发送一个空消息,handler 的 handleMessage 接收到消息后创建bitmap。

class MemoryDemoActivity : AppCompatActivity() {
    val MSG_MEMORY_JITTER = 0x001
    private val mHandler: Handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                MSG_MEMORY_JITTER -> {
                    while (true) {
                        Bitmap.createBitmap(1920, 1080, Bitmap.Config.ARGB_8888)
                    }
                    this.sendEmptyMessageDelayed(MSG_MEMORY_JITTER, 20)
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory_demo)
        btn_go.setOnClickListener {
            mHandler.sendEmptyMessage(MSG_MEMORY_JITTER)
        }
    }
}

点击AS的Profiler按钮,正常情况下内存是平稳的。如图
图片

点击按钮,开始发送message。

图片

从图片中可以看到,已经发生了内存抖动。

接下来,我们按住shift键,选择内存变化锯齿状的区域,然后在Memory Profiler可显示下面的图示。

图片

在上图中,我们点击Allocations进行对象分配数量排序,之所以点击Allocations是因为一般在循环,频繁调用的地方可能发生内存抖动。NativeAllocationRegistry是内存分配相关的对象,之后我们在点击图示红框内容,可以看到具体造成内存抖动的代码位置。点击可以定位到具体代码位置。

四、防止内存抖动方案

1)如果频繁的创建对象,进行资源复用。使用缓存池,复用频繁创建和释放的对象。结束使用后,要手动释放缓存池的对象。

2)减少不合理的对象创建,比如在自定义view的时候,不要在onDraw方法中进行对象的创建。在循环的时候,尽量避免去创建对象。

3)使用合理的数据结构

使用 SparseArray类族、ArrayMap 来替代 HashMap。

1、如果key的类型为int、long、boolean类型,可以使用SparseArray。SparseArray 是 Android 中一种特有的数据结构。HashMap底层是数组+链表+红黑树,占据内存大。SparseArray是两个数组实现的。一个存放key,一个存放value。

2、如果key类型为其它的类型,则使用ArrayMap。ArrayMap 的一个数组存 hashCode 值,一个数组存 key-value 键值对。

4)在App可用内存过低时主动释放内存

在App退到后台内存紧张即将被Kill掉时选择重写 onTrimMemory/onLowMemory 方法去释放掉图片缓存、静态缓存来自保。

四、包体积优化

一、去除无效代码

使用Lint检测无效代码,步骤:点击菜单栏 Analyze -> Run Inspection(检测) by Name -> unused declaration -> Moudule ‘app’ -> OK。可以检测出没有被使用的代码和资源。

注意:lint不会分析assets文件夹下的资源,因为assets文件可以通过文件名直接访问。

二、使用 Proguard 工具混淆

1)、瘦身:它可以检测并移除未使用到的类、方法、字段以及指令、冗余代码,并能够对字节码进行深度优化。最后,它还会将类中的字段、方法、类的名称改成简短无意义的名字。

2)、安全:增加代码被反编译的难度,一定程度上保证代码的安全。

	     // 1、是否进行混淆
        minifyEnabled true
        // 2、开启zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗
        zipAlignEnabled true
        // 3、移除无用的resource文件
        shrinkResources true

三、统一封装第三方库代码

我们将项目中使用到的一些 第三方库进行了统一,比如说图片库、网络库、数据库等,不允许项目中出现功能相同,但是却实现不一样的库。同时也做了 规范,之后引入的三方库,需要去考量它的大小、方法数等,而且呢,如果只是需要一个很大库的一个小功能,那我们就修改源码,只引入部分代码即可。

四、资源的瘦身

我们可以在tinypng这个网站进行图片压缩,使用AndroidResGuard 插件来对资源进行混淆。

使用webp,Android 4.0(API 级别 14)以上支持有损 WebP 图片,Android 4.3以上支持无损且透明的 WebP 图片。

图片

五、So 的瘦身

只保留了 armeabi 这个目录,它可以 兼容别的 CPU 架构,这点的优化效果非常的明显。移除了对别的架构适配 So 之后,我们还做了另外一个处理,对于项目当中使用到的视频模块的 So,它对性能要求非常高,所以我们采用了另外一种方式,我们将所有这个模块下的 So 都放到了 armeabi 这个目录下,然后在代码中做判断,如果是别的 CPU 架构,那我们就加载对应 CPU 架构的 So 文件即可。这样即减少了包体积,同时又达到了性能最佳。最后,通过实践可以看出 So瘦身的效果一般是最好的

defaultConfig {
    ndk {
        abiFilters "armeabi"
    }
}

六、发版之前与上个版本包体积对比,超过阈值则必须优化。AS提供的Analyze APK分析apk,可以对文件进行大小排序。

七、推进插件化架构改进。

八、可以使用H5。

五、布局优化:

一:布局卡顿的原因

布局加载,首先要将xml文件通过IO方式加载到内存,这是耗时的过程。之后解析xml,获取xml里面的控件的name和属性值,然后通过反射创建view,再添加到根布局上。如果布局的层级如果比较深,那么进行布局遍历的过程就会比较耗时。

二、布局优化方案总结

(1)使用低端机进行优化,以发现性能瓶颈。

(2)合理使用ConstraintLayout,ConstraintLayout可以减少界面布局的嵌套层级。

(3)尽可能少用wrap_content,wrap_content会增加布局measure时的计算成本,已知宽高为固定值时,不用wrap_content。

(4)避免过度绘制,移除XML中非必需的背景,手机开发者选项中的(调试GPU过度绘制)Show GPU Overdraw,无、蓝、绿、淡红、深红,分别对应0-4次过度绘制。

(5)合理使用动画。帧动画消耗资源最多补间动画只是改变View的显示效果,不会真正改变View的属性,也导致View重绘非常频繁。优先使用属性动画。

(6)通过 include 标签来实现布局复用。

(7)可以使用代码的方式添加view,不使用xml的方式。使用X2C框架。它的一个核心原理就是在开发过程我们还是使用的XML进行编写布局,但是在编译的时候它会使用APT的方式将XML布局转换为Java的方式进行布局,通过这样的方式去写布局,它有以下优点:1、它省去了使用IO的方式去加载XML布局的耗时过程。2、它是采用Java代码直接new的方式去创建控件对象,所以它也没有反射带来的性能损耗。这样就从根本上解决了布局加载过程中带来的问题。

三、卡顿性能分析工具

(1)Layout Inspector

Layout Inspector是AndroidStudio自带的工具,它的主要作用就是用来查看视图层级结构的,可以帮助我们对层级进行优化。

(2)Systrace

Systrace可以很方便地看到每帧的具体耗时以及这一帧在布局当中它真正做了什么。

(3)AOP的方式

AOP的方式,检测每一个布局的耗时。它没有侵入性,同时也不需要别的开发同学进行接入,就可以方便地获取每一个布局加载的耗时。

(4) LayoutInflaterCompat

LayoutInflaterCompat.setFactory2这个方法去进行Hook。检测每一个控件的加载耗时

六、稳定性优化

一、Crash治理三点原则

1.由点到面。一个crash发生了,我们不能针对这一个crash去解决问题。而需要考虑这一类的crash怎么解决和预防。只有这样,才能使这一类crash真正被解决。

2.异常不能被随便try-catch。try-catch只会隐蔽真正的问题,要根据业务场景去处理,保证后续的流程正常。

3.提前预防crash,不能crash产生再去处理。

二、稳定性方案

(1)灰度发布,及时发现异常,及时止损。

(2)使用kotlin开发,避免大量的空指针NullPointerException。

(3)避免内存泄漏和大内存对象加载,容易造成oom。

(4)页面跳转统一由scheme协议控制。

优点

1、可以配置功能开关,针对功能新增,代码改动,代码重构,可以服务端下发跳转协议,在异常发生的情况下,跳转到稳定页面。

2、通过scheme路由实现页面,在工程架构上保证所有业务都是解耦的,模块间不需要相互依赖就可以实现页面的跳转和基本类型参数的传递;

3、统一协议控制,可以在最外层进行异常捕获。

(5)Lint检查

Lint是Google提供的Android静态代码检查工具,可以扫描并发现代码中潜在的问题,提醒开发人员及早修正,提高代码质量。开发自定义Lint,强制使用封装好的工具类。

(6)设置兜底策略

也是通过控制协议,进行控制APP。正常,我们都是在首页提示用户升级APP。如果首页发生崩溃,我们需要在开屏页,进行APP升级。

(7)自我修复

App里也可以实现备用的降级方案,然后设置特定条件的触发策略,从而达到自动修复Crash的目的。比如:

• 部分使用JNI实现的模块,在SO加载失败或者运行时发生异常则可以降级为Java版实现。

RenderScript实现的图片模糊效果,也可以在失败后降级为普通的Java版高斯模糊算法。

  • 在使用Retrofit网络库时发现OkHttp3或者HttpURLConnection网络通道失败率高,可以主动切换到另一种通道。

(8)热修复

三、设置APP开发规范

1、开发阶段

  • 统一编码规范、增强编码功底、技术评审、CodeReview机制
  • 架构优化
  • 能力收敛
  • 统一容错:如在网络库utils中统一对返回信息进行预校验,如不合法就直接不走接下来的流程。

2、测试阶段

  • 功能测试、自动化测试、回归测试、覆盖安装
  • 特殊场景、机型等边界测试:如服务端返回异常数据、服务端宕机
  • 云测平台:提供更全面的机型进行测试

3、合码阶段

  • 编译检测、静态扫描
  • 预编译流程、主流程自动回归

4、发布阶段

  • 多轮灰度
  • 分场景、纬度全面覆盖

5、运维阶段

  • 灵敏监控

  • 回滚、降级策略

  • 热修复、本地容灾方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值