參考:
http://www.reibang.com/p/5bb8c01e2bc7
http://blog.csdn.net/yaphetzhao/article/details/48521581
郭霖的分析內存的使用
胡凱大大內存優(yōu)化之OOM
對于Java來說颤专,就是new出來的Object 放在Heap上無法被GC回收
Context
Context類本身是一個純abstract類一膨,它有兩個具體的實現(xiàn)子類:ContextImpl和ContextWrapper。
Context數(shù)量=Activity數(shù)量+Service數(shù)量+進程數(shù)
Activity Context被傳遞到其他實例中慨代,這可能導致自身被引用而發(fā)生泄漏
盡可能地使用application context 毯炮,除非是Dialog這種必須使用activity context的情況瘟则,其他情況都使用application context,這樣能避免實例不被回收導致的內存泄漏膜钓。
InnerClass匿名內部類(Handler)
以Handler為例嗽交。引用關系鏈是Looper -> MessageQueue -> Message -> Handler -> Activity。
當activity destory之后颂斜,消息處理有可能還沒執(zhí)行夫壁,這樣就導致消息的處理常駐在內存當中,不能被回收沃疮。
避免這種情況:
1盒让、執(zhí)行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所在的進程可以根據業(yè)務的需要選擇合適的時機進行銷毀左医,從而達到內存的完整釋放。因為webview引發(fā)的 資源無法釋放等問題 全部可以解決同木。
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 動態(tài)加載
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的時候唁盏,系統(tǒng)會首先把你的WebView作為父控件内狸,然后在該控件上繪制flash,他想找一個Activity的Context來繪制他厘擂,但是你傳入的是ApplicationContext昆淡。
在Chromium WebView的實現(xiàn)中,因為WebView不是基于SurfaceView類的(因為歷史遺留問題)刽严,所以昂灵,繪制內容到畫布上必須在主線程來操作。當從有WebView的Activity退出到沒有WebView的Activity舞萄,但是這個時候這個Activity需要繪制bitmap眨补,就會造成崩潰:ELG繪制錯誤。
建議:盡量少使用getContext()倒脓,而使用getApplicationContext()來代替撑螺。
AnimationDrawable
在使用幀動畫的時候,檢測到oom問題崎弃。原來幀動畫會一次性加載所需要的圖片甘晤,如果一次性加載10多張就會發(fā)生內存泄漏問題含潘。
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 時候循環(huán)播放
* @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的加載塞弊,過程當中可能會發(fā)生OOM
移步:http://www.reibang.com/p/9d6e0bdfcf0e
static
因為static的生命周期過長漱逸,和應用的進程保持一致,使用不當很可能導致對象泄漏居砖,在Android中應該謹慎使用static對象虹脯。
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為靜態(tài)對象奏候,其生命周期與application的生命周期一致循集,當application銷毀的時候才被回收。假如是activity持有instance這個對象蔗草,當activity destroy之后咒彤,instance還是會常駐在內存當中,并不會被回收咒精,這樣就會導致了內存泄漏的問題出現(xiàn)镶柱。
shareSDK
常常會忽略
ShareSDK.stopSDK();
Enum(枚舉)
“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”
枚舉通常要求的是靜態(tài)常量的兩倍多的內存。應該嚴格避免在Android上使用枚舉
Android 提供了注解來優(yōu)化枚舉模叙,使用方法如下:
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();
}
}
在程序中我們經常會進行查詢數(shù)據庫的操作范咨,但時常會存在不小心使用Cursor之后沒有及時關閉的情況故觅。這些Cursor的泄露,反復多次出現(xiàn)的話會對內存管理產生很大的負面影響渠啊,我們需要謹記對Cursor對象的及時關閉输吏。
注冊監(jiān)聽器的泄漏
在Android程序里面存在很多需要register與unregister的監(jiān)聽器,我們需要確保在合適的時候及時unregister那些監(jiān)聽器替蛉。自己手動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的系統(tǒng)腌乡,把drawable添加到緩存容器盟劫,因為drawable與View的強應用,很容易導致activity發(fā)生泄漏与纽。而從4.0開始侣签,就不存在這個問題。解決這個問題急迂,需要對2.3系統(tǒng)上的緩存drawable做特殊封裝影所,處理引用解綁的問題,避免泄漏的情況僚碎。
Drawable.Callback引起的內存泄漏
Drawable對象持有Drawable.callback的引用猴娩。當把一個Drawable對象設置到一個View時,Drawable對象會持有該View的引用作為Drawable.Callback
避免Drawable.Callback引起內存泄漏
? 盡量不要在static成員中保存Drawable對象
? 對于需要保存的Drawable對象勺阐, 在需要時調用Drawable#setCallback(null).
該問題主要產生在 4.0 以前卷中,因為在 2.3.7 及以下版本 Drawable 的 setCallback 方法的實現(xiàn)是直接賦值,而從 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 的匿名實現(xiàn)類持有了 MainActivity 的引用懒闷;
而在 AlertDialog 的實現(xiàn)中十减,OnClickListener 類將被包裝在一個 Message 對象中(具體可以看 AlertController 類的 setButton 方法),而且這個 Message 會在其內部被復制一份(AlertController 類的 mButtonHandler 中可以看到)愤估,兩份 Message 中只有一個被 recycle嫉称,另一個(OnClickListener 的成員變量引用的 Message 對象)將會泄露!
解決辦法:
Android 5.0 以上不存在此問題灵疮;
Message 對象的泄漏無法避免,但是如果僅僅是一個空的 Message 對象壳繁,將被放入對象池作為后用震捣,是沒有問題的;
讓 DialogInterface.OnClickListener 對象不持有外部類的強引用闹炉,如用 static 類實現(xiàn)蒿赢;
在 Activity 退出前 dismiss dialog!
Bitmap
首先,Android對Bitmap內存(像素數(shù)據)的分配區(qū)域在不同版本上是有區(qū)分的:
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像素數(shù)據和Bitmap對象一起存放在Dalvik堆中羡棵,而在3.0之前,Bitmap像素數(shù)據存放在Native內存中嗅钻。
所以皂冰,在3.0之前店展,Bitmap像素數(shù)據在Nativie內存的釋放是不確定的,容易內存溢出而Crash秃流,官方強烈建議調用recycle()(當然是在確定不需要的時候)赂蕴;而在3.0之后,則無此要求舶胀。
首先強調一點概说,加載圖片屬于耗時操作請放到非 UI 線程進行!
Android 中加載圖片時一般是按每像素占 4 byte 來處理的嚣伐,拿計算器算一下可以發(fā)現(xiàn)糖赔,如果原封不動的加載一張圖片是非常占內存的!因此非常容易 OOM轩端。
Bitmap是一個極容易消耗內存的大胖子放典,減小創(chuàng)建出來的Bitmap的內存占用是很重要的,通常來說有下面2個措施:使用對象池和縮放 Bitmap船万。
? 對象池
在啟動時預先申請一塊內存給對象池使用刻撒。加載圖片時根據特定算法(LRU),從對象池中找到要淘汰的 Bitmap 對象耿导,將其內存騰出來給新圖片用声怔,這樣每次加載圖片也不用去向 JVM 申請內存,也避免了啟動 GC 來騰出內存舱呻,可以有效防止內存抖動醋火,提升加載效率。
在 Android 中可以讓 BitmapOption 的 inBitmap 屬性指向當前某個已創(chuàng)建的 Bitmap 對象箱吕,后續(xù)在解碼時傳入這個 option 就可以復用這個 Bitmap 對象的內存空間(要求兩者像素格式必須一樣芥驳,例如都是 ARGB8888。也可以按像素格式創(chuàng)建不同的對象來復用)茬高。
注意兆旬,這個 inBitmap 參數(shù)在 API 11-18 時,后續(xù)要解碼的圖片大小必須和當前這個 Bitmap 一模一樣怎栽,才能復用丽猬,否則后面的圖片就無法復用了。在 API 19 以后就沒這個限制了熏瞄,只要后續(xù) Bitmap 大小小于等于要復用的 Bitmap 即可脚祟。
inBitmap
BitmapFactory.Options.inBitmap是Android3.0新增的一個屬性,如果設置了這個屬性則會重用這個Bitmap的內存從而提升性能强饮。
但是這個重用是有條件的由桌,在Android4.4之前只能重用相同大小的Bitmap,Android4.4+則只要比重用Bitmap小即可。
? 縮放 Bitmap
createScaledBitmap() 傳入指定寬高即可行您,該方法缺陷是需要傳入一個已經加載完畢的 Bitmap 圖片铭乾。。邑雅。都加載完了還要你干嘛片橡?
inSampleSize。該值只能是 2 的倍數(shù)或者 1淮野。原理是解碼時根據這個值捧书,如果是 1,就記錄每一個像素的值骤星。如果值為 2经瓷,Android 就從每 4 個像素中取出兩個像素記錄下來。
如果我們需要縮放的倍數(shù)不是 2 的倍數(shù)洞难,即 inSampleSize 滿足不了需求時舆吮,可以考慮設置
BitmapOption 的 inScaled 為 true,同時設置 inDensity 和 inTargetDensity 屬性队贱,這樣就可以指定想要的 Bitmap 為原來的任意分之一大小了色冀。該算法很復雜,如果原圖較大柱嫌,那么縮放加載時可能會耗時較長锋恬。可以和 inSampleSize 結合使用编丘,用 inSampleSize 縮放与学,減小大小后,再用這個方法縮放 嘉抓。
decode format:解碼格式索守,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差異抑片。