总结Android 性能优化的几个方面

一 、总结Android 性能优化的几个方面

1. 布局优化

尽量减少布局文件的层级,这样Android 在绘制的时候工作量减少了,就会提高性能

  1. 删除布局中无用的控件和层级
  2. 选择使用性能较低的ViewGroup,比如RelativeLayout
    RelativeLayoutLinearLayout 中劲量使用 LinearLayout,因为RelativeLayout相对于比较复杂,布局过程需要花费更多的CPU时间,LinearLayoutFrameLayout 都是一种简单高效的ViewGroup,如果单纯的使用LinearLayoutFrameLayout不能达到想要的效果,需要嵌套才可以,那么还是使用RelativeLayout,嵌套相当于添加了布局的层级,这样更影响效率
  3. 布局优化还可以使用 <include>标签、 <merge> 标签 、ViewStub

    先简单介绍下它们的作用:

    <include>标签:用于布局重用,将一个布局文件引入到当前布局
    <merge> 标签:一般配合<include>标签 使用,他可以降 低布局的层级
    ViewStub:提供了动态加载的功能,当需要的时候才会将ViewStub中的布局加载出来,提高了程序初始化的效率

    下面介绍下它们的使用:

    <include> 标签的使用:

    先创建布局文件,名称为 manager_top

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="one" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="two" />


</LinearLayout>
在布局文件中引用并一个布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include
        layout="@layout/top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="text" />

</LinearLayout>

如果别的地方也要用到manager_top 布局文件中的布局,就不需要再重新写一次,直接使用include,达到复用效果

<merge> 标签 的使用:

以上面的代码为例,由于外层布局是竖直方向的LinearLayout ,这个时候被包裹的布局文件也是一个竖直方向的LinearLayout,显然被包裹的布局文件的LinearLayout是多余的,这个时候可以使用<merge> 标签,可以去掉多余的那个LinearLayout 层级

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="one" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="two" />

</merge>

使用<include>包含上面的布局的时候,系统会自动忽略merge层级,而把两个button直接放置与include平级

ViewStub继承了View,它非常轻量级且宽,高都是0,因此它本身不参加与任何的布局和绘制过程,ViewStub的意义在于按需加载所需的布局文件,有很多布局文件在正常情况下不显示,不如网络异常时的界面,这个时候没有必要在整个界面初始化的时候将其加载进来,通过ViewStub就可以在使用的时候再加载这个布局,提高程序初始化时的性能

使用:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout="@layout/top" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="text" />

</LinearLayout>

布局文件top

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="one" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="two" />

</LinearLayout>

注意ViewStub 引用的布局文件中暂不支持<merge>标签,<ViewStub>中的布局文件默认是不显示,要显示需要这样做

  ViewStub view_stub = (ViewStub)findViewById(R.id.view_stub);
        view_stub.setVisibility(ViewStub.VISIBLE);//或者view_stub.inflate();

2. 绘制优化

绘制优化是指View 的onDraw 方法要避免执行大量的操作,这主要体现在两个方面。

  1. onDraw 中不要创建新的布局对象,因为onDraw方法可能会被频繁调用,防止产生大量的临时对象
  2. onDraw 方法中不能做耗时的任务,也不能执行大量的循环操作,尽量是轻量级的,大量的循环会占用CPU,会造成View的绘制过程不流畅

3. 内存泄露优化

内存泄漏优化从两个方面
  1. 在开发中避免写出内存泄漏的代码
  2. 通过一些检查内存泄漏的工具来查找内存泄漏的地方,进行解决
一些导致内存泄漏的地方
  1. 注意使用静态属性
  2. 在Activity 中播放动画,注意要在onDestroy 中去停止动画
  3. 要注意一些资源的回收
内存检测工具的使用

LeakCanary的使用请看文章:http://blog.csdn.net/hjiangshujing/article/details/51690187
MAT 内存泄漏的工具的使用后续有时间会在另一篇补上的


4. 响应速度优化–ANR 日志分析

Android 规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现 ANR,在BroadcastReceiver中,如果10秒钟之内还未执行完操作也会出现ANR,当出现ANR 后,系统会在/data/anr目录下创建一个文件traces.txt 可以通过分析这个文件能定位出ANR的原因

下面我们模拟下能出现ANR的场景

在应用中添加代码

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main1);
        SystemClock.sleep(30000);
    }

然后不断点击屏幕就会出现ANR ,这时可以通过DDMS 中的File Explorer 导出traces.txt 文件,也可以通过adb 命令

adb pull /data/anr/traces.txt

traces.txt 文件内容比较多,可以搜索下自己应用的包名

截取部分日志如下:

DALVIK THREADS (14):
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x74c67000 self=0xb4025800
  | sysTid=1953 nice=0 cgrp=default sched=0/0 handle=0xb772cea0
  | state=S schedstat=( 272113617 55357345 236 ) utm=12 stm=14 core=1 HZ=100
  | stack=0xbf151000-0xbf153000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at com.customview.jsj.customview.MyView.onCreate(MyView.java:20)
  at android.app.Activity.performCreate(Activity.java:5990)
  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
  at android.app.ActivityThread.access$800(ActivityThread.java:151)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5254)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

通过以上日志可以看出,Thread.sleep 线程睡了,在com.customview.jsj.customview.MyView.onCreate(MyView.java:20) 20行的位置,
这样就能定位到问题了,仔细查看日志,在一下几行日志中清楚的说明了产生ARN的原因

at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at 

5. Bitmap

Bitmap高效加载,使用BitmapFactory.Obtions,可以对图片进行采样缩放
通过BitmapFactory.Obtions 来缩放图片,主要是用到它的inSampleSize 参数,即采样率。当inSampleSize为1时,采样后的图片大小为图片的原始大小,当inSampleSize的大小大于1时,比如2,那么采样后的图片其宽,高为原图片的1/2,而像素数为原图的1/4,其占用的内存大小也为原图的1/4.

图片像素:
Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存

比如:一张图片的分辨率为1024*1024,假定采用ARGB_8888格式存储,那么它占有的内存为1024*1024*4,即4MB,如果inSampleSize 为2,那么采样后的图片所占有的内存大小为 512*512*4 即1MB。采样率inSampleSize 必须是大于1的整数,图片才会有缩小的效果,并且采样率同事作用于宽,高,这将导致缩放后的图片大小以采样率的2次方形式递减,即缩放比例1/(inSampleSize 的 2 次方),比如inSampleSize 为 4,那么缩放比例就是 1/16。
注意:当inSampleSize小于1时,其作用相当于1,即无缩放效果,
官网指出,inSampleSize的取值应该总是为2的指数,比如:1,2,4,6,8,16等,如果inSampleSize不为2的指数,那么系统会向下取整并选择一个最近的2的指数来代替,比如3,系统会选择2来代替,这个结论并非所有的Android 版本上都成立,因此把它当成一个开发建议

缓存策略
目前常用的缓存算法LRU(Least Recently Used),是近期最少使用的
核心思想是:当缓存满时,会优先淘汰那些近期最少使用的缓存对象,采用LRU算法的缓存有两种:LruCache 和 DiskLruCache, LruCache 用于实现内存缓存,DiskLruCache 用于存储设备缓存(磁盘缓存),LruCache 和 DiskLruCache具体在这里不做解释了
在项目中我们也可以借助一些三方库,如:ImageLoader
ImageLoader 内部对BitMap 高效加载进行了处理,以及LruCache 和 DiskLruCache


6. 线程优化

线程优化的思想是采用线程池,避免程序中存在大量的Thread ,线程池可以复用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还能有效的控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致堵塞现象

有关线程池的知识请看:http://blog.csdn.net/hjiangshujing/article/details/51719573

线程池终结版:http://www.xuanyusong.com/archives/2439


7. 资源的回收

  1. Cursor(游标)回收:cursor.close();
  2. Receiver(接收器)回收:当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册
  3. 图片回收:使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间
// 先判断是否已经回收  
if(bitmap != null && !bitmap.isRecycled()){  
    // 回收并且置为null  
    bitmap.recycle();  
    bitmap = null;  
}  
System.gc(); 

4.Stream/File(流/文件)回收:主要针对各种流,文件资源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。

发布了33 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/hjiangshujing/article/details/51719616