Android性能优化---内存泄漏及解决

Android内存泄漏专题分析

一、   资源对象未关闭引起的内存泄漏

Android的资源对象在不再使用时,及时关闭对象,这类资源对象有Receiver、Cursor、Bitmap、文件对象File以及InputStream、OutputStream衍生类对象未关闭。

举几个例子:

1、  registerReceiver(mReceiver, filter)后,在退出Activity后未调用unregisterReceiver(mReceiver)。

下面是用LeakCanary抓取的Receiver泄漏:


2、创建Bitmap,但是未在退出Activity时调用Bitmap的recycle()方法。

为什么Bitmap那么特殊,还需要调用recycle()来释放内存?简单地说是因为Bitmap通过BitmapFactory.decodeXXX接口解码后会在java和native层都存有内存,java层的Bitmap实际上是native层通过反射调用Bitmap的createBitmap方法得到的引用。调用recycle()主要是主动释放native层的内存。

有兴趣可以参考以下链接,过程分析的比较清晰:

https://blog.csdn.net/qq_26984087/article/details/89320850

如果不调用recycle(),通过LeakCanary等工具是无法检测出来的,因为内存泄漏在native层。


其他Cursor、File、InputStream、OutputStream等,就不详细举例了。

二、   Activity的Context引起的内存泄漏

Activity的Context生命周期和Activity是一致的,如果被其他对象持有,在Activity生命周期结束后未释放,就会引起内存泄漏。解决方法很简单,需要用到Context的地方使用getApplicationContext()代替。


三、  属性动画未取消引起的内存泄漏

从 Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画且没有在 onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看见动画效果了,并且这个时候 Activity的 View会被动画持有,而View又持有了Activty, 最终Activity无法释放。源码分析可以参考:https://blog.csdn.net/qq_36356379/article/details/97125062。

下面的动画是无限动画,会泄露当前的Activity, 解决方法是在Activity的onDestroy中调用animator.cancel()来停止动画。


四、  WebView引起的内存泄漏

在android5.1及以上版本中,webview会引起内存泄漏。解决方法是在onDetachedFromWindow中先将父view中移除WebView,再调用destroy方法。例如:

public voidonDestroy() {

    if (mWebView != null) {

          ViewParent parent = mWebView.getParent();

          if(parent != null) {

                   ((ViewGroup)parent).remove(mWebView);

          }

          mWebView.stopLoading();

          mWebView.clearHistory();

          mWebView.clearView();

          mWebView.removeAllViews();

          try{

                   mWebView.destroy();

                   mWebView= null;

          } catch (Exception err) {

          }

    }

}

五、  单例引起的内存泄漏

单例因为声明周期是和整个应用相同的,即使某些对象(如Activity)退出了,但是如果这个单例持有这个Activity,例如常见的单例参数中传入Context,Activity退出后,这个Context就被单例持有,引起内存泄漏。举个例子,我们有时候在Activity里写个管理类用来做逻辑处理:

mAppTestMgr = AppTestManager.getInstance(this);

public class AppTestManager {

    private static AppTestManager sInstance;

    private AppTestManager(Context context) {

        mContext = context;

    }

    public static AppTestManager getInstance(Context context) {

        if (sInstance == null) {

            synchronized (AppTestManager.class) {

                if (sInstance == null) {

                    sInstance = new AppTestManager(context);

                }

            }

        }

        return sInstance;

    }

}

这个AppTestManager就是个简单的单例实现,getInstance传入的参数就是Context类型,AppTestManager类持有Activity实例。Activity退出后,就会报内存泄漏:


正确的方法是初始化单例时,传入Application

Context,这个是和进程生命周期一致的:

AppTestManager.getInstance(getApplicationContext());


六、   Java非静态内部类引起的内存泄漏

Android开发中比较常见的内部类有Handler、AsyncTask、自定义的非静态内部类等等,如果类似以下采用以下方式,就会引起内存泄漏。


6.1 Handler的错误用法:

private void updateMemLeakTitle() {

    if (mTitleTextView != null) {

        mTitleTextView.setText(R.string.mem_leak_title_text);

    }

    if (mMainHandler != null) {

        mMainHandler.sendEmptyMessageDelayed(MSG_UPDATE_TEXT, 2000);

    }

}

private class MainHanlder extends Handler {

    @Override

    public void handleMessage(@NonNull Message msg) {

        switch (msg.what) {

        case MSG_UPDATE_TEXT:

                updateMemLeakTitle();

                break;

        }

    }

};

java语法指出在内部类Outer$Inner中, 存在一个名字为this$0 , 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用。也就是说像以上MainHandler类有个隐含的引用指向了外部类Activity,在Activity退出后,内部类执行的内容尚未执行完,这个引用并未被释放,从而引起内存泄漏。


Handler的内存泄漏修改方法就是在退出Activity时,移除所有的消息并将handler置空,或者将Handler改为静态内部类。

方法一:在退出Activity时,移除所有的消息并将handler置空。

@Override

protected void onStop() {

    super.onStop();

    if (mMainHandler != null) {

        mMainHandler.removeCallbacksAndMessages(null);

        mMainHandler = null;

    }

}

方法二:将Handler改为静态内部类,采用WeakReference引用外部接口。

private static class MainHanlder extends Handler {

    private WeakReference mActivity;

    public MainHanlder(MainActivity activity) {

        mActivity = new WeakReference<>(activity);

    }

    @Override

    public void handleMessage(@NonNull Message msg) {

        switch (msg.what) {

        case MSG_UPDATE_TEXT:

            mActivity.get().updateMemLeakTitle();

            break;

        }

    }

};

6.2 AsyncTask的错误用法

AsyncTask和Handler有些类似,也是非静态内部类引起的内存泄漏,修改方案也可以参考方法二。例如这么写就会出现内存泄漏问题:

private class UpdateTask extends AsyncTask {

    @Override

    protected String doInBackground(Void... voids) {

        try {

            Thread.sleep(5000);

        } catch (Exception err) {

        }

        String text = mContext.getResources().getString(R.string.mem_leak_title_text);

        return text;

    }

    @Override

    protected void onPostExecute(String text) {

        super.onPostExecute(text);

        updateMemLeakTitle(text);

    }

};


同样的把AsyncTask的内部类改造成静态内部类,并用WeakReference引用Activity的方法:

private static class UpdateTask extends AsyncTask {

    private WeakReference mActivityRef;

    public UpdateTask(MainActivity activity) {

        mActivityRef = new WeakReference<>(activity);

    }

    @Override

    protected String doInBackground(Void... voids) {

        Log.d(TAG, "");

        try {

            Thread.sleep(5000);

        } catch (Exception err) {

        }

        String text = mActivityRef.get().getResources().getString(R.string.mem_leak_title_text);

        return text;

    }

    @Override

    protected void onPostExecute(String text) {

        super.onPostExecute(text);

        mActivityRef.get().updateMemLeakTitle(text);

    }

};

6.3 自定义的非静态内部类

类似Handler和AsyncTask的原因及解决方案,因为情况比较多,就不一一列举了。

七、   未释放过期的对象引用

先看个Effective Java中提到的例子:

publicclass Stack {

         private Object[] elements;

         private int size = 0;

         private static final intDEFAULT_INITIAL_CAPACITY = 16;

         public Stack() {

                   elements = newObject[DEFAULT_INITIAL_CAPACITY];

         }

         public void push(Object e) {

                   ensureCapacity();

                   elements[size++] = e;

         }

         public Object pop() {

                   if (size == 0) {

                            throw newEmptyStackException();

                   }

                   return elements[--size];

         }

         private void ensureCapacity() {

                   if (elements.length == size){

                            elements =Arrays.copyOf(elements, 2 * size + 1);

                   }

         }

}

能不能看出来,内存泄漏的问题在哪里?

如果一个栈显示增长,然后再收缩,那么从栈中弹出的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象。因为栈内部维护者对这些对象的过期引用(永远也不会再被解除的引用)。

修复方案很简单:一旦对象引用已经过期了,只需清空这些引用即可。

publicObject pop() {

    if (size == 0) {

        throw newEmptyStackException();

    }

    Object result = elements[--size];

    elements[size] = null;

    return result;

}


八、  Sensor Manager的使用

通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。例如:

private SensorManager mSensorManager;

    private Sensor mSensor;

    private void registerListener() {

    mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

    mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ALL);

    mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_FASTEST);

}

解决方法是在Activity退出时,在onDestroy方法中调用unregisterListener方法。

@Override

protected void onDestroy() {

    super.onDestroy();

    unregisterListener();

}

private void unregisterListener() {

    if (mSensorManager != null) {

        mSensorManager.unregisterListener(this, mSensor);

    }

}

九、   内存泄漏的检测工具介绍

MAT工具、AndroidStudio、LeakCanary

LeakCanary的使用:

1、在工程app目录下的build.gradle中找到dependencies区域,增加如下代码:

1、在build.gradle中找到dependencies区域,增加如下代码:

debugCompile'com.squareup.leakcanary:leakcanary-android:1.6.1'

releaseCompile'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'


重新编译工程,这样AndroidStudio会自动为App增加LeakCanary的库文件。

2、App文件的TvApplication.java找到onCreate方法,增加如下代码:

    增加变量定义:

private RefWatcher refWatcher;//leak canary test

public void onCreate() {

    sRefWatcher = initLeakCanary();

}

增加如下函数:

private RefWatcher initLeakCanary() {

    if (LeakCanary.isInAnalyzerProcess(this)) {

        return RefWatcher.DISABLED;

    }

    return LeakCanary.install(this);

}

public static RefWatcher getRefWatcher(Context context) {

    return sRefWatcher;

}

3、在需要测试的java文件的onStop中增加如下代码:

@Override

protected void onStop() {

    super.onStop();

    RefWatcher refWatcher = MemLeakTestApplication.getRefWatcher(this);

    refWatcher.watch(this);

}

然后就可以开始测试了,按照测试原理,只要退出应用,即调用到onStop即可抓取泄漏内容。

重新编译Launcher,这样AndroidStudio会自动为App增加LeakCanary的库文件。


参考文献:

Android内存泄漏的八种可能:

https://www.jianshu.com/p/ac00e370f83d?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

https://www.jianshu.com/p/c5ac51d804fa

https://www.cnblogs.com/zhaoyanjun/p/5981386.html


内存泄漏工具:

https://blog.csdn.net/yulianlin/article/details/50393872

你可能感兴趣的:(Android性能优化---内存泄漏及解决)