關於Android中的三級緩存

来源:http://www.cnblogs.com/huangjie123/archive/2016/12/04/6130665.html
-Advertisement-
Play Games

三級緩存的提出就是為了提升用戶體驗。當我們第一次打開應用獲取圖片時,先到網路去下載圖片,然後依次存入記憶體緩存,磁碟緩存,當我們再一次需要用到剛纔下載的這張圖片時,就不需要再重覆的到網路上去下載,直接可以從記憶體緩存和磁碟緩存中找,由於記憶體緩存速度較快,我們優先到記憶體緩存中尋找該圖片,如果找到則運用,如 ...


        三級緩存的提出就是為了提升用戶體驗。當我們第一次打開應用獲取圖片時,先到網路去下載圖片,然後依次存入記憶體緩存,磁碟緩存,當我們再一次需要用到剛纔下載的這張圖片時,就不需要再重覆的到網路上去下載,直接可以從記憶體緩存和磁碟緩存中找,由於記憶體緩存速度較快,我們優先到記憶體緩存中尋找該圖片,如果找到則運用,如果沒有找到(記憶體緩存大小有限),那麼我們再到磁碟緩存中去找。只要我們合理的去協調這三層緩存運用,便可以提升應用性能。三級緩存指的是:記憶體緩存、本地緩存、網路緩存。其各自的特點是記憶體緩存速度快, 優先讀取,本地緩存速度其次, 記憶體沒有,讀本地,網路緩存速度最慢, 本地也沒有,才訪問網路。對於網路緩存理解起來較為容易直接從網路中獲取資源,本地緩存可以存在SD卡中,記憶體緩存一般存在數組或集合中。需要在註意的是,數組和集合的生命周期依賴於它存在的activity中,因此當程式退出,一般情況下數組和集合中的資源會被釋放。在具體瞭解三級緩存的工作原理之前有必要先介紹幾個概念。

       實例和對象:
          對象是類的一個實例,創建對象的過程也叫類的實例化。對象是以類為模板來創建的。這樣在安卓的底部就會用堆來存儲對象的實例,棧來存儲類的對象。引用是指某些對象的實例化需要其它的對象實例,比如ImageView的實例化就需要Context對象,就是表示ImageView對於Context持有引用(ImageView holds a reference to Context)。

       垃圾回收機制(GC):
          對虛擬機可用記憶體空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其為存活對象,反之,如果對象不再被引用,則為垃圾對象,可以回收其占據的空間,用於再分配。更細緻來講就是對於GC來說,當程式員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常GC採用有向圖的方式記錄並管理堆中的所有對象,通過這種方式確定哪些對象時“可達”,哪些對象時“不可達”。當對象不可達的時候,即對象不再被引用的時候,就會被垃圾回收。該機制對虛擬機中的記憶體進行標記,並確定哪些記憶體需要回收,根據一定的回收策略,自動的回收記憶體,永不停息(Nerver Stop)的保證虛擬機中的記憶體空間,防止出現記憶體泄露和溢出問題。

       記憶體泄露:
          當不再需要某個實例後,但是這個對象卻仍然被引用,這個情況就叫做記憶體泄露(Memory Leak)。安卓虛擬機為每一個應用分配一定的記憶體空間,當記憶體泄露到達一定的程度就會造成記憶體溢出。

       記憶體的引用:
          記憶體的引用級別包括,強引用、軟引用、弱引用、虛引用。強引用是預設的引用方式, 即使記憶體溢出,也不會回收。軟引用(softReference), 記憶體不夠時, 會考慮回收。 弱引用 (WeakReference)記憶體不夠時, 更會考慮回收。虛引用(PhantomReference) 記憶體不夠時, 最優先考慮回收! 一般我們常用到的引用就是強引用,比如引用創建一個成員變數裡面的引用。對於GC來說, SoftReference的強度明顯低於 SrongReference。SoftReference修飾的引用,其告訴GC:我是一個 軟引用,當記憶體不足的時候,我指向的這個記憶體是可以給你釋放掉的。一般對於這種占用記憶體資源比較大的,又不是必要的變數;或者一些占用大量記憶體資源的一些緩存的變數,就需要考慮 SoftReference。對於GC來說, WeakReference 的強度又明顯低於 SoftReference 。 WeakReference 修飾的引用,其告訴GC:我是一個弱引用,對於你的要求我沒有話說,我指向的這個記憶體是可以給你釋放掉的。虛引用其實和上面講到的各種引用不是一回事的,他主要是為跟蹤一個對象何時被GC回收。在android裡面也是有用到的:FileCleaner.java 。這些避免記憶體溢出的引用方式在Android 2.3+的版本上已經不再起太大作用, 因為垃圾回收器會頻繁回收非強引用的對象, Android官方建議使用LRUCache。所以當我們用軟引用進行記憶體緩存時會發現記憶體中的資源會被系統頻繁回收。最終是從本地進行讀數據。

        這樣我們就能很好的理解要三級緩存了。首先,在記憶體讀數據。記憶體中讀數據需要用到最近最少引用演算法(lrucache)。Lrucache演算法要求為new LruCache<String, Bitmap>()傳入一個分配給軟體的最大記憶體,同時重寫sizeof()方法,計算每一張圖片的大小。這樣就可以直接調用LruCache的put()和get()方法。當發現記憶體中沒用數據是時,找到SD卡中的存儲文件。通過Bitmap的compress()方法向文件夾中寫數據,通過點陣圖工廠BitmapFactory的decodeStream()讀取數據,同時可以為decodeStream()方法傳入options參數,縮小圖片。最後如果,本地仍然沒有獲取數據,在從網路獲取。網路獲取數據可以用非同步任務來執行(耗時操作不能再主線程中執行)。非同步任務需要重寫onPostExecute()方法和doInBackground()方法。doInBackground()方法中訪問網路,這裡用到的是Httpurlconnection,通過連接得到輸入流,利用點陣圖工廠轉換成點陣圖,返回。onPostExecute()方法在doInBackground()方法執行後執行,傳入的參數數doInBackground()方法的返回值。

      接下來是代碼實,這是我調用工具類的寫法:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;

import com.example.huang.demo.utils.CacheUtils;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getBitmap();
    }

    private void getBitmap() {
        iv = (ImageView) findViewById(R.id.iv);
        CacheUtils utils = new CacheUtils();
        utils.diaplay(iv,"http://192.168.23.48:8080/test.jpg");    
    }
}

         佈局文件只要一個imageview就不單獨寫出來,直接來看緩存工具類:

import android.graphics.Bitmap;
import android.util.Log;
import android.widget.ImageView;

/**
 * Created by huang on 2016/12/3.
 */

public class CacheUtils {
    private static final String TAG = "CacheUtils";
    private MemoryCacheUtils mMemoryCacheUtils;
    private LocalCacheUtils mLocalCacheUtils;
    private NetCacheUtils mNetCacheUtils;

    public CacheUtils() {
        mMemoryCacheUtils = new MemoryCacheUtils();
        mLocalCacheUtils = new LocalCacheUtils();
        mNetCacheUtils = new NetCacheUtils(mMemoryCacheUtils, mLocalCacheUtils);
    }

    public void diaplay(ImageView imageView, String url) {

        //記憶體緩存   生命周期同調用者
        Bitmap bitmap = mMemoryCacheUtils.getBitmapToMemory(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            Log.i(TAG, "diaplay: 221111111111");
            return;
        }


        //本地緩存
        bitmap = LocalCacheUtils.getBitmapToLoacl(url);
        if (bitmap != null) {
            Log.i(TAG, "diaplay: 1111111");
            imageView.setImageBitmap(bitmap);
            mMemoryCacheUtils.putBitmapToMemory(bitmap, url);
            return;
        }

        //網路緩存
        mNetCacheUtils.getBitmapFromNet(imageView, url);
    }
}

          記憶體緩存的工具類

import android.graphics.Bitmap;
import android.util.Log;
import android.util.LruCache;

import static android.content.ContentValues.TAG;

/**
 * Created by huang on 2016/12/3.
 */

public class MemoryCacheUtils {

    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCacheUtils() {
        int maxmemory = (int) Runtime.getRuntime().maxMemory();
        Log.i(TAG, "MemoryCacheUtils: " + maxmemory);
        mMemoryCache = new LruCache<String, Bitmap>(maxmemory / 8) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    public void putBitmapToMemory(Bitmap bitmap, String url) {
        Log.i(TAG, "putBitmapToMemory: ");
        mMemoryCache.put(url, bitmap);
    }

    public Bitmap getBitmapToMemory(String url) {
        Log.i(TAG, "getBitmapToMemory: ");
        Bitmap bitmap = mMemoryCache.get(url);
        return bitmap;
    }
}

 

        本地緩存(SD卡)的工具類:

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import static android.content.ContentValues.TAG;

/**
 * Created by huang on 2016/12/3.
 */

public class LocalCacheUtils {
    public static void putBitmapToLoacl(Bitmap bitmap, String url) {
        String encode = Md5Utils.encode(url);
        File file = new File(Environment.getExternalStorageDirectory(), encode);
        Log.i(TAG, "putBitmapToLoacl: " + file.toString());
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        try {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Bitmap getBitmapToLoacl(String url) {
        String encode = Md5Utils.encode(url);
        File file = new File(Environment.getExternalStorageDirectory(), encode);
        if (file.exists()) {
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inSampleSize = 3;
            try {
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, opts);
                return bitmap;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
}

       網路緩存的工具類:

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Created by huang on 2016/12/3.
 */

public class NetCacheUtils {
    private MemoryCacheUtils mMemoryCacheUtils;
    private LocalCacheUtils mLocalCacheUtils;

    public NetCacheUtils(MemoryCacheUtils mMemoryCacheUtils, LocalCacheUtils mLocalCacheUtils) {
        this.mLocalCacheUtils = mLocalCacheUtils;
        this.mMemoryCacheUtils = mMemoryCacheUtils;
    }

    public void getBitmapFromNet(ImageView imageView, String url) {
        imageView.setTag(url);
        Bitmaptask task = new Bitmaptask();
        task.execute(imageView, url);
    }

    class Bitmaptask extends AsyncTask<Object, Void, Bitmap> {

        private HttpURLConnection urlConnection;
        private ImageView imageView;
        private String url;

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (bitmap != null) {
                if (url.equals(imageView.getTag())) {
                    imageView.setImageBitmap(bitmap);
                    System.out.print("onPostExecute");
                    mLocalCacheUtils.putBitmapToLoacl(bitmap, url);
                    mMemoryCacheUtils.putBitmapToMemory(bitmap, url);
                }
            }
        }

        @Override
        protected Bitmap doInBackground(Object[] params) {
            imageView = (ImageView) params[0];
            url = (String) params[1];
            Bitmap bitmap = downloadFromNet(url);
            return bitmap;
        }

        private Bitmap downloadFromNet(String url) {
            try {
                urlConnection = (HttpURLConnection) new URL(url).openConnection();
                urlConnection.setConnectTimeout(5000);
                urlConnection.setRequestMethod("GET");
                int responseCode = urlConnection.getResponseCode();
                if (responseCode == 200) {
                    InputStream inputStream = urlConnection.getInputStream();
                    BitmapFactory.Options opts = new BitmapFactory.Options();
                    opts.inSampleSize = 2;
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, opts);
                    return bitmap;
                }
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            } finally {
                urlConnection.disconnect();
            }
            return null;
        }
    }
}

      本地緩存中用到的MD5Utils工具類:

import java.security.MessageDigest;

/**
 * Created by huang on 2016/12/3.
 */

public class Md5Utils {
    public static String encode(String pwd) {
        try {
            MessageDigest digest = MessageDigest.getInstance("md5");
            byte[] bs = digest.digest(pwd.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte b : bs) {
                int number = b & 0xff;
                String str = Integer.toHexString(number);
                if (str.length() == 1) {
                    sb.append("0");
                }
                sb.append(number);
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

       最會別忘了添加許可權:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 英文文檔: __import__(name, globals=None, locals=None, fromlist=(), level=0) This function is invoked by the import statement. It can be replaced (by impor ...
  • 閑來無事,寫了一段通過類模板實現棧的代碼,分享給大家..... (關於棧的更多的詳細信息,詳見:http://www.cplusplus.com/reference/stack/stack/?kw=stack) 棧的聲明及實現 測試代碼 總結: 以上代碼,僅供個人娛樂. 在STL中的棧(stack) ...
  • 我遇到的問題 在做上位機軟體的時候,需要將上位軟體的命令傳輸到每個被控席位,也需要和被控電腦進行數據交換,我們的被控端是伺服器,也可能是客戶端,甚至有時候會遇到客戶端先啟動服務端後啟動情況,要控制的機器也可能是多台,同時我們要支持TCP和UDP兩種協議。 好酒加冰塊-交互過程 如果採用Tcp作為連 ...
  • 閱讀目錄 前言 明確業務細節 建模 實現 結語 一、前言 上一篇我們已經確立的購買上下文和銷售上下文的交互方式,傳送門在此:http://www.cnblogs.com/Zachary-Fan/p/DDD_6.html,本篇我們來實現售價上下文的具體細節。 二、明確業務細節 電商市場越來越成熟,競爭 ...
  • 前面寫過了使用ViewFlipper和ViewPager實現屏幕中視圖切換的效果(ViewPager未實現輪播)附鏈接: Android中使用ViewFlipper實現屏幕切換 Android中使用ViewPager實現屏幕頁面切換和頁面切換效果 今天我們在換一種實現方式ImageViewSwitc ...
  • 活動的啟動模式對我們來說是個新的概念,在實際項目中我們會根據活動的需求為每個活動指定恰當的啟動模式。共分為四種分別是:standard,singletop,singletast,singleinstance,可以在androidMainfest.xml中進得指定,android:launchMode... ...
  • 當運行下一個活動時,上一個活動被K掉了,當我們返回上一個活動時,系統會重啟create一個活動,問題來了我們之前在保存的數據怎麼辦?onSaveInstanceState可以用這個方法來進行保存,鍵值對[ke,"value"],其實和Intent一樣,也是通過這樣保存。 ...
  • 今天碰到了在XML中應用以內部類形式定義的自定義view,結果遇到了一些坑。雖然通過看了一些前輩寫的文章解決了這個問題,但是我看到的幾篇都沒有完整說清楚why,於是決定做這個總結。 使用自定義內部類view的規則 本文主要是總結why,所以先把XML佈局文件中引用內部類的自定義view的做法擺出來, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...