Android仿人人客户端(v5.7.1)——对从服务器端(网络)获取的图片进行本地双缓存处理(编码实现)

分享到:

      转载请标明出处:http://blog.csdn.net/android_ls/article/details/8797740

       声明:常有人说,能把复杂的事情,用最简单通俗方式向大家描述清楚,此乃“牛人”或称“大师级别”。我自知离那个级别还差很远。因此,我发表的仿人人Android客户端系列博文,是与有一定Andoird基础知识的朋友来分享的。

       这篇是基于上一篇Android仿人人客户端(v5.7.1)——对从服务器端(网络)获取的图片进行本地双缓存处理(流程图或活动图)来进行讲解,没看过的可以先阅读下上一篇博文,其实我个人觉得图片双缓存处理这块,一张流程图已足以说明一切。至于编码实现,不同的人有不同的实现方式,下面我就和大家聊一聊我的实现方式:

一、图片双缓存处理,类图如下:

二、网络图片本地双缓存的编码实现(以获取用户图像为例):

       1、发出需要显示用户图像的请求

                        String headUrl = user.getHeadurl();
                        LogUtil.i(TAG, "headUrl = " + user.getHeadurl());
                       
                        // 用户图像的大小48x48,单位为dip,转换为px
                        int widthPx = DensityUtil.dip2px(mContext, 48);
                        
                        // 要一张圆角高质量的图片
                        ImageInfo imgInfo = new ImageInfo(mLeftPanelLayout.ivUserIcon, headUrl, widthPx, widthPx, true, false);
                        mImageLoader.displayImage(imgInfo);

        注:mLeftPanelLayout.ivUserIcon为ImageView;ImageInfo对象封装了图片请求参数。

        2、根据URL从内存缓存中获取Bitmap对象,找到了Bitmap对象,用ImageView对象显示图像,到这里终止。

        Bitmap bitmap = memoryCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } 

           注:memoryCache是MemoryCache(内存缓存类)的对象引用。

        3 、没有从缓存中找到了Bitmap对象,则根据URL从文件缓存中获取File对象,将File对象解码(解析)成Bitmap对象,用ImageView对象显示用户图像,到这里终止。

            final File file = fileCache.getFile(url);
            if(file.exists()){
                String pathName = file.getAbsolutePath();
                System.out.println("pathName = " + pathName);
                System.out.println("file.length() = " + file.length());
                
                bitmap = BitmapFactory.decodeFile(pathName);
                imageView.setImageBitmap(bitmap);
            } 

           注:fileCache为文件缓存类的引用             

        4、没有从文件缓存中找到File对象,则开启网络请求业务线程。

               // 开启线程加载图片
                try {
                    AsyncBaseRequest asyncRequest = new AsyncHttpGet(url, null, null, new ResultCallback() {

                        @Override
                        public void onSuccess(Object obj) {
                            
                        }

                        @Override
                        public void onFail(int errorCode) {
                            System.out.println("Loading image error. errorCode = " + errorCode);
                        }
                    });

                    mDefaultThreadPool.execute(asyncRequest);
                    mAsyncRequests.add(asyncRequest);
                } catch (IOException e) {
                    e.printStackTrace();
                }

         5、网络请求返回的图片数据流可能会很大,直接解码生成Bitmap对象,可能会造成OOM。因此,要根据指定的压缩比例,获得合适的Bitmap

  Bitmap bitmap = BitmapUtil.decodeStream((InputStream) obj, imgInfo.getWidth(), imgInfo.getHeight());

        6、上一步处理过后,可能解码生成的Bitmap对象还会很大,可能还会造成OOM,因此,对Bitmap对象再次进行质量压缩。

                                if (imgInfo.isCompress()) {
                                    // 对Bitmap进行质量压缩
                                    bitmap = BitmapUtil.compressBitmap(bitmap);
                                }

      7、进行本地文件缓存

                            try {
                                fileCache.writeToFile(inStream, file);
                            } catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

      8、进行本地内存缓存

                            // 将数据流将其转换成Bitmap
                            bitmap = BitmapFactory.decodeStream(inStream);

                            // 存入内存缓存中
                            memoryCache.put(url, bitmap);

      9、用ImageView对象显示用户图像,到这里终止。

                            // 用ImageView对象显示图片
                            final Bitmap btm = bitmap;
                            mHandler.post(new Runnable() {

                                @Override
                                public void run() {
                                    imageView.setImageBitmap(btm);
                                }
                            });

       加载图片的完整方法,代码如下:

    /**
     * 加载图片
     * @param imgInfo 图片信息
     */
    public void displayImage(final ImageInfo imgInfo) {
        final ImageView imageView = imgInfo.getImageView();
        final String url = imgInfo.getUrl();

        imageViews.put(imageView, url);

        // 从内存缓存中查找
        Bitmap bitmap = memoryCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } else {
            // 从文件缓存中查找
            final File file = fileCache.getFile(url);
            if (file.exists()) {
                String pathName = file.getAbsolutePath();
                System.out.println("pathName = " + pathName);
                System.out.println("file.length() = " + file.length());

                bitmap = BitmapFactory.decodeFile(pathName);
                imageView.setImageBitmap(bitmap);
            } else {
                // 开启线程加载图片
                try {
                    AsyncBaseRequest asyncRequest = new AsyncHttpGet(url, null, null, new ResultCallback() {

                        @Override
                        public void onSuccess(Object obj) {
                            if (obj == null || !(obj instanceof InputStream)) {
                                System.out.println("Loading image return Object is null or not is InputStream.");
                                return;
                            }

                            try {
                                // 根据指定的压缩比例,获得合适的Bitmap
                                Bitmap bitmap = BitmapUtil.decodeStream((InputStream) obj, imgInfo.getWidth(), imgInfo.getHeight());
                                
                                if (imgInfo.isRounded()) {
                                     // 将图片变成圆角
                                     // bitmap = BitmapUtil.drawRoundCorner(bitmap, 8);
                                     bitmap = BitmapUtil.drawRoundBitmap(bitmap, 8);
                                }
                                
                                if (imgInfo.isCompress()) {
                                    // 对Bitmap进行质量压缩
                                    bitmap = BitmapUtil.compressBitmap(bitmap);
                                }
                                
                                // 将Bitmap转换成ByteArrayInputStream
                                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                                ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
                                
                                // 将进行质量压缩后的数据写入文件(文件缓存)
                                fileCache.writeToFile(inStream, file);
                                
                                // 存入内存缓存中
                                memoryCache.put(url, bitmap);

                                // 防止图片错位
                                String tag = imageViews.get(imageView);
                                if (tag == null || !tag.equals(url)) {
                                    System.out.println("tag is null or url and ImageView disaccord.");
                                    return;
                                }
                                
                                // 用ImageView对象显示图片
                                final Bitmap btm = bitmap;
                                mHandler.post(new Runnable() {

                                    @Override
                                    public void run() {
                                        imageView.setImageBitmap(btm);
                                    }
                                });
                                
                                
                            } catch (IOException e) {
                                // 这里不做处理,因为默认显示的图片在xml组件配置里已设置
                                e.printStackTrace();
                            }
                            
                        }

                        @Override
                        public void onFail(int errorCode) {
                            System.out.println("Loading image error. errorCode = " + errorCode);
                        }
                    });

                    mDefaultThreadPool.execute(asyncRequest);
                    mAsyncRequests.add(asyncRequest);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

      三、在上述业务处理过程中,遇到的问题及解决思路(记录处理过程)

       1、根据指定的压缩比例,获得合适的Bitmap,阅读如下代码:

    /**
     * 根据指定的压缩比例,获得合适的Bitmap
     * @param inStream InputStream
     * @param width 指定的宽度
     * @param height 指定的高度
     */
    public static Bitmap decodeStream(InputStream inStream, int width, int height) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inStream, null, options);

        int w = options.outWidth;
        int h = options.outHeight;

        // 从服务器端获取的图片大小为:80x120
        // 我们想要的图片大小为:40x40
        // 缩放比:120/40 = 3,也就是说我们要的图片大小为原图的1/3

        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int ratio = 1; // 默认为不缩放
        if (w >= h && w > width) {
            ratio = (int) (w / width);
        } else if (w < h && h > height) {
            ratio = (int) (h / height);
        }

        if (ratio <= 0) {
            ratio = 1;
        }

        System.out.println("图片的缩放比例值ratio = " + ratio);

        options.inJustDecodeBounds = false;
        // 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,
        // 则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
        options.inSampleSize = ratio;

        return BitmapFactory.decodeStream(inStream, null, options);
    }

       注:inStream为从网络获取后,直接传进来的。

       运行上面的后,返回的Bitmap对象为null。究其原因,在设置 options.inJustDecodeBounds = true后,我们调用了BitmapFactory.decodeStream(inStream, null, options)方法获取图片的大小,但是该方法在执行完后,应该在内部把传进去的InputStream关闭掉了。第二次的时候就读不到数据了。解决思路,将从网络获取到的数据流先保存起来。解决方法一:

  /**
     * 根据指定的压缩比例,获得合适的Bitmap(方法一)
     * @param file File
     * @param width 指定的宽度
     * @param height 指定的高度
     * @return Bitmap
     */
    public static Bitmap decodeStream(File file, int width, int height) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);

        int w = options.outWidth;
        int h = options.outHeight;

        // 从服务器端获取的图片大小为:80x120
        // 我们想要的图片大小为:40x40
        // 缩放比:120/40 = 3,也就是说我们要的图片大小为原图的1/3

        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int ratio = 1; // 默认为不缩放
        if (w >= h && w > width) {
            ratio = (int) (w / width);
        } else if (w < h && h > height) {
            ratio = (int) (h / height);
        }

        if (ratio <= 0) {
            ratio = 1;
        }

        System.out.println("图片的缩放比例值ratio = " + ratio);

        options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        // 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,
        // 则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
        options.inSampleSize = ratio;

        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
    }

解决方法二:

  /**
     * 根据指定的压缩比例,获得合适的Bitmap(方法二)
     * @param inStream InputStream
     * @param width 指定的宽度
     * @param height 指定的高度
     * @return Bitmap
     * @throws IOException
     */
    public static Bitmap decodeStream(InputStream inStream, int width, int height) throws IOException {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        // 从输入流读取数据
        byte[] data = StreamTool.read(inStream);
        BitmapFactory.decodeByteArray(data, 0, data.length, options);

        int w = options.outWidth;
        int h = options.outHeight;

        // 从服务器端获取的图片大小为:80x120
        // 我们想要的图片大小为:40x40
        // 缩放比:120/40 = 3,也就是说我们要的图片大小为原图的1/3

        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int ratio = 1; // 默认为不缩放
        if (w >= h && w > width) {
            ratio = (int) (w / width);
        } else if (w < h && h > height) {
            ratio = (int) (h / height);
        }

        if (ratio <= 0) {
            ratio = 1;
        }

        System.out.println("图片的缩放比例值ratio = " + ratio);

        options.inJustDecodeBounds = false;
        // 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,
        // 则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
        options.inSampleSize = ratio;
        return BitmapFactory.decodeByteArray(data, 0, data.length);
    }

解决方法三:从网络返回的数据流中只读取图片的信息(宽度和高度),计算压缩比例,之后再次从网络读取数据按第一次计算出的压缩比例,获得合适的Bitmap。(这个是下下策,要访问两次网络)

       2、对Bitmap进行质量压缩,阅读如下代码:

   /**
     * 对Bitmap进行质量压缩
     * @param bitmap Bitmap
     * @return ByteArrayInputStream
     */
    public static Bitmap compressBitmap(Bitmap bitmap) {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        // 图片质量默认值为100,表示不压缩
        int quality = 100;
        // PNG是无损的,将会忽略质量设置。因此,这里设置为JPEG
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);

        // 判断压缩后图片的大小是否大于100KB,大于则继续压缩
        while (outStream.toByteArray().length / 1024 > 100) {
            outStream.reset();

            // 压缩quality%,把压缩后的数据存放到baos中
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
            quality -= 10;
        }

        System.out.println("quality = " + quality);

        byte[] data = outStream.toByteArray();
        return BitmapFactory.decodeByteArray(data, 0, data.length);
    }

        注意:  bitmap.compress(Bitmap.CompressFormat.PNG, quality, outStream);如果这么写,是没有压缩效果的。因为PNG是无损的,将会忽略质量设置。

四、上述讲解中涉及到的类,完整的源文件如下:

加载(装载)图片类

package com.everyone.android.bitmap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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

import com.everyone.android.AppBaseActivity;
import com.everyone.android.callback.ResultCallback;
import com.everyone.android.entity.ImageInfo;
import com.everyone.android.net.AsyncBaseRequest;
import com.everyone.android.net.AsyncHttpGet;
import com.everyone.android.net.DefaultThreadPool;
import com.everyone.android.utils.BitmapUtil;

/**
 * 功能描述:加载(装载)图片
 * 
 * 在以前,一个非常流行的内存缓存的实现是使用SoftReference or WeakReference ,但是这种办法现在并不推荐。
 * 从Android 2.3开始,垃圾回收器会更加积极的去回收软引用和弱引用引用的对象,这样导致这种做法相当的无效。
 * 另外,在Android 3.0之前,图片数据保存在本地内存中,它们不是以一种可预见的方式来释放的,
 * 这样可能会导致应用内存的消耗量出现短暂的超限,应用程序崩溃 。
 * 
 * @author android_ls
 */
public class ImageLoader {

    /**
     * 内存缓存
     */
    private MemoryCache memoryCache;

    /**
     * 文件缓存
     */
    private FileCache fileCache;

    /**
     * 存放图片的显示视图ImageView和图片的URL
     */
    private Map<ImageView, String> imageViews = Collections.synchronizedMap(new LinkedHashMap<ImageView, String>());

    private List<AsyncBaseRequest> mAsyncRequests;

    private DefaultThreadPool mDefaultThreadPool;

    private Handler mHandler;

    public ImageLoader(AppBaseActivity activity) {
        this.memoryCache = new MemoryCache();
        this.fileCache = new FileCache(activity.getContext());
        this.mAsyncRequests = activity.getAsyncRequests();
        this.mDefaultThreadPool = activity.getDefaultThreadPool();
        this.mHandler = activity.getHandler();
    }

    /**
     * 加载图片
     * @param imgInfo 图片信息
     */
    public void displayImage(final ImageInfo imgInfo) {
        final ImageView imageView = imgInfo.getImageView();
        final String url = imgInfo.getUrl();

        imageViews.put(imageView, url);

        // 从内存缓存中查找
        Bitmap bitmap = memoryCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } else {
            // 从文件缓存中查找
            final File file = fileCache.getFile(url);
            if (file.exists()) {
                String pathName = file.getAbsolutePath();
                System.out.println("pathName = " + pathName);
                System.out.println("file.length() = " + file.length());

                SimpleDateFormat mDateFormat = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss");
                System.out.println("file.lastModified() = " + mDateFormat.format(file.lastModified()));
                
                bitmap = BitmapFactory.decodeFile(pathName);
                imageView.setImageBitmap(bitmap);
            } else {
                // 开启线程加载图片
                try {
                    AsyncBaseRequest asyncRequest = new AsyncHttpGet(url, null, null, new ResultCallback() {

                        @Override
                        public void onSuccess(Object obj) {
                            if (obj == null || !(obj instanceof InputStream)) {
                                System.out.println("Loading image return Object is null or not is InputStream.");
                                return;
                            }

                            try {
                                // 根据指定的压缩比例,获得合适的Bitmap
                                Bitmap bitmap = BitmapUtil.decodeStream((InputStream) obj, imgInfo.getWidth(), imgInfo.getHeight());
                                
                                if (imgInfo.isRounded()) {
                                     // 将图片变成圆角
                                     // bitmap = BitmapUtil.drawRoundCorner(bitmap, 8);
                                     bitmap = BitmapUtil.drawRoundBitmap(bitmap, 8);
                                }
                                
                                if (imgInfo.isCompress()) {
                                    // 对Bitmap进行质量压缩
                                    bitmap = BitmapUtil.compressBitmap(bitmap);
                                }
                                
                                // 将Bitmap转换成ByteArrayInputStream
                                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                                ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
                                
                                // 将进行质量压缩后的数据写入文件(文件缓存)
                                fileCache.writeToFile(inStream, file);
                                
                                // 存入内存缓存中
                                memoryCache.put(url, bitmap);

                                // 防止图片错位
                                String tag = imageViews.get(imageView);
                                if (tag == null || !tag.equals(url)) {
                                    System.out.println("tag is null or url and ImageView disaccord.");
                                    return;
                                }
                                
                                // 用ImageView对象显示图片
                                final Bitmap btm = bitmap;
                                mHandler.post(new Runnable() {

                                    @Override
                                    public void run() {
                                        imageView.setImageBitmap(btm);
                                    }
                                });
                                
                                
                            } catch (IOException e) {
                                // 这里不做处理,因为默认显示的图片在xml组件配置里已设置
                                e.printStackTrace();
                            }
                            
                        }

                        @Override
                        public void onFail(int errorCode) {
                            System.out.println("Loading image error. errorCode = " + errorCode);
                        }
                    });

                    mDefaultThreadPool.execute(asyncRequest);
                    mAsyncRequests.add(asyncRequest);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


内存缓存类

package com.everyone.android.bitmap;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

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

/**
 * 功能描述:内存缓存类
 * 
 * @author android_ls
 */
public class MemoryCache {

    /**
     * 打印LOG的TAG
     */
    private static final String TAG = "MemoryCache";

    /**
     * 放入缓存时是个同步操作
     * LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,
     * 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
     */
    private Map<String, Bitmap> cacheMap = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));

    // 缓存只能占用的最大堆内存
    private long maxMemory;

    public MemoryCache() {
        // 使用25%的可用的堆大小
        maxMemory = Runtime.getRuntime().maxMemory() / 4;
        Log.i(TAG, "MemoryCache will use up to " + (maxMemory / 1024 / 1024) + "MB");
    }

    /**
     * 根据key获取相应的图片
     * @param key
     * @return Bitmap
     */
    public Bitmap get(String key) {
        if (!cacheMap.containsKey(key)){
            return null;
        }
        return cacheMap.get(key);
    }

    /**
     * 添加图片到缓存
     * @param key
     * @param bitmap
     */
    public synchronized void put(String key, Bitmap bitmap) {
        checkSize();
        cacheMap.put(key, bitmap);
       
        Log.i(TAG, "cache size=" + cacheMap.size() + " bitmap size = " +  getBitmapSize(bitmap));
    }

    /**
     * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
     */
    private void checkSize() {
        long count = 0;
        Iterator<Entry<String, Bitmap>> iterator = cacheMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Bitmap> entry = iterator.next();
            count += getBitmapSize(entry.getValue());
        }

        Log.i(TAG, "cache size=" + count + " length=" + cacheMap.size());

        if (count > maxMemory) {
            while (iterator.hasNext()) {
                Entry<String, Bitmap> entry = iterator.next();
                count -= getBitmapSize(entry.getValue());

                iterator.remove();
                if (count <= maxMemory) {
                    System.out.println("够用了,不用在删除了");
                    break;
                }
            }
            Log.i(TAG, "Clean cache. New size " + cacheMap.size());
        }
    }

    /**
     * 获取bitmap的字节大小
     * @param bitmap
     * @return
     */
    private long getBitmapSize(Bitmap bitmap) {
        if (bitmap == null) {
            return 0;
        }
        return bitmap.getRowBytes() * bitmap.getHeight();
    }

    /**
     * 清空缓存
     */
    public void clear() {
        cacheMap.clear();
    }

}

网络下载文件本地缓存类

package com.everyone.android.bitmap;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;

import android.content.Context;
import android.os.StatFs;

/**
 * 功能描述:网络下载文件本地缓存类
 * 
 * @author android_ls
 */
public class FileCache {

    /**
     * 本地与我们应用程序相关文件存放的根目录
     */
    private static final String ROOT_DIR_PATH = "CopyEveryone";

    /**
     * 下载文件存放的目录
     */
    private static final String IMAGE_DOWNLOAD_CACHE_PATH = ROOT_DIR_PATH + "/Download/cache";

    /**
     * 默认的磁盘缓存大小(20MB)
     */
    private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 20;

    /**
     * 缓存文件存放目录
     */
    private File cacheDir;

    /**
     * 缓存根目录
     */
    private String cacheRootDir;
    
    private Context mContext;

    public FileCache(Context context) {
        mContext = context;
        
        if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
            cacheRootDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
        } else {
            cacheRootDir = mContext.getCacheDir().getAbsolutePath();
        }

        cacheDir = new File(cacheRootDir + File.separator + IMAGE_DOWNLOAD_CACHE_PATH);
        // 检测文件缓存目录是否存在,不存在则创建
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
    }

    /**
     * 获取下载的文件要存放的缓存目录
     * /mnt/sdcard/CopyEveryone/Download/cache
     * @return 缓存目录的全路径
     */
    public String getCacheDirPath() {
        return cacheDir.getAbsolutePath();
    }

    /**
     * 根据URL从文件缓存中获取文件
     * @param url url的hashCode为缓存的文件名
     */
    public File getFile(String url) {
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }

        String filename = String.valueOf(url.hashCode());
        File file = new File(cacheDir, filename);
        return file;
    }

    /**
     * 计算存储可用的大小
     * @return
     */
    public long getAvailableMemorySize() {
        StatFs stat = new StatFs(cacheRootDir);
        long blockSize = stat.getBlockSize();
        long availableBlocks = stat.getAvailableBlocks();
        return availableBlocks * blockSize;
    }

    /**
     * 将指定的数据写入文件
     * @param inputStream InputStream
     * @param outputStream OutputStream
     * @throws IOException 
     */
    public synchronized void writeToFile(InputStream inputStream, File file) throws IOException {
        int fileSize  = inputStream.available();
        System.out.println("fileSize = " + fileSize);
        
        long enabledMemory  = getAvailableMemorySize();
        System.out.println("当前可用硬盘: " + (enabledMemory/1024/1024)); // 单位:MB
        
        // 当前可用存储空间不足20M
        if(DEFAULT_DISK_CACHE_SIZE > enabledMemory){
            if (fileSize > enabledMemory) {
                // 检测可用空间大小,若不够用则删除最早的文件
                File[] files = cacheDir.listFiles();
                Arrays.sort(files, new FileLastModifSort());
                
                int length = files.length;
                for (int i = 0; i < length; i++) {
                    files[i].delete();
                    length = files.length;
                    
                    enabledMemory  = getAvailableMemorySize();
                    System.out.println("当前可用内存: " + enabledMemory);
                    
                    if (fileSize <= enabledMemory) {
                        System.out.println("够用了,不用在删除了");
                        break;
                    }
                }
            }
        } else {
            int count = 0;
            File[] files = cacheDir.listFiles();
            for (int i = 0; i < files.length; i++) {
                count += files[i].length();
            }
            
            System.out.println("file cache size = " + count);
            
            // 使用的空间大于上限
            enabledMemory = DEFAULT_DISK_CACHE_SIZE - count;
            if(fileSize > enabledMemory){
                Arrays.sort(files, new FileLastModifSort());
                
                int length = files.length;
                for (int i = 0; i < length; i++) {
                    count -= files[i].length();
                    files[i].delete();
                    length = files.length;
                   
                    enabledMemory = DEFAULT_DISK_CACHE_SIZE - count;
                    if (fileSize <= enabledMemory) {
                        System.out.println("够用了,不用在删除了");
                        break;
                    }
                }
            }
        }
        
        if(enabledMemory == 0){
            return;
        }
        
        // 将数据写入文件保存
        FileOutputStream outStream = new FileOutputStream(file);
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        outStream.flush();
        outStream.close();
        inputStream.close();
        
        // 设置最后修改的时间
        long newModifiedTime = System.currentTimeMillis();
        file.setLastModified(newModifiedTime);
        
        System.out.println("file.length() = " + file.length());
        
        SimpleDateFormat mDateFormat = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss");
        System.out.println("writeToFile file.lastModified() = " + mDateFormat.format(file.lastModified()));
    }

   /**
    * 根据文件的最后修改时间进行排序
    * @author android_ls
    *
    */
    class FileLastModifSort implements Comparator<File> {
        public int compare(File file1, File file2) {
            if (file1.lastModified() > file2.lastModified()) {
                return 1;
            } else if (file1.lastModified() == file2.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }

    /**
     * 清空缓存的文件
     */
    public void clear() {
        if (!cacheDir.exists()) {
            return;
        }

        File[] files = cacheDir.listFiles();
        if (files != null) {
            for (File f : files) {
                f.delete();
            }
        }
    }

}

图片信息实体类

package com.everyone.android.entity;

import android.widget.ImageView;

/**
 * 功能描述:图片信息实体类
 * 
 * @author android_ls
 */
public class ImageInfo {

    private int id; // 唯一标识

    private ImageView imageView; // 用于显示的组件

    private String url; // 网络URL

    private int width; // 宽度

    private int height; // 高度

    private boolean rounded; // 是否要转换成圆角

    private boolean compress; // 是否要进行质量压缩

    public ImageInfo(ImageView imageView, String url) {
        this.imageView = imageView;
        this.url = url;
    }

    public ImageInfo() {
    }

    public ImageInfo(ImageView imageView, String url, int width, int height, boolean rounded, boolean compress) {
        this.imageView = imageView;
        this.url = url;
        this.width = width;
        this.height = height;
        this.rounded = rounded;
        this.compress = compress;
    }

    public ImageInfo(ImageView imageView, String url, boolean rounded) {
        this.imageView = imageView;
        this.url = url;
        this.rounded = rounded;
    }

    public ImageInfo(ImageView imageView, String url, int width, int height) {
        this.imageView = imageView;
        this.url = url;
        this.width = width;
        this.height = height;
    }

    public ImageInfo(ImageView imageView, String url, int width, int height, boolean rounded) {
        this.imageView = imageView;
        this.url = url;
        this.width = width;
        this.height = height;
        this.rounded = rounded;
    }

    public boolean isCompress() {
        return compress;
    }

    public void setCompress(boolean compress) {
        this.compress = compress;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public ImageView getImageView() {
        return imageView;
    }

    public void setImageView(ImageView imageView) {
        this.imageView = imageView;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public boolean isRounded() {
        return rounded;
    }

    public void setRounded(boolean rounded) {
        this.rounded = rounded;
    }

}

Bitmap加工处理工具类

package com.everyone.android.utils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

/**
 * 功能描述:Bitmap加工处理工具类
 * @author android_ls
 *
 */
public class BitmapUtil {

    /**
     * 将图片变成圆角(方法一)
     * @param bitmap Bitmap
     * @param pixels 圆角的弧度
     * @return 圆角图片
     */
    public static Bitmap drawRoundBitmap(Bitmap bitmap, float pixels) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        // paint.setColor()的参数,除不能为Color.TRANSPARENT外,可以任意写
        paint.setColor(Color.RED);
        canvas.drawRoundRect(rectF, pixels, pixels, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }

    /**
     * 将图片变成圆角(方法二)
     * @param bitmap Bitmap
     * @param pixels 圆角的弧度
     * @return 圆角图片
     */
    public static Bitmap drawRoundCorner(Bitmap bitmap, float pixels) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

        RectF outerRect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        // paint.setColor()的参数,除不能为Color.TRANSPARENT外,可以任意写
        paint.setColor(Color.WHITE);
        canvas.drawRoundRect(outerRect, pixels, pixels, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        Drawable drawable = new BitmapDrawable(bitmap);
        drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
        canvas.saveLayer(outerRect, paint, Canvas.ALL_SAVE_FLAG);
        drawable.draw(canvas);
        canvas.restore();

        return output;
    }

    /**
     * 对Bitmap进行质量压缩
     * @param bitmap Bitmap
     * @return ByteArrayInputStream
     */
    public static Bitmap compressBitmap(Bitmap bitmap) {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        // 图片质量默认值为100,表示不压缩
        int quality = 100;
        // PNG是无损的,将会忽略质量设置。因此,这里设置为JPEG
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);

        // 判断压缩后图片的大小是否大于100KB,大于则继续压缩
        while (outStream.toByteArray().length / 1024 > 100) {
            outStream.reset();

            // 压缩quality%,把压缩后的数据存放到baos中
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
            quality -= 10;
        }

        System.out.println("quality = " + quality);

        byte[] data = outStream.toByteArray();
        return BitmapFactory.decodeByteArray(data, 0, data.length);
    }

    /**
     * 根据指定的压缩比例,获得合适的Bitmap(方法一)
     * @param file File
     * @param width 指定的宽度
     * @param height 指定的高度
     * @return Bitmap
     */
    public static Bitmap decodeStream(File file, int width, int height) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);

        int w = options.outWidth;
        int h = options.outHeight;

        // 从服务器端获取的图片大小为:80x120
        // 我们想要的图片大小为:40x40
        // 缩放比:120/40 = 3,也就是说我们要的图片大小为原图的1/3

        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int ratio = 1; // 默认为不缩放
        if (w >= h && w > width) {
            ratio = (int) (w / width);
        } else if (w < h && h > height) {
            ratio = (int) (h / height);
        }

        if (ratio <= 0) {
            ratio = 1;
        }

        System.out.println("图片的缩放比例值ratio = " + ratio);

        options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        // 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,
        // 则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
        options.inSampleSize = ratio;

        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
    }

    /**
     * 根据指定的压缩比例,获得合适的Bitmap(方法二)
     * @param inStream InputStream
     * @param width 指定的宽度
     * @param height 指定的高度
     * @return Bitmap
     * @throws IOException
     */
    public static Bitmap decodeStream(InputStream inStream, int width, int height) throws IOException {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        // 从输入流读取数据
        byte[] data = StreamTool.read(inStream);
        BitmapFactory.decodeByteArray(data, 0, data.length, options);

        int w = options.outWidth;
        int h = options.outHeight;

        // 从服务器端获取的图片大小为:80x120
        // 我们想要的图片大小为:40x40
        // 缩放比:120/40 = 3,也就是说我们要的图片大小为原图的1/3

        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int ratio = 1; // 默认为不缩放
        if (w >= h && w > width) {
            ratio = (int) (w / width);
        } else if (w < h && h > height) {
            ratio = (int) (h / height);
        }

        if (ratio <= 0) {
            ratio = 1;
        }

        System.out.println("图片的缩放比例值ratio = " + ratio);

        options.inJustDecodeBounds = false;
        // 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,
        // 则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
        options.inSampleSize = ratio;
        return BitmapFactory.decodeByteArray(data, 0, data.length);
    }

    /**
     * 根据指定的压缩比例,获得合适的Bitmap(会出错的方法,仅用于测试)
     * @param inStream
     * @param width
     * @param height
     * @return
     * @throws IOException
     */
    public static Bitmap decodeStreamError(InputStream inStream, int width, int height) throws IOException {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inStream, null, options);

        int w = options.outWidth;
        int h = options.outHeight;

        // 从服务器端获取的图片大小为:80x120
        // 我们想要的图片大小为:40x40
        // 缩放比:120/40 = 3,也就是说我们要的图片大小为原图的1/3

        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int ratio = 1; // 默认为不缩放
        if (w >= h && w > width) {
            ratio = (int) (w / width);
        } else if (w < h && h > height) {
            ratio = (int) (h / height);
        }

        if (ratio <= 0) {
            ratio = 1;
        }

        System.out.println("图片的缩放比例值ratio = " + ratio);

        options.inJustDecodeBounds = false;
        // 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,
        // 则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
        options.inSampleSize = ratio;

        return BitmapFactory.decodeStream(inStream, null, options);
    }

}


单位转换工具类

package com.everyone.android.utils;

import android.content.Context;

/**
 * 功能描述:单位转换工具类
 * @author android_ls
 *
 */
public class DensityUtil {

    /**
     * 将单位为dip的值转换成单位为px的值
     * @param context Context
     * @param dipValue dip值
     * @return px值
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 将单位为px的值转换成单位为dip的值
     * @param context Context
     * @param pxValue 像素值
     * @return dip值
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 将px值转换为sp值,保证文字大小不变
     * 
     * @param pxValue
     * @param fontScale(DisplayMetrics类中属性scaledDensity)
     * @return
     */
    public static int px2sp(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     * 
     * @param spValue
     * @param fontScale(DisplayMetrics类中属性scaledDensity)
     * @return
     */
    public static int sp2px(Context context, float spValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (spValue * scale + 0.5f);
    }
}


数据流处理工具类数据流处理工具类数据流处理工具类

package com.everyone.android.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 功能描述:数据流处理工具类
 * @author android_ls
 */
public final class StreamTool {

    /**
     * 从输入流读取数据
     * 
     * @param inStream
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static byte[] read(InputStream inStream) throws IOException {
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        return outSteam.toByteArray();
    }

}

 

五、网络模块修改的文件源码:

      网络请求线程基类

package com.everyone.android.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.util.Map;

import org.json.JSONException;

import com.everyone.android.callback.ParseCallback;
import com.everyone.android.callback.ResultCallback;
import com.everyone.android.utils.Constant;
import com.everyone.android.utils.LogUtil;
import com.everyone.android.utils.StreamTool;

/**
 * 功能描述:网络请求线程基类
 * @author android_ls
 *
 */
public abstract class AsyncBaseRequest implements Runnable, Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * LOG打印标签
     */
    private static final String TAG = "AsyncBaseRequest";
    
    /**
     * 网络连接超时,默认值为5秒
     */
    protected int connectTimeout = 5 * 1000;

    /**
     * 网络数据读取超时,默认值为5秒
     */
    protected int readTimeout = 5 * 1000;

    private boolean interrupted;

    public boolean isInterrupted() {
        return interrupted;
    }

    public void setInterrupted(boolean interrupted) {
        this.interrupted = interrupted;
    }

    protected void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    protected void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    protected String requestUrl;

    protected Map<String, String> parameter;

    private ParseCallback parseHandler;

    private ResultCallback requestCallback;

    protected HttpURLConnection mHttpURLConn;

    protected InputStream mInStream;

    public AsyncBaseRequest(String url, Map<String, String> parameter, ParseCallback handler, ResultCallback requestCallback) {
        this.parseHandler = handler;
        this.requestUrl = url;
        this.parameter = parameter;
        this.requestCallback = requestCallback;
    }

    /**
     * 发送网络请求
     * 
     * @return 网络请求返回的InputStream数据流
     * @throws IOException
     */
    protected abstract InputStream getRequestResult() throws IOException;

    @Override
    public void run() {
        if (interrupted) {
            LogUtil.i(TAG, "访问网络前中断业务处理线程(终止)");
            return;
        }
        
        try {
            mInStream = getRequestResult();
            if (mInStream != null) {
                if (interrupted) {
                    LogUtil.i(TAG, "读取数据前中断业务处理线程(终止)");
                    return;
                }

                Object obj = null;
                if(parseHandler != null){
                    byte[] data = StreamTool.read(mInStream);
                    if (interrupted) {
                        LogUtil.i(TAG, "解析数据前中断业务处理线程(终止)");
                        return;
                    }
                    
                    String result = new String(data);
                    obj = parseHandler.parse(result);
                }
                
                if (interrupted) {
                    LogUtil.i(TAG, "刷新UI前中断业务处理线程(终止)");
                    return;
                }
                
                if(obj != null){
                    requestCallback.onSuccess(obj);
                } else {
                    requestCallback.onSuccess(mInStream);
                }
               
            } else {
                LogUtil.i(TAG, "get InputStream By HttpURLConnection return result is NULL.");
                requestCallback.onFail(Constant.NETWORK_REQUEST_RETUN_NULL); // 网络请求返回NULL
            }
        } catch (JSONException e) {
            requestCallback.onFail(Constant.NETWORK_REQUEST_RESULT_PARSE_ERROR); // 网络请求返回结果解析出错
            e.printStackTrace();
        } catch (IOException e) {
            requestCallback.onFail(Constant.NETWORK_REQUEST_IOEXCEPTION_CODE); // IO异常标识
            e.printStackTrace();
        }
        
    }

    public HttpURLConnection getRequestConn() {
        return mHttpURLConn;
    }

}

      通过HTTP协议发送GET请求

package com.everyone.android.net;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

import org.apache.http.protocol.HTTP;

import com.everyone.android.callback.ParseCallback;
import com.everyone.android.callback.ResultCallback;

/**
 * 功能描述:通过HTTP协议发送GET请求
 * @author android_ls
 *
 */
public class AsyncHttpGet extends AsyncBaseRequest {

    /**
     * 
     */
    private static final long serialVersionUID = 2L;

    public AsyncHttpGet(String url, Map<String, String> parameter, ParseCallback handler, ResultCallback requestCallback) throws IOException {
        super(url, parameter, handler, requestCallback);
    }

    @Override
    protected InputStream getRequestResult() throws IOException {
        StringBuilder sb = new StringBuilder(requestUrl);
        if (parameter != null && !parameter.isEmpty()) {
            sb.append('?');
            for (Map.Entry<String, String> entry : parameter.entrySet()) {
                sb.append(entry.getKey()).append('=').append(URLEncoder.encode(entry.getValue(), HTTP.UTF_8)).append('&');
            }
            sb.deleteCharAt(sb.length() - 1);
        }

        URL url = new URL(sb.toString());
        mHttpURLConn = (HttpURLConnection) url.openConnection();
        mHttpURLConn.setConnectTimeout(connectTimeout);
        mHttpURLConn.setRequestMethod("GET");
        if (mHttpURLConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
            return mHttpURLConn.getInputStream();
        }
        return null;
    }

}


六、运行后的效果图:

 

 图片双缓存这块,拖了很久,这一篇博文我花了一晚上时间,搞了一个通宵,终于写完了。天亮了,说:朋友们,晚安!

 

 

昵    称:
验证码:

相关文档: