日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線(xiàn)咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747


 

前言

內(nèi)存優(yōu)化目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,就是該被釋放的對(duì)象沒(méi)有釋放,一直被某個(gè)或某些實(shí)例所持有卻不再被使用導(dǎo)致 GC 不能回收。既然說(shuō)到內(nèi)存泄漏和優(yōu)化,就不得不先簡(jiǎn)單了解一下內(nèi)存分配策略,然后再舉常見(jiàn)泄漏例子和解決方法,最后做一下總結(jié),這樣更直觀全面了解Android內(nèi)存方面處理。

內(nèi)存分配

內(nèi)存分配策略有三種,分別是靜態(tài)、棧式和堆式。對(duì)應(yīng)的的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(也稱(chēng)方法區(qū))、棧區(qū)和堆區(qū)。如下:

  • 靜態(tài)存儲(chǔ)區(qū):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在。
  • 棧區(qū) :當(dāng)方法被執(zhí)行時(shí),方法體內(nèi)的局部變量都在棧上創(chuàng)建,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會(huì)自動(dòng)被釋放。因?yàn)闂?nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。
  • 堆區(qū) : 又稱(chēng)動(dòng)態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時(shí)直接 new 出來(lái)的內(nèi)存。這部分內(nèi)存在不使用時(shí)將會(huì)由 JAVA 垃圾回收器來(lái)負(fù)責(zé)回收。

棧與堆 區(qū)別:

在方法體內(nèi)定義的(局部變量)一些基本類(lèi)型的變量和對(duì)象的引用變量都是在方法的棧內(nèi)存中分配的。當(dāng)在一段方法塊中定義一個(gè)變量時(shí),Java 就會(huì)在棧中為該變量分配內(nèi)存空間,當(dāng)超過(guò)該變量的作用域后,該變量也就無(wú)效了,分配給它的內(nèi)存空間也將被釋放掉,該內(nèi)存空間可以被重新使用。堆內(nèi)存用來(lái)存放所有由 new 創(chuàng)建的對(duì)象(包括該對(duì)象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,將由 Java 垃圾回收器來(lái)自動(dòng)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,這個(gè)特殊的變量就是我們上面說(shuō)的引用變量。我們可以通過(guò)這個(gè)引用變量來(lái)訪(fǎng)問(wèn)堆中的對(duì)象或者數(shù)組。

舉個(gè)例子:

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 類(lèi)的局部變量 s2 和引用變量 mSample2 都是存在于棧中,但 mSample2 指向的對(duì)象是存在于堆上的。

mSample3 指向的對(duì)象實(shí)體存放在堆上,包括這個(gè)對(duì)象的所有成員變量 s1 和 mSample1,而它自己存在于棧中。

小結(jié)

局部變量的基本數(shù)據(jù)類(lèi)型和引用存儲(chǔ)于棧中,引用的對(duì)象實(shí)體存儲(chǔ)于堆中:因?yàn)樗鼈儗儆诜椒ㄖ械淖兞浚芷陔S方法而結(jié)束。 成員變量全部存儲(chǔ)與堆中(包括基本數(shù)據(jù)類(lèi)型,引用和引用的對(duì)象實(shí)體):因?yàn)樗鼈儗儆陬?lèi),類(lèi)對(duì)象終究是要被new出來(lái)使用的。

管理內(nèi)存

內(nèi)存管理就是對(duì)象的分配和釋放問(wèn)題。

在 Java 中,程序員需要通過(guò)關(guān)鍵字 new 為每個(gè)對(duì)象申請(qǐng)內(nèi)存空間 (基本類(lèi)型除外),所有的對(duì)象都在堆 (Heap)中分配空間。另外,對(duì)象的釋放是由 GC 決定和執(zhí)行的。在 Java 中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由 GC 完成的,這種收支兩條線(xiàn)的方法確實(shí)簡(jiǎn)化了程序員的工作。但同時(shí),它也加重了JVM的工作。這也是 Java 程序運(yùn)行速度較慢的原因之一。因?yàn)椋珿C 為了能夠正確釋放對(duì)象,GC 必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)、引用、被引用、賦值等,GC 都需要進(jìn)行監(jiān)控。監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對(duì)象,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用。

內(nèi)存泄漏

內(nèi)存泄漏就是存在一些被分配的對(duì)象,這些對(duì)象有下面兩個(gè)特點(diǎn),首先,這些對(duì)象是可達(dá)的,即在有向圖中,存在通路可以與其相連;其次,這些對(duì)象是無(wú)用的,即程序以后不會(huì)再使用這些對(duì)象。如果對(duì)象滿(mǎn)足這兩個(gè)條件,這些對(duì)象就可以判定為Java中的內(nèi)存泄漏,這些對(duì)象不會(huì)被GC所回收,然而它卻占用內(nèi)存。

在C++中,內(nèi)存泄漏的范圍更大一些。有些對(duì)象被分配了內(nèi)存空間,然后卻不可達(dá),由于C++中沒(méi)有GC,這些內(nèi)存將永遠(yuǎn)收不回來(lái)。在Java中,這些不可達(dá)的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。

通過(guò)分析,我們得知,對(duì)于C++,程序員需要自己管理邊和頂點(diǎn),而對(duì)于Java程序員只需要管理邊就可以了(不需要管理頂點(diǎn)的釋放)。通過(guò)這種方式,Java提高了編程的效率。

因此,通過(guò)以上分析,我們知道在Java中也有內(nèi)存泄漏,但范圍比C++要小一些。因?yàn)镴ava從語(yǔ)言上保證,任何對(duì)象都是可達(dá)的,所有的不可達(dá)對(duì)象都由GC管理。

對(duì)于程序員來(lái)說(shuō),GC基本是透明的,不可見(jiàn)的。雖然,我們只有幾個(gè)函數(shù)可以訪(fǎng)問(wèn)GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語(yǔ)言規(guī)范定義, 該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行。因?yàn)椋煌腏VM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常,GC的線(xiàn)程的優(yōu)先級(jí)別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開(kāi)始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來(lái)說(shuō),我們不需要關(guān)心這些。除非在一些特定的場(chǎng)合,GC的執(zhí)行影響應(yīng)用程序的性能,例如對(duì)于基于Web的實(shí)時(shí)系統(tǒng),如網(wǎng)絡(luò)游戲等,用戶(hù)不希望GC突然中斷應(yīng)用程序執(zhí)行而進(jìn)行垃圾回收,那么我們需要調(diào)整GC的參數(shù),讓GC能夠通過(guò)平緩的方式釋放內(nèi)存,例如將垃圾回收分解為一系列的小步驟執(zhí)行,Sun提供的HotSpot JVM就支持這一特性。

Java 內(nèi)存泄漏的典型例子:

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
 Object o = new Object();
 v.add(o);
 o = null; 
}

在這個(gè)例子中,我們循環(huán)申請(qǐng)Object對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè) Vector 中,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì) GC 來(lái)說(shuō)是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從 Vector 中刪除,最簡(jiǎn)單的方法就是將 Vector 對(duì)象設(shè)置為 null。

常見(jiàn)內(nèi)存泄漏

(本篇重點(diǎn))

1、集合類(lèi)泄漏

如果一個(gè)集合類(lèi)是全局性變量(比如類(lèi)中的靜態(tài)變量或全局性map即有靜態(tài)引用又或者final指向它)只有添加元素的方法,而沒(méi)有相應(yīng)的清除機(jī)制,就會(huì)占用內(nèi)存只增不減,造成內(nèi)存泄漏。比如我們通常用HashMap做一些緩存之類(lèi)的事,這種情況就多留點(diǎn)心,做好相應(yīng)刪除機(jī)制。

2、單例造成泄漏

由于單例的靜態(tài)性使得生命周期跟應(yīng)用的生命周期一樣長(zhǎng),很容易造成內(nèi)存泄漏。

典型的例子

public class AppManager {
 private static AppManager instance;
 private Context context; 
 
 private AppManager(Context context) {
 this.context = context;
 }
 
 public static AppManager getInstance(Context context) {
 if (instance != null) {
 instance = new AppManager(context);
 }
 return instance;
 }
}

這是一個(gè)普通的單例模式,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長(zhǎng)短至關(guān)重要:

1:如果此時(shí)傳入的是 Application 的 Context,因?yàn)?Application 的生命周期就是整個(gè)應(yīng)用的生命周期,所以這將沒(méi)有任何問(wèn)題。

2:如果此時(shí)傳入的是 Activity 的 Context,當(dāng)這個(gè) Context 所對(duì)應(yīng)的 Activity 退出時(shí),由于該 Context 的引用被單例對(duì)象所持有,其生命周期等于整個(gè)應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收,這就造成泄漏了

3、非靜態(tài)內(nèi)部類(lèi)創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏

有的時(shí)候我們可能會(huì)在啟動(dòng)頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會(huì)出現(xiàn)這種寫(xiě)法:

public class MainActivity extends AppCompatActivity { 
 private static TestResource mResource = null; 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_main); 
 if(mManager == null){ 
 mManager = new TestResource(); 
 } 
 } 
 class TestResource { 
 //… 
 } 
}

這樣就在Activity內(nèi)部創(chuàng)建了一個(gè)非靜態(tài)內(nèi)部類(lèi)的單例TestResource,每次啟動(dòng)Activity時(shí)都會(huì)使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建,不過(guò)這種寫(xiě)法卻會(huì)造成內(nèi)存泄漏,因?yàn)榉庆o態(tài)內(nèi)部類(lèi)默認(rèn)會(huì)持有外部類(lèi)的引用,而該非靜態(tài)內(nèi)部類(lèi)又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正常回收。

正確的做法為:將該內(nèi)部類(lèi)設(shè)為靜態(tài)內(nèi)部類(lèi)或?qū)⒃搩?nèi)部類(lèi)抽取出來(lái)封裝成一個(gè)單例,如果需要使用Context,請(qǐng)按照上面推薦的使用Application 的Context。當(dāng)然,Application 的 context 不是萬(wàn)能的,所以也不能隨便亂用,對(duì)于有些地方則必須使用 Activity 的 Context,對(duì)于Application,Service,Activity三者的Context的應(yīng)用場(chǎng)景如下:Application 和 Service 可以啟動(dòng)一個(gè) Activity,不過(guò)需要?jiǎng)?chuàng)建一個(gè)新的 task 任務(wù)隊(duì)列。而對(duì)于 Dialog 而言,只有在 Activity 中才能創(chuàng)建。

4、匿名內(nèi)部類(lèi)線(xiàn)程異步導(dǎo)致泄漏

在繼承實(shí)現(xiàn)Activity/Fragment/View時(shí),此時(shí)如果你使用了匿名類(lèi),并被異步線(xiàn)程持有了,那要小心了,沒(méi)有任何措施一定會(huì)導(dǎo)致泄露。

舉個(gè)栗子:

public class MainActivity extends Activity { 
 ...{
 Runnable re1 = new MyRunable(); 
 Runnable re2 = new Runnable() { 
 @Override 
 public void run() { 
 } 
 }; 
}

re1和re2的區(qū)別是,re2使用了匿名內(nèi)部類(lèi)。運(yùn)行時(shí)這兩個(gè)引用的內(nèi)存可以看到,re1沒(méi)什么特別的。

但ref2這個(gè)匿名類(lèi)的實(shí)現(xiàn)對(duì)象里面多了一個(gè)引用:this$0這個(gè)引用指向MainActivity.this。

也就是說(shuō)當(dāng)前的MainActivity實(shí)例會(huì)被re2持有,如果將這個(gè)引用再傳入一個(gè)異步線(xiàn)程,此線(xiàn)程和此Acitivity生命周期不一致的時(shí)候,就造成了Activity的泄露。

5、Handler 造成的內(nèi)存泄漏

handler為了避免ANR而不在主線(xiàn)程進(jìn)行耗時(shí)操作,去處理網(wǎng)絡(luò)任務(wù)或者封裝一些請(qǐng)求回調(diào)等api等。我們知道 Handler、Message 和 MessageQueue 都是相互關(guān)聯(lián)在一起的,萬(wàn)一 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對(duì)象將被線(xiàn)程 MessageQueue 一直持有造成內(nèi)存泄漏。

由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的。因此這種實(shí)現(xiàn)方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導(dǎo)致無(wú)法正確釋放。

知識(shí)點(diǎn):在java里 ,非靜態(tài)內(nèi)部類(lèi) 和匿名類(lèi)都會(huì)潛在的引用它們所屬的外部類(lèi)。但是靜態(tài)內(nèi)部類(lèi)卻不會(huì)。

接下里看個(gè)案例:

public class SampleActivity extends Activity {
 
 private final Handler mHandler = new Handler() {
 @Override
 public void handleMessage(Message msg) {
 // ...
 }
 }
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 // Post a message and delay its execution for 10 minutes.
 mHandler.postDelayed(new Runnable() {
 @Override
 public void run() { ... }}, 1000 * 60 * 10);
 
 // Go back to the previous Activity.
 finish();
 }
}

分析:

當(dāng)activity結(jié)束(finish)時(shí),里面的延時(shí)消息在得到處理前,會(huì)一直保存在主線(xiàn)程的消息隊(duì)列里持續(xù)10分鐘。而且,由上文可知這個(gè)message持有handler引用,而handler又持有對(duì)其外部類(lèi)(activity)的潛引用。這條引用關(guān)系會(huì)一直保持直到消息得到處理,從而阻止了acitivty被垃圾回收器回收,造成應(yīng)用程序的泄漏。另外非靜態(tài)匿名類(lèi)Runnable同樣持有外部類(lèi),導(dǎo)致泄漏。總結(jié)2條原因:

小結(jié):

  • 只要有未處理的消息,那么消息會(huì)引用handler,非靜態(tài)的handler又會(huì)引用外部類(lèi),即Activity,導(dǎo)致Activity無(wú)法被回收,造成泄漏;
  • Runnable類(lèi)屬于非靜態(tài)匿名類(lèi),同樣會(huì)引用外部類(lèi)。

解決方案:

  • 我們可以把handler類(lèi)放在單獨(dú)的類(lèi)文件中,或者使用靜態(tài)內(nèi)部類(lèi)便可以避免泄漏。另外,如果想要在handler內(nèi)部去調(diào)用所在的外部類(lèi)Activity,那么可以在handler內(nèi)部使用弱引用的方式指向所在Activity,這樣統(tǒng)一不會(huì)導(dǎo)致內(nèi)存泄漏。
  • 對(duì)于匿名類(lèi)Runnable,同樣可以將其設(shè)置為靜態(tài)類(lèi)。因?yàn)殪o態(tài)的匿名類(lèi)不會(huì)持有對(duì)外部類(lèi)的引用。

再看源碼:

public class SampleActivity extends Activity {
 
 private static class MyHandler extends Handler {
 private final WeakReference<SampleActivity> mactivity;
 
 public MyHandler(SampleActivity activity) {
 mActivity = new WeakReference<SampleActivity>(activity); //弱引用
 }
 
 @Override
 public void handleMessage(Message msg) {
 SampleActivity activity = mActivity.get();
 if (activity != null) {
 // ...
 }
 }
 }
 
 private final MyHandler mHandler = new MyHandler(this);
 
 
 private static final Runnable sRunnable = new Runnable() { //靜態(tài)匿名類(lèi)
 @Override
 public void run() { /* ... */ }
 };
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 // Post a message and delay its execution for 10 minutes.
 mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
 
 // Go back to the previous Activity.
 finish();
 }
}

如果一個(gè)內(nèi)部類(lèi)實(shí)例的生命周期比Activity更長(zhǎng),那么我們千萬(wàn)不要使用非靜態(tài)的內(nèi)部類(lèi)。最好的做法是,使用靜態(tài)內(nèi)部類(lèi),然后在該類(lèi)里使用弱引用來(lái)指向所在的Activity。綜述,推薦使用靜態(tài)內(nèi)部類(lèi) + WeakReference 這種方式。每次使用前注意判空。

知識(shí)點(diǎn):

前面提到了 WeakReference,所以這里就簡(jiǎn)單的說(shuō)一下 Java 對(duì)象的幾種引用類(lèi)型。

Java對(duì)引用的分類(lèi)有強(qiáng)(Strong reference),軟(SoftReference),弱 (WeakReference),虛 PhatomReference 四種。

在Android應(yīng)用的開(kāi)發(fā)中,為了防止內(nèi)存溢出,在處理一些占用內(nèi)存大而且聲明周期較長(zhǎng)的對(duì)象時(shí)候,可以盡量應(yīng)用軟引用和弱引用技術(shù)。

軟/弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。利用這個(gè)隊(duì)列可以得知被回收的軟/弱引用的對(duì)象列表,從而為緩沖器清除已失效的軟/弱引用。

假設(shè)我們的應(yīng)用會(huì)用到大量的默認(rèn)圖片,比如應(yīng)用中有默認(rèn)的頭像,默認(rèn)游戲圖標(biāo)等等,這些圖片很多地方會(huì)用到。如果每次都去讀取圖片,由于讀取文件需要硬件操作,速度較慢,會(huì)導(dǎo)致性能較低。所以我們考慮將圖片緩存起來(lái),需要的時(shí)候直接從內(nèi)存中讀取。但是,由于圖片占用內(nèi)存空間比較大,緩存很多圖片需要很多的內(nèi)存,就可能比較容易發(fā)生OutOfMemory異常。這時(shí),我們可以考慮使用軟/弱引用技術(shù)來(lái)避免這個(gè)問(wèn)題發(fā)生。以下就是高速緩沖器的雛形:首先定義一個(gè)HashMap,保存軟引用對(duì)象——private Map。

6、盡量避免使用 static 成員變量

如果成員變量被聲明為 static,那我們都知道其生命周期將與整個(gè)app進(jìn)程生命周期一樣。

這會(huì)導(dǎo)致一系列問(wèn)題,如果你的app進(jìn)程設(shè)計(jì)上是長(zhǎng)駐內(nèi)存的,那即使app切到后臺(tái),這部分內(nèi)存也不會(huì)被釋放。按照現(xiàn)在手機(jī)app內(nèi)存管理機(jī)制,占內(nèi)存較大的后臺(tái)進(jìn)程將優(yōu)先回收,如果此app做過(guò)進(jìn)程互保保活,那會(huì)造成app在后臺(tái)頻繁重啟。當(dāng)手機(jī)安裝了你參與開(kāi)發(fā)的app以后一夜時(shí)間手機(jī)被消耗空了電量、流量,你的app不得不被用戶(hù)卸載或者靜默。

修復(fù)的方法:

  • 不要在類(lèi)初始時(shí)初始化靜態(tài)成員。可以考慮lazy初始化。

7、AsyncTask對(duì)象造成的泄漏

AsyncTask確實(shí)需要額外注意一下。它的泄露原理和前面Handler,Thread泄露的原理差不多,它的生命周期和Activity不一定一致。

解決方案:在activity退出的時(shí)候,終止AsyncTask中的后臺(tái)任務(wù)。

但是,問(wèn)題是如何終止?

AsyncTask提供了對(duì)應(yīng)的API:public final boolean cancel (boolean mayInterruptIfRunning)。

它的說(shuō)明有這么一句話(huà):

// Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason.
// If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

cancel是不一定成功的,如果正在運(yùn)行,它可能會(huì)中斷后臺(tái)任務(wù)。怎么感覺(jué)這話(huà)說(shuō)的這么不靠譜呢?

是的,就是不靠譜。

那么,怎么才能靠譜點(diǎn)呢?我們看看官方的示例:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
 protected Long doInBackground(URL... urls) {
 int count = urls.length;
 long totalSize = 0;
 for (int i = 0; i < count; i++) {
 totalSize += Downloader.downloadFile(urls[i]);
 publishProgress((int) ((i / (float) count) * 100));
 // Escape early if cancel() is called
 // 注意下面這行,如果檢測(cè)到cancel,則及時(shí)退出
 if (isCancelled()) break;
 }
 return totalSize;
 }
 
 protected void onProgressUpdate(Integer... progress) {
 setProgressPercent(progress[0]);
 }
 
 protected void onPostExecute(Long result) {
 showDialog("Downloaded " + result + " bytes");
 }
 }

官方的例子是很好的,在后臺(tái)循環(huán)中時(shí)刻監(jiān)聽(tīng)cancel狀態(tài),防止沒(méi)有及時(shí)退出。

為了提醒大家,google特意在AsyncTask的說(shuō)明中撂下了一大段英文:

// AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as Executor, ThreadPoolExecutor and FutureTask.

可憐我神州大陸幅員遼闊,地大物博,什么都不缺,就是缺對(duì)英語(yǔ)閱讀的敏感。

AsyncTask適用于短耗時(shí)操作,最多幾秒鐘。如果你想長(zhǎng)時(shí)間耗時(shí)操作,請(qǐng)使用其他java.util.concurrent包下的API,比如Executor, ThreadPoolExecutor 和 FutureTask.

學(xué)好英語(yǔ),避免踩坑!

8、 BroadcastReceiver對(duì)象

種種原因沒(méi)有調(diào)用到unregister()方法。

解決方法很簡(jiǎn)單,就是確保調(diào)用到unregister()方法。

順帶說(shuō)一下,我在工作中碰到一種相反的情況,receiver對(duì)象沒(méi)有registerReceiver()成功(沒(méi)有調(diào)用到),于是unregister的時(shí)候提示出錯(cuò):

// java.lang.IllegalArgumentException: Receiver not registered ...

解決方案:

方案一:在registerReceiver()后設(shè)置一個(gè)FLAG,根據(jù)FLAG判斷是否unregister()。網(wǎng)上搜到的文章幾乎都這么寫(xiě),我以前碰到這種bug,也是一直都這么解。但是不可否認(rèn),這種代碼看上去確實(shí)有點(diǎn)丑陋。

方案二:我后來(lái)無(wú)意中聽(tīng)到某大牛提醒,在Android源碼中看到一種更通用的寫(xiě)法:

// just sample, 可以寫(xiě)入工具類(lèi)
// 第一眼我看到這段代碼,靠,太粗暴了,但是回頭一想,要的就是這么簡(jiǎn)單粗暴,不要把一些簡(jiǎn)單的東西搞的那么復(fù)雜。
private void unregisterReceiverSafe(BroadcastReceiver receiver) {
 try {
 getContext().unregisterReceiver(receiver);
 } catch (IllegalArgumentException e) {
 // ignore
 }
}

9、BitMap對(duì)象造成的泄漏

Bitmap 對(duì)象不用的時(shí)候最好調(diào)用一下recycle 方法再賦值null,清空資源的直接或間接引用,但是有人要問(wèn),android源碼里面好多地方也沒(méi)有調(diào)用啊?

是的,我這里說(shuō)的是最好,如果不調(diào)用的話(huà),只能依賴(lài)于Java GC 執(zhí)行的時(shí)候,調(diào)用Bitmap 的 finalize方法,

這里面會(huì)執(zhí)行navtive的方法 nativeDestructor() 去釋放資源,其實(shí)查看一下那個(gè)函數(shù),就是一句 delete bitmap。

總結(jié)

1.對(duì) Activity 等組件的引用應(yīng)該控制在 Activity 的生命周期之內(nèi); 如果不能就考慮使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部長(zhǎng)生命周期的對(duì)象引用而泄露。

2.盡量不要在靜態(tài)變量或者靜態(tài)內(nèi)部類(lèi)中使用非靜態(tài)外部成員變量(包括context ),即使要使用,也要考慮適時(shí)把外部成員變量置空;也可以在內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的變量。

3.對(duì)于生命周期比Activity長(zhǎng)的內(nèi)部類(lèi)對(duì)象,并且內(nèi)部類(lèi)中使用了外部類(lèi)的成員變量,可以將內(nèi)部類(lèi)改為靜態(tài)內(nèi)部類(lèi)、靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的成員變量 。

4.Handler 的持有的引用對(duì)象最好使用弱引用,資源釋放時(shí)也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的時(shí)候,取消掉該 Handler 對(duì)象的 Message和 Runnable。

5.在 Java 的實(shí)現(xiàn)過(guò)程中,也要考慮其對(duì)象釋放,最好的方法是在不使用某對(duì)象時(shí),顯式地將此對(duì)象賦值為 null,比如使用完Bitmap 后先調(diào)用 recycle(),再賦為null,清空對(duì)圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null)等,最好遵循誰(shuí)創(chuàng)建誰(shuí)釋放的原則。

6.正確關(guān)閉資源,對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo)。

7.保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期。

好了,文章到這里就結(jié)束了,如果你覺(jué)得文章寫(xiě)得不錯(cuò)就給個(gè)贊唄?如果你覺(jué)得那里值得改進(jìn)的,請(qǐng)給我留言。一定會(huì)認(rèn)真查詢(xún),修正不足。謝謝。

如果你覺(jué)得還算有用的話(huà),不妨把它們推薦給你的朋友。

分享到:
標(biāo)簽:內(nèi)存優(yōu)化 Android
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定