缓存图片
2015-07-17 00:04
435 查看
安卓社交软件中,浏览信息时,经常要显示图片之类的信息,如果图片不缓存的话,会让给用户体验带来糟糕的感觉。
缓存图片的好处有以下两点:
减少用户的流量的消耗
缩短显示图片信息的时间,带来更好的用户体验
不过缓存图片需要一定存储开销,本人参考了前辈写的开源库https://github.com/nostra13/Android-Universal-Image-Loader的部分源码以及这篇前辈博客,来写自己简单的缓存图片下载的工具类,思路和前辈是一样的,下载图片时,首先先从内存查找,没有再去SD卡找,如果再没有,则从网络拉取,一般来说,图片只下载一次,就被缓存起来了,因此在用户老是浏览同一张图片时,就减少再次从网络拉取图片的流量和时间。
代码例子:
图片下载工具类
本人使用例子:
当运行其程序,ListView第一次显示图片,logcat会打印出”网络拉取”,如果第二次显示其图片时,logcat会打印出“内存命中”。
运行结果如图:
缓存图片的好处有以下两点:
减少用户的流量的消耗
缩短显示图片信息的时间,带来更好的用户体验
不过缓存图片需要一定存储开销,本人参考了前辈写的开源库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会打印出“内存命中”。
运行结果如图:
相关文章推荐
- protocol的基本使用与代理设计模式的应用
- Course Schedule
- 节点相关
- Android 仿余额宝数字跳动动画效果完整代码
- 教你如何使用VS远程调试
- VS2015 免费插件Refactoring Essentials
- redis中关于过期键的删除策略
- ASP.NET 5中使用AzureAD实现单点登录
- 浅谈python中截取字符函数strip,lstrip,rstrip
- Python的Django框架中的数据库配置指南
- 在Django框架中运行Python应用全攻略
- 在Python的Django框架中更新数据库数据的方法
- Python的Django框架中的数据过滤功能
- 在Python的Django框架中获取单个对象数据的简单方法
- Django中对数据查询结果进行排序的方法
- Django框架中数据的连锁查询和限制返回数据的方法
- Django中更新多个对象数据与删除对象的方法
- 数组
- Python的Django框架下管理站点的基本方法
- Python的Django框架中设置日期和字段可选的方法