【Android 內(nèi)存泄漏】
引用:
★★★ 【知識必備】內(nèi)存泄漏全解析眼虱,從此拒絕ANR,讓OOM遠離你的身邊汉买,跟內(nèi)存泄漏say byebye
★★★★★ Android 中內(nèi)存泄漏的原因和解決方案
引言
對于C++來說,內(nèi)存泄漏就是new出來的對象沒有delete佩脊,俗稱野指針蛙粘;而對于java來說,就是new出來的Object放在Heap上無法被GC回收威彰。
java的內(nèi)存分配:
靜態(tài)存儲區(qū)(方法區(qū))
編譯時就分配好出牧,在程序整個運行期間都存在。它主要存放靜態(tài)數(shù)據(jù)歇盼、全局 static 數(shù)據(jù)和常量舔痕。
棧區(qū)
當方法執(zhí)行時,會在棧區(qū)內(nèi)存中創(chuàng)建方法體內(nèi)部的局部變量(其中包括基礎數(shù)據(jù)類型豹缀、對象的引用)伯复,方法結束后自動釋放內(nèi)存。
因為棧內(nèi)存分配運算內(nèi)置于處理器的指令集中耿眉,效率很高边翼,但是分配的內(nèi)存容量有限。
堆區(qū)
又稱動態(tài)內(nèi)存分配鸣剪,通常用來存放new出來的對象组底,也就是對象的實例。由java垃圾回收期回收筐骇。
棧與堆的區(qū)別
在方法體內(nèi)定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧內(nèi)存中分配的债鸡。
當在一段方法塊中定義一個變量時,Java 就會在棧中為該變量分配內(nèi)存空間铛纬,當超過該變量的作用域后厌均,該變量也就無效了,分配給它的內(nèi)存空間也將被釋放掉告唆,該內(nèi)存空間可以被重新使用棺弊。
堆內(nèi)存用來存放所有由 new 創(chuàng)建的對象(包括該對象其中的所有成員變量)和數(shù)組。
在堆中分配的內(nèi)存擒悬,將由 Java 垃圾回收器來自動管理模她。
在堆中產(chǎn)生了一個數(shù)組或者對象后,還可以在棧中定義一個特殊的變量懂牧,這個變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址侈净,這個特殊的變量就是我們上面說的引用變量。我們可以通過這個引用變量來訪問堆中的對象或者數(shù)組。
舉個例子:
public class Sample {
int s1 = 0;
Sample mSample1 = new Sample();
public void method() {
int s2 = 1;
Sample mSample2 = new Sample();
}
}
Sample mSample3 = new Sample();
Sample 類的局部變量 s2 和引用變量 mSample2 都是存在于棧中畜侦,但 mSample2 指向的對象是存在于堆上的元扔。 mSample3 指向的對象s實體存放在堆上,包括這個對象的所有成員變量 s1 和 mSample1旋膳,而它自己存在于棧中澎语。
結論:
局部變量的基本數(shù)據(jù)類型和引用存儲于棧中,引用的對象實體存儲于堆中验懊∮搅—— 因為它們屬于方法中的變量,生命周期隨方法而結束鲁森。
成員變量全部存儲與堆中(包括基本數(shù)據(jù)類型祟滴,引用和引用的對象實體)—— 因為它們屬于類,類對象終究是要被new出來使用的歌溉。
Java如何管理內(nèi)存
Java的內(nèi)存管理就是對象的分配和釋放問題垄懂。
在 Java 中,程序員需要通過關鍵字 new 為每個對象申請內(nèi)存空間 (基本類型除外)痛垛,所有的對象都在堆 (Heap)中分配空間草慧。
在 Java 中,內(nèi)存的分配是由程序完成的匙头,而內(nèi)存的釋放是由 GC 完成的漫谷,這種收支兩條線的方法確實簡化了程序員的工作。但同時蹂析,它也加重了JVM的工作舔示。這也是 Java 程序運行速度較慢的原因之一。因為电抚,GC 為了能夠正確釋放對象惕稻,GC 必須監(jiān)控每一個對象的運行狀態(tài),包括對象的申請蝙叛、引用俺祠、被引用、賦值等借帘,GC 都需要進行監(jiān)控蜘渣。
監(jiān)視對象狀態(tài)是為了更加準確地、及時地釋放對象肺然,而釋放對象的根本原則就是該對象不再被引用蔫缸。
為了更好理解 GC 的工作原理,我們可以將對象考慮為有向圖的頂點狰挡,將引用關系考慮為圖的有向邊捂龄,有向邊從引用者指向被引對象。另外加叁,每個線程對象可以作為一個圖的起始頂點倦沧,例如大多程序從 main 進程開始執(zhí)行,那么該圖就是以 main 進程頂點開始的一棵根樹它匕。在這個有向圖中展融,根頂點可達的對象都是有效對象,GC將不回收這些對象豫柬。如果某個對象 (連通子圖)與這個根頂點不可達(注意告希,該圖為有向圖),那么我們認為這個(這些)對象不再被引用烧给,可以被 GC 回收燕偶。 以下,我們舉一個例子說明如何用有向圖表示內(nèi)存管理础嫡。對于程序的每一個時刻指么,我們都有一個有向圖表示JVM的內(nèi)存分配情況。以下右圖榴鼎,就是左邊程序運行到第6行的示意圖伯诬。
Java使用有向圖的方式進行內(nèi)存管理,可以消除引用循環(huán)的問題巫财,例如有三個對象盗似,相互引用,只要它們和根進程不可達的平项,那么GC也是可以回收它們的赫舒。這種方式的優(yōu)點是管理內(nèi)存的精度很高,但是效率較低闽瓢。另外一種常用的內(nèi)存管理技術是使用計數(shù)器号阿,例如COM模型采用計數(shù)器方式管理構件,它與有向圖相比鸳粉,精度行低(很難處理循環(huán)引用的問題)扔涧,但執(zhí)行效率很高。
Java中的內(nèi)存泄漏
在Java中届谈,內(nèi)存泄漏就是存在一些被分配的對象枯夜,這些對象有下面兩個特點:
首先,這些對象是可達的艰山,即在有向圖中湖雹,存在通路可以與其相連;
其次曙搬,這些對象是無用的摔吏,即程序以后不會再使用這些對象鸽嫂。
如果對象滿足這兩個條件,這些對象就可以判定為Java中的內(nèi)存泄漏征讲,這些對象不會被GC所回收据某,然而它卻占用內(nèi)存。
四種引用類型的介紹
1. 強引用(StrongReference)
JVM 寧可拋出 OOM 诗箍,也不會讓 GC 回收具有強引用的對象癣籽;
2. 軟引用(SoftReference)
只有在內(nèi)存空間不足時,才會被回收的對象滤祖;
3. 弱引用(WeakReference)
在 GC 時筷狼,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否匠童,都會回收它的內(nèi)存埂材;
4. 虛引用(PhantomReference)
任何時候都可以被GC回收,當垃圾回收器準備回收一個對象時汤求,如果發(fā)現(xiàn)它還有虛引用楞遏,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之關聯(lián)的引用隊列中首昔。程序可以通過判斷引用隊列中是否存在該對象的虛引用寡喝,來了解這個對象是否將要被回收±掌妫可以用來作為GC回收Object的標志预鬓。
對比
資源來自 https://developer.android.com/topic/performance/graphics/cache-bitmap.html(Google官網(wǎng),你可能需要梯子)
在 Google 官網(wǎng)介紹中赊颠,有這樣一段話格二。
大意是說,從API 9 開始竣蹦,垃圾收集器會更積極收集軟/弱引用顶猜,使軟/弱引用相當無效。而且在API 3 之前痘括,位圖的備份數(shù)據(jù)存儲在本地內(nèi)存中长窄,該內(nèi)存不以可預測的方式釋放,可能導致應用程序短暫超出其內(nèi)存限制和崩潰纲菌。
內(nèi)存抖動
這樣的圖很熟悉有木有挠日?當這樣的時候,說明你的內(nèi)存真的在變少了翰舌。
內(nèi)存泄漏檢測工具
在這里先推薦兩種內(nèi)存檢查的方式:
MAT
MAT(Memory Analyzer Tool)嚣潜,點我下載。具體使用這個網(wǎng)上一大篇椅贱。
LeakCanary
強大的開源內(nèi)存檢測工具LeakCanary懂算。
leakcanary是一個開源項目只冻,一個內(nèi)存泄露自動檢測工具,是著名的GitHub開源組織Square貢獻的计技,它的主要優(yōu)勢就在于自動化過早的發(fā)覺內(nèi)存泄露喜德、配置簡單、抓取貼心酸役,缺點在于還存在一些bug,不過正常使用百分之九十情況是OK的驾胆,其核心原理與MAT工具類似涣澡。
配置十分簡單,可以看官方文檔丧诺,簡單直白入桂。
Android 內(nèi)存泄漏
概述
在 Android 中內(nèi)存泄漏的原因其實和在 Java 中是一樣的,即某個對象已經(jīng)不需要再用了驳阎,但是它卻沒有被系統(tǒng)所回收抗愁,一直在內(nèi)存中占用著空間,而導致它無法被回收的原因大多是由于它被一個生命周期更長的對象所引用呵晚。其實要分析 Android 中的內(nèi)存泄漏的原因非常簡單坦袍,只要理解一句話弧岳,那就是生命周期較長的對象持有生命周期較短的對象的引用。
舉個例子,如果一個 Activity 被一個單例對象所引用娩嚼,那么當退出這個 Activity 時,由于單例的對象依然存在(單例對象的生命周期跟整個 App 的生命周期一致)毙玻,而單例對象又持有 Activity 的引用失驶,這就導致了此 Activity 無法被回收,從而造成內(nèi)存泄漏驶俊。
單例造成的內(nèi)存泄漏
? 單例的使用在我們的程序中隨處可見娶耍,因為使用它可以完美的解決我們在程序中重復創(chuàng)建對象的問題,不過可別小瞧它饼酿。由于單例的靜態(tài)特性榕酒,使得它的生命周期和應用的生命周期會一樣長,所以一旦使用有誤故俐,小心無限制的持有Activity的引用而導致內(nèi)存泄漏奈应。比如,下面的例子购披。
public class SingletonBad {
private static SingletonBad singletonBad;
private Context context;
private SingletonBad(Context context){
this.context = context;
}
public static SingletonBad getInstance(Context context){
if (singletonBad == null){
singletonBad = new SingletonBad(context);
}
return singletonBad;
}
}
原因:
這個錯誤在生活中再普遍不過杖挣,很正常的一個單例模式,可就由于傳入的是一個Context刚陡,而這個Context的生命周期的長短就尤為重要了惩妇。如果我們傳入的是Activity的Context株汉,當這個Context所對應的Activity退出的時候,由于該Context的引用被單例對象所持有歌殃,其生命周期等于整個應用程序的生命周期乔妈,所以當前Activity退出時它的內(nèi)存并不會回收,這造成的內(nèi)存泄漏就可想而知了氓皱。
解決:
正確的方式應該是把傳入的Context換為和應用的生命周期一樣長的Application的Context;
public class SingletonGood {
private static SingletonGood singletonGood;
private Context context;
private SingletonGood(Context context){
this.context = context.getApplicationContext();//獲取Application的context避免內(nèi)存泄漏
}
public static SingletonGood getInstance(Context context){
if (singletonGood == null){
singletonGood = new SingletonGood(context);
}
return singletonGood;
}
}
當然路召,你也可以直接連Context都不用傳入了。重寫application波材,提供靜態(tài)的getContext方法
public class DemoApplication extends Application {
private static DemoApplication demoApplication;
public static DemoApplication getInstance(){
return demoApplication;
}
public static Context getContext(){
return demoApplication.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
demoApplication = this;
}
}
自然就可以直接不用傳入Context
public class SingletonGoodNew {
private static SingletonGoodNew singletonGoodNew;
private Context context;
private SingletonGoodNew(){
this.context = DemoApplication.getContext();
}
public static SingletonGoodNew getInstance(){
if (singletonGoodNew == null){
singletonGoodNew = new SingletonGoodNew();
}
return singletonGoodNew;
}
}
非靜態(tài)內(nèi)部類造成的內(nèi)存泄漏
我們知道股淡,非靜態(tài)內(nèi)部類默認會持有外部類的引用,如果這個非靜態(tài)的內(nèi)部類的生命周期比它的外部類的生命周期長廷区,那么當銷毀外部類的時候唯灵,它無法被回收,就會造成內(nèi)存泄漏隙轻。
外部類中持有非靜態(tài)內(nèi)部類的靜態(tài)對象
假設 Activity 的代碼是這樣的
public class MainActivity extends AppCompatActivity {
private static Test test;
private class Test {
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (test == null) {
test = new Test();
}
}
}
原因:
這個其實和單例的原理是一樣的埠帕,由于static靜態(tài)對象 test 的生命周期和整個應用的生命周期一致,而非靜態(tài)內(nèi)部類 Test 持有外部類 MainActivity 的引用玖绿,導致 MainActivity 退出的時候不能被回收敛瓷,從而造成內(nèi)存泄漏。
解決:
把 test 改成非靜態(tài)斑匪,這樣 test 的生命周期和 MainActivity 是一樣的了琐驴,就避免了內(nèi)存泄漏。
或者也可以把 Test 改成靜態(tài)內(nèi)部類秤标,讓 test 不持有 MainActivity 的引用绝淡,不過一般沒有這種操作。
補充:
成員變量是static的時候苍姜,那么它的生命周期將和整個app的生命周期一致牢酵。
這必然會導致一系列問題,如果你的app進程設計上是長駐內(nèi)存的衙猪,那即使app切到后臺馍乙,這部分內(nèi)存也不會被釋放。按照現(xiàn)在手機app內(nèi)存管理機制垫释,占內(nèi)存較大的后臺進程將優(yōu)先回收丝格,因為如果此app做過進程互保保活棵譬,那會造成app在后臺頻繁重啟显蝌。當手機安裝了你參與開發(fā)的app以后一夜時間手機被消耗空了電量、流量订咸,你的app不得不被用戶卸載或者靜默曼尊。
這里修復的方法是:
不要在類初始時初始化靜態(tài)成員酬诀。可以考慮lazy初始化(延遲加載)骆撇。架構設計上要思考是否真的有必要這樣做瞒御,盡量避免。如果架構需要這么設計神郊,那么此對象的生命周期你有責任管理起來肴裙。
補充:各種context使用場景
Application 的 context 不是萬能的,所以也不能隨便亂用涌乳,對于有些地方則必須使用 Activity 的 Context蜻懦,對于Application,Service爷怀,Activity三者的Context的應用場景如下:
其中: NO1表示 Application 和 Service 可以啟動一個 Activity阻肩,不過需要創(chuàng)建一個新的 task 任務隊列带欢。而對于 Dialog 而言运授,只有在 Activity 中才能創(chuàng)建
Handler 或 Runnable 作為非靜態(tài)內(nèi)部類
Handler、Message和MessageQueue都是相互關聯(lián)在一起的乔煞,萬一Handler發(fā)送的Message尚未被處理吁朦,則該Message及發(fā)送它的Handler對象將會被線程MessageQueue一直持有。
由于Handler屬于TLS(Thread Local Storage)變量渡贾,生命周期和Activity是不一致的逗宜。
因此這種實現(xiàn)方式一般很難保證跟View或者Activity的生命周期一致,故很容易導致無法正確釋放空骚。比如:
問題1:
handler 和 runnable 都有定時器的功能纺讲,當它們作為非靜態(tài)內(nèi)部類的時候,同樣會持有外部類的引用囤屹,如果它們的內(nèi)部有延遲操作熬甚,在延遲操作還沒有發(fā)生的時候,銷毀了外部類肋坚,那么外部類對象無法回收乡括,從而造成內(nèi)存泄漏,假設 Activity 的代碼如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
}
}, 10 * 1000);
}
}
上面的代碼中智厌,Handler 和 Runnable 作為匿名內(nèi)部類诲泌,都會持有 MainActivity 的引用,而它們內(nèi)部有一個 10 秒鐘的定時器铣鹏,如果在打開 MainActivity 的 10 秒內(nèi)關閉了 MainActivity敷扫,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 長,會導致 MainActivity 無法被回收诚卸,從而造成內(nèi)存泄漏呻澜。
解決1:
那么應該如何避免內(nèi)存泄漏呢递礼?這里的一般套路就是把 Handler 和 Runnable 定義為靜態(tài)內(nèi)部類,這樣它們就不再持有 MainActivity 的引用了羹幸,從而避免了內(nèi)存泄漏
public class MainActivity extends AppCompatActivity {
private Handler handler;
private static class TestHandler extends Handler {
}
private Runnable runnable;
private static class TestRunnable implements Runnable {
@Override
public void run() {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new TestHandler();
runnable = new TestRunnable();
handler.postDelayed(runnable, 10 * 1000);
}
}
最好再在 onDestory 調(diào)用 handler 的 removeCallbacks 方法來移除 Message脊髓,這樣不但能避免內(nèi)存泄漏,而且在退出 Activity 時取消了定時器栅受,保證 10 秒以后也不會執(zhí)行 run 方法
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacks(runnable);
}
下面幾個方法都可以移除 Message:
問題2:
還有一種特殊情況将硝,如果 Handler 或者 Runnable 中持有 Context 對象,那么即使使用靜態(tài)內(nèi)部類屏镊,還是會發(fā)生內(nèi)存泄漏
public class MainActivity extends AppCompatActivity {
private Handler handler;
private static class TestHandler extends Handler {
private Context context;
private TestHandler(Context context) {
this.context = context;
}
}
private Runnable runnable;
private static class TestRunnable implements Runnable {
@Override
public void run() {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new TestHandler(this);
runnable = new TestRunnable();
handler.postDelayed(runnable, 10 * 1000);
}
}
上面的代碼依疼,使用 leakcanary工具會發(fā)現(xiàn)依然會發(fā)生內(nèi)存泄漏,而且造成內(nèi)存泄漏的原因和之前用非靜態(tài)內(nèi)部類是一樣的而芥,那么為什么會出現(xiàn)這種情況呢律罢?
這是由于在 Handler 中持有 Context 對象,而這個 Context 對象是通過 TestHandler 的構造方法傳入的棍丐,它是一個 MainActivity 對象误辑,也就是說,雖然 TestHandler 作為靜態(tài)內(nèi)部類不會持有外部類 MainActivity 的引用歌逢,但是我們在調(diào)用它的構造方法時巾钉,自己傳入了 MainActivity 的對象,從而 handler 對象持有了 MainActivity 的引用秘案,handler 的生命周期比 MainActivity 的生命周期長砰苍,因此會造成內(nèi)存泄漏。
解決2:
這種情況可以使用弱引用的方式來引用 Context 來避免內(nèi)存泄漏阱高,代碼如下
推薦使用靜態(tài)內(nèi)部類+弱引用WeakReference這種方式赚导,但要注意每次使用前判空。
public class HandlerGoodActivity extends AppCompatActivity {
private final MyHandler myHandler = new MyHandler(this);
private static final class MyHandler extends Handler{
private final WeakReference<HandlerGoodActivity> mActivity;
public MyHandler(HandlerGoodActivity activity){
this.mActivity = new WeakReference<HandlerGoodActivity>(activity);//使用弱引用
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HandlerGoodActivity activity = mActivity.get();
if (activity != null){
}
}
}
// 匿名內(nèi)部類在static的時候絕對不會持有外部類的引用
private static final Runnable RUNNABLE = new Runnable() {
@Override
public void run() {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_good);
myHandler.postDelayed(RUNNABLE,1000*60*5);
}
}
綜述:推薦使用靜態(tài)內(nèi)部類+弱引用WeakReference這種方式赤惊,但要注意每次使用前判空吼旧。
創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對 Handler 持有的對象使用弱引用荐捻,這樣在回收時也可以回收 Handler 持有的對象黍少,但是這樣做雖然避免了 Activity 泄漏,不過 Looper 線程的消息隊列中還是可能會有待處理的消息处面,所以我們在 Activity 的 Destroy 時或者 Stop 時應該移除消息隊列 MessageQueue 中的消息厂置。
匿名類被異步線程所引用
android開發(fā)經(jīng)常會繼承實現(xiàn)Activity或者Fragment或者View。如果你使用了匿名類魂角,而又被異步線程所引用昵济,那得小心,如果沒有任何措施同樣會導致內(nèi)存泄漏的
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inner_bad);
Runnable runnable1 = new MyRunnable();
Runnable runnable2 = new Runnable() {
@Override
public void run() {
}
};
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
}
}
}
runnable1 和 runnable2的區(qū)別就是,runnable2使用了匿名內(nèi)部類访忿,我們看看引用時的引用內(nèi)存
可以看到瞧栗,runnable1是沒有什么特別的。但runnable2多出了一個MainActivity的引用海铆,若是這個引用再傳入到一個異步線程迹恐,此線程在和Activity生命周期不一致的時候,也就造成了Activity的泄露卧斟。
其他的內(nèi)存泄漏情況
- 創(chuàng)建與關閉沒有成對出現(xiàn)造成的泄露:譬如Cursor資源必須手動關閉殴边,IO流等對象必須手動關閉,WebView必須手動銷毀等珍语。
- 對象的注冊與反注冊沒有成對出現(xiàn)造成的內(nèi)存泄露锤岸;譬如注冊廣播接收器、注冊觀察者(典型的譬如數(shù)據(jù)庫的監(jiān)聽)等板乙。
- 構造Adapter時是偷,沒有使用緩存的 convertView
- Bitmap對象不在使用時調(diào)用recycle()釋放內(nèi)存
- 警惕線程未終止造成的內(nèi)存泄露:
譬如在Activity中關聯(lián)了一個生命周期超過Activity的Thread,在退出Activity時切記結束線程募逞。一個典型的例子就是HandlerThread的run方法是一個死循環(huán)蛋铆,它不會自己結束,線程的生命周期超過了Activity生命周期凡辱,我們必須手動在Activity的銷毀方法中中調(diào)運thread.getLooper().quit();才不會泄露戒职。 - 不要在執(zhí)行頻率很高的方法或者循環(huán)中創(chuàng)建對象(比如onmeasure)
- 可以使用HashTable等創(chuàng)建一組對象容器從容器中取那些對象栗恩,而不用每次new與釋放透乾。
- 避免代碼設計模式的錯誤造成內(nèi)存泄露;譬如循環(huán)引用磕秤,A持有B乳乌,B持有C,C持有A市咆,這樣的設計誰都得不到釋放汉操。
集合對象及時清除
我們通常會把一些對象的引用加入到集合容器(比如ArrayList)中,當我們不再需要該對象時蒙兰,并沒有把它的引用從集合中清理掉磷瘤,這樣這個集合就會越來越大。如果這個集合是static的話搜变,那情況就更嚴重了采缚。
所以在退出程序之前,將集合里面的東西clear挠他,然后置為null扳抽,再退出程序,如下:
private List<String> nameList;
private List<Fragment> list;
@Override
public void onDestroy() {
super.onDestroy();
if (nameList != null){
nameList.clear();
nameList = null;
}
if (list != null){
list.clear();
list = null;
}
}
webView
當我們不再需要使用webView的時候,應該調(diào)用它的destory()方法來銷毀它贸呢,并釋放其占用的內(nèi)存镰烧,否則其占用的內(nèi)存長期也不能回收,從而造成內(nèi)存泄漏楞陷。
解決方案:
為webView開啟另外一個進程怔鳖,通過AIDL與主線程進行通信,webView所在的進程可以根據(jù)業(yè)務的需要選擇合適的時機進行銷毀固蛾,從而達到內(nèi)存的完整釋放败砂。
而另外一些諸如listView的Adapter沒有緩存之類的這里就不再多提了。
應用
MVP
在 MVP 的架構中魏铅,通常 Presenter 要同時持有 View 和 Model 的引用昌犹,如果在 Activity 退出的時候,Presenter 正在進行一個耗時操作览芳,那么 Presenter 的生命周期會比 Activity 長斜姥,導致 Activity 無法回收,造成內(nèi)存泄漏
public class MainActivity extends AppCompatActivity implements TestView {
private Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new Presenter(this);
presenter.request();
}
}
public class Presenter {
private TestView view;
private Model model;
public Presenter(TestView view) {
this.view = view;
model = new Model();
}
public void request() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
上面的代碼中沧竟,假設 request 是一個需要耗時 10 秒的操作铸敏,那么在 10 秒之內(nèi)如果退出 Activity 就會內(nèi)存泄漏。
解決:
使用弱引用悟泵,引用視圖
public class Presenter {
private WeakReference<T> mWeakReference;
private Model model;
public Presenter(T baseView) {
setView(baseView);
model = new Model();
}
public T getView() {
return mWeakReference != null ? mWeakReference.get() : null;
}
public void setView(T view) {
if (view != null) {
this.mWeakReference = new WeakReference<>(view);
}
}
public void request() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
補充:占用內(nèi)存大且生命周期較長的對象
在android開發(fā)中杈笔,為了防止內(nèi)存溢出,在處理一些占用內(nèi)存大并且生命周期較長的對象的時候糕非,可以盡量地使用軟引用和弱引用技術蒙具。
public class CacheBySoftRef {
// 首先定義一個HashMap,保存軟應用對象
private HashMap<String,SoftReference<Bitmap>> imageCache = new HashMap<>();
// 再來定義一個方法,保存Bitmap的軟引用到HashMap
public void addBitmapToCache(String path){
// 強引用的Bitmap對象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 軟引用的Bitmap對象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加該對象到Map使其緩存
imageCache.put(path,softBitmap);
}
// 獲取的時候朽肥,可以通過SoftReference的get()的方法得到Bitmap對象
public Bitmap getBitmapByPath(String path){
// 從緩存中取軟引用的Bitmap對象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判斷是否存在軟引用
if (softBitmap == null){
return null;
}
// 通過軟引用取出Bitmap對象禁筏,如果由于內(nèi)存不足Bitmap被回收,則取得空衡招;
// 如果未被回收篱昔,則可重復使用,提高速度
Bitmap bitmap = softBitmap.get();
return bitmap;
}
}
使用軟引用以后始腾,在OutOfMemory異常發(fā)生之前州刽,這些緩存的圖片資源的內(nèi)存空間可以被釋放掉的,從而避免內(nèi)存達到上限浪箭,避免Crash發(fā)生穗椅。
如果只是想避免OutOfMemory異常的發(fā)生,則可以使用軟引用山林。如果對于應用的性能更在意房待,想盡快回收一些占用內(nèi)存比較大的對象邢羔,則可以使用弱引用。
另外可以根據(jù)對象是否經(jīng)常使用來判斷選擇軟引用還是弱引用桑孩。如果該對象可能會經(jīng)常使用的拜鹤,就盡量用軟引用。如果該對象不被使用的可能性更大些流椒,就可以用弱引用敏簿。