开发手机台项目中用户要求首页内容可以自定义显示,图片大致如下:
这不是一个列表吗?将每一行的封装成一个组件(使用了NoScrollGridView),使用列表展示就行了。下面讲述一下碰到的一些奇葩问题。
1.二级列表时UIL图片出现错位问题
当时整个APP图片加载都是使用的UIL(Univeral-Image-Loader),搜索了一下,网上有牛人说,在listview列表的图片加载中,涉及到组件的复用问题,给图片设置上tag,相关代码如下。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//关键点,给imageview设置tag;
holder.anchorPhoto.setTag(iconUrl);
mImageLoader.loadImage(
iconUrl,options,
//下面是UIL加载图片完成后的回调,类的详情,见下文
new ImageLoadingListener(
holder.anchorPhoto,defaultBitmap)));
}
public class ViewHolder {
public ImageView anchorPhoto;
}
}
public class ImageLoadingListener implements
com.nostra13.universalimageloader.core.listener.ImageLoadingListener {
private ImageView mView;
private Bitmap mDefaultBitmap;
public ImageLoadingListener(ImageView photo, Bitmap defaultBitmap) {
this.mView = photo;
this.mDefaultBitmap = defaultBitmap;
}
@Override
public void onLoadingComplete(String arg0, View arg1, Bitmap arg2) {
//通过tag,确定是否展示图片。1.如果tag和ImageView对应,也就是ImageView在可视范围内(没有被重用,则加载图片;否则取消)
if (arg0.equals(mView.getTag())) {
mView.setImageBitmap(arg2);
}
}
}
这个方案在理论上无懈可击。但是仅仅在一级列表中展示(也就是布局中仅仅用ListView、没有ViewGroup的嵌套)没有问题。
曾经以为是虽然调用了这个方法,也有可能是listview没有进行及时刷新的问题。就在ImageLoadingListener ,定义一个回调接口,在listview中实现(直接调用notifyDataSetChange进行刷新页面),后来就出现了,刷新的死循环,结果就是在部分手机上出现了频繁闪屏。依然没有解决问题。
2.探索其他加载图片的组件
在网络上简单搜索了一下,现在在安卓端图片加载的组件呈现鼎足之势。下面简单描述一下其他两个(UIL上面提到了,在这里就不罗嗦了):
- Picasso
可以实现图片的网络加载和本地缓存(普通的图片可以,但是我们公司平台上的图片只能实现在线加载)。
- Volley
Google一个开源的项目,它的功能不仅仅局限于加载并缓存网络图片(sd卡缓存,需要自己进行添加),它最大的长处在于便捷的和服务器交互的方法(普通的图片可以,但是我们公司平台上的图片只能实现在线加载)。后来在一次无意中找到,“Volley中,通过网络获取来的数据其实都是字节流,Volley只是通过Response的Header信息来设置缓存记录的生命周期”,通过修改HttpHeaderParser.java中的parseCacheHeaders方法,实现了公司平台上网络图片的缓存。
默认保留一周,我的对该方法的修改如下:
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
final long weekInMill=7*24*3600*1000;
final long nowInMill=System.currentTimeMillis();
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
String serverEtag = null;
String headerValue;
// headerValue = headers.get("Date");
// if (headerValue != null) {
// serverDate = parseDateAsEpoch(headerValue);
// }
serverDate=now;
headerValue = "max-age=15552000";
// headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
return null;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
mustRevalidate = true;
}
}
}
// headerValue = headers.get("Expires");
// if (headerValue != null) {
serverExpires =nowInMill+weekInMill;
// serverExpires = parseDateAsEpoch(headerValue);
// }
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}