一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

分享 Android 开发性能优化的技术要点

时间:2015-11-07 编辑:简简单单 来源:一聚教程网

Android性能调优涉及到多方面的工作,因本人技术水平有限,目前只总结了以下部分,希望大家继续补充。

要点

使用异步

    保持 APP 的高度响应,不要在 UI 线程做耗时操作,多使用异步任务
    使用线程时要做好线程控制;使用队列、线程池
    谨慎使用糟糕的 AysncTask 、 Timer
    警惕异步任务引起的内存泄露
    应该异步任务分类,比如 HTTP ,图片下载,文件读写,每一类的异步任务维护一个任务队列,而不是每一个任务都开一个线程( Volley 表示我一个可以搞定这些全部 _(:з」∠)_)
    这些常用的任务应该做好优先级处理(一般 JSON 数据优先于图片等静态数据的请求)
    一般异步任务应该开启一个 SingleAsyncTask ,保证一时只有一个线程在工作
    HTTP 和图片下载尽量使用同一套网络请求
    使用 MVP 模式规范大型 Activity 类的行为,避免异步任务造成的内存泄露

避免内存泄露

    了解虚拟机内存回收机制
    频繁 GC 也会造成卡顿,避免不必要的内存开销
    错误的引用姿♂势造成的内存泄露(啊~要泄了~)
    常见的 Activity 泄露(单例、 Application 、后台线程、无限动画、静态引用)
    Bitmap 泄露( HoneyComb 这个问题之前压力好大)
    尽量使用 IntentService 代替 Service ,前者会自动 StopItself
    排查内存泄露问题的方法(我一直以来都是简单暴力的人肉 dump 检查大法)
    使用 LeakCanary 自动检查 Activity 泄露问题
    对内存负载要保持敏感( Sharp )

视图优化

    布局优化、减少层次, Include Merge
    使用 ViewStub 避免不必要的 LayoutInflate ,使用 GONE 代替重复 LayoutInflate 同一个布局
    避免过度绘制,应该减少不必要的布局背景;布局层次太深会造成过度绘制以及 Measure 、 Layout 等方法时间复杂度的指数增长
    使用过渡动画,比如给图片的呈现加一个轻量的淡入效果会让视觉上变得流畅许多
    避免过度的动画,不要让一个界面同时出现多出动画,比如 List 滚动时 Item 项要停止播放动画或者 GIF
    复杂动画使用 SurfaceView 或 TextureView
    尽量提供多套分辨率的图片,使用矢量图

Adapter 优化

    复用 convertView ,用 ViewHolder 代替频繁 findViewById
    不要重复 setListener ,要使用 v.getId 来复用 Listener ,不然会创建一堆 Listener 导致频繁 GC
    多布局要采用 MutilItemView ,而不是使用一个大布局然后动态控制需要现实的部分
    不要在 getView 方法做做耗时的操作
    快速滚动列表的时候,可以停止加载列表项的图片,停止列表项的动画,不要在这时候改变列表项的布局
    尽量用 RecyclerView (增量 Notify 和 RecycledViewPool 带你飞)

代码优化

    算法优化,减少时间复杂度,参考一些经典的优化算法
    尽量使用 int ,而不是 float 或者 double
    尽量采用基本类型,避免无必要的自动装箱和拆箱,浪费时间和空间
    选用合适的集合类(尽量以空间换时间)、选用 Android 家的 SparseArray,SparseBooleanArray 和 LongSparseArray
    避免创建额外的对象( StringBuilder )
    使用 SO 库完成一些比较独立的功能(高斯模糊)
    预处理(提前操作)一些比较耗时的初始化工作统一放到启动图处理
    懒加载(延迟处理)规避 Activity 的敏感生命周期
    Log 工具类,要在编译时删掉调试代码,而不是在运行时通过判断条件规避
    优先使用静态方法、公有方法还是公有方法?速度区别很大哦
    类内部直接对成员变量进行操作,不要使用 getter/setter 方法,调用方法耗额外的时间
    给内部类访问的外部类成员变量要声明称包内可访问,而不是私有,不然编译的时候还是会自动创建用于访问外部类成员变量的方法
    遍历集合时,使用 i++代替 Iterator ,后者需要额外的对象操作,应在循环体内避免这种情况
    如果一个基本类型或者 String 的值不会改变,尽量用 final static ,编译时会直接用变量的值替换变量,也就不需要在查询变量的值了

其他优化

    数据库优化:使用索引、使用异步线程
    网络优化 …… 一堆优秀的轮子
    避免过度使用依赖注入框架,大量的反射
    不过过度设计 /抽象,多态看起来很有设计感,代价就是额外的代码、空间、时间
    尽量不要开启多进程,进程的开销很大

APK 瘦身

    开启混淆
    使用 zipalign 工具优化 APK
    适当有损图片压缩、使用矢量图
    删除项目中冗余的资源,之前写过一些删除没有 res 资源的脚本
    动态加载模块化,项目拆分啊!

性能问题的排查方法

    GPU 条形图,没事开来看看淘宝
    过度绘制颜色,嗯,不要一篇姨妈红就好
    LeakCanary ,自动检测 Activity 泄露,挺好用的
    TraceView ( Device Monitor ), Systrace ,分析哪些代码占用的 CPU 时间太大,屡试不爽
    Lint ,检查不合理的 res 资源
    layoutopt (还是 optlayout ?),对当前布局提出优化建议,已被 lint 替代,但是还能用
    HierarchyViewer ,查看手机当前界面的布局层次,布局优化时常用(只用于模拟器,真机上用要 ROOT ,不想 ROOT 加得使用 ViewServer )
    StrictMode , UI 操作、网络操作等容易出现性能问题的地方,如果出现异常情况 StrictMode 会报警


Android内存性能优化


刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成。其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统的gc,很容易就造成了内存的浪费。


Java基于垃圾回收的内存机制


Java的内存管理机制会自动回收无用对象所占用的内存,减轻手工管理内存的负担

1、C/C++: 从申请、使用、释放都需要手工管理

2、Java:无用的对象的内存会被自动回收


\

什么样的对象是无用的对象

1、Java通过引用来操作一个具体的对象,引用类似于C 中的指针。一个对象可以持有其他对象的引用。

2、从一组根对象(GC Roots)开始,按对象之前的引用关系遍历所有对象,在遍历过程中标记所有的可达对象。如果一个对象由根对象出发不可达,则将它作为垃圾收集。

GCRoot 都有哪些?

1、 Class:由系统的类加载器加载的类对象

2、 Static Fields

3、 Thread:活着的线程

4、 Stack Local: java方法的局部变量或参数

5、 JNI Local: JNI方法中的局部引用

6、 JNI Global: 全局的JNI引用

7、 Monitor used: 用于同步的监控对象

8、Help by VM: 用于JVM特殊目的由GC保留的对象


\


\


\

Java程序中的内存泄漏

对象的内存在分配之后无法通过程序的执行逻辑释放对该对象的引用,不能被回收该对象所占内存

内存泄漏的危害

1、 引起OutOfMemoryError

2、 内存占用高时JVM虚拟机会频繁触发GC, 影响程序响应速度

3、内存占用大的程序容易被各种清理优化程序中止,用户也更倾向于卸载这些程序

Android应用的开发语言为Java,每个应用最大可使用的堆内存受到Android系统的限制

Android每一个应用的堆内存大小有限

1、 通常的情况为16M-48M

2、 通过ActivityManager的getMemoryClass()来查询可用堆内存限制

3、3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存

Nexus S(4.2.1):normal 192, largeHeap 512

4、如果试图申请的内存大于当前余下的堆内存就会引发OutOfMemoryError()

5、应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。

用MAT工具来检测内存泄漏

在试图窗口中新建一个Memory Analysis会出现一个


\

没有的可以去http://www.eclipse.org/mat/downloads.php安装一下MAT

在Android 的调试环境DDMS下,找到Heap dump


\


\

Dump下当前内存中的镜像文件,*****.hprof


\

能清楚的看到每一个部分暂用的内存大小。

也可以切换试图,group查看不同包不同类的占用细节。


\

Heap dump

? 包含了触发Heap dump生成的时刻Java进程的内存快照,主要内容为各个Java类和对象在堆内存中的分配情况

Memory Analyzer Tool (MAT)


常见内存泄露原因

Context对象泄漏

1、如果一个类持有Context对象的强引用,就需要检查其生存周期是否比Context对象更长。否则就可能发生Context泄漏。

2、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

例如View#setTag(int, Object)的内存泄漏https://code.google.com/p/android/issues/detail?id=18273

3、把Context对象赋给static变量。

避免Context对象泄漏Checklist

1、检查所有持有对Context对象强引用的对象的生命周期是否超出其所持有的Context对象的生命周期。

2、检查有没有把View传出到View所在Context之外的地方,如果有的话就需要检查生命周期。

3、工具类中最好不要有Context成员变量,尽量在调用函数时直接通过调用参数传入。如果必须有Context成员变量时,可以考虑使用WeakReference来引用Context对象。

4、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

5、 检查把Context或者View对象赋给static变量的地方,看是否有Context泄漏。

6、检查所有把View放入容器类的地方(特别是static容器类),看是否有内存泄漏。7、使用WeakHashMap也需要注意有没有value-key的引用。

7、尽量使用ApplicationContext。

Handler对象泄漏

1、发送到Handler的Message实际上是加入到了主线程的消息队列等待处理,每一个Message持有其目标Handler的强引用。

如我们通常使用的匿名内部类Handler


HandlermHandler = new Handler() {
    @Override
    public voidhandleMessage(Message msg) {
       mImageView.setImageBitmap(mBitmap);
    }
}


上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用,因为View会依附着一个Activity。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给 Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条 Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

当然,应为是Handler对外部持有引用的原因,我们就可以将Activity设置为一个弱引用,在不必要的时候,不再执行内部方法。



/**
 * @author zhoushengtao
 * @since 2013-12-16 下午3:25:36
 */
 
import android.app.Activity;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
 
importjava.lang.ref.WeakReference;
 
publicclass WeakRefHandler extends Handler
{
    WeakReference mWeakContext;
 
    public WeakRefHandler(Context context)
    {
        mWeakContext = newWeakReference(context);
    }
 
    @Override
    public void handleMessage(Message msg)
    {
        if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing())
                return ;
        if(mWeakContext==null){
            return ;
        }
        super.handleMessage(msg);
    }
}


2、Non-staticinner class 和anonymous class持有其outer class的引用。


Drawable.Callback引起的内存泄漏

Drawable对象持有Drawable.callback的引用。当把一个Drawable对象设置到一个View时,Drawable对象会持有该View的引用作为Drawable.Callback


\

避免Drawable.Callback引起内存泄漏

? 尽量不要在static成员中保存Drawable对象

? 对于需要保存的Drawable对象, 在需要时调用Drawable#setCallback(null).


\

其他内存泄漏<??#65533;"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4KPHAgYWxpZ249"left"> 1、Android DigitalClock引起的内存泄漏http://code.google.com/p/android/issues/detail?id=17015

2、使用Map容器类时,作为Key 的类没有正确的实现hashCode和equal函数

其他内存泄漏

? JNI程序中的内存泄漏

1、 Malloc/free。

2、 JNI Global reference

? Thread-Local Variable

1、 相当于Thread对象的成员变量, 可以存储线程相关的状态

2、 如果thread是alive状态,那么Thread-Local中的对象就无法被GC。

进程内存占用监测工具

Dumpsys

? $ dumpsys meminfo [pid]


\

Procrank + Shell脚本

? #procrank

1、 VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

2、 RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)

3、 PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

4、 USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)


\

Shell脚本

#!/bin/bash

while true; do

adbshell procrank " grep "com.qihoo360.mobilesafe"

sleep1

done


当然,部分机型的sh都是经过第三方手机商精简过的,很多命令都用不了。Procrank,就是一个经常被精简掉的命令。

鉴于此:

自己写了一个小工具,检测内存的实时变化,

Github地址:https://github.com/stchou/JasonTest


\


\

小结

1. 保存对象前要三思

I. 对象本身有无隐含的引用

II. 保存后何时能够回收

2. 要了解常见的隐含引用

I. anonymous class outer class

II. View to context

3. 要通过各种工具检查内存占用是否有异常

4. 创建大对象时,要检查它的生命周期


热门栏目