Android进阶系列之性能优化篇-(内存优化)

徘徊边缘 提交于 2019-11-30 10:35:54

Android中,应用的性能优化是一个比较重要的问题,应用性能直接影响到用户的体验,应用的流畅度,崩溃率,流量使用量,耗电量,以及启动的快慢,内存使用等,都会严重影响用户的使用。
而内存优化更是Android应用性能优化中的重要部分,这篇文章就来记录总结一下Android应用的内存优化

首先先来了解一下两个概念:

内存泄漏:由于某种原因,导致程序中动态分配的堆内存,无法被释放,导致系统内存的浪费。
主要表现为长生命周期对象持有短生命周期对象引用,从而导致短生命周期对象无 法被回收。

内存抖动:程序运行过程中,频繁的创建和销毁对象,导致频繁GC,
从而引起应用卡顿。
主要表现为在循环中创建对象,导致短时间内有大量的对象创建和回收,
如果严重的话,就会导致应用卡顿。

下面来总结一下Android中内存泄漏的场景以及对应的解决方案:

1.单例引起的内存泄漏
由于单例的静态特性,导致单例的生命周期和整个应用的生命周期一样长,如果对象不再被使用,但是被单例持有引用的话,那么这个对象就没有办法被系统回收,导致内存泄漏。

public class SingleInstance {
    private static SingleInstance mInstance;
    private Context mContext;

    private SingleInstance(Context context) {
        mContext = context;
    }

    public static SingleInstance getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new SingleInstance(context);
        }
        return mInstance;
    }

}

上面定义的单例类,持有了一个上下文对象,如果上下文对象是Activity,
那么当Activity被关闭时,单例对象会一直持有Activity的引用,系统是无法回收Activity对象的。

解决方法:如果不是必须要以Activity作为上下文对象传递给单例持有,我们可以使用 Application上下文对象,因为Application生命周期本来就是和整个应用一样长,如果非要持有Activity类型上下文对象,我们可以持有Activity的弱引用。

2.非静态内部类(包括匿名内部类)导致内存泄漏
由于非静态内部(包括匿名内部类)持有外部类的引用,;如果非静态内部类的实例的生命周期比外部类的生命周期长,就会导致内存泄漏。

//Thread
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyThread myThread = new MyThread();
        myThread.start();
    }

    public class MyThread extends Thread {
        @Override
        public void run() {
            super.run();
            //耗时操作
           Thread.sleep(10000) 
        }
    }
}
//Handler
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler myHandler = new MyHandler();
        myHandler.sendEmptyMessageDelayed(0, 10000);
    }
    public class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //操作
        }
    }
}

上面也是Android种两种比较常用的场景,线程Thread和Handler的使用,都是非静态内部类,创建了实例,那么当Activity被关闭的时候,如果Thread的或者Handler的操作没有结束,那么Activity就会被持有引用,无法被回收,导致内存泄漏。

解决方法:由于非静态内部类(包括匿名内部类)会持有外部内的引用,所以我们我们避免使用非静态内部类,改为使用静态内部类,如果需要外部类
的引用,可以持有弱引用的方式。 即静态内部类+弱引用的解决方法。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler myHandler = new MyHandler(new WeakReference<Activity>(this));
        myHandler.sendEmptyMessageDelayed(0, 10000);
    }

    public void doSomething() {

    }

    public static class MyHandler extends Handler {
        private WeakReference<Activity> mActivityWeakReference;

        public MyHandler(WeakReference<Activity> reference) {
            mActivityWeakReference = reference;
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
          //操作
            if (mActivityWeakReference.get()!=null){
                ((MainActivity) mActivityWeakReference.get()).doSomething();
            }
        }
    }

对于Handler我们也可以在Activity销毁时,从消息队列中移除所有的msg
来避免内存泄漏,即在onDestory()中移除Handler的发送的所有消息

  @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: ");
        myHandler.removeCallbacksAndMessages(null);
    }

3.静态变量引起的内存泄漏
静态变量生命周期从类加载开始到整个应用进程结束,所以如果静态变量持有了Activity的引用,那么Activity被关闭后,系统也是无法回收Activity的
对象的,导致内存泄漏。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    public static Activity mActivity; 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mActivity=this;
    }

}

解决方法:最好不要让静态变量持有Activity的强引用。也可以通过持有弱引用的方式
来避免内存泄漏。

4.注册监听器,没有取消导致的内存泄漏
比如广播,注册了之后,要在Activity的onDestory()中取消注册。

5.各种资源未释放导致内存泄漏
如Cursor,IO等使用了之后都要及时关闭。

6.Bitmap使用之后没有调用recycle导致内存泄漏
Bitmap使用完之后,需要手动调用recycle()方法回收,并置为null,否则
无法被回收。

 if (mBitmap != null) {
            mBitmap.recycle();
            mBitmap = null;
        }

7.List集合中存放对象,不用的时候需要移除

 List<Object> objectList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Object o = new Object();
            objectList.add(o);
        }
        
 //不用时,需要清空集合,       
 objectList.clear();
 objectList=null;

8.还有像Timer,TimerTask,CountDownTimer等使用之后都要及时cancel,并置
成null。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
            mTimerTask.cancel();
            mTimerTask = null;
        }
    }

以上就是一些内存泄漏的场景以及对应的解决方法。

下面再来看一下内存抖动的一些场景以及解决方法
前面我们已经说过,引起内存抖动主要是由于短时间内频繁的创建回收对象导致的。
比如在循环中创建对象,或者在可能频繁调用的方法中创建对象,都可能导致内存抖动。
所以如果可以避免,我们尽量不要在循环中创建对象,或者在可能频繁调用的方法中
创建对象。
1.如字符串的拼接:

         String str="";
        List<UserBean> userList=new ArrayList<>();
        for (int i = 0; i < userList.size(); i++) {
            if (i==userList.size()-1){
                str=str+userList.get(i).getId();
            }else{
                str=str+userList.get(i).getId()+",";
            }
        }

上面的代码中,由于String类型的对象是一个常量,是不能改变的,所以每次拼接都会创建一个新的String对象,所以循环中会频繁的创建对象,可能导致内存抖动。
解决方法:在单线程中我们可以使用StringBuilder来替换String进行字符串拼接,在多线程
中我们使用StringBuffer替换String进行字符串拼接。

        StringBuilder stringBuilder=new StringBuilder();
        List<UserBean> userList=new ArrayList<>();
        for (int i = 0; i < userList.size(); i++) {
            if (i==userList.size()-1){
                stringBuilder.append(userList.get(i).getId());
            }else{
                stringBuilder.append(userList.get(i).getId()).append(",");
            }
        }

2.在自定义View中,我们会重写onDraw()方法,View的onDraw()方法每次刷新View 即invalidate()时都会被调用,所以尽量避免在onDraw()方法中创建对象。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!