例子:从客户端发送联网请求来获取服务器端列表数据,包括
图片
三级缓存:这里的第三级缓存是用了AsyncTask
个人理解三级缓存:
1,一级缓存是将图片资源加载进内存,能保证在使用时加载过的图片,快速滑动时,不需要加载
2,二级缓存是将图片写入硬盘,即使关了机,也是保存到了本地
3,三级缓存是去服务器端读取对应的图片
在ListView使用三级缓存会存在图片闪动的bug
原因:
converView被复用
解决:
1,每次getView()都将图片的url保存ImageView上:imageView.setTag(imagePath)
2,在分线程中准备请求服务器加载图片之前,比较加载图片的url与ImageView中保存的最新图片的Url是否同一个,
如果是,继续执行加载远程图片
如果不是,当前加载的图片的任务不应该再执行
3,在主线程准备显示图片之前,比较加载到图片的url与ImageView中保存的最新图片的url是否同一个
如果不是同一个,不需要显示此图片
如果相同,显示
/*
用于加载图片,并显示的类
*/
public class ImageLoader {
private Context context;
private int loadingImage;
private int errorImage;
public ImageLoader(Context context, int loadingImage, int errorImage) {
this.context = context;
this.loadingImage = loadingImage;
this.errorImage = errorImage;
}
private Map<String, Bitmap> cacheMap=new HashMap<String,Bitmap>();
public void loadIamge(String imagePath, ImageView imageView) {
//将需要显示的图片URL保存到imageView身上
//getTag()相当于标识
//为了检测视图是否被复用
imageView.setTag(imagePath);
//一级缓存
/*
如果有,显示
如果没有,进入二级缓存
*/
Bitmap bitmap=getFristCache(imagePath);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
//二级缓存
/*
如果有,显示,并保存在一级缓存
如果没有,进入三级缓存
*/
bitmap=getSecondCasche(imagePath);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
cacheMap.put(imagePath,bitmap);
return;
}
//三级缓存
/*
没有显示错误,
有则保存进一二级缓存,并显示
*/
loadingImageFromThirdCache(imagePath,imageView);
}
/*
根据图片url从三级缓存中取对应的bitmap对象
*/
private void loadingImageFromThirdCache(final String imagePath, final ImageView imageView) {
new AsyncTask<Void,Void,Bitmap>() {
@Override
protected void onPreExecute() {
imageView.setImageResource(loadingImage);
}
//联网请求得到Bimap对象
//在分线程中执行,可能需要等待一定时间才会被执行
//在等待过程中imageVIew得tag值就可能改变了
//如果改变了,就不应该再去加载(此图片此时不需要显示)
@Override
protected Bitmap doInBackground(Void... voids) {
//得到连接
Bitmap bitmap=null;
try {
/*
在准备请求服务器图片前,判断是否需要加载
也就是我们为每张图片设置标识,但是当我们快速滑动时
*/
@SuppressLint("WrongThread")
String newImagePath= (String) imageView.getTag();
if(newImagePath!=imagePath){//视图已经复用
return null;
}
URL url=new URL(imagePath);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.connect();
int responseCode = connection.getResponseCode();
if(responseCode==200){
InputStream is=connection.getInputStream();
//将is封装为bitmap
bitmap = BitmapFactory.decodeStream(is);
is.close();
if(bitmap!=null){
//缓存到一级缓存(分线程)
cacheMap.put(imagePath,bitmap);
//缓存到二级缓存(分线程)
String filePath = context.getFilesDir().getAbsolutePath();
String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1);
filePath=filePath+"/"+fileName;
//第一个参数是图片的格式,png,jpeg这些,点进去就有了。第二个参数是压缩,也就是这个图片原本的大小,然后压缩成百分之几,。第三个参数是二级缓存的文件输出流
bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(filePath));
}
}
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {//从联网请求图片到得到图片对象需要哦一定时间,视图可能被复用,不需要显示
String newImagePath= (String) imageView.getTag();
if(newImagePath!=imagePath){
return;
}
if(bitmap==null){
//如果没有,显示错误图片
imageView.setImageResource(errorImage);
}else{//如果有,显示
imageView.setImageBitmap(bitmap);
}
}
}.execute();
}
/*
根据图片url从二级缓存中取对应的bitmap对象
*/
private Bitmap getSecondCasche(String imagePath) {
// /data/data/packageName/files/
String filePath = context.getFilesDir().getAbsolutePath();
String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1);
filePath=filePath+"/"+fileName;
return BitmapFactory.decodeFile(filePath);
}
/*
根据图片url从一级缓存中取对应的bitmap对象
*/
private Bitmap getFristCache(String imagePath) {
return cacheMap.get(imagePath);
}
}
在这个事例中,我们在BaseAdapter中调用了这个方法:
public class ItemBaseAdapter extends BaseAdapter{
private ImageLoader imageLoader;
public ItemBaseAdapter(){
imageLoader=new ImageLoader(MainActivity.this,R.drawable.loading,R.drawable.error);
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
convertView=View.inflate(MainActivity.this,R.layout.item_main,null);
}
ShopInfo shopInfo=data.get(position);
ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_item);
TextView name=(TextView)convertView.findViewById(R.id.name_item);
TextView price=(TextView)convertView.findViewById(R.id.price_item);
name.setText(shopInfo.getName());
price.setText(shopInfo.getPrice()+"元");
String imagePath = shopInfo.getImagePath();
//根据匹配路径动态请求服务加载图片并显示
imageLoader.loadIamge(imagePath,imageView);
return convertView;
}
}
}
服务器Servlet代码(用Json返回相关的数据):
@WebServlet("/ShopInfoServlet")
public class ShopInfoServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取所有商品信息
List<ShopInfo> list=new ArrayList<ShopInfo>();
list=getAllShops(request);
//转换为json字符串
String s = new Gson().toJson(list);
System.out.println(s);
//将数据写向客户端
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(s);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/*
返回所有商品信息对象集合
*/
private List<ShopInfo> getAllShops(HttpServletRequest request){
//准备一个空集合
List<ShopInfo> list=new ArrayList<ShopInfo>();
//得到image的真实路径
String imagesPath=getServletContext().getRealPath("/image");
//创建images文件file对象
File file=new File(imagesPath);
//得到images文件下的所有图片文件的file对象数组
File[] files=file.listFiles();
for(int i=0;i<files.length;i++){
int id =i+1;
//图片名称
String imageName=files[i].getName();
//System.out.println(imageName);
//商品名称
String name=imageName.substring(0,imageName.lastIndexOf("."))+"商品名称";
//System.out.println(name);
//图片路径
String imagePath="http://"+request.getLocalAddr()+":"+request.getLocalPort()
+"/"+request.getContextPath()+"/image/"+imageName;
System.out.println(imagePath);
//图片价格
float price =new Random().nextInt(20)+20;
ShopInfo info=new ShopInfo(id,name,price,imagePath);
list.add(info);
}
return list;
}
}
其他代码也就是布局,还有视图,handler发送消息的一些操作。这里就不展示了。
还有就是,我在测试时遇到一个问题,很重点!!!!也就是为什么我虚拟机上运行成功了,但是真机上确不行呢????
这一行是这样子的,看起来没什么问题。
最后经过询问老师,原来是:
targetSdkVersion版本的问题,高版本不允许明文http,除非上https,或者配置网络安全
之后我就将targetSdkVersion版本调到了25(老师叫我调到27)
之后,就运行成功了!!!!