android对图片操作的原理

项目地址http://download.csdn.net/detail/syb666eee/9547503
web服务项目地址http://download.csdn.net/detail/syb666eee/9547523
今天看了一个黑马的项目,很久之前的2013年左右。应该当时还没有xutil,或者当时他们没用,就自己写的从网络下载图片,以及把图片缓存到内存与本地中。
我就不大量粘贴代码了,然后把实际项目放在后面和前面。大家可以自己下载。然后对这项目看这篇文章,当然启动服务器,去获取图片,我会一起上传。url的配置就靠自己了,只需改一下你主机的地址就行,服务器中的json文件的url也要改成你自己服务器的,有什么不懂的可以加我好友问我,qq315019528

首先重新进入某个应用的时候加载图片要先从内存中加载,内存中没有的话从本地sd卡中加载,本地没有最后再从网络中加载。从网络下载的图片先存到内存中,再存到本地中。

第一个调用的比较特殊,直接从一个自己写的软引用map中取数据

String imgUrl = hot.getImg_url();
if (StringUtils.isNotBlank(imgUrl)) {
holder.itemImg.setTag(hot.getWorks_id());
//从软引用map内存中加载。
Bitmap bm=GloableParameters.IMGCACHE.get(hot.getWorks_id());
if (bm != null) {
    holder.itemImg.setImageBitmap(bm);
} else {
        if (callback != null) {
        //加载网络中
        new ImageDownload(callback).execute(imgUrl, hot.getWorks_id());
        }
    }
}

第二个调用就比较正常了,遵循三级缓存的原理

//这下面的代码是从内存中和本地中读取图片,具体可以看源码。我把ImageCache这个累的代码粘贴在文章的最后面。
                Bitmap bm = ImageCache.getInstance().get(video.getWorks_id());

                    if (bm != null) {
                        holder.itemImg.setImageBitmap(bm);
                    } else {
                        holder.itemImg.setImageResource(R.drawable.id_default);
                        if (callback != null) {

//从网络加载图片
new ImageDownload(callback).execute(imgUrl, video.getWorks_id(),ImageDownload.CACHE_TYPE_LRU);
}
}

接着看GloableParameters.IMGCACHE中的代码 里面有如下一个map集合用于存图片。

public static SoftValueMap IMGCACHE = new SoftValueMap();

我们再接着看 SoftValueMap 中的代码,这个类就是底层的能够操作内存的类。一个软引用用来存储图片到内存中
下面的代码看不懂没关系的,上面的就是底层存储的原理。反正现在android存储图片也不使用软引用了
。没兴趣可以直接跳过,因为后面的类中有别的方式对内存和本地存储图片进行操作,且更加方便。

public class SoftValueMap<K, V> extends HashMap<K, V> {
    // 降低V的引用级别到软引用
    private HashMap> temp;


    private ReferenceQueue queue;// 装破袋子(放置手机V)
//当引用对象所指向的内存空间被GC回收后,该引用对象则被追加到引用队列的末尾
    public SoftValueMap() {
        // 软引用
        // Object v=new Object();//占用内存多
        // SoftReference sr=new SoftReference(v);// 降低了v的引用界别

        // ①将手机(占用内存较多的对象)添加到袋子(SoftReference)里面
        // ②一旦手机被偷了,将破袋子回收

        temp = new HashMap>();
        queue = new ReferenceQueue();
    }

    @Override
    public V put(K key, V value) {
        // SoftValue sr = new SoftValue(value);
        // 当GC在回收手机时候,会将sr添加到queue
        SoftValue sr = new SoftValue(value, key, queue);// 将sr与queue绑定
        temp.put(key, sr);
        return null;
    }

    @Override
    public V get(Object key) {
        clearSR();
        SoftValue sr = temp.get(key);
        if (sr != null) {
            // 如果此引用对象已经由程序或垃圾回收器清除,则此方法将返回 null。
            return sr.get();
        } else {
            return null;
        }
    }

    @Override
    public boolean containsKey(Object key) {
        clearSR();
        // 什么才叫含有
        // 获取到袋子,从袋子里面拿到手机了,含有
        SoftValue sr = temp.get(key);
        // temp.containsKey(key);

        /*
         * if(sr.get()!=null) { return true; }else{ return false; }
         */
        if (sr != null) {
            return sr.get() != null;
        }
        return false;

    }

    /**
     * 清理被回收掉手机的破袋子
     */
    private void clearSR() {
        // 方案一:循环一下temp,如果被回收掉了,清理出temp
        // 内存如果内存还能够满足:temp不会有元素被回收

        // 方案二:如果GC把手机回收,将破袋子的引用记录到一个(自己创建)集合中
        // (自己创建)集合

        // 如果存在一个立即可用的对象,则从该队列中"移除"此对象并返回。否则此方法立即返回 null。
        SoftValue poll = (SoftValue) queue.poll();
        while (poll != null) {
            // 从temp将sr强引用对象清除
            temp.remove(poll.key);
            poll = (SoftValue) queue.poll();
        }
    }

    @Override
    public void clear() {
        temp.clear();
    }

    /**
     * 加强版的袋子,存储了一下key信息
     * 
     * @author Administrator
     * 
     * @param 
     * @param 
     */
    private class SoftValue<K, V> extends SoftReference<V> {
        private Object key;

        public SoftValue(V r, Object key, ReferenceQueuesuper V> q) {
            super(r, q);
            this.key = key;
        }
    }
}

我们再接着看ImageDownload 中的方法
//从网络加载数据
Bitmap bitmap = loadImageFromUrl(params[0]);
下面是loadImageFromUrl()方法

InputStream i = null;
        try {
            HttpClientUtil adapter = new HttpClientUtil();
            i = adapter.loadImg(url);

            // http://blog.csdn.net/xianming01/article/details/8280434
            BitmapFactory.Options opt = new BitmapFactory.Options();
            opt.inPreferredConfig = Bitmap.Config.RGB_565;
            opt.inPurgeable = true;
            opt.inInputShareable = true;

            return BitmapFactory.decodeStream(i, null, opt);
        } catch (Exception e) {
            e.printStackTrace();
        }

//下载完图片后然后把数据存到本地和内存中
    ImageCache.getInstance().put(tag, bitmap);

imagecache中的代码,首先了解一下LruCache
核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
DiskLruCache用于存储到存储卡上

在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

private LruCache mMemoryCache;//存到内存中

    private DiskLruCache diskLruCache;//存到存储卡上

//整个构造函数中实例化了上面两个类的对象,用于下面函数中调用
private ImageCache() {
        mMemoryCache = new LruCache(DEFAULT_MEM_CACHE_SIZE) {
            @Override
            protected int sizeOf(Object key, Bitmap value) {

                return getBitmapSize(value);
            }

            @Override
            protected void entryRemoved(boolean evicted, Object key,
                    Bitmap oldValue, Bitmap newValue) {
                if (evicted) {
                    // 被清除出集合的对象,可以将该对象添加到软引用中或者缓存起来
                    Log.i(TAG, key.toString());

                }
                super.entryRemoved(evicted, key, oldValue, newValue);
            }

        };
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
                //内存卡可用
            File externalStorageDirectory = Environment
                    .getExternalStorageDirectory();
            String path = externalStorageDirectory.getAbsolutePath()
                    + ConstantValue.IMAGE_PATH;//存储路径
            diskLruCache = DiskLruCache.openCache(GloableParameters.MAIN,
                    new File(path), DIS_CACHE_SIZE);
        }
    }
两个关键方法如下,
public Bitmap get(Object key) {
        Bitmap bitmap = mMemoryCache.get(key);//从内存中读图片
        if (bitmap == null) {
            if (diskLruCache != null)
                bitmap = diskLruCache.get(key.toString());//从本地中获取图片
            if (bitmap != null) {
                mMemoryCache.put(key, bitmap);//把图片存到内存中
            }
        }

        return bitmap;
    }

    public void put(Object key, Bitmap bitmap) {
        mMemoryCache.put(key, bitmap);//把图片存到内存中
        if (diskLruCache != null)
            diskLruCache.put(key.toString(), bitmap);//把图片存到本地
    }

//原理与我之前写的三级缓存的原理差不多,当然这其中还有很多的类 ,没有写出来,可以看我的项目源码,对着这篇文章。项目是可以直接导入eclipse中运行的,看源码时候从LrucacheAdapter和 SoftcacheAdapter中可以开始看。

项目地址http://download.csdn.net/detail/syb666eee/9547503

imagecache类的代码

public class ImageCache {
    private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 1024 * 8; // 5MB
    private static final int DIS_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
    protected static final String TAG = "ImageCache";
    private LruCache mMemoryCache;//存到内存中

    private DiskLruCache diskLruCache;//存到存储卡上

    private ImageCache() {
        mMemoryCache = new LruCache(DEFAULT_MEM_CACHE_SIZE) {
            @Override
            protected int sizeOf(Object key, Bitmap value) {

                return getBitmapSize(value);
            }

            @Override
            protected void entryRemoved(boolean evicted, Object key,
                    Bitmap oldValue, Bitmap newValue) {
                if (evicted) {
                    // 被清除出集合的对象,可以将该对象添加到软引用中或者缓存起来
                    Log.i(TAG, key.toString());

                }
                super.entryRemoved(evicted, key, oldValue, newValue);
            }

        };
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            File externalStorageDirectory = Environment
                    .getExternalStorageDirectory();
            String path = externalStorageDirectory.getAbsolutePath()
                    + ConstantValue.IMAGE_PATH;
            diskLruCache = DiskLruCache.openCache(GloableParameters.MAIN,
                    new File(path), DIS_CACHE_SIZE);
        }
    }

    private static ImageCache cache = new ImageCache();

    public static ImageCache getInstance() {
        return cache;
    }

    public Bitmap get(Object key) {
        Bitmap bitmap = mMemoryCache.get(key);
        if (bitmap == null) {
            if (diskLruCache != null)
                bitmap = diskLruCache.get(key.toString());
            if (bitmap != null) {
                mMemoryCache.put(key, bitmap);
            }
        }

        return bitmap;
    }

    public void put(Object key, Bitmap bitmap) {
        mMemoryCache.put(key, bitmap);
        if (diskLruCache != null)
            diskLruCache.put(key.toString(), bitmap);
    }

    public static int getBitmapSize(Bitmap bitmap) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
            return bitmap.getByteCount();
        }
        // Pre HC-MR1
        return bitmap.getRowBytes() * bitmap.getHeight();
    }

    public void clear() {
        mMemoryCache.evictAll();
    }
}

DiskLruCache类反正我看不懂,android应该有原生的这个类。
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the “License”);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cn.ithm.kmplayer1.util;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import cn.ithm.kmplayer1.BuildConfig;

/**
* A simple disk LRU bitmap cache to illustrate how a disk cache would be used
* for bitmap caching. A much more robust and efficient disk LRU cache solution
* can be found in the ICS source code
* (libcore/luni/src/main/java/libcore/io/DiskLruCache.java) and is preferable
* to this simple implementation.
*/
public class DiskLruCache {
private static final String TAG = “DiskLruCache”;
private static final String CACHE_FILENAME_PREFIX = “”;
private static final int MAX_REMOVALS = 4;
private static final int INITIAL_CAPACITY = 32;
private static final float LOAD_FACTOR = 0.75f;

private final File mCacheDir;
private int cacheSize = 0;
private int cacheByteSize = 0;
private final int maxCacheItemSize = 100; // 64 item default
private long maxCacheByteSize = 1024 * 1024 * 5; // 5MB default
private CompressFormat mCompressFormat = CompressFormat.JPEG;
private int mCompressQuality = 70;

private final Map mLinkedHashMap = Collections.synchronizedMap(new LinkedHashMap(INITIAL_CAPACITY,
        LOAD_FACTOR, true));

/**
 * A filename filter to use to identify the cache filenames which have
 * CACHE_FILENAME_PREFIX prepended.
 */
private static final FilenameFilter cacheFileFilter = new FilenameFilter() {
    @Override
    public boolean accept(File dir, String filename) {
        return filename.startsWith(CACHE_FILENAME_PREFIX);
    }
};

/**
 * Used to fetch an instance of DiskLruCache.
 * 
 * @param context
 * @param cacheDir
 * @param maxByteSize
 * @return
 */
public static DiskLruCache openCache(Context context, File cacheDir, long maxByteSize) {
    if (!cacheDir.exists()) {
        cacheDir.mkdir();
    }

    if (cacheDir.isDirectory() && cacheDir.canWrite() && Utils.getUsableSpace(cacheDir) > maxByteSize) {
        return new DiskLruCache(cacheDir, maxByteSize);
    }

    return null;
}

/**
 * Constructor that should not be called directly, instead use
 * {@link DiskLruCache#openCache(Context, File, long)} which runs some extra
 * checks before creating a DiskLruCache instance.
 * 
 * @param cacheDir
 * @param maxByteSize
 */
private DiskLruCache(File cacheDir, long maxByteSize) {
    mCacheDir = cacheDir;
    maxCacheByteSize = maxByteSize;
}

/**
 * Add a bitmap to the disk cache.
 * 
 * @param key
 *            A unique identifier for the bitmap.
 * @param data
 *            The bitmap to store.
 */
public void put(String key, Bitmap data) {
    synchronized (mLinkedHashMap) {
        if (mLinkedHashMap.get(key) == null) {
            try {
                final String file = createFilePath(mCacheDir, key);
                if (writeBitmapToFile(data, file)) {
                    put(key, file);
                    flushCache();
                }
            } catch (final FileNotFoundException e) {
                Log.e(TAG, "Error in put: " + e.getMessage());
            } catch (final IOException e) {
                Log.e(TAG, "Error in put: " + e.getMessage());
            }
        }
    }
}

private void put(String key, String file) {
    mLinkedHashMap.put(key, file);
    cacheSize = mLinkedHashMap.size();
    cacheByteSize += new File(file).length();
}

/**
 * Flush the cache, removing oldest entries if the total size is over the
 * specified cache size. Note that this isn't keeping track of stale files
 * in the cache directory that aren't in the HashMap. If the images and keys
 * in the disk cache change often then they probably won't ever be removed.
 */
private void flushCache() {
    Entry eldestEntry;
    File eldestFile;
    long eldestFileSize;
    int count = 0;

    while (count < MAX_REMOVALS && (cacheSize > maxCacheItemSize || cacheByteSize > maxCacheByteSize)) {
        eldestEntry = mLinkedHashMap.entrySet().iterator().next();
        eldestFile = new File(eldestEntry.getValue());
        eldestFileSize = eldestFile.length();
        mLinkedHashMap.remove(eldestEntry.getKey());
        eldestFile.delete();
        cacheSize = mLinkedHashMap.size();
        cacheByteSize -= eldestFileSize;
        count++;
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "flushCache - Removed cache file, " + eldestFile + ", " + eldestFileSize);
        }
    }
}

/**
 * Get an image from the disk cache.
 * 
 * @param key
 *            The unique identifier for the bitmap
 * @return The bitmap or null if not found
 */
public Bitmap get(String key) {
    synchronized (mLinkedHashMap) {
        final String file = mLinkedHashMap.get(key);
        if (file != null) {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Disk cache hit");
            }
            return BitmapFactory.decodeFile(file);
        } else {
            final String existingFile = createFilePath(mCacheDir, key);
            if (new File(existingFile).exists()) {
                put(key, existingFile);
                if (BuildConfig.DEBUG) {
                    Log.d(TAG, "Disk cache hit (existing file)");
                }
                return BitmapFactory.decodeFile(existingFile);
            }
        }
        return null;
    }
}

/**
 * Checks if a specific key exist in the cache.
 * 
 * @param key
 *            The unique identifier for the bitmap
 * @return true if found, false otherwise
 */
public boolean containsKey(String key) {
    // See if the key is in our HashMap
    if (mLinkedHashMap.containsKey(key)) {
        return true;
    }

    // Now check if there's an actual file that exists based on the key
    final String existingFile = createFilePath(mCacheDir, key);
    if (new File(existingFile).exists()) {
        // File found, add it to the HashMap for future use
        put(key, existingFile);
        return true;
    }
    return false;
}

/**
 * Removes all disk cache entries from this instance cache dir
 */
public void clearCache() {
    DiskLruCache.clearCache(mCacheDir);
}

/**
 * Removes all disk cache entries from the application cache directory in
 * the uniqueName sub-directory.
 * 
 * @param context
 *            The context to use
 * @param uniqueName
 *            A unique cache directory name to append to the app cache
 *            directory
 */
public static void clearCache(Context context, String uniqueName) {
    File cacheDir = getDiskCacheDir(context, uniqueName);
    clearCache(cacheDir);
}

/**
 * Removes all disk cache entries from the given directory. This should not
 * be called directly, call {@link DiskLruCache#clearCache(Context, String)}
 * or {@link DiskLruCache#clearCache()} instead.
 * 
 * @param cacheDir
 *            The directory to remove the cache files from
 */
private static void clearCache(File cacheDir) {
    final File[] files = cacheDir.listFiles(cacheFileFilter);
    for (int i = 0; i < files.length; i++) {
        files[i].delete();
    }
}

/**
 * Get a usable cache directory (external if available, internal otherwise).
 * 
 * @param context
 *            The context to use
 * @param uniqueName
 *            A unique directory name to append to the cache dir
 * @return The cache dir
 */
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.getExternalStorageState() == Environment.MEDIA_MOUNTED || !Utils.isExternalStorageRemovable() ? Utils
            .getExternalCacheDir(context).getPath() : context.getCacheDir().getPath();

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

/**
 * Creates a constant cache file path given a target cache directory and an
 * image key.
 * 
 * @param cacheDir
 * @param key
 * @return
 */
public static String createFilePath(File cacheDir, String key) {
    try {
        // Use URLEncoder to ensure we have a valid filename, a tad hacky
        // but it will do for
        // this example
        return cacheDir.getAbsolutePath() + File.separator + CACHE_FILENAME_PREFIX + URLEncoder.encode(key.replace("*", ""), "UTF-8");
    } catch (final UnsupportedEncodingException e) {
        Log.e(TAG, "createFilePath - " + e);
    }

    return null;
}

/**
 * Create a constant cache file path using the current cache directory and
 * an image key.
 * 
 * @param key
 * @return
 */
public String createFilePath(String key) {
    return createFilePath(mCacheDir, key);
}

/**
 * Sets the target compression format and quality for images written to the
 * disk cache.
 * 
 * @param compressFormat
 * @param quality
 */
public void setCompressParams(CompressFormat compressFormat, int quality) {
    mCompressFormat = compressFormat;
    mCompressQuality = quality;
}

/**
 * Writes a bitmap to a file. Call
 * {@link DiskLruCache#setCompressParams(CompressFormat, int)} first to set
 * the target bitmap compression and format.
 * 
 * @param bitmap
 * @param file
 * @return
 */
private boolean writeBitmapToFile(Bitmap bitmap, String file) throws IOException, FileNotFoundException {

    OutputStream out = null;
    try {
        out = new BufferedOutputStream(new FileOutputStream(file), Utils.IO_BUFFER_SIZE);
        return bitmap.compress(mCompressFormat, mCompressQuality, out);
    } finally {
        if (out != null) {
            out.close();
        }
    }
}

}

你可能感兴趣的:(android图片)