Android内存泄露

来源:互联网 发布:手机防辐射软件 编辑:程序博客网 时间:2024/06/07 02:44

内存泄露

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放。

内存泄露的危害

内存泄漏是造成OOM(内存溢出)主要原因之一。由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,就会造成内存溢出而导致应用crash。

常见内存泄漏以及解决办法

1、非静态内部类的静态实例容易造成内存泄漏

public class TestActivity extends AppCompatActivity {    static Demo mInstance = null;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //模拟内存泄露        if (sInstance == null) {            sInstance = new Demo();        }    }    class Demo {        void doSomething() {            Log.d("TAG", "doSomething");        }    }} 

上面的代码中的mInstance 实例类型为静态实例,在第一个TestActivity mActivity实例创建时,mInstance会获得并一直持有activity的引用。当MainAcitivity销毁后重建,因为mInstance持有mActivity的引用,所以mActivity是无法被GC回收的,进程中会存在2个MainActivity实例(mActivity和重建后的MainActivity实例),这个mActivity对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。
所以,对于lauchMode不是singleInstance的Activity, 应该避免在Activity里面实例化其非静态内部类的静态实例或者内部类改成静态内部类,不再持有对MainActivity的引用即可。

2、使用handler时的内存问题

public class TestActivity extends AppCompatActivity {    private Handler mHandler = new Handler();    private TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.text);//模拟内存泄露        mHandler.postDelayed(new Runnable() {            @Override            public void run() {                mTextView.setText("模拟内存泄露");            }        }, 3 * 60 * 1000);        finish();    }    @Override    protected void onDestroy() {        super.onDestroy();    }    ......}

上述代码通过内部类的方式创建mHandler对象,此时mHandler会隐式地持有一个外部类对象引用这里就是TestActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
要想避免Handler引起内存泄漏问题,需要我们在Activity关闭退出的时候的移除消息队列中所有消息和所有的Runnable。上述代码只需在onDestroy()中调用mHandler.removeCallbacksAndMessages(null);就行了。

3、错误使用单例造成的内存泄漏

public class Manager {    private static Manager mInstance;    private Context mContext;    private LoginManager(Context context) {        this.mContext = context;    }    public static Manager getInstance(Context context) {        if (mInstance == null) {            synchronized (LoginManager.class) {                if (mInstance == null) {                    mInstance = new Manager (context);                }            }        }        return mInstance;    }    ......}

如果我们在Activity中实例化了该类,当Activity被销毁时,就会引发内存泄露。
解决办法就是Context和app的生命周期一样长。

4、资源未关闭造成的内存泄漏
资源性对象比如BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于Java虚拟机内,还存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该立即调用它的close()函数,将其关闭掉,然后再置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。

5、线程造成的内存泄漏

public class TestActivity extends AppCompatActivity {    private AsyncTask<Void, Void, Integer> asyncTask;    private TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.text);        testAsyncTask();        finish();    }    private void testAsyncTask() {        asyncTask = new AsyncTask<Void, Void, Integer>() {            @Override            protected Integer doInBackground(Void... params) {                int i = 0;                //模拟耗时操作                while (!isCancelled()) {                    i++;                    if (i > 100000) {                        break;                    }                }                return i;            }            @Override            protected void onPostExecute(Integer integer) {                super.onPostExecute(integer);                mTextView.setText(String.valueOf(integer));            }        };        asyncTask.execute();    }    @Override    protected void onDestroy() {        super.onDestroy();    }}

对于上面的例子来说,在处理一个比较耗时的操作时,可能还没处理结束TestActivity就执行了退出操作,但是此时AsyncTask依然持有对TestActivity的引用就会导致TestActivity无法释放回收引发内存泄漏。

6、WebView造成的内存泄漏
WebView解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。
这种情况的解决办法:
1.不在xml中定义 Webview ,而是在需要的时候在Activity中创建,并且Context使用 getApplicationgContext()。

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(    ViewGroup.LayoutParams.MATCH_PARENT,    ViewGroup.LayoutParams.MATCH_PARENT);mWebView = new WebView(getApplicationContext());mWebView.setLayoutParams(params);mLayout.addView(mWebView);

2.在 Activity 销毁( WebView )的时候,先让 WebView 加载null内容,然后移除 WebView,再销毁 WebView,最后置空。

@Override    protected void onDestroy() {        if (mWebView != null) {            mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);            mWebView.clearHistory();            ((ViewGroup) mWebView.getParent()).removeView(mWebView);            mWebView.destroy();            mWebView = null;        }        super.onDestroy();    }

LeakCanary

leakCanary是Square开源框架,是一个Android和Java的内存泄露检测库,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知。通过它可以大幅度减少开发中遇到的oom问题,大大提高app的质量。
LeakCanary 中文使用说明
LeakCanary: 让内存泄露无所遁形

原创粉丝点击