zoukankan      html  css  js  c++  java
  • LruCache--远程图片获取与本地缓存

    Class Overview


    A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

    If your cached values hold resources that need to be explicitly released, override entryRemoved(boolean, K, V, V).

    If a cache miss should be computed on demand for the corresponding keys, override create(K). This simplifies the calling code, allowing it to assume a value will always be returned, even when there's a cache miss.

    By default, the cache size is measured in the number of entries. Override sizeOf(K, V) to size the cache in different units. For example, this cache is limited to 4MiB of bitmaps:

    int cacheSize = 4 * 1024 * 1024; // 4MiB
       LruCache bitmapCache = new LruCache(cacheSize) {
           protected int sizeOf(String key, Bitmap value) {
               return value.getByteCount();
           
       }}

    This class is thread-safe. Perform multiple cache operations atomically by synchronizing on the cache:

    synchronized (cache) {
         if (cache.get(key) == null) {
             cache.put(key, value);
         
       }}

    This class does not allow null to be used as a key or value. A return value of null from get(K)put(K, V) or remove(K) is unambiguous: the key was not in the cache.

    This class appeared in Android 3.1 (Honeycomb MR1); it's available as part of Android's Support Package for earlier releases.

    Summary


    Public Constructors
      LruCache(int maxSize)
    Public Methods
    synchronized final int createCount()
    Returns the number of times create(Object) returned a value.
    final void evictAll()
    Clear the cache, calling entryRemoved(boolean, K, V, V) on each removed entry.
    synchronized final int evictionCount()
    Returns the number of values that have been evicted.
    final V get(K key)
    Returns the value for key if it exists in the cache or can be created by #create.
    synchronized final int hitCount()
    Returns the number of times get(K) returned a value that was already present in the cache.
    synchronized final int maxSize()
    For caches that do not override sizeOf(K, V), this returns the maximum number of entries in the cache.
    synchronized final int missCount()
    Returns the number of times get(K) returned null or required a new value to be created.
    final V put(K key, V value)
    Caches value for key.
    synchronized final int putCount()
    Returns the number of times put(K, V) was called.
    final V remove(K key)
    Removes the entry for key if it exists.
    synchronized final int size()
    For caches that do not override sizeOf(K, V), this returns the number of entries in the cache.
    synchronized final Map<K, V> snapshot()
    Returns a copy of the current contents of the cache, ordered from least recently accessed to most recently accessed.
    synchronized final String toString()
    Returns a string containing a concise, human-readable description of this object.
    void trimToSize(int maxSize)
    Remove the eldest entries until the total of remaining entries is at or below the requested size.
    Protected Methods
    V create(K key)
    Called after a cache miss to compute a value for the corresponding key.
    void entryRemoved(boolean evicted, K key, V oldValue, V newValue)
    Called for entries that have been evicted or removed.
    int sizeOf(K key, V value)
    Returns the size of the entry for key and value in user-defined units.


    http://developer.android.com/reference/android/util/LruCache.html

    Demo分步讲解:

    1.布局文件

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" >
    
        <GridView
            android:id="@+id/gridView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:stretchMode="columnWidth" 
            android:columnWidth="120dip" 
            android:verticalSpacing="10dip"
            android:horizontalSpacing="10dip"
            android:cacheColorHint="@android:color/transparent"
            android:numColumns="auto_fit" >
        </GridView>
    
    </RelativeLayout>

    2. 保存图片到SD卡或者手机上(工具类)

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.Bitmap.CompressFormat;
    import android.graphics.BitmapFactory;
    import android.os.Environment;
    
    public class FileUtils {
        /**
         * sd卡的根目录
         */
        private static String mSdRootPath = Environment
                .getExternalStorageDirectory().getPath();
        /**
         * 手机的缓存根目录
         */
        private static String mDataRootPath = null;
        /**
         * 保存Image的目录名
         */
        private final static String FOLDER_NAME = "/AndroidImage";
    
        public FileUtils(Context context) {
            mDataRootPath = context.getCacheDir().getPath();
        }
    
        /**
         * 获取储存Image的目录
         * @return
         */
        private String getStorageDirectory() {
            return Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED) ? mSdRootPath + FOLDER_NAME
                    : mDataRootPath + FOLDER_NAME;
        }
    
        /**
         * 保存Image的方法,有sd卡存储到sd卡,没有就存储到手机目录
         * 
         * @param fileName
         * @param bitmap
         * @throws IOException
         */
        public void savaBitmap(String fileName, Bitmap bitmap) throws IOException {
            if (bitmap == null) {
                return;
            }
            String path = getStorageDirectory();
            File folderFile = new File(path);
            if (!folderFile.exists()) {
                folderFile.mkdir();
            }
            File file = new File(path + File.separator + fileName);
            file.createNewFile();
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();
        }
    
        /**
         * 从手机或者sd卡获取Bitmap
         * 
         * @param fileName
         * @return
         */
        public Bitmap getBitmap(String fileName) {
            return BitmapFactory.decodeFile(getStorageDirectory() + File.separator
                    + fileName);
        }
    
        /**
         * 判断文件是否存在
         * 
         * @param fileName
         * @return
         */
        public boolean isFileExists(String fileName) {
            return new File(getStorageDirectory() + File.separator + fileName)
                    .exists();
        }
    
        /**
         * 获取文件的大小
         * 
         * @param fileName
         * @return
         */
        public long getFileSize(String fileName) {
            return new File(getStorageDirectory() + File.separator + fileName)
                    .length();
        }
    
        /**
         * 删除SD卡或者手机的缓存图片和目录
         */
        public void deleteFile() {
            File dirFile = new File(getStorageDirectory());
            if (!dirFile.exists()) {
                return;
            }
            if (dirFile.isDirectory()) {
                String[] children = dirFile.list();
                for (int i = 0; i < children.length; i++) {
                    new File(dirFile, children[i]).delete();
                }
            }
    
            dirFile.delete();
        }
    }

          compress方法详解如下:

    public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream)

    Write a compressed version of the bitmap to the specified outputstream. If this returns true, the bitmap can be reconstructed by passing a corresponding inputstream to BitmapFactory.decodeStream(). Note: not all Formats support all bitmap configs directly, so it is possible that the returned bitmap from BitmapFactory could be in a different bitdepth, and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque pixels).

    写入位图的压缩版本到指定的OutputStream。如果返回true,则位图可以通过传递一个相应的InputStream BitmapFactory.decodeStream(重建)。注意:并非所有的格式直接支持所有的位图的configs,所以它可能是从BitmapFactory返回的位图可能是在不同的位深度,和/或可能已经失去了每个像素的alpha(如JPEG只支持不透明像素)。

    Parameters
    format The format of the compressed image
    quality Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some formats, like PNG which is lossless, will ignore the quality setting
    stream The outputstream to write the compressed data.
    Returns     true if successfully compressed to the specified stream.

    3. 主Activity代码片段

    import android.app.Activity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.widget.GridView;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
        private GridView mGridView;
        private String [] imageThumbUrls = Images.imageThumbUrls; 
        private ImageAdapter mImageAdapter;
        private FileUtils fileUtils;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            fileUtils = new FileUtils(this);
            mGridView = (GridView) findViewById(R.id.gridView);
            mImageAdapter = new ImageAdapter(this, mGridView, imageThumbUrls);
            mGridView.setAdapter(mImageAdapter);
        }
    
        @Override
        protected void onDestroy() {
            mImageAdapter.cancelTask();
            super.onDestroy();
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            super.onCreateOptionsMenu(menu);
            menu.add("删除手机中图片缓存");
            return super.onCreateOptionsMenu(menu);
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
            case 0:
                fileUtils.deleteFile();
                Toast.makeText(getApplication(), "清空缓存成功", Toast.LENGTH_SHORT).show();
                break;
            }
            return super.onOptionsItemSelected(item);
        }
    
    }

    4. 图片适配器ImageAdapter

    import android.content.Context;
    import android.graphics.Bitmap;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AbsListView;
    import android.widget.AbsListView.OnScrollListener;
    import android.widget.BaseAdapter;
    import android.widget.GridView;
    import android.widget.ImageView;
    
    import com.example.asyncimageloader.ImageDownLoader.onImageLoaderListener;
    
    public class ImageAdapter extends BaseAdapter implements OnScrollListener {
        /**
         * 上下文对象的引用
         */
        private Context context;
    
        /**
         * Image Url的数组
         */
        private String[] imageThumbUrls;
    
        /**
         * GridView对象的应用
         */
        private GridView mGridView;
    
        /**
         * Image 下载器
         */
        private ImageDownLoader mImageDownLoader;
    
        /**
         * 记录是否刚打开程序,用于解决进入程序不滚动屏幕,不会下载图片的问题。
         * 参考http://blog.csdn.net/guolin_blog/article/details/9526203#comments
         */
        private boolean isFirstEnter = true;
    
        /**
         * 一屏中第一个item的位置
         */
        private int mFirstVisibleItem;
    
        /**
         * 一屏中所有item的个数
         */
        private int mVisibleItemCount;
    
        public ImageAdapter(Context context, GridView mGridView,
                String[] imageThumbUrls) {
            this.context = context;
            this.mGridView = mGridView;
            this.imageThumbUrls = imageThumbUrls;
            mImageDownLoader = new ImageDownLoader(context);
            mGridView.setOnScrollListener(this);
        }
    
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // 仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
            if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
                showImage(mFirstVisibleItem, mVisibleItemCount);
            } else {
                cancelTask();
            }
        }
    
        /**
         * GridView滚动的时候调用的方法,刚开始显示GridView也会调用此方法
         */
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            mFirstVisibleItem = firstVisibleItem;
            mVisibleItemCount = visibleItemCount;
            // 因此在这里为首次进入程序开启下载任务。
            if (isFirstEnter && visibleItemCount > 0) {
                showImage(mFirstVisibleItem, mVisibleItemCount);
                isFirstEnter = false;
            }
        }
    
        @Override
        public int getCount() {
            return imageThumbUrls.length;
        }
    
        @Override
        public Object getItem(int position) {
            return imageThumbUrls[position];
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView mImageView;
            final String mImageUrl = imageThumbUrls[position];
            if (convertView == null) {
                mImageView = new ImageView(context);
            } else {
                mImageView = (ImageView) convertView;
            }
    
            mImageView.setLayoutParams(new GridView.LayoutParams(150, 150));
            mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
    
            // 给ImageView设置Tag,这里已经是司空见惯了
            mImageView.setTag(mImageUrl);
    
            /******************************* 去掉下面这几行试试是什么效果 ****************************/
            Bitmap bitmap = mImageDownLoader.showCacheBitmap(mImageUrl.replaceAll(
                    "[^\w]", ""));
            if (bitmap != null) {
                mImageView.setImageBitmap(bitmap);
            } else {
                mImageView.setImageDrawable(context.getResources().getDrawable(
                        R.drawable.ic_empty));
            }
            /**********************************************************************************/
    
            return mImageView;
        }
    
        /**
         * 显示当前屏幕的图片,先会去查找LruCache,LruCache没有就去sd卡或者手机目录查找,在没有就开启线程去下载
         * 
         * @param firstVisibleItem
         * @param visibleItemCount
         */
        private void showImage(int firstVisibleItem, int visibleItemCount) {
            Bitmap bitmap = null;
            for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                String mImageUrl = imageThumbUrls[i];
                final ImageView mImageView = (ImageView) mGridView
                        .findViewWithTag(mImageUrl);
                bitmap = mImageDownLoader.downloadImage(mImageUrl,
                        new onImageLoaderListener() {
    
                            @Override
                            public void onImageLoader(Bitmap bitmap, String url) {
                                if (mImageView != null && bitmap != null) {
                                    mImageView.setImageBitmap(bitmap);
                                }
    
                            }
                        });
    
                if (bitmap != null) {
                    mImageView.setImageBitmap(bitmap);
                } else {
                    mImageView.setImageDrawable(context.getResources().getDrawable(
                            R.drawable.ic_empty));
                }
            }
        }
    
        /**
         * 取消下载任务
         */
        public void cancelTask() {
            mImageDownLoader.cancelTask();
        }
    
    }

    5. (ImageDownLoader)缓存Image的类,当存储Image的大小大于LruCache设定的值,系统自动释放内存

    import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Handler;
    import android.os.Message;
    import android.support.v4.util.LruCache;
    
    public class ImageDownLoader {
        /**
         * 缓存Image的类,当存储Image的大小大于LruCache设定的值,系统自动释放内存
         */
        private LruCache<String, Bitmap> mMemoryCache;
        /**
         * 操作文件相关类对象的引用
         */
        private FileUtils fileUtils;
        /**
         * 下载Image的线程池
         */
        private ExecutorService mImageThreadPool = null;
    
        public ImageDownLoader(Context context) {
            // 获取系统分配给每个应用程序的最大内存,每个应用系统分配32M
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            int mCacheSize = maxMemory / 8;
            // 给LruCache分配1/8 4M
            mMemoryCache = new LruCache<String, Bitmap>(mCacheSize) {
    
                // 必须重写此方法,来测量Bitmap的大小
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes() * value.getHeight();
                }
    
            };
    
            fileUtils = new FileUtils(context);
        }
    
        /**
         * 获取线程池的方法,因为涉及到并发的问题,我们加上同步锁
         * 
         * @return
         */
        public ExecutorService getThreadPool() {
            if (mImageThreadPool == null) {
                synchronized (ExecutorService.class) {
                    if (mImageThreadPool == null) {
                        // 为了下载图片更加的流畅,我们用了2个线程来下载图片
                        mImageThreadPool = Executors.newFixedThreadPool(2);
                    }
                }
            }
    
            return mImageThreadPool;
    
        }
    
        /**
         * 添加Bitmap到内存缓存
         * 
         * @param key
         * @param bitmap
         */
        public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
            if (getBitmapFromMemCache(key) == null && bitmap != null) {
                mMemoryCache.put(key, bitmap);
            }
        }
    
        /**
         * 从内存缓存中获取一个Bitmap
         * 
         * @param key
         * @return
         */
        public Bitmap getBitmapFromMemCache(String key) {
            return mMemoryCache.get(key);
        }
    
        /**
         * 先从内存缓存中获取Bitmap,如果没有就从SD卡或者手机缓存中获取,SD卡或者手机缓存 没有就去下载
         * 
         * @param url
         * @param listener
         * @return
         */
        public Bitmap downloadImage(final String url,
                final onImageLoaderListener listener) {
            // 替换Url中非字母和非数字的字符,这里比较重要,因为我们用Url作为文件名,比如我们的Url
            // 是Http://xiaanming/abc.jpg;用这个作为图片名称,系统会认为xiaanming为一个目录,
            // 我们没有创建此目录保存文件就会保存
            final String subUrl = url.replaceAll("[^\w]", "");
            Bitmap bitmap = showCacheBitmap(subUrl);
            if (bitmap != null) {
                return bitmap;
            } else {
    
                final Handler handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        listener.onImageLoader((Bitmap) msg.obj, url);
                    }
                };
    
                getThreadPool().execute(new Runnable() {
    
                    @Override
                    public void run() {
                        Bitmap bitmap = getBitmapFormUrl(url);
                        Message msg = handler.obtainMessage();
                        msg.obj = bitmap;
                        handler.sendMessage(msg);
    
                        try {
                            // 保存在SD卡或者手机目录
                            fileUtils.savaBitmap(subUrl, bitmap);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
    
                        // 将Bitmap 加入内存缓存
                        addBitmapToMemoryCache(subUrl, bitmap);
                    }
                });
            }
    
            return null;
        }
    
        /**
         * 获取Bitmap, 内存中没有就去手机或者sd卡中获取,这一步在getView中会调用,比较关键的一步
         * 
         * @param url
         * @return
         */
        public Bitmap showCacheBitmap(String url) {
            if (getBitmapFromMemCache(url) != null) {
                return getBitmapFromMemCache(url);
            } else if (fileUtils.isFileExists(url)
                    && fileUtils.getFileSize(url) != 0) {
                // 从SD卡获取手机里面获取Bitmap
                Bitmap bitmap = fileUtils.getBitmap(url);
    
                // 将Bitmap 加入内存缓存
                addBitmapToMemoryCache(url, bitmap);
                return bitmap;
            }
    
            return null;
        }
    
        /**
         * 从Url中获取Bitmap
         * 
         * @param url
         * @return
         */
        private Bitmap getBitmapFormUrl(String url) {
            Bitmap bitmap = null;
            HttpURLConnection con = null;
            try {
                URL mImageUrl = new URL(url);
                con = (HttpURLConnection) mImageUrl.openConnection();
                con.setConnectTimeout(10 * 1000);
                con.setReadTimeout(10 * 1000);
                con.setDoInput(true);
                con.setDoOutput(true);
                bitmap = BitmapFactory.decodeStream(con.getInputStream());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (con != null) {
                    con.disconnect();
                }
            }
            return bitmap;
        }
    
        /**
         * 取消正在下载的任务
         */
        public synchronized void cancelTask() {
            if (mImageThreadPool != null) {
                mImageThreadPool.shutdownNow();
                mImageThreadPool = null;
            }
        }
    
        /**
         * 异步下载图片的回调接口
         * 
         * @author len
         * 
         */
        public interface onImageLoaderListener {
            void onImageLoader(Bitmap bitmap, String url);
        }
    
    }

    6.配置文件

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.asyncimageloader"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="16" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:configChanges="orientation"
                android:name="com.example.asyncimageloader.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        
      <uses-permission android:name="android.permission.INTERNET" /> 
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
    
    
    </manifest>

    DEMO下载地址:http://download.csdn.net/detail/androidsj/7171167

  • 相关阅读:
    CodeForces
    Vs2012在Linux开发中的应用(6):改写Makefile项目的Build过程
    Mac 上VitrualBox安装CentOS6.5 调整root分区的大小
    iOS面试常见题
    C语言入门(2)——安装VS2013开发环境并编写第一个C语言程序
    大数据
    HDU 5188 背包
    Android 上的 制表符(tab) —— 一个奇妙的字符 (cocos2dx crash)
    mysql读写分离(主从复制)实现
    高仿webqq做的一个webos桌面效果和web聊天工具,桌面效果完好,功能强大
  • 原文地址:https://www.cnblogs.com/androidsj/p/3656173.html
Copyright © 2011-2022 走看看