Android中加载网络资源时的优化可使用(线程+缓存)解决
程序员文章站
2023-11-12 10:05:22
网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化: 1、采用线程池 2、内存缓存+文件缓存 3、内存缓存中网上很多是采用softreferenc...
网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化:
1、采用线程池
2、内存缓存+文件缓存
3、内存缓存中网上很多是采用softreference来防止堆溢出,这儿严格限制只能使用最大jvm内存的1/4
4、对下载的图片进行按比例缩放,以减少内存的消耗
具体的代码里面说明。先放上内存缓存类的代码memorycache.java:
<span style="font-size: 18px"><strong>public class memorycache {
private static final string tag = "memorycache";
// 放入缓存时是个同步操作
// linkedhashmap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即lru
// 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
private map<string, bitmap> cache = collections
.synchronizedmap(new linkedhashmap<string, bitmap>(10, 1.5f, true));
// 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
private long size = 0;// current allocated size
// 缓存只能占用的最大堆内存
private long limit = 1000000;// max memory in bytes
public memorycache() {
// use 25% of available heap size
setlimit(runtime.getruntime().maxmemory() / 4);
}
public void setlimit(long new_limit) {
limit = new_limit;
log.i(tag, "memorycache will use up to " + limit / 1024. / 1024. + "mb");
}
public bitmap get(string id) {
try {
if (!cache.containskey(id))
return null;
return cache.get(id);
} catch (nullpointerexception ex) {
return null;
}
}
public void put(string id, bitmap bitmap) {
try {
if (cache.containskey(id))
size -= getsizeinbytes(cache.get(id));
cache.put(id, bitmap);
size += getsizeinbytes(bitmap);
checksize();
} catch (throwable th) {
th.printstacktrace();
}
}
/**
* 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
*
*/
private void checksize() {
log.i(tag, "cache size=" + size + " length=" + cache.size());
if (size > limit) {
// 先遍历最近最少使用的元素
iterator<entry<string, bitmap>> iter = cache.entryset().iterator();
while (iter.hasnext()) {
entry<string, bitmap> entry = iter.next();
size -= getsizeinbytes(entry.getvalue());
iter.remove();
if (size <= limit)
break;
}
log.i(tag, "clean cache. new size " + cache.size());
}
}
public void clear() {
cache.clear();
}
/**
* 图片占用的内存
*
* @param bitmap
* @return
*/
long getsizeinbytes(bitmap bitmap) {
if (bitmap == null)
return 0;
return bitmap.getrowbytes() * bitmap.getheight();
}
}</strong></span>
也可以使用softreference,代码会简单很多,但是我推荐上面的方法。
public class memorycache {
private map<string, softreference<bitmap>> cache = collections
.synchronizedmap(new hashmap<string, softreference<bitmap>>());
public bitmap get(string id) {
if (!cache.containskey(id))
return null;
softreference<bitmap> ref = cache.get(id);
return ref.get();
}
public void put(string id, bitmap bitmap) {
cache.put(id, new softreference<bitmap>(bitmap));
}
public void clear() {
cache.clear();
}
}
下面是文件缓存类的代码filecache.java:
public class filecache {
private file cachedir;
public filecache(context context) {
// 如果有sd卡则在sd卡中建一个lazylist的目录存放缓存的图片
// 没有sd卡就放在系统的缓存目录中
if (android.os.environment.getexternalstoragestate().equals(
android.os.environment.media_mounted))
cachedir = new file(
android.os.environment.getexternalstoragedirectory(),
"lazylist");
else
cachedir = context.getcachedir();
if (!cachedir.exists())
cachedir.mkdirs();
}
public file getfile(string url) {
// 将url的hashcode作为缓存的文件名
string filename = string.valueof(url.hashcode());
// another possible solution
// string filename = urlencoder.encode(url);
file f = new file(cachedir, filename);
return f;
}
public void clear() {
file[] files = cachedir.listfiles();
if (files == null)
return;
for (file f : files)
f.delete();
}
}
最后最重要的加载图片的类,imageloader.java:
public class imageloader {
memorycache memorycache = new memorycache();
filecache filecache;
private map<imageview, string> imageviews = collections
.synchronizedmap(new weakhashmap<imageview, string>());
// 线程池
executorservice executorservice;
public imageloader(context context) {
filecache = new filecache(context);
executorservice = executors.newfixedthreadpool(5);
}
// 当进入listview时默认的图片,可换成你自己的默认图片
final int stub_id = r.drawable.stub;
// 最主要的方法
public void displayimage(string url, imageview imageview) {
imageviews.put(imageview, url);
// 先从内存缓存中查找
bitmap bitmap = memorycache.get(url);
if (bitmap != null)
imageview.setimagebitmap(bitmap);
else {
// 若没有的话则开启新线程加载图片
queuephoto(url, imageview);
imageview.setimageresource(stub_id);
}
}
private void queuephoto(string url, imageview imageview) {
phototoload p = new phototoload(url, imageview);
executorservice.submit(new photosloader(p));
}
private bitmap getbitmap(string url) {
file f = filecache.getfile(url);
// 先从文件缓存中查找是否有
bitmap b = decodefile(f);
if (b != null)
return b;
// 最后从指定的url中下载图片
try {
bitmap bitmap = null;
url imageurl = new url(url);
httpurlconnection conn = (httpurlconnection) imageurl
.openconnection();
conn.setconnecttimeout(30000);
conn.setreadtimeout(30000);
conn.setinstancefollowredirects(true);
inputstream is = conn.getinputstream();
outputstream os = new fileoutputstream(f);
copystream(is, os);
os.close();
bitmap = decodefile(f);
return bitmap;
} catch (exception ex) {
ex.printstacktrace();
return null;
}
}
// decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
private bitmap decodefile(file f) {
try {
// decode image size
bitmapfactory.options o = new bitmapfactory.options();
o.injustdecodebounds = true;
bitmapfactory.decodestream(new fileinputstream(f), null, o);
// find the correct scale value. it should be the power of 2.
final int required_size = 70;
int width_tmp = o.outwidth, height_tmp = o.outheight;
int scale = 1;
while (true) {
if (width_tmp / 2 < required_size
|| height_tmp / 2 < required_size)
break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
// decode with insamplesize
bitmapfactory.options o2 = new bitmapfactory.options();
o2.insamplesize = scale;
return bitmapfactory.decodestream(new fileinputstream(f), null, o2);
} catch (filenotfoundexception e) {
}
return null;
}
// task for the queue
private class phototoload {
public string url;
public imageview imageview;
public phototoload(string u, imageview i) {
url = u;
imageview = i;
}
}
class photosloader implements runnable {
phototoload phototoload;
photosloader(phototoload phototoload) {
this.phototoload = phototoload;
}
@override
public void run() {
if (imageviewreused(phototoload))
return;
bitmap bmp = getbitmap(phototoload.url);
memorycache.put(phototoload.url, bmp);
if (imageviewreused(phototoload))
return;
bitmapdisplayer bd = new bitmapdisplayer(bmp, phototoload);
// 更新的操作放在ui线程中
activity a = (activity) phototoload.imageview.getcontext();
a.runonuithread(bd);
}
}
/**
* 防止图片错位
*
* @param phototoload
* @return
*/
boolean imageviewreused(phototoload phototoload) {
string tag = imageviews.get(phototoload.imageview);
if (tag == null || !tag.equals(phototoload.url))
return true;
return false;
}
// 用于在ui线程中更新界面
class bitmapdisplayer implements runnable {
bitmap bitmap;
phototoload phototoload;
public bitmapdisplayer(bitmap b, phototoload p) {
bitmap = b;
phototoload = p;
}
public void run() {
if (imageviewreused(phototoload))
return;
if (bitmap != null)
phototoload.imageview.setimagebitmap(bitmap);
else
phototoload.imageview.setimageresource(stub_id);
}
}
public void clearcache() {
memorycache.clear();
filecache.clear();
}
public static void copystream(inputstream is, outputstream os) {
final int buffer_size = 1024;
try {
byte[] bytes = new byte[buffer_size];
for (;;) {
int count = is.read(bytes, 0, buffer_size);
if (count == -1)
break;
os.write(bytes, 0, count);
}
} catch (exception ex) {
}
}
}
主要流程是先从内存缓存中查找,若没有再开线程,从文件缓存中查找都没有则从指定的url中查找,并对bitmap进行处理,最后通过下面方法对ui进行更新操作。
a.runonuithread(...);
在你的程序中的基本用法:
<span style="font-size: 18px"><strong>imageloader imageloader=new imageloader(context);
...
imageloader.displayimage(url, imageview);</strong></span>
比如你的放在你的listview的adapter的getview()方法中,当然也适用于gridview。
1、采用线程池
2、内存缓存+文件缓存
3、内存缓存中网上很多是采用softreference来防止堆溢出,这儿严格限制只能使用最大jvm内存的1/4
4、对下载的图片进行按比例缩放,以减少内存的消耗
具体的代码里面说明。先放上内存缓存类的代码memorycache.java:
复制代码 代码如下:
<span style="font-size: 18px"><strong>public class memorycache {
private static final string tag = "memorycache";
// 放入缓存时是个同步操作
// linkedhashmap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即lru
// 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
private map<string, bitmap> cache = collections
.synchronizedmap(new linkedhashmap<string, bitmap>(10, 1.5f, true));
// 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
private long size = 0;// current allocated size
// 缓存只能占用的最大堆内存
private long limit = 1000000;// max memory in bytes
public memorycache() {
// use 25% of available heap size
setlimit(runtime.getruntime().maxmemory() / 4);
}
public void setlimit(long new_limit) {
limit = new_limit;
log.i(tag, "memorycache will use up to " + limit / 1024. / 1024. + "mb");
}
public bitmap get(string id) {
try {
if (!cache.containskey(id))
return null;
return cache.get(id);
} catch (nullpointerexception ex) {
return null;
}
}
public void put(string id, bitmap bitmap) {
try {
if (cache.containskey(id))
size -= getsizeinbytes(cache.get(id));
cache.put(id, bitmap);
size += getsizeinbytes(bitmap);
checksize();
} catch (throwable th) {
th.printstacktrace();
}
}
/**
* 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
*
*/
private void checksize() {
log.i(tag, "cache size=" + size + " length=" + cache.size());
if (size > limit) {
// 先遍历最近最少使用的元素
iterator<entry<string, bitmap>> iter = cache.entryset().iterator();
while (iter.hasnext()) {
entry<string, bitmap> entry = iter.next();
size -= getsizeinbytes(entry.getvalue());
iter.remove();
if (size <= limit)
break;
}
log.i(tag, "clean cache. new size " + cache.size());
}
}
public void clear() {
cache.clear();
}
/**
* 图片占用的内存
*
* @param bitmap
* @return
*/
long getsizeinbytes(bitmap bitmap) {
if (bitmap == null)
return 0;
return bitmap.getrowbytes() * bitmap.getheight();
}
}</strong></span>
也可以使用softreference,代码会简单很多,但是我推荐上面的方法。
复制代码 代码如下:
public class memorycache {
private map<string, softreference<bitmap>> cache = collections
.synchronizedmap(new hashmap<string, softreference<bitmap>>());
public bitmap get(string id) {
if (!cache.containskey(id))
return null;
softreference<bitmap> ref = cache.get(id);
return ref.get();
}
public void put(string id, bitmap bitmap) {
cache.put(id, new softreference<bitmap>(bitmap));
}
public void clear() {
cache.clear();
}
}
下面是文件缓存类的代码filecache.java:
复制代码 代码如下:
public class filecache {
private file cachedir;
public filecache(context context) {
// 如果有sd卡则在sd卡中建一个lazylist的目录存放缓存的图片
// 没有sd卡就放在系统的缓存目录中
if (android.os.environment.getexternalstoragestate().equals(
android.os.environment.media_mounted))
cachedir = new file(
android.os.environment.getexternalstoragedirectory(),
"lazylist");
else
cachedir = context.getcachedir();
if (!cachedir.exists())
cachedir.mkdirs();
}
public file getfile(string url) {
// 将url的hashcode作为缓存的文件名
string filename = string.valueof(url.hashcode());
// another possible solution
// string filename = urlencoder.encode(url);
file f = new file(cachedir, filename);
return f;
}
public void clear() {
file[] files = cachedir.listfiles();
if (files == null)
return;
for (file f : files)
f.delete();
}
}
最后最重要的加载图片的类,imageloader.java:
复制代码 代码如下:
public class imageloader {
memorycache memorycache = new memorycache();
filecache filecache;
private map<imageview, string> imageviews = collections
.synchronizedmap(new weakhashmap<imageview, string>());
// 线程池
executorservice executorservice;
public imageloader(context context) {
filecache = new filecache(context);
executorservice = executors.newfixedthreadpool(5);
}
// 当进入listview时默认的图片,可换成你自己的默认图片
final int stub_id = r.drawable.stub;
// 最主要的方法
public void displayimage(string url, imageview imageview) {
imageviews.put(imageview, url);
// 先从内存缓存中查找
bitmap bitmap = memorycache.get(url);
if (bitmap != null)
imageview.setimagebitmap(bitmap);
else {
// 若没有的话则开启新线程加载图片
queuephoto(url, imageview);
imageview.setimageresource(stub_id);
}
}
private void queuephoto(string url, imageview imageview) {
phototoload p = new phototoload(url, imageview);
executorservice.submit(new photosloader(p));
}
private bitmap getbitmap(string url) {
file f = filecache.getfile(url);
// 先从文件缓存中查找是否有
bitmap b = decodefile(f);
if (b != null)
return b;
// 最后从指定的url中下载图片
try {
bitmap bitmap = null;
url imageurl = new url(url);
httpurlconnection conn = (httpurlconnection) imageurl
.openconnection();
conn.setconnecttimeout(30000);
conn.setreadtimeout(30000);
conn.setinstancefollowredirects(true);
inputstream is = conn.getinputstream();
outputstream os = new fileoutputstream(f);
copystream(is, os);
os.close();
bitmap = decodefile(f);
return bitmap;
} catch (exception ex) {
ex.printstacktrace();
return null;
}
}
// decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
private bitmap decodefile(file f) {
try {
// decode image size
bitmapfactory.options o = new bitmapfactory.options();
o.injustdecodebounds = true;
bitmapfactory.decodestream(new fileinputstream(f), null, o);
// find the correct scale value. it should be the power of 2.
final int required_size = 70;
int width_tmp = o.outwidth, height_tmp = o.outheight;
int scale = 1;
while (true) {
if (width_tmp / 2 < required_size
|| height_tmp / 2 < required_size)
break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
// decode with insamplesize
bitmapfactory.options o2 = new bitmapfactory.options();
o2.insamplesize = scale;
return bitmapfactory.decodestream(new fileinputstream(f), null, o2);
} catch (filenotfoundexception e) {
}
return null;
}
// task for the queue
private class phototoload {
public string url;
public imageview imageview;
public phototoload(string u, imageview i) {
url = u;
imageview = i;
}
}
class photosloader implements runnable {
phototoload phototoload;
photosloader(phototoload phototoload) {
this.phototoload = phototoload;
}
@override
public void run() {
if (imageviewreused(phototoload))
return;
bitmap bmp = getbitmap(phototoload.url);
memorycache.put(phototoload.url, bmp);
if (imageviewreused(phototoload))
return;
bitmapdisplayer bd = new bitmapdisplayer(bmp, phototoload);
// 更新的操作放在ui线程中
activity a = (activity) phototoload.imageview.getcontext();
a.runonuithread(bd);
}
}
/**
* 防止图片错位
*
* @param phototoload
* @return
*/
boolean imageviewreused(phototoload phototoload) {
string tag = imageviews.get(phototoload.imageview);
if (tag == null || !tag.equals(phototoload.url))
return true;
return false;
}
// 用于在ui线程中更新界面
class bitmapdisplayer implements runnable {
bitmap bitmap;
phototoload phototoload;
public bitmapdisplayer(bitmap b, phototoload p) {
bitmap = b;
phototoload = p;
}
public void run() {
if (imageviewreused(phototoload))
return;
if (bitmap != null)
phototoload.imageview.setimagebitmap(bitmap);
else
phototoload.imageview.setimageresource(stub_id);
}
}
public void clearcache() {
memorycache.clear();
filecache.clear();
}
public static void copystream(inputstream is, outputstream os) {
final int buffer_size = 1024;
try {
byte[] bytes = new byte[buffer_size];
for (;;) {
int count = is.read(bytes, 0, buffer_size);
if (count == -1)
break;
os.write(bytes, 0, count);
}
} catch (exception ex) {
}
}
}
主要流程是先从内存缓存中查找,若没有再开线程,从文件缓存中查找都没有则从指定的url中查找,并对bitmap进行处理,最后通过下面方法对ui进行更新操作。
复制代码 代码如下:
a.runonuithread(...);
在你的程序中的基本用法:
复制代码 代码如下:
<span style="font-size: 18px"><strong>imageloader imageloader=new imageloader(context);
...
imageloader.displayimage(url, imageview);</strong></span>
比如你的放在你的listview的adapter的getview()方法中,当然也适用于gridview。