幾周前我有幸參加了在波蘭舉行的國際移動會議失息,這是移動開發(fā)者最好的會議之一守伸。在“最佳實踐”系列演講中,我的朋友兼同事Jorge Barroso 的一個觀點引起了我的注意:
如果你是一個Android開發(fā)者,但是你沒有使用弱引用,那么你會有麻煩。
剛好,幾個月前我和Diego Grancini合作出版了我的上一本書,“Android High Performance”恨樟。其中最熱門的一章就是討論Android中的內存管理养晋。在這一章中,我們討論了在移動設備上內存是怎么工作的,內存泄漏是怎么發(fā)生的瓣赂,內存泄漏這個問題為什么如此重要以及我們需要采取什么技術來避免穆碎。自從我從事Android開發(fā)以來恭金,我注意到一種傾向揪惦,凡是和內存泄漏或者內存管理有關的事情钩杰,開發(fā)者總是不由自主的回避或者降低其優(yōu)先級媳荒。如果功能需求已經(jīng)滿足蝌借,為什么要自尋煩惱?我們總是急于開發(fā)新的功能勤揩,我們寧愿在我們的下一個Sprint演示中呈現(xiàn)一些視覺效果蛙埂,而不是關心那些人們不會第一眼就發(fā)現(xiàn)的問題燎悍。
一個不錯的觀點就是,這將不可避免的導致技術債務触机。我甚至還補充一點蔬胯,技術債務在現(xiàn)實世界也會產生一些影響迈勋,這些影響我們無法通過單元測試來衡量:失望鼻吮、開發(fā)者之間扯皮蹈垢、軟件交付質量低下以及工作激情消失后添。這個影響很難衡量的原因是它們常常發(fā)生在未來的某一個時間點硫嘶。它的發(fā)生有一點像政客:如果我僅僅當政8年,我為什么要為第12年發(fā)生的事情煩惱忆畅?與之不同的是軟件開發(fā)在飛速發(fā)展绊诲。
如果要寫適用于軟件開發(fā)的思維模式洽胶,需要很長的篇幅,并且已經(jīng)有很多書和文章可供您探索无切,然而簡單的介紹內存引用的不同類型荡短,它們各自的含義以及怎樣把它們應用到Android開發(fā)中則容易的多,這就是我在本文中要做得事情哆键。
首先:Java中什么是引用掘托?
引用指向一個已經(jīng)聲明的對象,你可以訪問它籍嘹。
Java默認有4種引用:強引用闪盔,軟引用,弱引用和虛引用辱士。也有一些人認為只有兩種引用泪掀,強引用和弱引用,并且弱引用可以表現(xiàn)為兩種形式颂碘。在生活中,我們傾向于將一切東西像植物學家一樣進行分類异赫。不管哪一種分類更適合你,首先你需要理解他們。然后你才能提出你自己的分類方式祝辣。
每一種引用的含義是什么贴妻?
強引用:強引用是Java中最常見的引用切油。每當我們創(chuàng)建一個新的對象蝙斜,默認就創(chuàng)建了一個強引用。比如澎胡,但我們寫下如下代碼:
MyObject object = new MyObject();
當一個MyObject類型的對象被創(chuàng)建孕荠,object就持有一個它的強引用。到目前為止攻谁,還是比較簡單的稚伍,你還能跟上嗎?那么戚宦,接下來會發(fā)生更多有趣的事情个曙。這個Object是強可達的-也就是說它可以通過一個強引用鏈到達。這將阻止垃圾回收器回收并且銷毀它受楼,我們最希望的是垃圾回收器及時的回收并銷毀它垦搬。但是現(xiàn)在讓我們一起來看一個例子,這將我和我們想要的不一樣艳汽。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyAsyncTask().execute();
}
private class MyAsyncTask extends AsyncTask {
@Override
protected Object doInBackground(Object[] params) {
return doSomeStuff();
}
private Object doSomeStuff() {
//do something to get result
return new MyObject();
}
}
}
花幾分鐘時間猴贰,盡可能找出任何容易出現(xiàn)問題的路徑。
不用擔心河狐,如果找不到就多花一點時間米绕。
現(xiàn)在呢?
AsyncTask將在Activity的OnCreate()函數(shù)中創(chuàng)建并執(zhí)行馋艺。但是這里會有一個問題栅干,內部類在他的整個生命周期中都需要訪問外部類。
當Activity被銷毀的時候會發(fā)生什么事情捐祠?AsyncTask持有一個Activity的引用非驮,從而導致Activity不能被垃圾回收器回收。這就是所謂的內存泄漏雏赦。
旁記:我以前在面試候選人時劫笙,我總是問他們怎樣制造一個內存泄漏,而不是問他們關于內存泄漏的理論知識星岗。通常都很有意思填大。
這里的內存泄漏實際上不僅僅在Activity自身銷毀時發(fā)生,同樣俏橘,由于系統(tǒng)配置發(fā)生變化或者系統(tǒng)需要更多的內存等而被強制銷毀時也會發(fā)生允华。如果這個AsyncTask比較復雜(比如持有Activity里的Views的引用等),這還會引發(fā)程序崩潰,因為view的引用是null靴寂。
那么怎樣才能防止這個問題再次發(fā)生磷蜀? 讓我們解釋另一種類型的引用:
弱引用:弱引用是指不夠強大到讓系統(tǒng)保存在內存中的引用。如果我們嘗試確定一個對象是否被強引用百炬,而剛好是通過WeakRerences方式引用褐隆,那么這該對象將被回收。為了理解剖踊,最好是消化掉理論知識庶弃,我們通過一個實際的樣例來展示怎樣通過使用WeakReference來避免上一個例子中的內存泄漏:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
private static class MyAsyncTask extends AsyncTask {
private WeakReference<MainActivity> mainActivity;
public MyAsyncTask(MainActivity mainActivity) {
this.mainActivity = new WeakReference<>(mainActivity);
}
@Override
protected Object doInBackground(Object[] params) {
return doSomeStuff();
}
private Object doSomeStuff() {
//do something to get result
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
if (mainActivity.get() != null){
//adapt contents
}
}
}
}
注意主要的變化:內部類是通過下面的方式引用Activity的:
private WeakReference<MainActivity> mainActivity;
當Activity需要被銷毀時會發(fā)生什么事情?由于它是通過弱引用的方式持有德澈,它可以被回收歇攻。所以不會有內存泄漏發(fā)生。
旁記:現(xiàn)在希望你已經(jīng)更好的理解了什么是弱引用梆造,你會發(fā)現(xiàn)一個非常有用的類 WeakHashMap缴守。 它就是一個HashMap,除了它的keys(注意是key镇辉, 不是values)是通過弱引用的方式被引用的屡穗。這使得它對于實現(xiàn)高速緩存之類的實體是非常有用的。
我們已經(jīng)提到了更多的引用摊聋。 讓我們看看在什么情況下它們是有用的鸡捐,以及我們如何能夠從中受益:
軟引用:把軟引用看做一個較強的弱引用。軟引用會要求垃圾回收將其保留在內存中麻裁,如果沒有其他選項時才將其回收箍镜,而弱引用是被立即回收。垃圾回收算法真的是令人興奮的東西煎源,所以有時你會花數(shù)小時去研究而不知疲倦色迂。但是基本規(guī)則就是:”我總是要回收弱引用。如果一個對象是軟引用手销,我會根據(jù)系統(tǒng)環(huán)境來決定做什么“歇僧。這使得軟引用對于實現(xiàn)緩存非常有用:只要內存是充裕的,我們不用擔心手動移除對象锋拖。如果你想查看一個實際的例子诈悍,你可以查看這個通過軟引用實現(xiàn)的緩存樣例。
虛引用:啊哈兽埃,虛引用侥钳!我想我一只手就能數(shù)過來在生產環(huán)境中我所見到的虛引用被使用的情形。一個對象如果僅僅被通過虛引用的方式引用柄错,那么它會被垃圾回收器隨心所欲的回收舷夺。沒有更多的解釋苦酱,沒有“召回”。這使得它難以描述给猾。為什么我們會喜歡使用這樣一個東西疫萤?難道其他的還不夠麻煩?為什么我選擇成為一名程序員敢伸?虛引用可以被用來精準的檢測一個對象是否被從內存中刪除扯饶。我記得我的整個職業(yè)生涯中一共使用了兩次虛引用。所以如果你現(xiàn)在感覺虛引用很難理解详拙,請不要沮喪帝际。
希望本文能夠理清一點點你以前對引用的認識蔓同。在任何學習過程中饶辙,你也許想開始實踐實踐,把弄一下自己的代碼并看看你如何改進它斑粱。首先你需要檢查你的代碼中是否存在內存泄漏的問題弃揽,進而使用你從這些課程中學習到的知識去消除這些令人難受的內存泄漏。如果喜歡本文或者你覺得本文對你有所幫助则北,請隨時分享并/或者留下評論矿微。這是對業(yè)余寫作者最大的鼓勵。
原文鏈接:Finally understanding how references work in Android and Java