主要功能:
图片的同步加载
图片的异步加载
图片压缩
内存缓存
磁盘缓存
网络拉取
一、图片的同步加载
loadBitmap
(String uri
, int
reqWidth
, int
reqHeight)
1、如果内存中已经存在,从内存中加载,
bitmap = loadBitmapFromMemCache(uri)
2、如果磁盘中已经存在,从磁盘中加载,
bitmap
= loadBitmapFromDiskCache(uri
,
reqWidth
,
reqHeight),这个方法会先从磁盘中取出原图,然后对图片进行压缩,将压缩后的图片返回并存放在内存中
3、如果内存、磁盘中都不存在,要从网络中加载
bitmap
= loadBitmapFromHttp(uri
,
reqWidth
,
reqHeight)
在网络中加载到图片之后,该方法先图片存入磁盘中
downloadUrlToStream
(url
,
outputStream)(此时存储的是原图,不对图片进行压缩)
然后根据url再从磁盘中取出对应的图片
loadBitmapFromDiskCache(url
,
reqWidth
,
reqHeight),这个方法首先会对图片进行压缩,将压缩后的图片返回并通过
addBitmapToMemoryCache
(key
,
bitmap)存放在内存中
4、如果磁盘缓存没有建立,则只从网络中拉取图片
bitmap = downloadBitmapFromUrl(uri),不对图片进行缓存,也没对图片进行压缩,可能不太好
注意:网络访问不能在主线程中进行,所以在访问网络时要有是否在主线程中的判断
if
(Looper.
myLooper
() == Looper.
getMainLooper
()) {
throw new
RuntimeException(
"can not visit network from UI Thread."
)
;
}
二、图片的异步加载
bindBitmap
(
final
String uri
, final
ImageView imageView
, final int
reqWidth
, final int
reqHeight)
1、给imageView设置tag,
imageView
.setTag(
TAG_KEY_URI
,
uri)
2、如果内存中存在,从内存中加载进imageView中
3、如果内存中不存在,在线程池中通过
loadBitmap(
uri
,
reqWidth
,
reqHeight
)获取图片
4、通过线程池运行loadBitmapTask,
THREAD_POOL_EXECUTOR
.execute(loadBitmapTask)
5、通过Handler将消息发送至主线程
mMainHandler
.obtainMessage(
MESSAGE_POST_RESULT
,
result).sendToTarget(),其中Handler的代码如下(handler由主线程的looper构造而成,所以handler中代码运行在主线程中):
private
Handler
mMainHandler
=
new
Handler(Looper.
getMainLooper
()) {
@Override
public void
handleMessage
(Message msg) {
LoaderResult result = (LoaderResult) msg.
obj
;
ImageView imageView = result.
imageView
;
String uri = (String) imageView.getTag(
TAG_KEY_URI
)
;
if
(uri.equals(result.
uri
)) {
imageView.setImageBitmap(result.
bitmap
)
;
}
else
{
Log.
w
(
TAG
,
"set image bitmap,but url has changed, ignored!"
)
;
}
}
}
;
三、图片压缩
使用BitmapFactory将从磁盘或Resource位置的图片压缩后加载进内存中:
1、将BitmapFactor.Options的inJustDecodeBounds参数设置为true并加载图片(将inJustDecodeBounds设置为true时,BitmapFactory只会去解析图片的原始宽/高信息并不会真正地去加载图片到内存)
2、从BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidth和outHeight参数
final int
height = options.
outHeight
;
final int
width = options.
outWidth
;
3、根据采样率的规则(inSampleSize = 2^n)并结合目标View的所需大小(reqWidth、reqHeight)计算出采样率inSampleSize(小于reqWidth及reqHeight的最大outWidth、outHeight)
4、将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片
bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options)
代码片段:
// First decode with inJustDecodeBounds=true to check dimensions
final
BitmapFactory.Options options =
new
BitmapFactory.Options()
;
options.
inJustDecodeBounds
=
true;
BitmapFactory.
decodeFileDescriptor
(fd
, null,
options)
;
// Calculate inSampleSize
options.
inSampleSize
= calculateInSampleSize(options
,
reqWidth
,
reqHeight)
;
// Decode bitmap with inSampleSize set
options.
inJustDecodeBounds
=
false;
return
BitmapFactory.
decodeFileDescriptor
(fd
, null,
options)
;
四、内存缓存
存入内存:
private void
addBitmapToMemoryCache
(String key
,
Bitmap bitmap) {
if
(getBitmapFromMemCache(key) ==
null
) {
mMemoryCache
.put(key
,
bitmap)
;
}
}
从内存中获取:
private
Bitmap
loadBitmapFromMemCache
(String url) {
final
String key = hashKeyFormUrl(url)
;
Bitmap bitmap = getBitmapFromMemCache(key)
;
return
bitmap
;
}
五、磁盘缓存
这是唯一一处将图片存入磁盘的操作,发生在图片从网络上取得之后,相比存入内存,存入磁盘的操作要复杂一些,包括
editor =
mDiskLruCache
.edit(key)、
OutputStream outputStream = editor.newOutputStream(
DISK_CACHE_INDEX
)、
editor.commit()
;、
mDiskLruCache
.flush()
代码如下:
private
Bitmap
loadBitmapFromHttp
(String url
, int
reqWidth
, int
reqHeight)
throws
IOException {
if
(Looper.
myLooper
() == Looper.
getMainLooper
()) {
throw new
RuntimeException(
"can not visit network from UI Thread."
)
;
}
if
(
mDiskLruCache
==
null
) {
return null;
}
String key = hashKeyFormUrl(url)
;
DiskLruCache.Editor editor =
mDiskLruCache
.edit(key)
;
if
(editor !=
null
) {
OutputStream outputStream = editor.newOutputStream(
DISK_CACHE_INDEX
)
;
if
(downloadUrlToStream(url
,
outputStream)) {
editor.commit()
;
}
else
{
editor.abort()
;
}
mDiskLruCache
.flush()
;
}
return
loadBitmapFromDiskCache(url
,
reqWidth
,
reqHeight)
;
}
六、Adapter中view复用的错位问题
一个是要在handler中处理,当uri发生改变时,直接忽略,不对imageView进行设置:
public void
handleMessage
(Message msg) {
LoaderResult result = (LoaderResult) msg.
obj
;
ImageView imageView = result.
imageView
;
String uri = (String) imageView.getTag(
TAG_KEY_URI
)
;
if
(uri.equals(result.
uri
)) {
imageView.setImageBitmap(result.
bitmap
)
;
}
else
{
Log.
w
(
TAG
,
"set image bitmap,but url has changed, ignored!"
)
;
}
}
另外,在发生滑动的时候,预先设置一张默认图片将以前的图片覆盖掉:
public
View
getView
(
int
position
,
View convertView
,
ViewGroup parent) {
ViewHolder holder =
null;
if
(convertView ==
null
) {
convertView =
mInflater
.inflate(R.layout.
image_list_item
,
parent
, false
)
;
holder =
new
ViewHolder()
;
holder.
imageView
= (ImageView) convertView.findViewById(R.id.
image
)
;
convertView.setTag(holder)
;
}
else
{
holder = (ViewHolder) convertView.getTag()
;
}
ImageView imageView = holder.
imageView
;
final
String tag = (String)imageView.getTag()
;
final
String uri = getItem(position)
;
//是为了在滑动的时候,先设置一张默认图片将以前的图片覆盖掉
if
(!uri.equals(tag)) {
imageView.setImageDrawable(
mDefaultBitmapDrawable
)
;
}
if
(
mIsGridViewIdle
&&
mCanGetBitmapFromNetWork
) {
imageView.setTag(uri)
;
mImageLoader
.bindBitmap(uri
,
imageView
,
mImageWidth
,
mImageWidth
)
;
}
return
convertView
;
}
github代码地址:https://github.com/Gaoee/android-art-res/tree/master/Chapter_12