Android优化之内存泄漏

最近公司要求优化项目,于是就检查了自己负责的代码,然后总结了一些内存泄漏的几种情况。

java是垃圾回收语言的一种,优点是开发者无需关心管理内存,缺点就是容易内存泄漏,容易浪费掉未释放的内存。

造成内存泄漏的原因:

逻辑内存泄漏(logical memory leak)的原因是:当应用不再需要这个对象,但是仍未释放该对象的所有引用。
一般内存泄漏(traditional memory leak)的原因是:当该对象的所有引用都已经释放了,对象仍未被释放。

android内存泄漏的几种情况:

1.使用static

一般使用static,如果不注意那就离内存泄漏不远了。

static Activity 

活在Activity生命周期之外的线程。没有清空对Activity的强引用。
在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。
如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

static View 

如果一个View初始化过于频繁,也许有必要在内存中保存一个View,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。释放资源时,把这个static view置null即可。

2.注意context

在Activity中上下文对象有时候被一个耗时操作或者长时间不能释放的对象依赖时,需要使用Application的上下文对象。如请求加载网络时,传入的最好是工程里面封装好的Application的上下文对象,不要使用Activity里面的,否则可能会发生内存泄漏。

3.内部类

内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。匿名内部类也是如此。下面就是一个内部类被标记为静态内部类造成的内存泄漏。

private static Object inner; 
void createInnerClass() { 
class InnerClass { } 
inner = new InnerClass();
}
 View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) { 
createInnerClass(); 
nextActivity(); }});

4.Thread

当Activity被销毁了,线程里面的任务还未完成,就造成了内存泄漏,这时有两种方法类处理:一种是能不能在Activity结束是也结束thread里面的任务。另一种是重写Thread进行相应的处理,与Activity进行分离。

解决例子:

//防止线程内存泄漏 重新封装Thread
    private static class MyThread extends Thread {
        private WeakReference<Activity> mThreadWR;
        private Activity activity;
        public MyThread(Activity activity) {
            mThreadWR = new WeakReference<Activity>(activity);
            this.activity = activity;
        }

        @Override
        public void run() {
            super.run();
            if (mThreadWR == null)
                return;
            if (mThreadWR.get() != null){
                mThreadWR.get().startDo();
            }
        }
    }
Thread、AsyncTask、Handler都可以使用这种方式解决内存泄漏问题

5.AsyncTask

匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了,这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。

6.Handler

AsyncTsk原理一样,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。解决方法:可已在Activity结束之前,清空Handler里面的内容。解决方法:一种是重新封装Handler,处理Handler。另一种是清空Handler消息队列里面的内容,使用方法handler.removeCallbacksAndMessages()

7.TimerTask

跟Thread类似,一定要在Activity结束前停止TimerTask里面的任务,并释放这个对象,否则会引发内存泄漏。

8.Sensor Manager

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

Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); 

sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
} 
View smButton = findViewById(R.id.sm_button);

smButton.setOnClickListener(new View.OnClickListener() { 
@Override public void onClick(View v) { 
registerListener(); 
nextActivity(); 
}});
9. 集合中的对象未清理造成内存泄露

如果一个对象放入到ArrayListHashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏。

10.资源未关闭或释放导致内存泄露
在使用IOFile流或者SqliteCursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。
11.属性动画造成内存泄露

动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

12.webView造成的内存泄漏

webView加载网页后会长期占用内存,所以需要在Activity销毁之前来释放内存。

webView的CallBack持有Activity对象造成无法释放内存。

在网上找到了一种解决办法,在Activity结束前调用即可:

public void destroy() {
    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();

        try {
            mWebView.destroy();
        } catch (Throwable ex) {

        }
    }
}
13.软键盘引起的内存泄漏
软键盘源码里面很复杂,看了一圈源码发现软件盘内部持有Activity的引用,导致内存泄漏。没有很直接的解决方法,通过源码知道了可以从内部入手,封装工具类,将软键盘与Activity进行分离。 去获取InputMethodManager的关联View,通过View.getContext()与Activity A进行对比,如果发现两者相同,就表示需要回收;如果两者不一样,则表示有新的界面已经在使用InputMethodManager了,直接不处理就可以了。


总结:一般常发生的内存泄漏大致就是这些,注意以上这些点应该能减少一些内存泄漏的问题。

         当然内存泄漏不易被发现,可以使用LeakCanary这个工具进行检查内存泄漏。这个工具的使用就不在介绍了,在网上一找一大把。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值