Android的性能优化

一个Android程序不可能无限制的使用内存和CPU资源,过多的使用内存会导致程序内存溢出,也就是OOM。过多的使用CPU资源,做一些大量的耗时任务会导致手机卡顿或者无法响应,也就是ANR。性能优化的主要内容包括布局优化、绘制优化、内存泄漏优化、响应速度优化、ListView优化、Bitmap优化、线程优化。


布局优化

布局优化主要思想当然是减少布局文件的层级,尽可能少用ViewGroup,尽可能选用性能高的ViewGroup,比如能使用LinearLayout代替RelativeLayout就使用LinearLayout,因为RelativeLayout相对来说要复杂一些。当然能使用一个ViewGroup就使用一个,尽量少的去嵌套。

使用<include>标签、<merge>标签和ViewStub。<include>标签主要用于布局的重用,相信都使用过。<merge>标签一般和<include>配合使用,减少布局的层级,ViewStub实现了按需加载,当需要的时候才进行加载,下面看看具体的使用。

<include>标签

<include>可以把指定的布局加载到当前布局中,使用<include>标签可以提高复用性

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.lzy.test.MainActivity">

    <include
        android:id="@+id/include"
        layout="@layout/message"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="40dp" />
</LinearLayout>

在这里用<include>标签添加了一个message布局,这样就不需要再把布局重写一遍。在<include>标签里面只支持android:layout_开头的属性和id属性,也就是说不能使用类似android:background这样的属性。还有一点需要注意,当<include>标签中使用了以android:layout_开头的属性,那么必须去指定android:layout_height和android:layout_width,如上所示。


<merge>标签

<merge>标签一般和<include>标签一起使用从而减少布局的层级。所有Activity的根节点都是FrameLayout,如果我们在写布局文件时,根节点也需要一个FrameLayout怎么办?如果直接再写一个那不是两个FrameLayout嵌套在一起,完全没有必要,所以这时根节点写成<merge>就Ok了。

又比如上面的<include>标签,如果include进来的布局里面还是一个LinearLayout,外面<include>标签的根节点也是一个LinearLayout,那么这时include布局的LinearLayout就可以使用<merge>标签代替,如下所示:

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

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

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

ViewStub的使用

ViewStub继承于View,宽高都为0,,本身不会参与任何的布局绘制过程,最佳用途就是实现View的延迟加载,避免资源浪费,在需要的时候才加载View,需要注意的是,加载view之后,viewstub本身就会被新加载进来的view替换掉。比如说一个加载失败的界面,是没有必要在初始化的时候就把它加载进来,这时通过ViewStub就可以做到在使用的时候再进行加载,提高了程序的性能。看看下面的例子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.lzy.test.MainActivity">

    <ViewStub
        android:id="@+id/viewStub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/tv" />
</LinearLayout>
加载进来的布局

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:gravity="center"
    android:textSize="18sp"></TextView>
final ViewStub viewStub = (ViewStub) findViewById(R.id.viewStub);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                TextView view = (TextView) viewStub.inflate();
                view.setText("加载进来的TextView");
            }
        }, 2000);
这里延迟两秒后调用ViewStub的inflate方法把ViewStub替换成要加载的布局,这时ViewStub就不存在了,因为被替换掉了,这个方法返回的正是加载进来的View,所以不需要采用findViewById的方法来找到它,我们加载进来的View是个TextView所以直接强转为TextView就OK,除了inflate方法也可以采用setVisibility方法也可以。


绘制优化

绘制优化是指在onDraw方法中应该要避免执行大量的操作,主要体现在两个方面:

首先,onDraw中不要创建新的局部变量,因为onDraw方法是会频繁的被调用的,这样每次调用后都会产生临时对象,这样不仅会占用过多的内存还会引起频繁的GC。

另一方面的话,在onDraw中不应该做耗时的任务,也不能执行千万次的循环操作,否则会造成绘制过程的不流畅,降低了用户体验。


内存泄漏优化

一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露。下面看看几种常见场景的内存泄漏

静态变量引起的内存泄漏

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "lzy";
    private static Drawable sDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);

        sDrawable = getDrawable(R.mipmap.ic_launcher);
        textView.setBackground(sDrawable);
        setContentView(textView);
    }
}
上述代码中sDrawable是一个静态变量,它持有一个textView的引用,而textView又持有这个Activity的引用,也就是说sDrawable持有了Activity的引用,如果销毁这个Activity,由于sDrawable是一个静态变量它不会被销毁,也就说无法释放Activity的引用,所以这个Activity也无法释放,于是导致了内存泄漏。


单例模式引起的内存泄漏

因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

如果Activity中使用这个单例,传入的Context为Activity,那么这个单例就持有了Activity的引用,将不会释放,所以这个周期应该和Application的周期一样,所以这里AppManager的Context应该这样传,context.getApplicationContext(),这样就可以得到全局的Context。


非静态内部类创建静态实例造成的内存泄漏

有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,会出现这种写法:

public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }

    class TestResource {
        //...
    }
}
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。

Handler造成的内存泄漏

Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }

    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。


线程优化

线程优化的思想是采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,避免线程的创建的销毁带来的开销,同时还能有效的控制线程池的最大并发数量。


一些优化建议

1.避免创建过多的对象。

2.不要过多使用枚举,它占用的空间比整型大

3.常亮使用static final修饰

避免资源未关闭造成的内存泄漏

当使用了BraodcastReceiver、Cursor、Bitmap等资源时,当不需要使用时,需要及时释放掉,若没有释放,则会引起内存泄漏。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值