动态显示带图片列表【Android】

一.功能描述:
        1. 动态获取服务器端商品信息显示
        2. 动态加载服务器端图片显示
二.技术点:
  1. ListView+BaseAdapter
  2. JSON数据解析
  3. Handler+Thread
  4. HttpUrlConnection
  5. AsyncTask
  6. HttpClient
  7. 图片的三级缓存

三.过程分析 :

1. 搭建服务器端
2. 界面布局
整体 : ListView+提示视图
Item : LinearLayout
3. 动态显示列表
使用Handler+Thread处理联网请求, 得到json数据, 解析成List
使用BaseAdapter显示文本列表
根据url, 异步请求显示图片(使用三级缓存)


4. 动态显示列表中的图片
--->Bitmap--->手机本地的图片文件--->服务器端的图片文件
1). 图片的三级缓存
一级缓存: 内存缓存, 缓存的是bitmap对象, 用Map结构保存, key是url
二级缓存: 本地(sd卡)缓存, 缓存的是图片文件,  /storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg) 
三级缓存: 远程服务器缓存, 缓存的是图片文件, 远程服务器上的应用中
2). 如何使用三级缓存?  -----如何根据图片的url动态显示图片?    
String iamgePath = http://192.168.10.165:8080//L05_Web/images/f10.jpg和ImageView对象
1). 根据url从一级缓存中取对应的bitmap对象
如果有, 显示(结束)
如果没有, 进入2)
2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象
如果有: 显示, 缓存到一级缓存中(结束)
如果没有, 进入3)
3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象
如果没有: 显示提示错误的图片(结束)
如果有: 
显示
缓存到一级缓存
缓存到二级缓存
3). 在ListView使用图片三级缓存会存在图片闪动的bug
1). 原因
converView被复用了
2). 解决
a. 每次getView()都将图片的url保存到ImageView上: imageView.setTag(imagePath)
b. 在分线程准备请求服务器加载图片之前, 比较准备加载图片的url与ImageView中保存的最新图片的url是同一个, 
如果不是同一个, 当前加载图片的任务不应该再执行
如果相同, 继续执行加载远程图片
c. 在主线程准备显示图片之前, 比较加载到图片的url与ImageView中保存的最新图片的url是同一个
如果不是同一个, 不需要显示此图片
如果相同, 显示图片
四.代码实现:

1.MainActivity.java

package com.example.apphandler;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class MainActivity extends Activity {

	protected static final int WHAT_REQUEST_SUCCESS = 1;
	protected static final int WHAT_REQUEST_ERROR = 2;
	private ListView lv_main;
	private LinearLayout ll_main_loading;
	private List data = new ArrayList();
	private ShopInfoAdapter adapter;
	private Handler handler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case WHAT_REQUEST_SUCCESS:
				ll_main_loading.setVisibility(View.GONE);
				//显示列表
				lv_main.setAdapter(adapter);
				break;
			case WHAT_REQUEST_ERROR:
				ll_main_loading.setVisibility(View.GONE);
				Toast.makeText(MainActivity.this, "加载数据失败", 1).show();
				break;

			default:
				break;
			}
		}
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		lv_main = (ListView) findViewById(R.id.lv_main);
		ll_main_loading = (LinearLayout) findViewById(R.id.ll_main_loading);
		adapter = new ShopInfoAdapter();
		
		//1. 主线程, 显示提示视图
		ll_main_loading.setVisibility(View.VISIBLE);
		//2. 分线程, 联网请求
		//启动分线程请求服务器动态加载数据并显示
		new Thread(){
			public void run() {
				//联网请求得到jsonString
				try {
					String jsonString = requestJson();
					Log.i("TAG",jsonString);
					//解析成List
					data = new Gson().fromJson(jsonString, new TypeToken>(){}.getType());
					Log.i("TAG", data+"");
					//3. 主线程, 更新界面
					handler.sendEmptyMessage(WHAT_REQUEST_SUCCESS);//发请求成功的消息
				} catch (Exception e) {
					e.printStackTrace();
					handler.sendEmptyMessage(WHAT_REQUEST_ERROR);//发送请求失败的消息
				}
			}
		}.start();
		
		
	}
	
	/**
	 * 联网请求得到jsonString
	 * @return
	 * @throws Exception 
	 */
	private String requestJson() throws Exception {
		String result = null;
		String path = "http://192.168.51.65:8080/L05_Web/ShopInfoListServlet";
		//1. 得到连接对象
		URL url = new URL(path);
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		//2. 设置
		connection.setConnectTimeout(5000);
		connection.setReadTimeout(5000);
		//连接
		connection.connect();
		//发请求并读取服务器返回的数据
		int responseCode = connection.getResponseCode();
		if(responseCode==200) {
			InputStream is = connection.getInputStream();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len = -1;
			while ((len = is.read(buffer)) != -1) {
				baos.write(buffer, 0, len);
			}
			baos.close();
			is.close();
			
			result = baos.toString();
		} else {
			//也可以抛出运行时异常
		}
		connection.disconnect();
		return result;
	}

	class ShopInfoAdapter extends BaseAdapter {

		private ImageLoader imageLoader;
		
		public ShopInfoAdapter() {
			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_mian, null);
			}
			//得到当前行的数据对象
			ShopInfo shopInfo = data.get(position);
			//得到当前行的子View
			TextView nameTV = (TextView) convertView.findViewById(R.id.tv_item_name);
			TextView priceTV = (TextView) convertView.findViewById(R.id.tv_item_price);
			ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_item_icon);
			//设置数据
			nameTV.setText(shopInfo.getName());
			priceTV.setText(shopInfo.getPrice()+"元");
			String imagePath = shopInfo.getImagePath();
			//根据图片路径启动分线程动态请求服务加载图片并显示
			imageLoader.loadImage(imagePath, imageView);
			return convertView;
		}
		
	}
}
2.ImageLoader.java

package com.example.apphandler;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

/**
 * 用于加载图片并显示的类
 * 
 * @author Xiaocici String iamgePath =
 *         http://192.168.10.165:8080//L05_Web/images/f10.jpg和ImageView对象 1).
 *         根据url从一级缓存中取对应的bitmap对象 如果有, 显示(结束) 如果没有, 进入2) 
 *         2). 从二级缓存中查找:
 *         得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象 如果有: 显示, 缓存到一级缓存中(结束) 如果没有, 进入3)
 *         3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象 如果没有: 显示提示错误的图片(结束) 如果有: 显示
 *         缓存到一级缓存 缓存到二级缓存
 */
public class ImageLoader {
	private Context context;
	private int loadingImageRes;
	private int errorImageRes;

	public ImageLoader(Context context, int loadingImageRes, int errorImageRes
			) {
		super();
		this.context = context;
		this.loadingImageRes = loadingImageRes;
		this.errorImageRes = errorImageRes;
	}

	// 用于缓存bitmap的容器对象
	private Map cacheMap = new HashMap();

	/**
	 * 加载图片并显示
	 * 
	 * @param imagePath
	 * @param imageView
	 */
	public void loadImage(String imagePath, ImageView imageView) {
		//将需要显示的图片保存在视图上
		imageView.setTag(imagePath);
		/**
		 * 1). 根据url从一级缓存中取对应的bitmap对象 如果有, 显示(结束) 如果没有, 进入2)
		 */
		Bitmap bitmap = getFormFirstCache(imagePath);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		}
		/*
		 * 2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象 如果有: 显示, 缓存到一级缓存中(结束)
		 * /storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg) 如果没有,
		 * 进入3)
		 */
		bitmap = getFromSecondCache(imagePath);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			cacheMap.put(imagePath, bitmap);
			return;
		}
		/*
		 * 3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象 如果没有: 显示提示错误的图片(结束) 如果有:
		 * 缓存到一级缓存(分线程) 缓存到二级缓存 (分线程) 显示(主线程)
		 */
		loadBitmapFromThirdCache(imagePath, imageView);
	}

	/**
	 * 根据图片url从三级缓存中取对应的bitmap对象并显示
	 * 
	 * @param imagePath
	 * @param imageView
	 *            AsyncTask
	 */
	private void loadBitmapFromThirdCache(final String imagePath, final ImageView imageView) {
		new AsyncTask(){
			protected void onPreExecute() {
				imageView.setImageResource(loadingImageRes);
			};
			//联网请求得到bitmap对象
			@Override
			protected Bitmap doInBackground(Void... params) {
				Bitmap bitmap = null;
				try {
					//在准备请求服务器图片之前, 判断是否需要加载
					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();
					//发请求读取返回的数据并封装为bitmap
					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 filesPath = context.getExternalFilesDir(null).getAbsolutePath();
							String fileName = imagePath.substring(imagePath.lastIndexOf("/") + 1);
							String filePath = filesPath + "/" + fileName;
							
							bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(filePath));
						}
					}
					connection.disconnect();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return bitmap;
			}
			
			protected void onPostExecute(Bitmap bitmap) {
				//在主线程准备显示图片之前, 需要判断是否需要显示
				String newImagePath = (String) imageView.getTag();
				if(newImagePath!=imagePath) {//视图已经被复用了
					return;
				}
				
				//如果没有: 显示提示错误的图片(结束)
				if(bitmap == null){
					imageView.setImageResource(errorImageRes);
				} else {
					imageView.setImageBitmap(bitmap);
				}
			};
		}.execute();
	}

	/**
	 * 根据图片url从二级缓存中取对应的bitmap对象
	 * 
	 * @param imagePath
	 * @return
	 */
	private Bitmap getFromSecondCache(String imagePath) {
		Log.i("TAG", imagePath+"");
		// /storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg)
		String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
		Log.i("TAG", imagePath+"");
		String fileName = imagePath.substring(imagePath.lastIndexOf("/") + 1);
		Log.i("TAG", "后");
		String filePath = filesPath + "/" + fileName;
		return BitmapFactory.decodeFile(filePath);
	}

	/**
	 * 根据图片url从一级缓存中取对应的bitmap对象
	 * 
	 * @param imagePath
	 * @return
	 */
	private Bitmap getFormFirstCache(String imagePath) {
		// TODO Auto-generated method stub
		return cacheMap.get(imagePath);
	}

}

3.ShopInfo.java

package com.example.apphandler;

public class ShopInfo {
	private int id;
	private String name;
	private double price;
	private String imagePath;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public String getImagePath() {
		return imagePath;
	}
	public void setImagePath(String imagePath) {
		this.imagePath = imagePath;
	}
	public ShopInfo() {
		super();
		// TODO Auto-generated constructor stub
	}
	public ShopInfo(int id, String name, double price, String imagePath) {
		super();
		this.id = id;
		this.name = name;
		this.price = price;
		this.imagePath = imagePath;
	}
	@Override
	public String toString() {
		return "ShopInfo [id=" + id + ", name=" + name + ", price=" + price
				+ ", imagePath=" + imagePath + "]";
	}
}



你可能感兴趣的:(android)