【Android进阶】ListView使用“内存双缓存+硬盘缓存”加载网络图片
2016-07-22 17:14
651 查看
ListView 加载网络图片是我们经常用到的方式,如果每次滚动ListView就去网络下载图片会非常影响性能(因为网络下载是比较慢的)而且非常耗费流量,所以这里介绍一种使用“内存双缓存+硬盘缓存”的方式来加载图片。
实现的效果如下:
这里使用了滚动时不去网络下载图片,停止时才加载,所以滚动时显示默认的,注意观察
> 从网络获取的速度
内存缓存中使用了LruCache,LRU算法请参考:http://blog.csdn.net/luoweifu/article/details/8297084/
我们在内存缓存中再将内存分为两层,强引用缓存和软引用缓存。
对强引用和软引用做简单的介绍(具体内容请看:http://blog.csdn.net/u010583599/article/details/51970515
① 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
② 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
Item_layout.xml
主Activity完成的功能是,请求并解析慕课网的接口的JSon字符串,创建ListView的适配器。
MainActivity.java
为了解析Json字符串,我们需要创建一个实体类
NewsBean.java
ListView 适配器类中我们完成了图片的下载,并且进行了控制,ListView滑动时不进行任何的下载,停止状态才进行网络下载,并且进行了优化,防止图片加载时错位、重复、闪烁
NewsAdapter.java
/**
* @author meng.li
* 内存缓存图片类,这里使用了两层内存缓存
*/
public class ImageMemoryCache {
/**
* 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
* 强引用缓存不会轻易被回收,用来保存常用数据
* 不常用的数据转入软引用缓存,不会影响GC的回收。
*/
private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量
private static LruCache<String, Bitmap> mLruCache; //硬引用缓存
private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; //软引用缓存
public ImageMemoryCache(Context context) {
//获取最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
Log.i("mengli","maxMemory = "+ maxMemory);
//强引用缓存容量,为系统可用内存的1/4
int cacheSize = maxMemory/4;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (value != null)
//每次加入缓存时会调用
return value.getByteCount();
else
return 0;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null)
//LRU算法会把最近使用的元素压入栈顶,所以栈底就是被移除的元素
// 强引用缓存容量满的时候,会根据LRU算法把最近最久没有被使用的图片转入此软引用缓存
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
};
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
// private static final long serialVersionUID = 6040103833179403725L;
@Override
protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
if (size() > SOFT_CACHE_SIZE){
return true;
}
return false;
}
};
}
/**
* 从缓存中获取图片
*/
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap;
//先从强引用缓存中获取
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if (bitmap != null) {
//如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中最后被删除
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
}
//如果强引用缓存中找不到,到软引用缓存中找
synchronized (mSoftCache) {
SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
if (bitmapReference != null) {
bitmap = bitmapReference.get();
if (bitmap != null) {
//将图片移回硬缓存
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
return bitmap;
} else {
//没找到,可能改bigmap已经被回收了,删除url
mSoftCache.remove(url);
}
}
}
return null;
}
/**
* 添加图片到缓存
*/
public void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
public void clearCache() {
mSoftCache.clear();
}
}文件缓存类 ImageFileCache.java
/**
* @author meng.li
* 文件缓存类
*/
public class ImageFileCache {
//缓存目录名
private static final String CACHDIR = "ImgeCache";
private static final String WHOLESALE_CONV = ".cach";
private static final int MB = 1024*1024;
//缓存大小
private static final int CACHE_SIZE = 10;
//剩余最小空间大小
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
public ImageFileCache() {
//清理文件缓存
removeCache(getDirectory());
}
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {
final String path = getDirectory() + "/" + getFileNameFromUrl(url);
File file = new File(path);
if (file.exists()) {
Bitmap bmp = BitmapFactory.decodeFile(path);
if (bmp == null) {
file.delete();
} else {
//获取时图片时需要更新文件的最后修改时间
updateFileTime(path);
return bmp;
}
}
return null;
}
/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
if (bm == null) {
return;
}
//判断sdcard上的空间 ,如果不足10M返回
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足
return;
}
String filename = getFileNameFromUrl(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
//创建文件
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
//将图片进行压缩并写入文件,100表示不压缩
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
/**
* 计算存储目录下的文件大小,
* 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
* 那么删除40%最近没有被使用的文件
*/
private boolean removeCache(String dirPath) {
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (files == null) {
return true;
}
//没有挂载外部存储设备
if (!android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
return false;
}
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
//遍历目录下的所有文件,如果包含.cach 则累加size
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
//如果缓存目录的文件大小 大于规定的缓存大小或者剩余内存不足10M,则删除40%最久未使用的文件
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
int removeFactor = (int) ((0.4 * files.length) + 1);
//对文件按时间排序
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
if (freeSpaceOnSd() <= CACHE_SIZE) {
return false;
}
return true;
}
/** 修改文件的最后修改时间 **/
public void updateFileTime(String path) {
File file = new File(path);
long newModifiedTime = System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}
/** 计算sdcard上的剩余空间 **/
private int freeSpaceOnSd() {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}
/**从url中获取文件名 **/
private String getFileNameFromUrl(String url) {
return url.substring(url.lastIndexOf("/")+1)+WHOLESALE_CONV;
}
/** 获得缓存目录 **/
private String getDirectory() {
String dir = getSDPath() + "/" + CACHDIR;
return dir;
}
/** 取SD卡路径 **/
private String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory(); //获取根目录
}
if (sdDir != null) {
return sdDir.toString();
} else {
return "";
}
}
/**
* 根据文件的最后修改时间进行排序,Java中对对象进行排序要实现Comparator 接口,自己实现比较规则
* 1 表示大于,0表示相等,-1表示小于
*/
private class FileLastModifSort implements Comparator<File> {
public int compare(File arg0, File arg1) {
if (arg0.lastModified() > arg1.lastModified()) {
return 1;
} else if (arg0.lastModified() == arg1.lastModified()) {
return 0;
} else {
return -1;
}
}
}
}
通过图片我们可以看到,图片被写入了文件
图片下载类,使用多线程下载图片用了AsyncTask封装类
AndroidManifest.xml 注意加上权限
菜鸟一只,刚踏上追求技术的不归路,记录所学,希望大家指点!
代码下载 :http://download.csdn.net/detail/u010583599/9583583
本文参考:
http://blog.csdn.net/a79412906/article/details/10180583
http://www.imooc.com/learn/406
实现的效果如下:
这里使用了滚动时不去网络下载图片,停止时才加载,所以滚动时显示默认的,注意观察
设计思想
内存读取速度 > 文件读取速度> 从网络获取的速度
基本代码逻辑如下
// 从内存缓存中获取图片 Bitmap result = memoryCache.getBitmapFromCache(url); if (result == null) { // 从文件缓存中获取 result = fileCache.getImage(url); if (result == null) { // 从网络获取 result = ImageGetFromHttp.downloadBitmap(url); if (result != null) { fileCache.saveBitmap(result, url); memoryCache.addBitmapToCache(url, result); } } else { // 添加到内存缓存 memoryCache.addBitmapToCache(url, result); } }
内存缓存中使用了LruCache,LRU算法请参考:http://blog.csdn.net/luoweifu/article/details/8297084/
我们在内存缓存中再将内存分为两层,强引用缓存和软引用缓存。
对强引用和软引用做简单的介绍(具体内容请看:http://blog.csdn.net/u010583599/article/details/51970515
① 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
② 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
程序介绍
主界面是一个ListView,该ListView的Item布局为Item_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <ImageView android:id="@+id/iv_icon" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/ic_launcher"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:paddingLeft="5dp" android:orientation="vertical"> <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="15sp" android:text="新闻的标题"/> <TextView android:id="@+id/tv_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:maxLines="3" android:text="新闻的内容"/> </LinearLayout> </LinearLayout>
主Activity完成的功能是,请求并解析慕课网的接口的JSon字符串,创建ListView的适配器。
MainActivity.java
/** * 主类,访问网络接口获取JSon字符串,并解析字符串,生成对象集合,创建listView适配器 * */ public class MainActivity extends Activity { private ListView listView; //网络接口来自慕课网,获取一个Json字符串并解析 private static final String URL = "http://www.imooc.com/api/teacher?type=4&num=30"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView)findViewById(R.id.listview); new NewsAsyncTask().execute(URL); } private List<NewsBean> getJsonData(String url){ List<NewsBean> newsBeansList = new ArrayList<NewsBean>(); try { //获得json数据并解析Json字符串 String jsonString = readStream(new URL(url).openStream()); JSONObject jsonObject; NewsBean newsBean; jsonObject = new JSONObject(jsonString); JSONArray jsonArray = jsonObject.getJSONArray("data"); for(int i = 0;i< jsonArray.length();i++){ jsonObject = jsonArray.getJSONObject(i); newsBean = new NewsBean(); newsBean.newsIconUrl = jsonObject.getString("picSmall"); newsBean.newsTitle = jsonObject.getString("name"); newsBean.newsContent = jsonObject.getString("description"); newsBeansList.add(newsBean); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } return newsBeansList; } //获取网络输入流数据,返回一个Json字符串 private String readStream(InputStream is){ InputStreamReader isr; String result = ""; try{ String line = ""; isr = new InputStreamReader(is,"utf-8"); BufferedReader br = new BufferedReader(isr); while((line = br.readLine()) != null){ result += line; } }catch(IOException e){ e.printStackTrace(); } return result; } /** * 使用AsyncTask来访问网络获取JSon数据 * */ class NewsAsyncTask extends AsyncTask<String , Void, List<NewsBean>>{ @Override protected List<NewsBean> doInBackground(String... params) { return getJsonData(params[0]); } @Override protected void onPostExecute(List<NewsBean> result) { super.onPostExecute(result); //创建并给listView设置适配器 NewsAdapter adapter = new NewsAdapter(getApplicationContext(), result,listView); listView.setAdapter(adapter); } } }
为了解析Json字符串,我们需要创建一个实体类
NewsBean.java
public class NewsBean { public String newsIconUrl; public String newsTitle; public String newsContent; public NewsBean(String newsIconUrl,String newsTitle,String newsContent){ this.newsIconUrl = newsIconUrl; this.newsTitle = newsTitle; this.newsContent = newsContent; } public NewsBean(){ } }
ListView 适配器类中我们完成了图片的下载,并且进行了控制,ListView滑动时不进行任何的下载,停止状态才进行网络下载,并且进行了优化,防止图片加载时错位、重复、闪烁
NewsAdapter.java
public class NewsAdapter extends BaseAdapter implemen 10a2a ts OnScrollListener{ private List<NewsBean> mList; private LayoutInflater mInflater; //图片加载类 private ImageLoader imageLoader; //listView开始下载和结束下载的位置 private int mStart,mEnd; //所有的URL的数组 public static String[] URLS; private boolean mFirstIn;//第一次启动 public NewsAdapter(Context context,List<NewsBean> mList,ListView listView){ mInflater = LayoutInflater.from(context); this.mList = mList; imageLoader = new ImageLoader(listView,context); //获取所有的URL并初始化数组 URLS = new String[mList.size()]; for(int i = 0;i<mList.size();i++){ URLS[i] = mList.get(i).newsIconUrl; } listView.setOnScrollListener(this); mFirstIn = true; } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if(convertView == null){ viewHolder = new ViewHolder(); convertView = mInflater.inflate(R.layout.item_layout, null); viewHolder.ivIcon = (ImageView)convertView.findViewById(R.id.iv_icon); viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title); viewHolder.tvContent = (TextView)convertView.findViewById(R.id.tv_content); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } //设置默认的图片 // viewHolder.ivIcon.setImageResource(R.drawable.ic_launcher); String url = mList.get(position).newsIconUrl; /*因为Itme是重复利用的,ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行, * 且滑动过程中该图片加载结束,第2行已不在屏幕内,根据缓存原理,第2行的view可能被第14行复用,这样我 * 们看到的就是第14行显示了本该属于第2行的图片,造成显示重复。如果14行的图片也加载结束则会造成闪烁,先显示前一张,再显示后一张 * 为了防止图片加载时错位,这里加上tag,把imageView和url标识绑定,在异步显示的位置,判断当前任务的url和item设置的url是否 * 相同,只有相同才去加载图片 */ viewHolder.ivIcon.setTag(url); //在滚动的时候加载图片,如果缓存中都没有,则使用默认的图片 imageLoader.showImagesFromCache(viewHolder.ivIcon, url); viewHolder.tvTitle.setText(mList.get(position).newsTitle); viewHolder.tvContent.setText(mList.get(position).newsContent); return convertView; } class ViewHolder{ public TextView tvTitle,tvContent; public ImageView ivIcon; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState == SCROLL_STATE_IDLE){ //当前状态处于停止状态,加载可见项 imageLoader.loadImages(mStart, mEnd); }else{ //停止任务 imageLoader.cancelAllTasks(); } } /** * 由于我们使用的是滚动状态改变时才去下载图片,但是第一次进入的时候要加载第一屏的图片 * listview初始化后会调用onScroll方法,我们在这里去加载第一屏的图片并把第一次进入 * 状态位置为false */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //start 为第一个可见的item的位置 mStart = firstVisibleItem; //end 为第一个可见的位置加上可见的item的数量 mEnd = firstVisibleItem + visibleItemCount; if(mFirstIn && visibleItemCount > 0){ //第一次显示的时候调用,加载图片 imageLoader.loadImages(mStart, mEnd); mFirstIn = false; } } }内存缓存类 ImageMemoryCache.java
/**
* @author meng.li
* 内存缓存图片类,这里使用了两层内存缓存
*/
public class ImageMemoryCache {
/**
* 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
* 强引用缓存不会轻易被回收,用来保存常用数据
* 不常用的数据转入软引用缓存,不会影响GC的回收。
*/
private static final int SOFT_CACHE_SIZE = 15; //软引用缓存容量
private static LruCache<String, Bitmap> mLruCache; //硬引用缓存
private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; //软引用缓存
public ImageMemoryCache(Context context) {
//获取最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
Log.i("mengli","maxMemory = "+ maxMemory);
//强引用缓存容量,为系统可用内存的1/4
int cacheSize = maxMemory/4;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (value != null)
//每次加入缓存时会调用
return value.getByteCount();
else
return 0;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null)
//LRU算法会把最近使用的元素压入栈顶,所以栈底就是被移除的元素
// 强引用缓存容量满的时候,会根据LRU算法把最近最久没有被使用的图片转入此软引用缓存
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
};
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
// private static final long serialVersionUID = 6040103833179403725L;
@Override
protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
if (size() > SOFT_CACHE_SIZE){
return true;
}
return false;
}
};
}
/**
* 从缓存中获取图片
*/
public Bitmap getBitmapFromCache(String url) {
Bitmap bitmap;
//先从强引用缓存中获取
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if (bitmap != null) {
//如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中最后被删除
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
}
//如果强引用缓存中找不到,到软引用缓存中找
synchronized (mSoftCache) {
SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
if (bitmapReference != null) {
bitmap = bitmapReference.get();
if (bitmap != null) {
//将图片移回硬缓存
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
return bitmap;
} else {
//没找到,可能改bigmap已经被回收了,删除url
mSoftCache.remove(url);
}
}
}
return null;
}
/**
* 添加图片到缓存
*/
public void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
public void clearCache() {
mSoftCache.clear();
}
}文件缓存类 ImageFileCache.java
/**
* @author meng.li
* 文件缓存类
*/
public class ImageFileCache {
//缓存目录名
private static final String CACHDIR = "ImgeCache";
private static final String WHOLESALE_CONV = ".cach";
private static final int MB = 1024*1024;
//缓存大小
private static final int CACHE_SIZE = 10;
//剩余最小空间大小
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
public ImageFileCache() {
//清理文件缓存
removeCache(getDirectory());
}
/** 从缓存中获取图片 **/
public Bitmap getImage(final String url) {
final String path = getDirectory() + "/" + getFileNameFromUrl(url);
File file = new File(path);
if (file.exists()) {
Bitmap bmp = BitmapFactory.decodeFile(path);
if (bmp == null) {
file.delete();
} else {
//获取时图片时需要更新文件的最后修改时间
updateFileTime(path);
return bmp;
}
}
return null;
}
/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
if (bm == null) {
return;
}
//判断sdcard上的空间 ,如果不足10M返回
if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
//SD空间不足
return;
}
String filename = getFileNameFromUrl(url);
String dir = getDirectory();
File dirFile = new File(dir);
if (!dirFile.exists())
dirFile.mkdirs();
//创建文件
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
//将图片进行压缩并写入文件,100表示不压缩
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
} catch (FileNotFoundException e) {
Log.w("ImageFileCache", "FileNotFoundException");
} catch (IOException e) {
Log.w("ImageFileCache", "IOException");
}
}
/**
* 计算存储目录下的文件大小,
* 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
* 那么删除40%最近没有被使用的文件
*/
private boolean removeCache(String dirPath) {
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (files == null) {
return true;
}
//没有挂载外部存储设备
if (!android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
return false;
}
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
//遍历目录下的所有文件,如果包含.cach 则累加size
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
//如果缓存目录的文件大小 大于规定的缓存大小或者剩余内存不足10M,则删除40%最久未使用的文件
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
int removeFactor = (int) ((0.4 * files.length) + 1);
//对文件按时间排序
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
if (freeSpaceOnSd() <= CACHE_SIZE) {
return false;
}
return true;
}
/** 修改文件的最后修改时间 **/
public void updateFileTime(String path) {
File file = new File(path);
long newModifiedTime = System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}
/** 计算sdcard上的剩余空间 **/
private int freeSpaceOnSd() {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}
/**从url中获取文件名 **/
private String getFileNameFromUrl(String url) {
return url.substring(url.lastIndexOf("/")+1)+WHOLESALE_CONV;
}
/** 获得缓存目录 **/
private String getDirectory() {
String dir = getSDPath() + "/" + CACHDIR;
return dir;
}
/** 取SD卡路径 **/
private String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory(); //获取根目录
}
if (sdDir != null) {
return sdDir.toString();
} else {
return "";
}
}
/**
* 根据文件的最后修改时间进行排序,Java中对对象进行排序要实现Comparator 接口,自己实现比较规则
* 1 表示大于,0表示相等,-1表示小于
*/
private class FileLastModifSort implements Comparator<File> {
public int compare(File arg0, File arg1) {
if (arg0.lastModified() > arg1.lastModified()) {
return 1;
} else if (arg0.lastModified() == arg1.lastModified()) {
return 0;
} else {
return -1;
}
}
}
}
通过图片我们可以看到,图片被写入了文件
图片下载类,使用多线程下载图片用了AsyncTask封装类
public class ImageLoader { /** * 使用多线程的方式去加载图片 */ private ImageView imageView; private String mUrl; //内存缓存 private ImageMemoryCache memoryCache; //文件缓存 private ImageFileCache fileCache; private ListView mListView; //任务集合,用来处理多个下载线程 private Set<NewsAsyncTask> mTasks; public ImageLoader(ListView listView,Context context){ mListView = listView; mTasks = new HashSet<ImageLoader.NewsAsyncTask>(); memoryCache = new ImageMemoryCache(context); fileCache = new ImageFileCache(); } /** * 用于从一个url获取bitmap */ public Bitmap getBitmapFromURL(String urlString){ Bitmap bitmap; InputStream is = null; try { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); is = new BufferedInputStream(connection.getInputStream()); bitmap = BitmapFactory.decodeStream(is); connection.disconnect(); return bitmap; } catch (Exception e) { }finally{ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } //在滚动的时候显示缓存的图片,如果没有缓存图片则显示默认的图片 public void showImagesFromCache(ImageView imageView,String url){ //从缓存取出图片 Bitmap result = memoryCache.getBitmapFromCache(url); if (result == null) { // 文件缓存中获取 result = fileCache.getImage(url); } if(result == null){ imageView.setImageResource(R.drawable.ic_launcher); }else{ imageView.setImageBitmap(result); } } //取消加载图片 public void cancelAllTasks(){ if(mTasks != null){ for(NewsAsyncTask task: mTasks){ task.cancel(false); } } } public void loadImages(int start,int end){ //加载从start到end的图片 for(int i = start;i<end;i++){ String url = NewsAdapter.URLS[i]; //从内存缓存取出图片 Bitmap bitmap = memoryCache.getBitmapFromCache(url); //如果缓存没有,则从文件中读取 if(bitmap == null){ //从文件获取图片 bitmap = fileCache.getImage(url); //文件中也为空,则必须从网络下载图片 if(bitmap == null){ //使用AsyncTask下载图片,这里会耗费流量 NewsAsyncTask task = new NewsAsyncTask(url); task.execute(url); //添加一个任务 mTasks.add(task); }else{ //文件中获取到了图片,则把图片加入到内存中 memoryCache.addBitmapToCache(url, bitmap); } } if(bitmap != null){ //根据url去获取 对应的imageView对象,防止显示混乱 ImageView imageView = (ImageView) mListView.findViewWithTag(url); imageView.setImageBitmap(bitmap); } } } private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{ private String mUrl; public NewsAsyncTask(String url){ mUrl = url; } @Override protected Bitmap doInBackground(String... params) { //从网络获取图片 Bitmap bitmap = getBitmapFromURL(params[0]); if(bitmap != null){ //把bitmap加入到缓存 memoryCache.addBitmapToCache(params[0], bitmap); //把bitmap 加入到文件 fileCache.saveBitmap(bitmap,params[0]); } return bitmap; } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl); if(imageView != null && result != null){ imageView.setImageBitmap(result); } //下载任务完成则移除这个任务 mTasks.remove(this); } } }
AndroidManifest.xml 注意加上权限
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.newsdemo" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@android:style/Theme.Black.NoTitleBar" > <activity android:name=".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> </manifest>
菜鸟一只,刚踏上追求技术的不归路,记录所学,希望大家指点!
代码下载 :http://download.csdn.net/detail/u010583599/9583583
本文参考:
http://blog.csdn.net/a79412906/article/details/10180583
http://www.imooc.com/learn/406
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories