最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Android應(yīng)用的內(nèi)存優(yōu)化及Handler的內(nèi)存泄漏問(wèn)題

 更新時(shí)間:2016年02月01日 16:23:18   作者:iam_wingjay  
這篇文章主要介紹了Android應(yīng)用的內(nèi)存優(yōu)化及Handler的內(nèi)存泄漏問(wèn)題,文中對(duì)Activity無(wú)法被回收而造成的內(nèi)存泄漏給出了通常的解決方案,需要的朋友可以參考下

一、Android內(nèi)存基礎(chǔ)
物理內(nèi)存與進(jìn)程內(nèi)存
物理內(nèi)存即移動(dòng)設(shè)備上的RAM,當(dāng)啟動(dòng)一個(gè)Android程序時(shí),會(huì)啟動(dòng)一個(gè)Dalvik VM進(jìn)程,系統(tǒng)會(huì)給它分配固定的內(nèi)存空間(16M,32M不定),這塊內(nèi)存空間會(huì)映射到RAM上某個(gè)區(qū)域。然后這個(gè)Android程序就會(huì)運(yùn)行在這塊空間上。Java里會(huì)將這塊空間分成Stack棧內(nèi)存和Heap堆內(nèi)存。stack里存放對(duì)象的引用,heap里存放實(shí)際對(duì)象數(shù)據(jù)。
在程序運(yùn)行中會(huì)創(chuàng)建對(duì)象,如果未合理管理內(nèi)存,比如不及時(shí)回收無(wú)效空間就會(huì)造成內(nèi)存泄露,嚴(yán)重的話(huà)可能導(dǎo)致使用內(nèi)存超過(guò)系統(tǒng)分配內(nèi)存,即內(nèi)存溢出OOM,導(dǎo)致程序卡頓甚至直接退出。

內(nèi)存泄露(Memory Leak)
Java內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒(méi)有使用價(jià)值了,但是它們卻可以直接或間接地引用到gc roots導(dǎo)致無(wú)法被GC回收。Dalvik VM具備的GC機(jī)制(垃圾回收機(jī)制)會(huì)在內(nèi)存占用過(guò)多時(shí)自動(dòng)回收,嚴(yán)重時(shí)會(huì)造成內(nèi)存溢出OOM。

內(nèi)存溢出OOM
當(dāng)應(yīng)用程序申請(qǐng)的java heap空間超過(guò)Dalvik VM HeapGrowthLimit時(shí),溢出。
注意:OOM并不代表內(nèi)存不足,只要申請(qǐng)的heap超過(guò)Dalvik VM HeapGrowthLimit時(shí),即使內(nèi)存充足也會(huì)溢出。效果是能讓較多進(jìn)程常駐內(nèi)存。

如果RAM不足時(shí)系統(tǒng)會(huì)做什么?
Android的Memory Killer會(huì)殺死優(yōu)先級(jí)較低的進(jìn)程,讓高優(yōu)先級(jí)進(jìn)程獲取更多內(nèi)存。

Android系統(tǒng)默認(rèn)內(nèi)存回收機(jī)制

進(jìn)程優(yōu)先級(jí):Foreground進(jìn)程、Visible進(jìn)程、Service進(jìn)程、Background進(jìn)程、Empty進(jìn)程;
如果用戶(hù)按Home鍵返回桌面,那么該app成為Background進(jìn)程;如果按Back返回,則成為Empty進(jìn)程
ActivityManagerService直接管理所有進(jìn)程的內(nèi)存資源分配。所有進(jìn)程要申請(qǐng)或釋放內(nèi)存都需要通過(guò)ActivityManagerService對(duì)象。
垃圾回收不定期執(zhí)行。當(dāng)內(nèi)存不夠時(shí)就會(huì)遍歷heap空間,把垃圾對(duì)象刪除。
堆內(nèi)存越大,則GC的時(shí)間更長(zhǎng)

201621162057205.png (439×249)

二、優(yōu)化
Bitmap優(yōu)化
Bitmap非常消耗內(nèi)存,而且在Android中,讀取bitmap時(shí), 一般分配給虛擬機(jī)的圖片堆棧只有8M,所以經(jīng)常造成OOM問(wèn)題。所以有必要針對(duì)Bitmap的使用作出優(yōu)化:

圖片顯示:加載合適尺寸的圖片,比如顯示縮略圖的地方不要加載大圖。
圖片回收:使用完bitmap,及時(shí)使用Bitmap.recycle()回收。
問(wèn)題:Android不是自身具備垃圾回收機(jī)制嗎?此處為何要手動(dòng)回收。
Bitmap對(duì)象不是new生成的,而是通過(guò)BitmapFactory生產(chǎn)的。而且通過(guò)源碼可發(fā)現(xiàn)是通過(guò)調(diào)用JNI生成Bitmap對(duì)象(nativeDecodeStream()等方法)。所以,加載bitmap到內(nèi)存里包括兩部分,Dalvik內(nèi)存和Linux kernel內(nèi)存。前者會(huì)被虛擬機(jī)自動(dòng)回收。而后者必須通過(guò)recycle()方法,內(nèi)部調(diào)用nativeRecycle()讓linux kernel回收。
捕獲OOM異常:程序中設(shè)定如果發(fā)生OOM的應(yīng)急處理方式。
圖片緩存:內(nèi)存緩存、硬盤(pán)緩存等
圖片壓縮:直接使用ImageView顯示Bitmap時(shí)會(huì)占很多資源,尤其當(dāng)圖片較大時(shí)容易發(fā)生OOM??梢允褂肂itMapFactory.Options對(duì)圖片進(jìn)行壓縮。
圖片像素:android默認(rèn)顏色模式為ARGB_8888,顯示質(zhì)量最高,占用內(nèi)存最大。若要求不高時(shí)可采用RGB_565等模式。圖片大?。簣D片長(zhǎng)度*寬度*單位像素所占據(jù)字節(jié)數(shù)
ARGB_4444:每個(gè)像素占用2byte內(nèi)存
ARGB_8888:每個(gè)像素占用4byte內(nèi)存 (默認(rèn))
RGB_565:每個(gè)像素占用2byte內(nèi)存
對(duì)象引用類(lèi)型

強(qiáng)引用 strong:Object object=new Object()。當(dāng)內(nèi)存不足時(shí),Java虛擬機(jī)寧愿拋出OOM內(nèi)存溢出異常,也不會(huì)輕易回收強(qiáng)引用對(duì)象來(lái)解決內(nèi)存不足問(wèn)題;
軟引用 soft:只有當(dāng)內(nèi)存達(dá)到某個(gè)閾值時(shí)才會(huì)去回收,常用于緩存;
弱引用 weak :只要被GC線(xiàn)程掃描到了就進(jìn)行回收;
虛引用
如果想要避免OOM發(fā)生,則使用軟引用對(duì)象,即當(dāng)內(nèi)存快不足時(shí)進(jìn)行回收;如果想盡快回收某些占用內(nèi)存較大的對(duì)象,例如bitmap,可以使用弱引用,能被快速回收。不過(guò)如果要對(duì)bitmap作緩存就不要使用弱引用,因?yàn)楹芸炀蜁?huì)被GC回收,導(dǎo)致緩存失敗。
關(guān)于java對(duì)象引用類(lèi)型,具體可參加本人另一篇文章
池 pool

對(duì)象池:如果某個(gè)對(duì)象在創(chuàng)建時(shí),需要較大的資源開(kāi)銷(xiāo),那么可以將其放入對(duì)象池,即將對(duì)象保存起來(lái),下次需要時(shí)直接取出使用,而不用再次創(chuàng)建對(duì)象。當(dāng)然,維護(hù)對(duì)象池也需要一定開(kāi)銷(xiāo),故要衡量。
線(xiàn)程池:與對(duì)象池差不多,將線(xiàn)程對(duì)象放在池中供反復(fù)使用,減少反復(fù)創(chuàng)建線(xiàn)程的開(kāi)銷(xiāo)。

三、Handler內(nèi)存泄漏分析及解決
1、介紹
首先,請(qǐng)瀏覽下面這段handler代碼:

public class SampleActivity extends Activity {
 private final Handler mLeakyHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   // ... 
  }
 }
}

在使用handler時(shí),這是一段很常見(jiàn)的代碼。但是,它卻會(huì)造成嚴(yán)重的內(nèi)存泄漏問(wèn)題。在實(shí)際編寫(xiě)中,我們往往會(huì)得到如下警告:

  In Android, Handler classes should be static or leaks might occur.
那么,handler是如何造成內(nèi)存泄漏的呢?

2、分析
(1)、Android角度
當(dāng)Android應(yīng)用程序啟動(dòng)時(shí),framework會(huì)為該應(yīng)用程序的主線(xiàn)程創(chuàng)建一個(gè)Looper對(duì)象。這個(gè)Looper對(duì)象包含一個(gè)簡(jiǎn)單的消息隊(duì)列Message Queue,并且能夠循環(huán)的處理隊(duì)列中的消息。這些消息包括大多數(shù)應(yīng)用程序framework事件,例如Activity生命周期方法調(diào)用、button點(diǎn)擊等,這些消息都會(huì)被添加到消息隊(duì)列中并被逐個(gè)處理。
另外,主線(xiàn)程的Looper對(duì)象會(huì)伴隨該應(yīng)用程序的整個(gè)生命周期。

然后,當(dāng)主線(xiàn)程里,實(shí)例化一個(gè)Handler對(duì)象后,它就會(huì)自動(dòng)與主線(xiàn)程Looper的消息隊(duì)列關(guān)聯(lián)起來(lái)。所有發(fā)送到消息隊(duì)列的消息Message都會(huì)擁有一個(gè)對(duì)Handler的引用,所以當(dāng)Looper來(lái)處理消息時(shí),會(huì)據(jù)此回調(diào)[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)方法來(lái)處理消息。

(2)、Java角度
在java里,非靜態(tài)內(nèi)部類(lèi) 和 匿名類(lèi) 都會(huì)潛在的引用它們所屬的外部類(lèi)。但是,靜態(tài)內(nèi)部類(lèi)卻不會(huì)。

(3)、泄漏來(lái)源
請(qǐng)瀏覽下面一段代碼:

public class SampleActivity extends Activity {

 private final Handler mLeakyHandler = 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.
  mLeakyHandler.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分鐘。而且,由上文可知,這條消息持有對(duì)handler的引用,而handler又持有對(duì)其外部類(lèi)(在這里,即SampleActivity)的潛在引用。這條引用關(guān)系會(huì)一直保持直到消息得到處理,從而,這阻止了SampleActivity被垃圾回收器回收,同時(shí)造成應(yīng)用程序的泄漏。
注意,上面代碼中的Runnable類(lèi)--非靜態(tài)匿名類(lèi)--同樣持有對(duì)其外部類(lèi)的引用。從而也導(dǎo)致泄漏。

3、泄漏解決方案
首先,上面已經(jīng)明確了內(nèi)存泄漏來(lái)源:

只要有未處理的消息,那么消息會(huì)引用handler,非靜態(tài)的handler又會(huì)引用外部類(lèi),即Activity,導(dǎo)致Activity無(wú)法被回收,造成泄漏;
Runnable類(lèi)屬于非靜態(tài)匿名類(lèi),同樣會(huì)引用外部類(lèi)。
為了解決遇到的問(wèn)題,我們要明確一點(diǎn):靜態(tài)內(nèi)部類(lèi)不會(huì)持有對(duì)外部類(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 {

 /**
  * Instances of static inner classes do not hold an implicit
  * reference to their outer class.
  */
 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);

 /**
  * Instances of anonymous classes do not hold an implicit
  * reference to their outer class when they are "static".
  */
 private static final Runnable sRunnable = new Runnable() {
   @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();
 }
}

4、小結(jié)
雖然靜態(tài)類(lèi)與非靜態(tài)類(lèi)之間的區(qū)別并不大,但是對(duì)于Android開(kāi)發(fā)者而言卻是必須理解的。至少我們要清楚,如果一個(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。

相關(guān)文章

最新評(píng)論

汤原县| 镇巴县| 红原县| 呈贡县| 苍溪县| 云林县| 库伦旗| 小金县| 都匀市| 湘西| 克东县| 三明市| 廉江市| 牟定县| 宜兰市| 阳原县| 陵川县| 元氏县| 隆林| 大埔区| 五峰| 新和县| 彩票| 南宫市| 温宿县| 罗江县| 西林县| 南漳县| 安徽省| 常宁市| 尉氏县| 苍梧县| 金华市| 安新县| 高密市| 乌兰浩特市| 沧源| 乐平市| 霍山县| 五寨县| 云浮市|