美文网首页
内存泄漏分析

内存泄漏分析

作者: 主音King | 来源:发表于2019-11-18 16:33 被阅读0次

前言

当短生命周期对象销毁且不再被使用时,长生命周期对象依然持有了短生命周期的对象引用,导致短生命周期对象一直不会被GC释放,造成内存泄漏。对内存一种浪费,一个项目中有多处内存泄漏,就会容易产生OOM。Android中一般内存泄漏由于Activity destroy后,其引用不能释放导致的。

内存泄漏的场景

单例造成的内存泄露

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

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;
    }
}

可以传Application的context来防止内存泄漏。

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

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(mResource == null){
            mResource = new TestResource();
        }
        //...
    }
    class TestResource {
    //...
    }

在Activity内部创建了一个非静态内部类的单例。非静态内部类默认会持有外部类的引用,使用该非静态内部类创建了一个静态实例。该静态实例一直持有该Activity,导致内存泄漏。
正确做法:将该内部类设为静态内部类或将内部类抽取出来封装一个单例。如果使用Context,请使用ApplicationContext。

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);
    }
}

由于mHandler是Handler的非静态匿名内部类的实例,它持有外部类Activity的引用,Looper线程中不断轮询处理消息,消息队列中的Message持有mHander实例的引用,mHandler又持有Activity的引用。导致泄漏。

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference 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持有的对象使用若引用,避免activity泄漏,不过Looper还可能有待处理的消息,所以在Activity的destroy中或者stop的时候移除队列中的消息

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference 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);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message

线程造成的内存泄漏

异步任务和Runnable都是一个匿名内部类,对当前activity都有隐式引用,如果Activity在销毁之前,任务还没完成,导致Activity资源无法回收。正确做法是使用静态内部类的方式:

static class MyAsyncTask extends AsyncTask {
    private WeakReference weakReference;

    public MyAsyncTask(Context context) {
        weakReference = new WeakReference<>(context);
    }

    @Override
    protected Void doInBackground(Void... params) {
        SystemClock.sleep(10000);
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        MainActivity activity = (MainActivity) weakReference.get();
        if (activity != null) {
        //...
        }
    }
}
static class MyRunnable implements Runnable{
    @Override
    public void run() {
        SystemClock.sleep(10000);
    }
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

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

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

集合类造成的内存泄漏

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null;   
}

webview造成的内存泄漏

即使我们在退出页面时的onDestroy中写如下销毁,但并不起作用。

mWebView.removeAllViews();
mWebView.destroy();
mWebView=null;

1.方法一
不要在布局中定义WebView节点,需要的时候动态代码生成。这个方法关键在于动态传入Webview的是ApplicationContext,而不是ActivityContext,但是很多情况会报错,Webview的特殊动作产生由Application到Activity的类型转换错误。不推荐使用。
2.方法二
为加载Webview界面开启新进程,在该页面退出后关闭这个进程
3.方法三
Webview内存泄漏主要是因为org.chromium.android_webview.AwContents类中注册了component callbacks,但是未正常反注册导致的。
org.chromium.android_webview.AwContents类中有两个方法onAttachedtoWindow和onDetachedWindow,系统会在attach和detach处理注册和反注册component callback,在onDetachedFromWindow()中执行

if (isDestroyed()) return;

如果isDestroyed()返回true的话,后续无法正常走。
解决办法:
让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉

ViewParent parent = mWebView.getParent();
if (parent != null) {
    ((ViewGroup) parent).removeView(mWebView);
}
mWebView.destroy();

完整的activity的onDestroy()方法

@Override
protected void onDestroy() {
    if( mWebView!=null) {

        // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
        // destory()
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }

        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        mWebView.destroy();

    }
    super.on Destroy();
}

EditText造成内存泄漏

Android输入法会导致内存泄漏,原因是inputMethodManager持有了EditText的引用,持有了activity的引用导致内存泄漏,可以通过反射把inputMethodManager以及相关的持有引用赋值null,因为Android多样性,所以实用性不高。
推荐使用方法:

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;

import java.lang.reflect.Field;

/**
 * 防止出现内存泄漏
 */
@SuppressLint("AppCompatCustomView")
public class BaseEditText extends EditText {
    private static Field mParent;

    static {
        try {
            mParent = View.class.getDeclaredField("mParent");
            mParent.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    public BaseEditText(Context context) {
        super(context.getApplicationContext());
    }

    public BaseEditText(Context context, AttributeSet attrs) {
        super(context.getApplicationContext(), attrs);
    }

    public BaseEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context.getApplicationContext(), attrs, defStyleAttr);
    }

    @Override
    protected void onDetachedFromWindow() {
        try {
            if (mParent != null)
                mParent.set(this, null);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        super.onDetachedFromWindow();
    }
}

属性动画造成内存泄漏

属性动画不用的时候及时cancel()

if (mUpAnimatorSet != null) {
     if (mUpAnimatorSet.isRunning()) {
         mUpAnimatorSet.cancel();
     }
     mUpAnimatorSet.removeAllListeners();
     for (Animator animator : mUpAnimatorSet.getChildAnimations()) {
         animator.removeAllListeners();
     }
     mUpAnimatorSet = null;
}

Fragment使用RecyclerView造成内存泄漏

 @Override
    public void onDestroyView() {
        super.onDestroyView();
        mRecyclerView = null;
        mSwipeRefreshLayout = null;
        mAdapter = null;
        notDataView = null;
        errorView = null;
    }

内存泄漏检测工具

Android Profiler
可以通过不断进行横竖屏切换(或者不断重复进入Activity又返回操作),来确定activity是否泄漏。
LeakCanary

总结

内存泄漏比较隐蔽,建议集成LeakCanary,注重平时的代码CodeReview

相关文章

网友评论

      本文标题:内存泄漏分析

      本文链接:https://www.haomeiwen.com/subject/vsembctx.html