Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Google官方 詳解 Android 性能優化

Google官方 詳解 Android 性能優化

編輯:關於Android編程

為什麼關注性能


對於一款APP,用戶首先關注的是 app的性能,而不是APP本身的屬性功能,用戶不關心你是否是搞社交,是否搞電商,是否是一款強大的美圖濾鏡app,用戶首先關注的是 性能—-性能不好,用戶會直接卸載,在應用市場給一個惡狠狠得差評,小則影響產品口碑,大則影響公司的品牌和聲譽,作為程序員,app的性能更應該作為我們關注的一個功能,而不是出了問題 才去門頭苦惱加班加點的負擔。

老實說,提高app性能的確非常難,處理這些問題 你必須知道:

應用速度慢的原因 還必須正確使用分析工具來分析數據

為此誕生了本文:

了解這些工具並用它們找到造成性能不好的原因 從理論角度了解它們

如何優化app的性能


性能優化聽上去是一項非常艱巨的功能,但仔細思考,其實非常簡單:

獲取信息,

有人說你應用慢,應用閃退(崩掉,crash掉)的時候你需要找到原因。

通過運行 分析和反饋工具軟件來收集應用相關的信息,我們需要明確哪些可以測量,哪些可以優化

也就是說任何應用開始優化時,整個過程取決於問題的可測性以及性能優化的可評價性。 開發中經常遇到的坎,問題不可復現,以及對於某一個細節是否需要優化 拿不定主意,這個需要我們自己身處其境 考慮分析各方面因素 得出結論,而不是純粹得靠感覺。

分析數據,

很多時候,我們並不能直接理解問題的原因,比如內存溢出(OOM)的error,判斷內存溢出需要計算很多個變量得內存大小,我們並不能直觀通過眼球看出來一個app 運行過程那些變量的內存,這裡我們就需要運行分析工具來幫助我們,將其轉化為可視化的圖表,

在這裡,我們可能還是看不懂那些圖表,橫線豎線,具體是個什麼玩意,

沒有關系,去弄懂它們!就可以成為性能大師了.

現在你看那些內存中的二進制轉換成圖表的過程,就類似於古代的算命大師,

步驟1和步驟2 會不斷的循環,搜集數據,分析數據···

有時候我們不只使用一種搜集工具和分析工具,這就需要自己針對性能得種類來深入研究了

Tack action!

發現了問題,找到了問題所在以及發生的原因,我們必須要恰當的去解決它,根據項目進度,該性能的優化成本,性能優先級,考慮項目中使用的java庫或者android開源框架,其中的一些嚴格限制,

在你的方案提出之前,這些因素都是我們需要考慮的,因為提出的優化方案,不一定會被公司高層接受(除非你就是高層)。

工具不是規則,理解事物的規則和流程更重要

為什麼關注內存

內存大小屬於手機性能之一

舉個簡單的例子,內存就像你的臥室一樣,當你在老家住著動辄幾百平的村莊,舒服慣了,突然變賣家產一門心思想創業來到北京,家裡的老本只夠你住幾平米的衛生間的時候,你就會注意到內存【房間】大小的重要性了。

首先我們要知道內存是如何影響系統運行

通常我們認為代碼執行速度等同於物理硬件的執行速度,我們的代碼指令都是通過使用內存來完成的。通過為實例對象,常量,變量分配內存,來完成操作,但是如何釋放這些內存,通常我們並不清楚。

\

一旦分配出去的內存沒有及時回收,會引造成系統卡頓,執行操作緩慢現象,這種現象稱之為內存洩漏,Memory leak

java垃圾回收機制官方詳解

java中的JVM就是一個抽象的計算機,和實際的計算機一樣,它具有指令集並使用不同的存儲區域,JVM負責執行代碼,管理數據,管理內存和寄存器。

垃圾回收機制只做兩件基本的事情:

發現無用的對象 回收被無用對象占用的內存空間,使得該空間可以被程序再次利用

通常,垃圾回收具有如下特點:

垃圾回收機制的工作目標是回收無用對象的內存空間,這些內存空間都是JVM堆內存的內存資源,對於其他物理資源,比如數據庫連接,磁盤I/O 等資源無能為力

為了讓垃圾回收機制盡快回收那些對象,可以將該對象引用變量置為null,

垃圾回收發生的不可預知性,不同JVM 采用不同的算法和機制,有可能定時回收,有可能cpu空閒回收,也有可能內存消耗極限發生,即使通過Runtime對象的gc(),System.gc()來建議系統進行回收,但這之屬於建議,不能精確控制垃圾回收機制的執行,

意思就是說,垃圾回收機制什麼時候開始執行,並不是我們程序員能控制的,我們只能給予建議。

那麼問題來了,如何精確的進行垃圾回收呢?

回答很明確,確保每一個對象都進行了有效的釋放。對於不再需要的對象,不要引用他們,一旦在別的地方保持對這個對象的引用,垃圾回收機制 暫時不會回收該對象,則會導致嚴重得問題—-系統可用內存越來越少,垃圾回收執行的頻率越來越高,cpu全都被垃圾回收的操作占有了,系統性能自然而然就下降了!

java8 已經刪除了永生代內存,即一些常駐內存,不會回收的數據,而是改為使用本地內存來存儲類的元數據,稱之為元空間(Metaspace),不過貌似和Android開發沒關系(-__-)。

回顧完java垃圾回收,下面介紹

Android 自己的回收機制-Runtime 回收機制

這裡寫圖片描述

經由為數據分配內存的類型,以及系統如何有效的利用gc回收內存,並為新的對象分配內存。

所有要申請的內存都被劃分到內存空間中,根據這些特點,哪些數據分配到哪些內存中,取決於Android的版本,

最為重要的一點,Android系統為每個運行中的app分配了預設的內存通常為16m-32m之間,當分配的內存越多,系統內存不足時,系統就可能會執行內存清理,注意,是可能會執行,是否執行垃圾清理是由系統自己判斷的。

進行垃圾回收,以確保有足夠的內存分配給其他的應用操作,不同的Android版本,會有不同的gc操作,例如在davailk中,gc代表終止程序操作,
這裡寫圖片描述

代碼是如何影響程序執行的?

這裡寫圖片描述

上圖是正常的界面刷新流程,<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160602/20160602091103271.png" title="\" />

上圖,gc占據了一大塊的時間,對於我們人類來說很短,但是對於系統來說很長了。

這裡寫圖片描述

綜合三張圖分析:代碼質量很差,使得系統為我們的app分配了過多內存,而且沒有及時回收,系統需要更多的時間去執行gc回收,那麼系統就沒時間去保持界面的活躍,所以就造成了卡頓的現象。,

在一個循環體中,重復得創建對象,就會造成內存污染,馬上就會有很多gc
啟動,由於這一額外的內存壓力,內存洩漏仍然會產生,當可用內存降低到一定總量時,會強制系統gc執行,那循環體中的那部分操作會顯示出卡頓的情況,甚至有可能在內存極限的時候,我們開發的應用會閃退。

所以,唯一的解決辦法是:減少代碼申請的內存量,不使用的對象及時回收。

使用內存分析工具Memory Monitor

\

整個層疊圖,代表還有多少內存可用
深藍色區域:代表正在使用的內存大小
淺灰色區域:代表空閒未分配內存

這裡寫圖片描述

這個紅色箭頭所指的坡度表示急需大量的內存,內存分配也急劇的增加。
上圖是一個內存管理良好的例子

下圖我們看一個內存糟糕的例子

這裡寫圖片描述

分析

這裡有一部分代碼占用了大量的內存,然後又一下子釋放了內存,生成不斷重復又窄又長的曲線,這就是程序在花大量的時間在進行垃圾清理,運行垃圾清理的時間越多,其他操作可用的時間就越少,比如跟網絡交互數據,頁面刷新,打電話,聽歌等等,這樣就造成了卡頓

使用Montior過程中遇到的 No Debugable Application的問題

\

solution:Tools-Android-Android adb interact

最初並不會見效,重啟app即可,

內存洩漏


在這裡我才開始引入內存洩漏的原因【雖然文章前面已經提到,但是在這裡才著重拿出來作為一節】

網絡上和一些書籍對內存洩漏解釋是 應該回收的對象沒有回收,有點不全面,我認為深一點來說,內存洩漏是針對系統而言的,內存洩漏指的是不能被使用的內存,但是垃圾回收器無法識別出來,對其進行回收,這些對象一直存在於堆中,並且持續占據著內存空間,無法被刪除,隨著不斷洩漏,系統可用的內存就越來越小,意味著系統又需要花更多的時間 去進行內存清理操作,進行垃圾回收操作的次數越來越多,

簡單的內存洩漏:對沒有使用的對象 循環引用
復雜一點的:在listview還沒有繪制完成時就添加到activity >

Heap Viewer

Heap viewer使用步驟,我錄制了gift圖,詳情請看:

\

了解Heap Viewer:
Heap Viewer可以有效的分析程序在堆中分配的數據類型及數量和 大小小

這裡寫圖片描述

這裡表示 byte數組和boolean數組的數量為177,占用了1.423M的內存

內存洩漏的情況

這裡寫圖片描述

綠色箭頭標出來的那部分 代碼就是有問題的部分,原因在於,可以內存幾乎為0,所有的內存已經被程序占用,首先記住,我們的代碼有問題,造成了內存洩漏,並且,垃圾回收機制無法回收那部分內存空間

下圖為30s之後的內存回收情況

這裡寫圖片描述

啟動第二次gc,此時Android調整並提高應用的內存上限,這樣做的同時,如果漏洞沒有修復,表明內存洩漏仍然存在,那麼還會有第三次,第四次同樣的gc操作,直至系統無法調整提高給應用更高的內存上限,造成內存溢出,甚至可能死機,

Trace Viewer分配追蹤器

這裡寫圖片描述

Trace Viewer可以精確追蹤到代碼的位置,限於篇幅請按照上圖點擊 那幾個按鈕 自行摸索考功它的功能能

高效加載圖片圖片

為什麼只關注圖片加載,而不去處理其他數據來解決內存不足的問題?

Android 加載圖片會創建Bitmap,drawable實例,占用內存空間,如果不進行高效處理,程序會很快達到 Android系統分配給APP的內存上限,直至掛掉

圖片資源相比文本資源,在內存中會占據更大的內存,從字節數就可以看出來

在我們的應用中正確恰當高效的加載 圖片資源 是一件非常棘手的事情

Android 系統會分配給單個APP至少 16M左右的內存,) Android Compatibility Definition Document (CDD)中,根據不同手機的尺寸和屏幕像素來要求應用最小內存,我們開發 的應用需要優化內存至最低內存限制,然而請記住,許多手機對內存有著更高的要求。 圖片消耗大量的內存,尤其是高像素的圖片,比如入門級單反相機拍攝出來的一張圖片,都有可能超出APP的最低內存限制 app 中一些常見的UI 比如 ListView, GridView and ViewPager,都需要立刻加載大量的圖片,注意是立刻,這對內存管理提出了很高的要求。

所以我們需要高效得加載圖片。

高效的加載大圖:

一張圖片的像素,尺寸,分別率,由可能超過Android的UI組件本身大小,UI組件的大小是由手機設備屏幕決定的,這些超出的部分,會消耗更多的內存。

應該讓圖片去匹配我們的手機設備,所以,我們需要對圖片進行處理:
1. 不能超過每個應用程序的內存限制
2. 用最小的內存加載圖片

只需三步

1. 讀取內存中Bitmap的尺寸和類型

BitmapFactory類提供了很多解析Bitmap的方法(decodeByteArray(), decodeFile(), decodeResource(), etc.),每一種解析方法都有一個額外的參數 BitmapFactory.Options,設置inJustDecodeBounds 屬性為true可以禁止應用分配內存,此時bitmap返回為null,但是我們可以通過BitmapFactory.Options對象來獲取很多有用的參數

此時 你可以通過BitmapFactory.Options來讀取圖片的尺寸和類型

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

總結:為了避免java.lang.OutOfMemory ,在加載圖片之前需要檢查原圖的大小是否超出最低內存限制。

2. 加載一張小圖,使得系統分配較少的內存給它。

現在我們已經獲取到了圖片的尺寸,加載一張圖片之前,我們需要考慮:

計算整張圖片需要多大的內存

我們希望給它多大的內存

加載圖片的組件比如Imageview的尺寸是多大 當前手機設備的屏幕尺寸和分辨率

例如一張1080*720的圖片要展示在一個128*72的Imageview上

實際 項目中,比如一張2048x1536的圖片,我們通過設置inSampleSize為4,來創建實際大小為512x384的bitmap,這樣需要的內存為0.75MB而不是之前的12MB(色彩模式都是ARGB_8888的情況下),

Google官方提供了兩個方法來供我們使用,可以封裝到自己的工具類中:

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // 獲得內存中圖片的寬高
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // 計算出一個數值,必須符合為2的冪(1,2,4,8,tec),賦值給inSampleSize
        // 圖片寬高應大於期望的寬高的時候,才進行計算
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // 第一次解析 inJustDecodeBounds=true 只是用來獲取bitmap在內存中的尺寸和類型,系統並不會為其分配內存,
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // 計算出一個數值
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 根據inSampleSize 數值來解析bitmap
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

研究一個新的函數,我們先關注函數的輸入和輸出提高閱讀能力

3.接著為我們的UI組件ImageView設置一張縮略圖咯:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

為了完全理解這一部分,初學者請自行查閱

BitmapFactory.decode BitmapFactory.Options

使用子線程來加載Bitmap

當圖片資源來自網絡或者硬盤的時候,最好不要直接在主線程中加載它,例如IO資源或者數據庫資源都會占用CPU,CPU 要做的事情過多,Android手機會造成卡頓得現象,

好在Google 提供了解決辦法–AsyncTask異步加載工具

class BitmapWorkerTask extends AsyncTask {
    private final WeakReference imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // 用弱引用確保能被垃圾回收機制回收
        imageViewReference = new WeakReference(imageView);
    }

    //在後台解析bitmap
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // 一旦完成,imageView將會加載bitmap
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

接著我們在主線程中執行它即可

public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

Caching緩存圖片

1. 為什麼要緩存圖片?

對於如何高效的加載一張圖片 ,我們似乎已經得心應手了,這裡要潑一盆涼水給大家,因為我們的應用不僅只是加載一張圖片這麼簡單,比如ListView, GridView or ViewPager,RecyclerView,需要立刻加載出大量的bitmap,滑動的過程不斷加載bitmap,還要求不卡頓,內存夠用,這似乎又是一件棘手的事情。

Google 又提供了一種解決思路:對於ListView,RecyclerView,有可見的item和不可見的item,回收不可見的item 內存,分配給可見的,這樣內存得到了重復利用,避免重復創建對象,不斷申請並分配新的內存空間,觸發最低內存限制的危險。

所以我們要管理 這些 已經創建好的內存。

2. 使用內存緩存

為什麼優先使用內存緩存?

答:相比硬盤緩存的讀取速度,讀取內存中的數據更快

Google官方有什麼建議?

答:Google推薦Lrucache類,底層使用用強引用封裝的LinkedHashMap,來存儲最近使用的對象,它自動會回收最近使用的對象當中,使用的最少的那一個對象的內存,這一點毋庸置疑值得推薦!

一種過時的做法,是用虛引用或者弱引用來標記bitmap,這種方法在Android 3.0以後已經不提倡了,因為類似JDK1.8那樣,bitmap的內存是放在本地內存中的,它的回收是不確定的,有可能導致APP掛掉,切勿使用。

Lrucache這麼棒,我們該如何用?

Lrucache就可以當作一種存儲數據的結構,類似list,set,可以存儲對象,獲取對象,對應的有add() 和get()方法,它與數組一樣,初始化的時候需要指定一個初始的大小。

那麼Lrucache實例 初始的大小該如何確定?
在計算大小之前,我們需要明確幾件事情:

我們的Activity和Application還有多少可用內存? 第一次加載的時候,需要為多少圖片分配內存?可見的item數量,決定了圖片的數量,圖片的數量*每張圖片的內存大小就是初始化需要分配的內存。 用戶當前手機的屏幕尺寸和屏幕密度(為什麼要這倆參數?大屏幕手機 ,初始化的時候會加載更多的item,需要的內存更大) 這張圖片我們要怎樣配置?圖片的尺寸如何設置,顏色模式如何配置?根據不同的需求,比如是做用戶頭像,還是信息展示?都有各自的應用場景的要求。我們需要分類判斷.

cache大小是由內存大小決定的,而不是 它存儲數據的個數決定

這讓我想起一個在項目開發中常見的bug,use a bitmap which has bean recycler

3. Lrucache使用實例

private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // 當Lrucache使用的內存大小超過虛擬機最大的可用內存時候,Android會拋出OutOfMemory exception

    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // 使用虛擬機可用1/8
        final int cacheSize = maxMemory / 8;

    //  在Lrucache構造函數中初始化它的大小
    // int in its constructor
    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {

     //cache大小是由內存大小決定的,而不是 它存儲數據的個數決定
         return bitmap.getByteCount() / 1024;
        }
   public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
        }
   }

 public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}  

loadBitmap的過程很簡潔:如果內存緩存中有這張bitmap,則直接刷新imageview,如果bitmap為空,則啟動後台線程去加載bitmap,接著刷新imageview

  public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}  

異步線程 BitmapWorkerTask

class BitmapWorkerTask extends AsyncTask {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

4. 磁盤緩存
與內存緩存相結合的還有磁盤緩存,雖然磁盤讀取速度較慢,但是持久存儲的,不像內存緩存那樣,在內存極限情況下仍然會被清理,比如後台正在執行數據加載,突然打進來一個電話,內存不足系統可能會進行垃圾回收。緩存就沒有了

Google官方提供直接DiskLruCache 類

為什麼要使用DiskLruCache 很明了:就是解決當內存緩存不可用的情形

內當需要頻繁訪問緩存的圖片資源時,比如APP的畫廊功能,可以考慮使用ContentProvider解決更為妥當。

下面是它的源碼

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

class InitDiskCacheTask extends AsyncTask {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}

class BitmapWorkerTask extends AsyncTask {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // Also add to disk cache
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}

5.處理運行時變更的緩存
當屏幕旋轉,或者其他原因導致Activity restart,這個時候難道又讓我們重新創建大量的圖像資源?
回答是否定的,Google提供了一種解決方案:

通過在Activity中使用fragment,構造Fragment時,通過設置 setRetainInstance(true))來設置緩存,
話不多說,直接上代碼:

private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = retainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache mRetainedCache;

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
            fm.beginTransaction().add(fragment, TAG).commit();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

有趣的是

當我通宵趕完這篇博客的時候,我的計算機也提醒我內存不足了 (●’?’●)

這裡寫圖片描述

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved