Explicación detallada de caché de memoria y caché de disco de mapa de bits

El texto original se publicó por primera vez en la cuenta pública de WeChat: Gongxingzhi (jzman-blog)

El uso de caché en Android es relativamente común. El uso de la estrategia de almacenamiento en caché correspondiente puede reducir el consumo de tráfico y también puede mejorar el rendimiento de la aplicación hasta cierto punto. Por ejemplo, al cargar imágenes de red, no debe cargar imágenes de la red cada vez. Se almacena en memoria caché en la memoria y el disco, y se obtendrá directamente de la memoria o del disco la próxima vez. La estrategia de caché generalmente utiliza el algoritmo LRU (menos utilizado recientemente) , que es el algoritmo menos utilizado recientemente. A continuación se tomarán imágenes de la memoria caché y el caché de disco. El ejemplo muestra cómo usar el caché en Android. Antes de leer este artículo, lea el artículo anterior:

Caché de memoria

LruCache es una clase de caché proporcionada por Android 3.1. A través de esta clase, puede acceder rápidamente al objeto de mapa de bits almacenado en caché. Un LinkedHashMap se usa internamente para almacenar el objeto de mapa de bits que necesita ser almacenado en caché por una referencia fuerte. Cuando el caché excede el tamaño especificado, el recientemente utilizado rara vez se libera La memoria ocupada por el objeto.

Nota : Antes de Android 3.1, un caché de memoria común era un caché de mapa de bits SoftReference o WeakReference, que ya no se recomienda. Después de Android 3.1, el recolector de basura presta más atención a la recuperación de SoftWeakference / WeakReference, lo que hace que el uso de esta forma para obtener caché sea en gran medida inválido, usando LruCache en el paquete de compatibilidad support-v4 puede ser compatible con versiones anteriores a Android 3.1.

Uso de LruCache

  1. Inicializar LruCache

Primero calcule el tamaño de caché requerido, de la siguiente manera:

//第一种方式:
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//获取当前硬件条件下应用所占的大致内存大小,单位为M
int memorySize = manager.getMemoryClass();//M
int cacheSize = memorySize/ 8;
//第二种方式(比较常用)
int memorySize = (int) Runtime.getRuntime().maxMemory();//bytes
int cacheSize = memorySize / 8;

Luego, inicialice LruCache de la siguiente manera:

//初始化 LruCache 且设置了缓存大小
LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(cacheSize){
    @Override
    protected int sizeOf(String key, Bitmap value) {
        //计算每一个缓存Bitmap的所占内存的大小,内存单位应该和 cacheSize 的单位保持一致
        return value.getByteCount();
    }
};
  1. Agregar objeto de mapa de bits a LruCache
//参数put(String key,Bitmap bitmap)
lruCache.put(key,bitmap)
  1. Obtenga la imagen en el caché y muéstrela
//参数get(String key)
Bitmap bitmap = lruCache.get(key);
imageView.setImageBitmap(bitmap);

Lo siguiente usa LruCache para cargar una imagen de red para demostrar el uso simple de LruCache.

Cargar imagen web

Cree un ImageLoader simple, que encapsule los métodos para obtener el mapa de bits en caché, agregue el mapa de bits al caché y elimine el mapa de bits del caché, de la siguiente manera:

//ImageLoader
public class ImageLoader {
    private LruCache<String , Bitmap> lruCache;
    public ImageLoader() {
        int memorySize = (int) Runtime.getRuntime().maxMemory() / 1024;

        int cacheSize = memorySize / 8;
        lruCache = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //计算每一个缓存Bitmap的所占内存的大小
                return value.getByteCount()/1024;
            }
        };
    }

    /**
     * 添加Bitmapd到LruCache中
     * @param key
     * @param bitmap
     */
    public void addBitmapToLruCache(String key, Bitmap bitmap){
        if (getBitmapFromLruCache(key)==null){
            lruCache.put(key,bitmap);
        }
    }

    /**
     * 获取缓存的Bitmap
     * @param key
     */
    public Bitmap getBitmapFromLruCache(String key){
        if (key!=null){
            return lruCache.get(key);
        }
        return null;
    }

    /**
     * 移出缓存
     * @param key
     */
    public void removeBitmapFromLruCache(String key){
        if (key!=null){
            lruCache.remove(key);
        }
    }
}

Luego, cree una clase de subproceso para cargar imágenes, de la siguiente manera:

//加载图片的线程
public class LoadImageThread extends Thread {
    private Activity mActivity;
    private String mImageUrl;
    private ImageLoader mImageLoader;
    private ImageView mImageView;

    public LoadImageThread(Activity activity,ImageLoader imageLoader, ImageView imageView,String imageUrl) {
        this.mActivity = activity;
        this.mImageLoader = imageLoader;
        this.mImageView = imageView;
        this.mImageUrl = imageUrl;
    }

    @Override
    public void run() {
        HttpURLConnection connection = null;
        InputStream is = null;
        try {
            URL url = new URL(mImageUrl);
            connection = (HttpURLConnection) url.openConnection();
            is = connection.getInputStream();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK){
                final Bitmap bitmap = BitmapFactory.decodeStream(is);
                mImageLoader.addBitmapToLruCache("bitmap",bitmap);
                mActivity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mImageView.setImageBitmap(bitmap);
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (connection!=null){
                connection.disconnect();
            }
            if (is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Luego, use ImageLoader en MainActivity para cargar y almacenar en caché la imagen de la red en la memoria, primero obtenerla de la memoria, si no se necesita un mapa de bits en la caché, luego obtener la imagen de la red y agregarla a la caché, una vez que salga de la aplicación durante el uso, el sistema Se liberará la memoria, los métodos clave son los siguientes:

//获取图片
private void loadImage(){
    Bitmap bitmap = imageLoader.getBitmapFromLruCache("bitmap");
   if (bitmap==null){
       Log.i(TAG,"从网络获取图片");
       new LoadImageThread(this,imageLoader,imageView,url).start();
   }else{
       Log.i(TAG,"从缓存中获取图片");
       imageView.setImageBitmap(bitmap);
   }
}

// 移出缓存
private void removeBitmapFromL(String key){
    imageLoader.removeBitmapFromLruCache(key);
}

Luego llame al método anterior para obtener la imagen y eliminarla del caché en el evento correspondiente, de la siguiente manera:

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.btnLoadLruCache:
            loadImage();
            break;
        case R.id.btnRemoveBitmapL:
            removeBitmapFromL("bitmap");
            break;
    }
}

La siguiente es una captura de pantalla de registro para ilustrar la implementación:
BitmapLruCache

Caché de disco

El almacenamiento en caché de disco se refiere a la escritura de objetos de caché en el sistema de archivos. El uso de almacenamiento en caché de disco puede ayudar a reducir el tiempo de carga cuando la memoria caché no está disponible. La obtención de imágenes de la memoria caché de disco es más lenta que la recuperación de la memoria caché. Procesamiento; Disk Cache utiliza una clase DiskLruCache para implementar el almacenamiento en caché del disco. DiskLruCache ha sido oficialmente recomendado por Google. DiskLruCache no forma parte del SDK de Android. Primero, pegue un enlace de
origen de DiskLruCache en la dirección de origen de DiskLruCache .

Creación de DiskLruCache

El método de construcción de DiskLruCache es privado, por lo que no se puede usar para crear DiskLruCache. Proporciona un método abierto para crearse de la siguiente manera:

/**
 * 返回相应目录中的缓存,如果不存在则创建
 * @param directory 缓存目录
 * @param appVersion 表示应用的版本号,一般设为1
 * @param valueCount 每个Key所对应的Value的数量,一般设为1
 * @param maxSize 缓存大小
 * @throws IOException if reading or writing the cache directory fails
 */
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
        throws IOException {
    ...
    // 创建DiskLruCache
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    if (cache.journalFile.exists()) {
        ...
        return cache;
    }
    //如果缓存目录不存在,创建缓存目录以及DiskLruCache
    directory.mkdirs();
    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    ...
    return cache;
}

Nota : El directorio de caché puede seleccionar el directorio de caché en la tarjeta SD y / sdcard / Android / data / nombre del paquete de la aplicación / directorio de caché, también puede seleccionar el directorio de caché bajo los datos de la aplicación actual, por supuesto, puede especificar otro directorio, si la aplicación está desinstalada Si desea eliminar el archivo de caché, seleccione el directorio de caché en la tarjeta SD. Si desea conservar los datos, elija otro directorio. Si hay un caché de memoria, el caché se borrará después de salir de la aplicación.

Adición de caché DiskLruCache

La adición de la caché DiskLruCache se realiza a través del Editor. El Editor representa un objeto editado del objeto caché. Puede obtener el objeto Editor correspondiente a través de su método de edición (Clave de cadena). Si el Editor está utilizando el método de edición (Clave de cadena), devuelve nulo. Es decir, DiskLruCache no puede operar el mismo objeto de caché al mismo tiempo. Por supuesto, el caché se agrega a través de una clave única, por lo que es más conveniente como clave, tomando la imagen como ejemplo, en general, el valor MD5 de la url se utiliza como clave y el método de cálculo es el siguiente:

//计算url的MD5值作为key
private String hashKeyForDisk(String url) {
    String cacheKey;
    try {
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");
        mDigest.update(url.getBytes());
        cacheKey = bytesToHexString(mDigest.digest());
    } catch (NoSuchAlgorithmException e) {
        cacheKey = String.valueOf(url.hashCode());
    }
    return cacheKey;
}

private String bytesToHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bytes.length; i++) {
        String hex = Integer.toHexString(0xFF & bytes[i]);
        if (hex.length() == 1) {
            sb.append('0');
        }
        sb.append(hex);
    }
    return sb.toString();
}

Después de obtener la clave a través del valor MD5 de la url, puede obtener el objeto Editor a través del método Edit (String key) del objeto DiskLruCache, y luego a través del método commit del objeto Editor, que significa aproximadamente liberar el objeto Editir, y luego puede realizar otras operaciones a través de la clave Ligeramente

Por supuesto, después de obtener la clave, puede agregar algo para almacenar en caché en DiskLruCache. Para cargar una imagen de red en el caché, obviamente escribe lo que se almacenará en caché en el sistema de archivos mediante descarga, luego necesita una secuencia de salida Hay dos formas principales de escribir cosas en él:

  1. Cree un OutputStream para escribir los datos que se almacenarán en caché, obtenga el objeto Editor a través del método Editar (clave de cadena) de DiskLruCache, luego conviértalo a Birmap a través de OutputStream, escriba el Mapa de bits en el OutputStream creado por el objeto Editor, y finalmente llame al método commit del objeto Editor para enviar ;
  2. Primero obtenga el objeto Editor, cree un OutputStream basado en el objeto Editor y escriba directamente los datos que se almacenarán en caché, y finalmente llame al método de confirmación del objeto Editor para enviar;

Tomando el primer método como ejemplo aquí, la imagen de red se agregará a la memoria caché del disco de acuerdo con la url, y también se agregará a la memoria caché de la siguiente manera:

//添加网络图片到内存缓存和磁盘缓存
public void putCache(final String url, final CallBack callBack){
    Log.i(TAG,"putCache...");
    new AsyncTask<String,Void,Bitmap>(){
        @Override
        protected Bitmap doInBackground(String... params) {
            String key = hashKeyForDisk(params[0]);
            DiskLruCache.Editor editor = null;
            Bitmap bitmap = null;
            try {
                URL url = new URL(params[0]);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(1000 * 30);
                conn.setConnectTimeout(1000 * 30);
                ByteArrayOutputStream baos = null;
                if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){
                    BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
                    baos = new ByteArrayOutputStream();
                    byte[] bytes = new byte[1024];
                    int len = -1;
                    while((len=bis.read(bytes))!=-1){
                        baos.write(bytes,0,len);
                    }
                    bis.close();
                    baos.close();
                    conn.disconnect();
                }
                if (baos!=null){
                    bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),300,200);
                    addBitmapToCache(params[0],bitmap);//添加到内存缓存
                    editor = diskLruCache.edit(key);
                    //关键
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0));
                    editor.commit();//提交
                }
            } catch (IOException e) {
                try {
                    editor.abort();//放弃写入
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            callBack.response(bitmap);
        }
    }.execute(url);
}

Obtención de caché DiskLruCache

Una vez que entienda cómo obtener la clave, obtener la clave, obtener el objeto de instantánea mediante el método objeto DiskLruCache get y luego llegar InputStream según objetos de instantáneas, y finalmente a través de la InputStream puede obtener Bitmap añadir DiskLruCache caché, por supuesto, puede utilizar los artículos en Ajuste correctamente el método de muestreo de Bitmap, también puede comprimir y luego almacenar en caché antes de almacenar en caché. El método para obtener InputStream es el siguiente:

//获取磁盘缓存
public InputStream getDiskCache(String url) {
    Log.i(TAG,"getDiskCache...");
    String key = hashKeyForDisk(url);
    try {
        DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
        if (snapshot!=null){
            return snapshot.getInputStream(0);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

Las partes principales de DiskLruCache son más o menos como las anteriores. A continuación se implementa un caché simple de tres niveles para ilustrar el uso específico de LruCache y DiskLruCache. El código de MainActivity es el siguiente:

//MainActivity.java
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "cache_test";
    public static String CACHE_DIR = "diskCache";  //缓存目录
    public static int CACHE_SIZE = 1024 * 1024 * 10; //缓存大小
    private ImageView imageView;
    private LruCache<String, String> lruCache;
    private LruCacheUtils cacheUtils;
    private String url = "http://img06.tooopen.com/images/20161012/tooopen_sy_181713275376.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        cacheUtils = LruCacheUtils.getInstance();
        //创建内存缓存和磁盘缓存
        cacheUtils.createCache(this,CACHE_DIR,CACHE_SIZE);
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        cacheUtils.flush();
    }

    @Override
    protected void onStop() {
        super.onStop();
        cacheUtils.close();
    }
    
    public void loadImage(View view){
        load(url,imageView);
    }
    
    public void removeLruCache(View view){
        Log.i(TAG, "移出内存缓存...");
        cacheUtils.removeLruCache(url);
    }
    
    public void removeDiskLruCache(View view){
        Log.i(TAG, "移出磁盘缓存...");
        cacheUtils.removeDiskLruCache(url);
    }
    
    private void load(String url, final ImageView imageView){
        //从内存中获取图片
        Bitmap bitmap = cacheUtils.getBitmapFromCache(url);
        if (bitmap == null){
            //从磁盘中获取图片
            InputStream is = cacheUtils.getDiskCache(url);
            if (is == null){
                //从网络上获取图片
                cacheUtils.putCache(url, new LruCacheUtils.CallBack<Bitmap>() {
                    @Override
                    public void response(Bitmap bitmap1) {
                        Log.i(TAG, "从网络中获取图片...");
                        Log.i(TAG, "正在从网络中下载图片...");
                        imageView.setImageBitmap(bitmap1);
                        Log.i(TAG, "从网络中获取图片成功...");
                    }
                });
            }else{
                Log.i(TAG, "从磁盘中获取图片...");
                bitmap = BitmapFactory.decodeStream(is);
                imageView.setImageBitmap(bitmap);
            }
        }else{
            Log.i(TAG, "从内存中获取图片...");
            imageView.setImageBitmap(bitmap);
        }
    }
}

El archivo de diseño es relativamente simple y el código no está publicado. La siguiente es una captura de pantalla del registro en ejecución para ilustrar la implementación, como se muestra en la siguiente figura:

BitmapDiskLruCache

Este artículo registra el uso básico de LruCache y DiskLruCache. Al menos debe tener cierta comprensión de estas dos clases auxiliares de caché. Para su implementación específica, consulte el código fuente.
[Código en el artículo]: Portal

Puede prestar atención al número público: jzman-blog, intercambiar aprendizaje juntos.
Inclinarse ante

Supongo que te gusta

Origin www.cnblogs.com/jzmanu/p/12688906.html
Recomendado
Clasificación