亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

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

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

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

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

201621162057205.png (439×249)

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

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

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

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

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

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

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

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

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

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

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

(3)、泄漏來源
請瀏覽下面一段代碼:

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

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

只要有未處理的消息,那么消息會引用handler,非靜態(tài)的handler又會引用外部類,即Activity,導(dǎo)致Activity無法被回收,造成泄漏;
Runnable類屬于非靜態(tài)匿名類,同樣會引用外部類。
為了解決遇到的問題,我們要明確一點:靜態(tài)內(nèi)部類不會持有對外部類的引用。所以,我們可以把handler類放在單獨(dú)的類文件中,或者使用靜態(tài)內(nèi)部類便可以避免泄漏。
另外,如果想要在handler內(nèi)部去調(diào)用所在的外部類Activity,那么可以在handler內(nèi)部使用弱引用的方式指向所在Activity,這樣統(tǒng)一不會導(dǎo)致內(nèi)存泄漏。
對于匿名類Runnable,同樣可以將其設(shè)置為靜態(tài)類。因為靜態(tà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)類與非靜態(tài)類之間的區(qū)別并不大,但是對于Android開發(fā)者而言卻是必須理解的。至少我們要清楚,如果一個內(nèi)部類實例的生命周期比Activity更長,那么我們千萬不要使用非靜態(tài)的內(nèi)部類。最好的做法是,使用靜態(tài)內(nèi)部類,然后在該類里使用弱引用來指向所在的Activity。

相關(guān)文章

  • android getActivity.findViewById獲取ListView 返回NULL的方法

    android getActivity.findViewById獲取ListView 返回NULL的方法

    下面小編就為大家?guī)硪黄猘ndroid getActivity.findViewById獲取ListView 返回NULL的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-11-11
  • Android RxJava異步數(shù)據(jù)處理庫使用詳解

    Android RxJava異步數(shù)據(jù)處理庫使用詳解

    RxJava是一種異步數(shù)據(jù)處理庫,也是一種擴(kuò)展的觀察者模式。對于Android開發(fā)者來說,使用RxJava時也會搭配RxAndroid,它是RxJava針對Android平臺的一個擴(kuò)展,用于Android 開發(fā),它提供了響應(yīng)式擴(kuò)展組件,使用RxAndroid的調(diào)度器可以解決Android多線程問題
    2022-11-11
  • Kotlin?掛起函數(shù)CPS轉(zhuǎn)換原理解析

    Kotlin?掛起函數(shù)CPS轉(zhuǎn)換原理解析

    這篇文章主要為大家介紹了Kotlin?掛起函數(shù)CPS轉(zhuǎn)換原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • android之SeekBar控件用法詳解

    android之SeekBar控件用法詳解

    下面小編就為大家?guī)硪黄猘ndroid之SeekBar控件用法詳解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • Android 路徑查詢具體實現(xiàn)

    Android 路徑查詢具體實現(xiàn)

    可以通過RasterMap的getDirection()方法來查詢路徑,和查詢地址類似,路徑查詢的結(jié)果也是通過回調(diào)函數(shù)的方式來通知應(yīng)用程序的,下面的例子返回南京到北京的路徑
    2013-10-10
  • Android之RecyclerView實現(xiàn)時光軸效果示例

    Android之RecyclerView實現(xiàn)時光軸效果示例

    本篇文章主要介紹了Android之RecyclerView實現(xiàn)時光軸效果,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • 最新評論