读书笔记(15) Android性能优化

来源:互联网 发布:异构系统数据集成 编辑:程序博客网 时间:2024/06/05 08:19

这些读书笔记是根据《Android开发艺术探索》和《Android群英传》这两本书,然后将书上的一些知识点做一下记录。方便学习和理解,如果有存在侵犯版权的地方,还麻烦告知。个人强烈建议购买这两本书。真心不错。

本节是和《Android开发艺术探索》中的第15章 “Android性能优化” 有关系,建议先买书查看这一章。

[]Android 性能优化

2015年Google在YouTube上发布了关于Android性能优化典范的专题视频。
https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE

过多地使用内存会导致程序内存溢出,即OOM。而过多的使用CPU资源,一般是指做大量的耗时任务,会导致手机变得卡顿甚至出现程序无法响应的情况,即ANR。

内存泄漏并不会导致程序功能异常,但是它会导致程序的内存占用过大,这将提高内存溢出的发生几率。

()布局优化

布局优化其实就是尽量减少布局文件的层级,因为布局中的层级少了,程序绘制时的工作量少啦,那么布局性能就有提高。

{}删除布局中无用的控件和层级,有选择性的使用性能较低的ViewGroup。

如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,因为RelativeLayout的功能比较复杂,它的布局过程需要发费更多的CPU时间。

如果通过一个LinearLayout无法实现,需要嵌套的方式来实现。通过RelativeLayout也能实现,那么就采用RelativeLayout,因为嵌套的LinearLayout就增加了布局的层级。

{}采用< include>标签< merge>标签和ViewStub控件。

include标签主要用于布局重用。

include标签只支持以android:layout_*开头的(可以通过代码提示查看)和android:id这些属性,其他属性不支持,

如果include标签指定了id属性,同时include标签的布局文件的根元素也指定了id属性,那么以include标签指定的id属性为准。

如果include标签指定了android:layout_*属性,那么必须指定 android:layout_height和android:layout_width,否则其他android:layout_*属性无效,并且不能通过编译。

merge标签一般和include标签配合使用,主要用于降低减少布局的层级。

如果include标签中的布局是竖直的LinearLayout,引用include标签的布局文件也是竖直的LinearLayout,那么include标签中的竖直的LinearLayout可以用merge标签代替。

ViewStub控件主要用于按需加载的功能,当需要时才会将ViewStub中的布局加载到内存中显示。比如网络连接失败时,加载一个网络异常界面。

ViewStub继承View,它非常轻量级且宽和高都是0,因此ViewStub不参与任何的布局和绘制过程。

ViewStub可以通过setVisibility(View.VISIBLE)或者inflate()加载后,ViewStub就不存在了,被它内部的布局替换掉。

  <ViewStub    android:id="@+id/***"    android:layout="@layout/***"    android:layout_width="match_parent"    android:layout_height="match_parent" />

()绘制优化

绘制优化是指View的onDraw方法中要避免执行大量的操作。

{}onDraw方法中不要创建新的局部对象,因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁gc。

{}onDraw方法中不要做耗时的任务,也不能执行大量的循环操作。尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU资源,这会导致View的绘制过程不流畅。

()内存泄漏优化

内存泄露,就是该被释放的内存没有被释放,一直被某个或某些实例所引用但不能被使用,导致GC不能回收。可以理解为长生命周期的对象一直持有短生命周期对象的引用,导致短生命周期对象一直被引用而无法被GC回收。内存泄漏是造成OOM的主要原因之一。

{}静态变量导致的内存泄漏

在Activity中一个静态变量持有当前Activity对象,会导致Activity无法释放。

下列代码会出现内存泄漏

public class TextActivity extends Activity {    private static View view;    @Override    protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    view=new View(this);    }}

{}非静态内部类导致的内存泄漏

非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。

下列代码会出现内存泄漏

()非静态的内部类public class TextActivity extends Activity {    private static MyInside myInside;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        myInside = new MyInside();    }    public class MyInside {    }}()非静态匿名内部类public class TexActivity extends Activity {    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.text);        Message message = Message.obtain();        mHandler.sendMessage(message);    }}

mHandler作为一个非静态匿名内部类,会持有外部类(TexActivity)的引用,对于消息机制是Looper不断的轮询从消息队列取出未处理的消息交给Handler处理,而每一个消息又持有一个mHandler的引用,每一个mHandler又持有TexActivity的引用,所以如果在Activity退出后,消息队列中还存在未处理完的消息,导致该Activity一直被引用,其内存资源无法被回收,导致了内存泄漏。

解决方案:将内部类定义为静态内部类或将内部类抽取出来封装成一个单例。

{}单例模式导致的内存泄漏

因为单例模式有其静态的特点,其生命周期和应用一样长,如果单例对象中包含了一个其他对象的引用,那么即使这个对象不再使用,依然存在一个单例对象引用它,造成无法回收。

下列代码会出现内存泄漏

public class DataListener {    private static DataListener mDataListener;    private Context mContext;    private DataListener(Context mContext) {        this.mContext = mContext;    }    public static DataListener getDataListener(Context mContext) {        if (mDataListener == null) {            mDataListener = new DataListener(mContext);        }    return mDataListener;    }}public class TextActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.text);        DataListener.getDataListener(this);    }}

解决方案:将Context对象用Application对象来替代。

{}资源未回收导致内存泄漏

()属性动画导致的内存泄漏

属性动画中有一类无限循环的动画,如果在Activity中播放此类动画且没有在onDestroy中去停止动画,那么动画会一直播放下去。因为Activity中的View会被动画持有,而View又持有Activity,最终Activity无法释放。解决的方法是在onDestroy中调用cancel()来停止动画。

()BraodcastReceiver、Cursor、File、FileStream、Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

()响应速度优化和ANR日志分析

响应速度优化主要是避免在主线程中做耗时操作,但是有时候的确要执行耗时操作,可以将耗时操作放在子线程中执行,即采用异步的方式执行耗时操作。

Android中规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现ANR,BroadcastReceiver如果10秒钟之内还未执行完操作就会出现ANR。

如果一个进程出现ANR,系统会在data/anr目录下创建一个文件traces.tex和一系列traces_***.tex,分析这个文件就能定位出ANR的原因。

()ListView和Bitmap优化

ListView优化在12章中已经做了详细的介绍,主要是采用ViewHolder,并避免在getView中执行耗时操作,其次是根据列表的滑动状态来控制任务的执行频率。最后可以尝试开启硬件加速来使ListView的滑动更加流畅。

Bitmap优化在12章中已经做了详细的介绍,主要是通BitmapFactory.Options来对图片进行二次采样。

()线程优化

线程优化主要是采用线程池,因为线程池可以重用内部线程,从而避免了线程的创建和销毁使带来的性能开销,同时线程池还能有效地控制线程的最大并发数,避免大量的线程因互相抢占系统资源从而导致阻塞现象发生。

()一些性能优化建议

{}避免创建过多的对象
{}不要过多的使用枚举,枚举占用的内存空间要比整型大
{}常量使用 static final 修饰
{}采用一些Android特有的数据结构,比如SparseArray和Pair等,因为它们具有更好的性能。
{}适当的使用软应用和弱引用
{}采用内存缓存和磁盘缓存
{}尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏

[]内存泄漏分析工具

()MAT

()LeakCanary

{}导LeakCanary的jar包

dependencies {..........debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'}

{}LeakCanary引用

public class ExampleApplication extends Application {    private RefWatcher mRefWatcher;    @Override    public void onCreate() {        super.onCreate();        mRefWatcher = LeakCanary.install(this);    }}

注:
LeakCanary会单独安装一个app,如果出现内存溢出会发出通知。点击通知就能了解内存溢出的详情。

LeakCanary默认监听Activity内存溢出,如果需要监听其他的类需要调用mRefWatcher.watch()方法。

0 0