android 内存泄漏(OOM)问题总结

参考:
http://www.jianshu.com/p/5bb8c01e2bc7
http://blog.csdn.net/yaphetzhao/article/details/48521581
郭霖的分析内存的使用
胡凯大大内存优化之OOM

对于Java来说,就是new出来的Object 放在Heap上无法被GC回收


Paste_Image.png
Context
Context

Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。

Context数量=Activity数量+Service数量+进程数

Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏

尽可能地使用application context ,除非是Dialog这种必须使用activity context的情况,其他情况都使用application context,这样能避免实例不被回收导致的内存泄漏。


InnerClass匿名内部类(Handler)

以Handler为例。引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。
当activity destory之后,消息处理有可能还没执行,这样就导致消息的处理常驻在内存当中,不能被回收。
避免这种情况:
1、执行remove Handler消息队列中的消息与runnable对象。
2、使用static + WeakReference

private static class OuterHandler extends Handler {
    private final WeakReference<MainActivity> mActivity;

    public OuterHandler(MainActivity activity) {
      mActivity = new WeakReference<MainActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = mActivity.get();
      if (activity != null) {
        // do something...
      }
    }
  }

Webview

标准的WebView存在内存泄露的问题 https://code.google.com/p/android/issues/detail?id=5067

android 4.4之前是webkit内核,android4.4之后就用chromium内核了

1、通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。因为webview引发的 资源无法释放等问题 全部可以解决。

2、使用Crosswalk-lite 或者 Cordova webview代替

3、用个反射,自己关掉

public void setConfigCallback(WindowManager windowManager) {  
    try {  
        Field field = WebView.class.getDeclaredField("mWebViewCore");  
        field = field.getType().getDeclaredField("mBrowserFrame");  
        field = field.getType().getDeclaredField("sConfigCallback");  
        field.setAccessible(true);  
        Object configCallback = field.get(null);  
   
        if (null == configCallback) {  
            return;  
        }  
   
        field = field.getType().getDeclaredField("mWindowManager");  
        field.setAccessible(true);  
        field.set(configCallback, windowManager);  
    } catch(Exception e) {  
    }  
}  
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));  
}  
   
public void onDestroy() {  
    setConfigCallback(null);  
    super.onDestroy();  
}  

缺点是,这个方法是依赖android.webkit implementation,android4.4之后就用chromium内核了,也就是4.4之后这个方法就不适用了。

4、WebView 动态加载

WebView      mWebView = new WebView(getApplicationgContext());  
LinearLayout mll      = findViewById(R.id.xxx);  
mll.addView(mWebView); 
protected void onDestroy() {  
      super.onDestroy();  
      mWebView.removeAllViews();  
      mWebView.destroy()  
}  

第四种方法,如果你需要在WebView中打开链接或者你打开的页面带有flash,获得你的WebView想弹出一个dialog,都会导致从ApplicationContext到ActivityContext的强制类型转换错误,从而导致你应用崩溃。这是因为在加载flash的时候,系统会首先把你的WebView作为父控件,然后在该控件上绘制flash,他想找一个Activity的Context来绘制他,但是你传入的是ApplicationContext。


在Chromium WebView的实现中,因为WebView不是基于SurfaceView类的(因为历史遗留问题),所以,绘制内容到画布上必须在主线程来操作。当从有WebView的Activity退出到没有WebView的Activity,但是这个时候这个Activity需要绘制bitmap,就会造成崩溃:ELG绘制错误。
建议:尽量少使用getContext(),而使用getApplicationContext()来代替。


AnimationDrawable

在使用帧动画的时候,检测到oom问题。原来帧动画会一次性加载所需要的图片,如果一次性加载10多张就会发生内存泄漏问题。

Looking at the source code for AnimationDrawable, it appears to load all of the frames into memory at once, which it would basically have to do for good performance.

1、try to add largeHeap=true in your application tag of your manifest.or try using small size image.

2、分布式加载。
更改了一下网上的代码

public class LoadingImageView {

    private static final int MSG_START = 0x01;
    private static final int MSG_STOP = 0x02;
    private static final int STATE_STOP = 0xf3;
    private static final int STATE_RUNNING = 0xf4;


    private SimpleDraweeView mImageView;
    private Timer mTimer = null;
    private AnimTimerTask mTimeTask = null;

    private int mState = STATE_RUNNING;

    public static int[] mResourceIdList = new int[]{
            R.drawable.mangocity_loading_img1,
            R.drawable.mangocity_loading_img2,
            R.drawable.mangocity_loading_img3,
            R.drawable.mangocity_loading_img4,
            R.drawable.mangocity_loading_img5,
            R.drawable.mangocity_loading_img6,
            R.drawable.mangocity_loading_img7,
            R.drawable.mangocity_loading_img8,
            R.drawable.mangocity_loading_img9,
            R.drawable.mangocity_loading_img10,
            R.drawable.mangocity_loading_img11,
            R.drawable.mangocity_loading_img12
    };

    /* 记录播放位置*/
    private int mFrameIndex = 0;
    /* 播放形式*/
    private boolean isLooping = false;

    private WeakHandler AnimHanlder;


    public LoadingImageView(SimpleDraweeView imageView) {
        this.mImageView = imageView;
        this.mTimer = new Timer();
        this.mTimeTask = new AnimTimerTask();
        AnimHanlder = new WeakHandler(callback);
    }


    /**
     * 开始播放动画
     *
     * @param loop     时候循环播放
     * @param duration 动画播放时间间隔
     */
    public void start(boolean loop, long duration) {
        stop();
        isLooping = loop;
        mFrameIndex = 0;
        mState = STATE_RUNNING;
        mTimeTask = new AnimTimerTask();
        mTimer.schedule(mTimeTask, 0, duration);

    }

    /**
     * 停止动画播放
     */
    public void stop() {
        if (mTimeTask != null) {
            mFrameIndex = 0;
            mState = STATE_STOP;
            mTimer.purge();
            mTimeTask.cancel();
            mTimeTask = null;
            mImageView.setImageURI(resourceToUri(0));
        }
    }


    /**
     * 定时器任务
     */
    class AnimTimerTask extends TimerTask {
        @Override
        public void run() {
            if (mFrameIndex < 0 || mState == STATE_STOP) {
                return;
            }

            if (mFrameIndex < mResourceIdList.length) {
                Message msg = AnimHanlder.obtainMessage(MSG_START, 0, 0, null);
                msg.sendToTarget();
            } else {
                mFrameIndex = 0;
                if (!isLooping) {
                    Message msg = AnimHanlder.obtainMessage(MSG_STOP, 0, 0, null);
                    msg.sendToTarget();
                }
            }
        }
    }


    private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {

            switch (msg.what) {
                case MSG_START: {
                    if (mFrameIndex >= 0 && mFrameIndex < mResourceIdList.length && mState == STATE_RUNNING) {
                        mImageView.setImageURI(resourceToUri(mResourceIdList[mFrameIndex]));
                        mFrameIndex++;
                    }
                }
                break;
                case MSG_STOP: {
                    if (mTimeTask != null) {
                        mFrameIndex = 0;
                        mTimer.purge();
                        mTimeTask.cancel();
                        mState = STATE_STOP;
                        mTimeTask = null;
                        mImageView.setImageURI(resourceToUri(0));
                    }
                }
                break;
                default:
                    break;
            }


            return false;
        }
    };


    public static Uri resourceToUri(int resId) {

        return new Uri.Builder()
                .scheme(UriUtil.LOCAL_RESOURCE_SCHEME) // "res"
                .path(String.valueOf(resId))
                .build();
    }


}



Html.for

当使用textview加载富文本的时候,涉及到bitmap的加载,过程当中可能会发生OOM

移步:http://www.jianshu.com/p/9d6e0bdfcf0e


static

因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象。

Paste_Image.png

singleton(单例模式)
public class Singleton {
    private static Singleton instance;
    private Context mContext;

    private Singleton(Context context) {
        this.mContext = context;
    }

    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }
}

这是一个非线性安全的单例,instance为静态对象,其生命周期与application的生命周期一致,当application销毁的时候才被回收。假如是activity持有instance这个对象,当activity destroy之后,instance还是会常驻在内存当中,并不会被回收,这样就会导致了内存泄漏的问题出现。


shareSDK

常常会忽略

ShareSDK.stopSDK();

Enum(枚举)

“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”

枚举通常要求的是静态常量的两倍多的内存。应该严格避免在Android上使用枚举

Android 提供了注解来优化枚举,使用方法如下:

Paste_Image.png

Cursor,Stream没有close,View没有recyle

在View中调用reset()

 public void reset() {
     if (mHasRecyled) {            
         return;
     }
 ...
     SubAreaShell.recycle(mActionBtnShell);
     mActionBtnShell = null;
 ...
     mIsDoingAvatartRedPocketAnim = false;        
     if (mAvatarArea != null) {
             mAvatarArea.reset();
     }        
     if (mNickNameArea != null) {
         mNickNameArea.reset();
     }
 }

在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。


注册监听器的泄漏

在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。


集合中对象没清理造成的内存泄漏

通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

 private List<EmotionPanelInfo> data;    
 public void onDestory() {        
     if (data != null) {
         data.clear();
         data = null;
     }
 }

Drawable

2.3的系统,把drawable添加到缓存容器,因为drawable与View的强应用,很容易导致activity发生泄漏。而从4.0开始,就不存在这个问题。解决这个问题,需要对2.3系统上的缓存drawable做特殊封装,处理引用解绑的问题,避免泄漏的情况。

Drawable.Callback引起的内存泄漏
Drawable对象持有Drawable.callback的引用。当把一个Drawable对象设置到一个View时,Drawable对象会持有该View的引用作为Drawable.Callback

Paste_Image.png

避免Drawable.Callback引起内存泄漏
• 尽量不要在static成员中保存Drawable对象
• 对于需要保存的Drawable对象, 在需要时调用Drawable#setCallback(null).

Paste_Image.png

该问题主要产生在 4.0 以前,因为在 2.3.7 及以下版本 Drawable 的 setCallback 方法的实现是直接赋值,而从 4.0.1 开始,setCallback 采用了弱引用处理这个问题,避免了内存泄露问题。


AlertDialog 造成的内存泄露
new AlertDialog.Builder(this)
    .setPositiveButton("Baguette", new DialogInterface.OnClickListener() {
    @Override public void onClick(DialogInterface dialog, int which) {
      MyActivity.this.makeBread();
    }
  })
    .show();

DialogInterface.OnClickListener 的匿名实现类持有了 MainActivity 的引用;

而在 AlertDialog 的实现中,OnClickListener 类将被包装在一个 Message 对象中(具体可以看 AlertController 类的 setButton 方法),而且这个 Message 会在其内部被复制一份(AlertController 类的 mButtonHandler 中可以看到),两份 Message 中只有一个被 recycle,另一个(OnClickListener 的成员变量引用的 Message 对象)将会泄露!

解决办法:

Android 5.0 以上不存在此问题;
Message 对象的泄漏无法避免,但是如果仅仅是一个空的 Message 对象,将被放入对象池作为后用,是没有问题的;
让 DialogInterface.OnClickListener 对象不持有外部类的强引用,如用 static 类实现;
在 Activity 退出前 dismiss dialog!


Bitmap

首先,Android对Bitmap内存(像素数据)的分配区域在不同版本上是有区分的:

As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.

详情见Managing Bitmap Memory

从3.0开始,Bitmap像素数据和Bitmap对象一起存放在Dalvik堆中,而在3.0之前,Bitmap像素数据存放在Native内存中。
所以,在3.0之前,Bitmap像素数据在Nativie内存的释放是不确定的,容易内存溢出而Crash,官方强烈建议调用recycle()(当然是在确定不需要的时候);而在3.0之后,则无此要求。

首先强调一点,加载图片属于耗时操作请放到非 UI 线程进行!

Android 中加载图片时一般是按每像素占 4 byte 来处理的,拿计算器算一下可以发现,如果原封不动的加载一张图片是非常占内存的!因此非常容易 OOM。
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用是很重要的,通常来说有下面2个措施:使用对象池和缩放 Bitmap。

• 对象池

在启动时预先申请一块内存给对象池使用。加载图片时根据特定算法(LRU),从对象池中找到要淘汰的 Bitmap 对象,将其内存腾出来给新图片用,这样每次加载图片也不用去向 JVM 申请内存,也避免了启动 GC 来腾出内存,可以有效防止内存抖动,提升加载效率。

在 Android 中可以让 BitmapOption 的 inBitmap 属性指向当前某个已创建的 Bitmap 对象,后续在解码时传入这个 option 就可以复用这个 Bitmap 对象的内存空间(要求两者像素格式必须一样,例如都是 ARGB8888。也可以按像素格式创建不同的对象来复用)。

注意,这个 inBitmap 参数在 API 11-18 时,后续要解码的图片大小必须和当前这个 Bitmap 一模一样,才能复用,否则后面的图片就无法复用了。在 API 19 以后就没这个限制了,只要后续 Bitmap 大小小于等于要复用的 Bitmap 即可。

inBitmap

BitmapFactory.Options.inBitmap是Android3.0新增的一个属性,如果设置了这个属性则会重用这个Bitmap的内存从而提升性能。
但是这个重用是有条件的,在Android4.4之前只能重用相同大小的Bitmap,Android4.4+则只要比重用Bitmap小即可。

• 缩放 Bitmap

createScaledBitmap() 传入指定宽高即可,该方法缺陷是需要传入一个已经加载完毕的 Bitmap 图片。。。都加载完了还要你干嘛?

inSampleSize。该值只能是 2 的倍数或者 1。原理是解码时根据这个值,如果是 1,就记录每一个像素的值。如果值为 2,Android 就从每 4 个像素中取出两个像素记录下来。
如果我们需要缩放的倍数不是 2 的倍数,即 inSampleSize 满足不了需求时,可以考虑设置

BitmapOption 的 inScaled 为 true,同时设置 inDensity 和 inTargetDensity 属性,这样就可以指定想要的 Bitmap 为原来的任意分之一大小了。该算法很复杂,如果原图较大,那么缩放加载时可能会耗时较长。可以和 inSampleSize 结合使用,用 inSampleSize 缩放,减小大小后,再用这个方法缩放 。

Paste_Image.png

decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,577评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,214评论 0 17
  • HereAndroid的内存优化是性能优化中很重要的一部分,而避免OOM又是内存优化中比较核心的一点。这是一篇关于...
    HarryXR阅读 3,703评论 1 24
  • 本文转载来源 http://www.csdn.net/article/2015-09-18/2825737/1 (...
    yoosir阅读 1,043评论 0 5
  • 黑色蔓延过我的身体 淹没了天空 沉默了我的大地 就连我的实地也空了下去 雨水忽然刷过寒冷的空气 带着几分惆怅和幽怨...
    王不烦阅读 165评论 0 0