Android-性能优化

启动优化

冷启动时间的统计:通过 注解+AOP面向切面编程的方式。先定义有value和type两个参数的注解文件,在Application的attachBaseContext方法,在第一个 Activity 获取焦点,即onWindowFocusChanged方法加注解,AOP选取的切入点就是加了我们自定义注解的方法,AOP的Advice用的around,在记录和计算冷启动时间之后,会调用joinPoint.proceed()让流程继续。
优化的措施:1、启动优化中,启动任务的删减和重排最复杂,业务线过于多,甚至是杂乱,把启动任务分为四类:刚需任务(不可延迟,必须第一时间最高优先级执行完成,比如网络库、存储库等基础库的初始化。如果不在启动阶段初始化完成,根本无法进入到后续流程。)非刚需高优任务 异步化(这类任务的特征就是高优,但是并非刚需,并不是说不初始化完成后续首页就没法进没法用,比如拉取ab实验配置、ip直连、push、长链接相关非刚需基础建设项,这类可以高优在启动阶段执行,但是没必要放在 UI 线程 block 执行,就可以放到启动阶段的后台工作线程中去跑。 )非刚需低优任务 异步化+延迟化(这类任务常见的特征就是对业务能否运作无决定性影响或者业务本身流程靠后,完全可以放在我们认为的启动阶段结束之后再后台执行,比如 x5内核初始化、在线客服sdk预初始化 之类的。)可删除任务 删除。设计了一套启动过程的任务排布框架TaskManager,定义模块声明周期的ModuleLifecycle,里面把任务分成三类,必须 在主线程里直接运行;在子线程里运行;在主线程空闲时运行,idleHandler;区分业务线,不同的业务线都定义一个Lifecycle继承ModuleLifecycle,实现解耦的同时为统计不同业务线的初始化耗时提供了方便。
上面是Application层面的。
Activity:
异步预加载布局AsyncLayoutInflater,在Android中,其实有封装较好的 AsyncLayoutInflater 用于进行布局的异步加载,我们在App的启动阶段启动异步加载View的任务,同时调高工作线程优先级以尽量在使用View之前就 inflate 结束,这样在首页上要使用该布局时,就可以直接从内存中读取。
减少布局层级、优化过度绘制;
懒加载布局(广告View,完全可以在后端广告接口返回可用的广告数据结构且经过了素材校验等流程确定要开始渲染广告时,进行布局的加载。)
启动阶段 IO 优化,不仅仅 UI 线程执行太多耗时任务会影响启动速度,工作线程执行太多的耗时任务也会影响到启动速度,特别是重IO的任务,我们将 App 首页渲染完成前的网络请求数量,控制在 10 个左右,大大的减少了启动阶段的网络请求量。

https://juejin.cn/post/7306692609497546752
https://juejin.cn/post/7299742103688953897

内存优化:

内存抖动:锯齿状、GC频繁导致卡顿
内存泄露:可用内存逐渐减少、频繁GC
内存溢出:OOM、程序异常

内存抖动比较常见的地方:
自定义View的 onMeasure()、onLayout()、onDraw() 直接 new 创建对象
列表比如 RecyclerView 的 onBindViewHolder() 直接 new 创建对象
有循环的代码中创建对象

Android的内存优化在我看来分为两点:避免内存泄漏、扩大内存,其实就是开源节流。其实内存泄漏的本质就是较长生命周期的对象引用了较短生命周期的对象。常见的内存泄漏单例模式导致的内存泄漏。 最常见的例子就是创建这个单例对象需要传入一个 Context,这时候传入了一个 Activity 类型的 Context,由于单例对象的静态属性,导致它的生命周期是从单例类加载到应用程序结束为止,所以即使已经 finish 掉了传入的 Activity,由于我们的单例对象依然持有 Activity 的引用,所以导致了内存泄漏。解决办法也很简单,不要使用 Activity 类型的 Context,使用 Application 类型的 Context 可以避免内存泄漏。静态变量导致的内存泄漏。 静态变量是放在方法区中的,它的生命周期是从类加载到程序结束,可以看到静态变量生命周期是非常久的。最常见的因静态变量导致内存泄漏的例子是我们在 Activity 中创建了一个静态变量,而这个静态变量的创建需要传入 Activity 的引用 this。在这种情况下即使 Activity 调用了 finish 也会导致内存泄漏。原因就是因为这个静态变量的生命周期几乎和整个应用程序的生命周期一致,它一直持有 Activity 的引用,从而导致了内存泄漏。**非静态内部类导致的内存泄漏。**非静态内部类导致内存泄漏的原因是非静态内部类持有外部类的引用,最常见的例子就是在 Activity 中使用 Handler 和 Thread 了。使用非静态内部类创建的 Handler 和 Thread 在执行延时操作的时候会一直持有当前Activity的引用,如果在执行延时操作的时候就结束 Activity,这样就会导致内存泄漏。解决办法有两种:第一种是使用静态内部类,在静态内部类中使用弱引用调用Activity。第二种方法是在 Activity 的 onDestroy 中调用 handler.removeCallbacksAndMessages 来取消延时事件。使用资源未及时关闭导致的内存泄漏。常见的例子有:操作各种数据流未及时关闭,操作 Bitmap 未及时 recycle 等等。使用第三方库未能及时解绑。有的三方库提供了注册和解绑的功能,最常见的就 EventBus 了,我们都知道使用 EventBus 要在 onCreate 中注册,在 onDestroy 中解绑。如果没有解绑的话,EventBus 其实是一个单例模式,他会一直持有 Activity 的引用,导致内存泄漏。同样常见的还有 RxJava,在使用 Timer 操作符做了一些延时操作后也要注意在 onDestroy 方法中调用 disposable.dispose()来取消操作。属性动画导致的内存泄漏。常见的例子就是在属性动画执行的过程中退出了 Activity,这时 View 对象依然持有 Activity 的引用从而导致了内存泄漏。解决办法就是在 onDestroy 中调用动画的 cancel 方法取消属性动画。WebView 导致的内存泄漏。WebView 比较特殊,即使是调用了它的 destroy 方法,依然会导致内存泄漏。其实避免WebView导致内存泄漏的最好方法就是让WebView所在的Activity处于另一个进程中,当这个 Activity 结束时杀死当前 WebView 所处的进程即可,我记得阿里钉钉的 WebView 就是另外开启的一个进程,应该也是采用这种方法避免内存泄漏。扩大内存为什么要扩大我们的内存呢?有时候我们实际开发中不可避免的要使用很多第三方商业的 SDK,这些 SDK 其实有好有坏,大厂的 SDK 可能内存泄漏会少一些,但一些小厂的 SDK 质量也就不太靠谱一些。那应对这种我们无法改变的情况,最好的办法就是扩大内存。扩大内存通常有两种方法:一个是在清单文件中的 Application 下添加largeHeap="true"这个属性,另一个就是同一个应用开启多个进程来扩大一个应用的总内存空间。第二种方法其实就很常见了,比方说我使用过个推的 SDK,个推的 Service 其实就是处在另外一个单独的进程中。Android 中的内存优化总的来说就是开源和节流,开源就是扩大内存,节流就是避免内存泄漏。

onTrimMemory、onLowMemory(系统给的低内存的回调,可以根据不同的回调等级去处理一些逻辑)

大图监控:自如客当前崩溃占比较高的为OOM崩溃,且该类型崩溃,60%以上是因为图片过大造成的,为了减少OOM造成的崩溃,对图片建立实时监控,保证后台不会配置过大的图片;
通过AspectJ面向切面编程,选取okhttp3.OkHttpClient build()作为Pointcut切入点,增加网络拦截器的逻辑,通过重写intercept方法,在里面chain.proceed拿到request和response,可以分析出网络url,数据大小,是否是图片等信息。
主要分为五步:
移动端通过Bi将图片链接、图片大小、下载结果及图片所在页面上传到天眼;
天眼判断是否为图片,如果是图片则标记该事件为图片请求事件,并存入数据库;
判断当前是否是T+1日00:00,如果是,则运行数据分析任务;
通过页面信息匹配到所属组件,如果匹配不到组件,则标记为主工程;
对于匹配上组件的,从方舟获取组件负责人并通过ehr匹配到其所属业务部门;

https://juejin.cn/post/7123452813656457253

页面卡顿:

目前自如客根据fps数据来采集页面及帧率,并获取一下最近方法的调用栈,利用Choreographer的postcallback方法接口轮询方式,能够对帧率进行统计。choreographer.postCallback()内部是挂载了一个CALLBACK_ANIMATION类型的callback。轮训方式往choreographer内添加callback,相邻两个callback执行时间间隔即能粗略统计单帧的耗时。严谨的讲这不是单帧的耗时而是两个【半帧】拼凑的耗时。(缺点:1、无活动时,也会监控,无效信息会把帧率较低时给平均掉。2、对应用带来不必要的负担。)如果现在让我做的话,我通过官方推荐的Window.OnFrameMetricsAvailableListener可以监听每帧的执行状态。包含总耗时,绘制耗时,布局耗时,动画耗时,测量耗时。依次我们可以计算出帧率。
分析工具:Systrace 是谷歌提供的最常用的Android 系统级性能分析工具。它以视觉化的方式将系统内所有的“工作线程”(内核+框架+应用)与系统模块状态(如 CPU 调度、IO 活动、Binder 调用等信息)按时间轴铺开展示。Systrace 可以收集Android关键子系统(如CPU调度、SurfaceFlinger渲染系统、System Server系统框架、Input系统等)的运行信息,并以图像的形式按照时间轴铺开展示出来,帮助开发者直观的看到整个系统的运行状态,从而分析出系统的性能问题瓶颈所在。

https://www.jianshu.com/p/cf531a3af828
https://www.jianshu.com/p/386bbb5fa29a

崩溃

Java 层崩溃的捕获:在BI组件自定义一个Handler,继承Thread.UncaughtExceptionHandler,通过Thread.setDefaultUncaughtExceptionHandler 来进行全局异常的捕获。重写uncaughtException方法,该方法的两个参数一个是线程,一个是异常数据Throwable,把异常数据的StackTrace整理上传给服务器。如果系统默认异常处理器处理,就调用uncaughtException,否则Process.killProcess(Process.myPid()) & System.exit(10) 就杀死进程。 BI组件初始化的时候设置了appId、appKey、versionName、uid等。

AOP
AspectJ是一种用来支持AOP的语言,几乎和java一样的,需要ajc来编译,
AOP 是在编译完成后生成 dex 文件之前的时候,直接通过修改 .class 文件的方式,来直接添加或者修改代码逻辑的。around是替代了原JPoint,如果要执行原JPoint的话,需要调用proceed。

反射、ASM:
反射是读取持久堆上存储的类信息。而 ASM 是直接处理 .class 字节码的小工具(工具虽小,但是功能非常强大!)
反射只能读取类信息,而 ASM 除了读还能写。
反射读取类信息时需要进行类加载处理,而 ASM 则不需要将类加载到内存中。
反射相对于 ASM 来说使用方便,想直接操纵 ASM 的话需要有 JVM 指令基础。

https://blog.csdn.net/innost/article/details/49387395
https://www.jianshu.com/p/42fc513a912b
注解:
https://www.jianshu.com/p/9ca78aa4ab4d

你可能感兴趣的:(android,性能优化)