写在开头:毕业一年了,从零基础到现在接触android也已经一年多了,由一只小小菜鸟变成了一只小菜鸟。这一年来收获了很多,总想写些东西来总结过去一年的收获,奈何不知从何入手。刚好最近有空,想着是不是该学点新的东西呢,突然又想到自己的“拿来主义”太严重了,虽然整体上有完成一个项目,但是项目用到的一些框架,其实我也是一知半解的。所以我决定重新对整个项目熟悉一遍,分某块去总结技术点,刚好也是对过去的一点总结。想想还是有点小激动的
男怕入错行,女怕嫁错郎。以前我一直认为我不会从事it工作的,印象中加班如无底洞一样,所以我避之不及呢。一年多前,我甚至不懂java常见的数据类型有哪几种,什么mvc更不用说了,参加过笔试,这类题目只好放空了,想想那时候真是搞笑,已经是菜到不能再菜了。一次偶然的机会,我接触到Android,也不知道为啥,我就觉得这门技术很有前途。于是在大四上学期,我开始自学android,学习java的基本知识,终于有了一定的基础,顺便完成毕业设计,通过毕业答辩,也找到了一份android开发的工作。现在回头想想,我还是挺感谢那时候的自己,没有在最后一年继续荒废,不然也不会现在写这篇文章了。
额......废话好像有点多了,不好意思哈!现在进入主题。过去一年我就开发过一个项目,项目经验好少啊。项目改来改去的,迭代了十几个版本,好无奈啊。。。在此我就不吐槽公司了。这个项目我觉得主要重点技术有几个方面:1.图片的加载与处理;2.网络的加载和处理;3.自定义控件的使用;4.动画的使用...似乎每个应用都离不开以上几个知识点,当然还有其他的一些技术 ,在此我就不一一罗列了.......一般每个应用都会跟图片打交道的,我还没见过没有图片的应用哦。那么本篇文章我们将重点总结项目中的图片加载,我会将项目中应用的抽离出来写个demo,以后也将是这样,有不对的地方,欢迎大家吐槽啊,毕竟我也还是一只小菜鸟而已。
好了啥都不都说,先上图:(图一)从SD卡上读取图片
除了加载本地图片,我们还可以读取网络图片(图二)
由于制作gif麻烦,图片有点模糊,与实际情况不符合,实际运行结果清晰。接下来,我们开始着手学习。
一、谷歌对图片处理的建议
Google为Android开发提供了一个培训教程,在加载图片一节中提供了示例程序BitmapFun,实现了图片下载、缓存、解析加载的功能,至于BitmapFun的使用,我就不废话了,早有人对其进行了剖析了一番。你若想了解你可以到官网去看一下,或者搜一下博客,我随便搜了一下,发现这个仁兄讲得不错,你可以点击去看一下。当然也有人受到BitmapFun的迫害,哈哈,你也可以点击去瞧瞧。
二、你不得不知的LruCache和DiskLruCache
1.LruCache
Android用LruCache来取代原来强引用和软引用实现内存缓存,因为据说自2.3以后Android将更频繁的调用GC,导致软引用缓存的数据极易被释放。LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。他的主要原理在trimToSize方法中。需要了解两个主要的变量size和maxSize。maxSize是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。size在添加和移除缓存都被更新值,他通过safeSizeOf这个方法更新值。safeSizeOf默认返回1,但一般我们会根据maxSize重写这个方法,比如认为maxSize代表是KB的话,那么就以KB为单位返回该项所占的内存大小。LruCache的初始化如下:
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@SuppressLint("NewApi")
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
2.DiskLruCache
磁盘缓存利用原理是:开辟一定的空间用来缓存文件,将文件保存到磁盘上,利用最近最少使用算法,当空间已满时,写入新的文件替换最近最少使用的那个文件。DiskLruCache比较麻烦,利用了日志文件journal,保存着文件存储过程。具体原理大伙可以参考这位大神写的这篇文章,本篇文章的一部分代码也是引自其博客。下面我们就来看看怎么使用这个DiskLruCache:
初始化:
/**
* 创建磁盘缓存
*
* @param context
*/
private void createDiskLruCache(Context context) {
try {
// 获取图片缓存路径
File cacheDir = FileHelper.getDiskCacheDir(context, "thumb");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache.open(cacheDir, FileHelper.getAppVersion(context), 1, 20 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
写入缓存:
Snapshot snapShot = null;
try {
// 生成图片URL对应的key
final String key = FileHelper.hashKeyForDisk(imageUrl);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (DownloadUtil.downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
// 缓存被写入后,再次查找key对应的缓存
snapShot = mDiskLruCache.get(key);
} else {
System.out.println("从DiskLruCache上加载");
}
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
bitmap = BitmapUtil.decodeBitmapFromInputStream(is, mReqWidth, mReqHeight);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} catch (OutOfMemoryError oom) {
oom.printStackTrace();
System.gc();
}
好了,了解了LruCache和DiskLruCache的使用后,我们就要来完善一下其他细节了.。
三、LruCache和DiskLruCache结合使用。
经过上面的初始化后,图片加载器ImageLoader对外提供了DisplayImageView()至于该方法的参数,大伙可以根据自己需要来扩展。
/**
* 加载图片
*
* @param url
* @param imageView
*/
public void DisplayImage(String url, ImageView imageView) {
imageView.setTag(url);
mIamgeViews.put(url, imageView);
Bitmap bitmap = getBitmapFromMemoryCache(url);
if (bitmap != null) {
System.out.println("从LruCache上加载");
imageView.setImageBitmap(bitmap);
} else {
Log.d(TAG, "isLock:" + isLock);
if (isLock)
return;
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(url);
}
}
BitmapWorkTask 是一个异步操作,学过android的人都知道,一些耗时的操作一定不能在主线程进行,你可以选择普通线程runnable或者使用异步线程AsyncTask。由于篇幅关系,我就不贴它的全部代码了,有兴趣的可以到文章的末尾去下载Demo的源码,这边就说一个地方。
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 防止图片错位
String tag = (String) imageView.getTag();
if (tag.equals(imageUrl) && imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
taskCollection.remove(this);
}
一定要做图片错位防止措施哦......
还以为要结束这篇文章呢,发现忘了说一个问题,ListView和gridView快速滑动时候,不用加载图片,节省内存的同时,还是提高加载其他图片的速度。
// 添加滑动监听,快速滑动时候不加载数据
gridView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
mImageLoader.lock();
break;
case OnScrollListener.SCROLL_STATE_IDLE:
mImageLoader.unLock();
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
mImageLoader.unLock();
break;
default:
break;
}
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
嗦嘎!终于要写完这篇文章了,那个鸡冻啊。第一次写博客,写得不好,望读者原谅哈,希望下次能写得越来越好。最后我得感谢下,郭神的博客让我学习了很多。关于图片加载,他有更好的文章可以供大伙阅读哦,见连接
Android照片墙完整版,完美结合LruCache和DiskLruCache
Android DiskLruCache完全解析,硬盘缓存的最佳方案
Android高效加载大图、多图解决方案,有效避免程序OOM
收工,end~