您的位置:首页 > 其它

缓存图片

2015-07-17 00:04 435 查看
安卓社交软件中,浏览信息时,经常要显示图片之类的信息,如果图片不缓存的话,会让给用户体验带来糟糕的感觉。

缓存图片的好处有以下两点:

减少用户的流量的消耗

缩短显示图片信息的时间,带来更好的用户体验

不过缓存图片需要一定存储开销,本人参考了前辈写的开源库https://github.com/nostra13/Android-Universal-Image-Loader的部分源码以及这篇前辈博客,来写自己简单的缓存图片下载的工具类,思路和前辈是一样的,下载图片时,首先先从内存查找,没有再去SD卡找,如果再没有,则从网络拉取,一般来说,图片只下载一次,就被缓存起来了,因此在用户老是浏览同一张图片时,就减少再次从网络拉取图片的流量和时间。

代码例子:

/**可以提供应用文件路径来创建缓存目录,或默认目录下创建缓存目录*/
public  final class StorageUtils
{
    private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
    private static final String INDIVIDUAL_DIR_NAME = "uil-images";
    private static final String TAG =StorageUtils.class.getName() ;   
    private StorageUtils()
    {

    }
    /**
     * @see #getCacheDirectory(Context, boolean)
     */
    public static File getCacheDirectory(Context context)
    {
        return getCacheDirectory(context, true);
    }
    /**
     * 如果有内存卡,将在SD卡上创建缓存目录,否则直接在根目录上创建缓存目录
     * @param context 上下文
     * @param preferExternal 决定是否要在SD卡上创建缓存目录
     * @return /Android/data/[app_package_name]/cache或者data/data/[app_package_name]/cache
     */
    public static File getCacheDirectory(Context context,boolean preferExternal)
    {
        File appCacheDir=null;
        String externalStorageState;
        externalStorageState= Environment.getExternalStorageState();
        if(externalStorageState.equals(Environment.MEDIA_MOUNTED)&&preferExternal&&hasExternalStoragePermisson(context))
        {
            appCacheDir=getExternalCacheDir(context);
        }
        if(appCacheDir==null)
        {
            appCacheDir=context.getCacheDir();
        }
        if(appCacheDir==null)
        {
            String cacheDirPath="/data/data/"+context.getPackageName()+"/cache";
            L.w("Can't define system cache directory! '%s' will be used.", cacheDirPath);
            appCacheDir=new File(cacheDirPath);
        }
        return appCacheDir;
    }
    /**
     * @see #getIndividualCacheDirectory(Context, String)
     */
    public static File getIndividualCacheDirectory(Context context)
    {
        return getIndividualCacheDirectory(context, INDIVIDUAL_DIR_NAME);
    }
    /**
     * 再缓存目录下创建名为cacheDir文件夹
     * @param context 上下文
     * @param cacheDir 文件夹的名字
     * @return 返回创建的文件名
     */
    public static File getIndividualCacheDirectory(Context context,String cacheDir)
    {
        File appCacheDir = getCacheDirectory(context);
        File individualCacheDir = new File(appCacheDir, cacheDir);
        if (!individualCacheDir.exists())
        {
            if (!individualCacheDir.mkdir())
            {
                individualCacheDir = appCacheDir;
            }
        }
        return individualCacheDir;
    }
    /**
     * 返回应用的指定缓存文件夹
     * @param context 上下文
     * @param cacheDir 缓存目录的路劲(e.g: "AppCacheDir","AppDir/cache/images")
     * @return Cache {@link File directory}
     */
    public static File getOwnCacheDirectory(Context context, String cacheDir)
    {
        File appCacheDir = null;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermisson(context))
        {
            appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);
        }
        if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs()))
        {
            appCacheDir = context.getCacheDir();
        }
        return appCacheDir;
    }
    /**
     * 返回应用的指定缓存文件夹
     * @param context 上下文
     * @param cacheDir 缓存目录的路劲(e.g: "AppCacheDir","AppDir/cache/images")
     * @param preferExternal 决定是否从SDK返回缓存目录文件夹
     * @return Cache {@link File directory}
     */
    public static File getOwnCacheDirectory(Context context, String cacheDir, boolean preferExternal)
    {
        File appCacheDir = null;
        if (preferExternal &&Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermisson(context))
        {
            appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);
        }
        if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs()))
        {
            appCacheDir = context.getCacheDir();
        }
        return appCacheDir;
    }
    /**
     * 在SD卡上创建缓存目录
     * @param context
     * @return  mnt/sdcard/Android/data/cache
     */
    private static File getExternalCacheDir(Context context)
    {
        File dataDir=new File(new File(Environment.getExternalStorageDirectory(),"Android"),"data");
        File appCacheDir=new File(dataDir,"cache");
        if(!appCacheDir.exists())
        {
            if(!appCacheDir.mkdirs())
            {
                L.w(TAG,"Unable to create external cache directory");
                return null;
            }
            try
            {
                new File(appCacheDir,".nomedia").createNewFile();
            } catch (IOException e)
            {
                L.i(TAG,"Can't create \".nomedia\" file in application external cache directory");
                Toast.makeText(context,"存储空间不够",Toast.LENGTH_SHORT);
            }
        }
        return appCacheDir;
    }
    private static boolean hasExternalStoragePermisson(Context context)
    {
        int perm=context.checkCallingPermission(EXTERNAL_STORAGE_PERMISSION);
        return perm== PackageManager.PERMISSION_GRANTED;
    }
}


图片下载工具类

/**
 * 下载图片,如果内存缓存有图片,则从内存拉取图片
 * 如果内存没有缓存该图片,看SD卡上是否缓存有图片
 * 如果有,直接从SD卡上拉取图片,否则异步的从网络
 * 上拉取图片,并缓存到SD卡和内存上
 * 内存缓存机采用LRU机制策略
 */
public class LoadImageUtils
{
    private static final String TAG =LoadImageUtils.class.getName() ;
    /**内存利用该集合的LRU机制缓存图片*/
    private Map<String,Bitmap> mCacheMap;
    /**单利对象*/
    private static LoadImageUtils sImageUtils;
    /**下载失败显示的图片*/
    private Bitmap mFailBitmap;
    /**建立线程缓存池,最大线程是五个,并且线程空闲状态超过60s将会自动删除其线程*/
    public static ExecutorService mExecutorService= new ThreadPoolExecutor(0, 5,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
    private LoadImageHandle mImageHandle;
    private LoadImageUtils()
    {
        if(mCacheMap==null)
        {
            mCacheMap= Collections.synchronizedMap(new LinkedHashMap<String,Bitmap>(100,0.75f,true));
        }
    }
    public void setImageHandle(LoadImageHandle imageHandle)
    {
        mImageHandle = imageHandle;
    }
    /**
     * 返回LoadImageUtils单例对象
     */
    public static LoadImageUtils getInstance()
    {
        if(sImageUtils==null)
        {
            sImageUtils=new LoadImageUtils();
        }
        return sImageUtils;
    }
    /**
     * 下载图片,如果内存缓存有图片,则从内存拉取图片
     * 如果内存没有缓存该图片,看SD卡上是否缓存有图片
     * 如果有,直接从SD卡上拉取图片,否则异步的从网络
     * 上拉取图片,并缓存到SD卡和内存上
     * 内存缓存机制采用LRU策略
     */
    @SuppressWarnings("UnnecessaryReturnStatement")
    public void loadImage(final ImageView ImageView,final  String ImageUrl,Context context)
    {
        final String key=MD5.encode(ImageUrl);
        if(mCacheMap.containsKey(key))
        {
            Bitmap bitmap=mCacheMap.get(key);
            if(bitmap!=null)
            {
                ImageView.setImageBitmap(bitmap);
                if(mImageHandle!=null)
                    mImageHandle.success();
                L.i(TAG,"内存命中");
                return;
            }
        }
        final File cacheDirectory=StorageUtils.getCacheDirectory(context);
        final File file=new File(cacheDirectory,key);
        if(file.exists()&&file.length()>0)
        {
            try
            {
                FileInputStream is=new FileInputStream(file);
                Bitmap bitmap1= BitmapFactory.decodeStream(is);
                //放入内存缓存
                mCacheMap.put(key, bitmap1);
                ImageView.setImageBitmap(bitmap1);
                if(mImageHandle!=null)
                    mImageHandle.success();
                L.i(TAG,"Sdcard命中");
                return;
            } catch (FileNotFoundException e)
            {
                if(mImageHandle!=null)
                    mImageHandle.failure();
                e.printStackTrace();
            }
        }
        //从网络拉取
        mExecutorService.execute(new Runnable()
        {
            @SuppressWarnings("TryFinallyCanBeTryWithResources")
            @Override
            public void run()
            {
                L.i(TAG, "网络拉取");
                try
                {
                    final byte[] imageBytes = HttpClientUtils.getUrlBytes(ImageUrl);
                    if (imageBytes != null)
                    {
                        final Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
                        //缓存到内存
                        mCacheMap.put(key, bitmap);
                        File file = new File(cacheDirectory, key);
                        //将图片缓存到SD卡中
                        FileOutputStream out = new FileOutputStream(file);
                        try
                        {
                            out.write(imageBytes);
                        } finally
                        {
                            out.close();
                        }
                        ImageView.post(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                ImageView.setImageBitmap(bitmap);
                                if(mImageHandle!=null)
                                    mImageHandle.success();
                            }
                        });
                    }
                } catch (IOException e)
                {
                    ImageView.post(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            if (mFailBitmap != null)
                                ImageView.setImageBitmap(mFailBitmap);
                            else
                            {
                                ImageView.setImageResource(R.drawable.ic_launcher);
                            }
                        }
                    });
                    if (mImageHandle != null)
                        mImageHandle.failure();
                    e.printStackTrace();
                }
            }
        });
    }
    //设置开始下载之前显示的图片
    public void setStartImage(ImageView image,Bitmap bitmap)
    {
        if(mImageHandle!=null)
            mImageHandle.start(image,bitmap);
    }
    /**
     * 修改下载失败的图片
     * @param failBitmap 设置下载失败的图片
     */
    public void setFailBitmap(Bitmap failBitmap)
    {
        this.mFailBitmap = failBitmap;
    }
    /**
     * 图片下载回调接口
     */
    public interface LoadImageHandle
    {
        public abstract void failure();
        public abstract void success();
        public void start(ImageView imageView,Bitmap bitmap);
    }
}


本人使用例子:

public final class HomeFragment extends Fragment implements LoadImageUtils.LoadImageHandle
{

    private static final String ABSOLUTEURL="https://api.weibo.com/2/statuses/friends_timeline.json";
    private static final String TAG =HomeFragment.class.getName() ;
    /**下拉更新的listView*/
    private PullToRefreshListView mListView;
    private HomeAdapter mAdapter;
    private LinkedList<ItemEntity> mItemEntities;
    private static LoadImageUtils sImageUtils=LoadImageUtils.getInstance();
    /**设置刚开始下载图片*/
    private BitmapDrawable mStartImage;
    private Future<Boolean> mResult;
    private OAuth mOAuth;
    /**建立线程缓存池,最大线程是五个,并且线程空闲状态超过60s将会自动删除其线程*/
    private ExecutorService mExecutorService= new ThreadPoolExecutor(0, 5,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
    public HomeFragment()
    {
        L.i(TAG,"开始创建HomeFragment对象");
        //设置回调接口
        sImageUtils.setImageHandle(this);
    }
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        mStartImage=(BitmapDrawable)getResources().getDrawable(R.drawable.start);
        L.i(TAG,"执行onCreate");
        mItemEntities=new LinkedList<ItemEntity>();
        mOAuth=OAuth.getOAuth(getActivity());
        mResult=mExecutorService.submit(new Task());//todo这里要做出更改
        //mExecutorService.shutdown();
        View view=inflater.inflate(R.layout.fragment_home,container,false);
        mListView=(PullToRefreshListView)view.findViewById(R.id.pull_to_refresh_listview);
        mListView.setShowLastUpdatedText(true);
        mAdapter=new HomeAdapter();
        return view;
    }
    public class HomeAdapter extends BaseAdapter
    {
        @Override
        public int getCount()
        {
            return mItemEntities.size();
        }
        @Override
        public Object getItem(int position)
        {
            return mItemEntities.get(position);
        }
        @Override
        public long getItemId(int position)
        {
            return position;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            ViewHolder holder=null;
            if(convertView==null)
            {
                holder=new ViewHolder();
                convertView=LayoutInflater.from(getActivity()).inflate(R.layout.list_item,parent,false);
                holder.HeadUrl=(ImageView)convertView.findViewById(R.id.user_head);
                holder.Name=(TextView)convertView.findViewById(R.id.user_name);
                holder.Date=(TextView)convertView.findViewById(R.id.user_express_date);
                holder.Content=(TextView)convertView.findViewById(R.id.user_express_content);
                convertView.setTag(holder);
            }
            else
            {
                holder=(ViewHolder)convertView.getTag();
            }
            ItemEntity entity=mItemEntities.get(position);
            sImageUtils.setStartImage(holder.HeadUrl, mStartImage.getBitmap());
            //异步下载图片
            sImageUtils.loadImage(holder.HeadUrl, entity.getHeadUrl(), getActivity());
            holder.Name.setText(entity.getName());
            holder.Date.setText(entity.getDate());
            holder.Content.setText(entity.getContent());
            return convertView;
        }
    }
    public class  ViewHolder
    {
        /**用户头像的url*/
        public ImageView HeadUrl;
        /**用户名字*/
        public TextView Name;
        /**用户发表时间*/
        public TextView Date;
        /**用户发表图片的URL*/
        public ArrayList<String> mImageUrls;//todo 这里要修改
        /**用户发表内容*/
        public TextView Content;
    }
    @Override
    public void failure()
    {
        Toast.makeText(getActivity(), "下载图片失败", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void success()
    {
        Toast.makeText(getActivity(), "下载图片成功", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void start(ImageView imageView, Bitmap bitmap)
    {
        imageView.setImageBitmap(bitmap);
    }
    /**
     * 开启一条线程负责从获取当前登陆
     * 用户及其所关注用户的最新微博
     */
    private class Task implements Callable<Boolean>
    {
        @SuppressWarnings("ConstantConditions")
        @Override
        public Boolean call() throws Exception
        {
            String url= Uri.parse(ABSOLUTEURL).buildUpon()
                    .appendQueryParameter("access_token", mOAuth.getAccessToken())
                    .appendQueryParameter("count","20") //todo 暂时显示20条信息
                    .build().toString();
            String result= HttpClientUtils.getUrl(url);
            JSONObject object=new JSONObject(result);
            JSONArray array=object.getJSONArray("statuses");
            for(int i=0;i<array.length();i++)
            {
                ItemEntity entity=new ItemEntity();
                JSONObject object1=array.getJSONObject(i);
                JSONArray jsonArray=object1.getJSONArray("pic_urls");
                ArrayList<String> list=new ArrayList<>(5);
                //获取图片的URL
                for(int j=0;j<jsonArray.length();j++)
                {
                    JSONObject jsonObject=jsonArray.getJSONObject(j);
                    String imageUrl=jsonObject.getString("thumbnail_pic");
                    if(imageUrl!=null)
                        list.add(imageUrl);
                }
                entity.setImageUrls(list);
                entity.setContent(object1.getString("text"));
                entity.setDate(TimeUtils.converTime(TimeUtils.getLongByGMT(object1.getString("created_at"))));
                JSONObject object2=object1.getJSONObject("user");
                entity.setName(object2.getString("screen_name"));
                entity.setHeadUrl(object2.getString("profile_image_url"));
                mItemEntities.add(entity);
            }
            //调用这个方法主线程更改UI,就不会抛异常了
            mListView.post(new Runnable()
            {
                @Override
                public void run()
                {
                    if(mListView.getAdapter()==null)
                    {
                        mListView.setAdapter(mAdapter);
                    }
                    else
                    {
                        mAdapter.notifyDataSetChanged();
                    }
                }
            });
            return true;
        }
    }
}


当运行其程序,ListView第一次显示图片,logcat会打印出”网络拉取”,如果第二次显示其图片时,logcat会打印出“内存命中”。

运行结果如图:





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: