对于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 mActivity;
public OuterHandler(MainActivity activity) {
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
// do something...
}
}
}
Webview
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
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 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.
从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,存在很大差异。