图片的三级缓存:
1. 服务器端serlet:
/**
* 返回包含所有商品信息Json数据的Servlet
*/
public class ShopInfoListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
System.out.println("deGet()...");
//得到所有商品信息的集合
List<ShopInfo> list = getAllShops(request);
//转换为json字符串[{},{}]
String json = new Gson().toJson(list);
System.out.println(json);
//将数据写向客户端
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(json);
}
/**
* 得到所有商品信息的集合
* @param request
* @return
*/
/*
* 得到所有商品信息对象的集合
*/
private List<ShopInfo> getAllShops(HttpServletRequest request) {
//准备一个空集合
List<ShopInfo> list = new ArrayList<ShopInfo>();
// 得到images文件夹的真实路径
String imagesPath = getServletContext().getRealPath("/images");
//创建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();
//商品名称
String name = imageName.substring(0, imageName.lastIndexOf("."))+"的商品名称";
//图片路径
String imagePath = "http://"+request.getLocalAddr()+":"+request.getLocalPort()
+"/"+request.getContextPath()+"/images/"+imageName;
//图片价格
float price = new Random().nextInt(20)+20;//[20,40]
//封装成对象
ShopInfo info = new ShopInfo(id, name, price, imagePath);
//添加到集合中
list.add(info);
}
return list;
}
}
2.界面实现:
2.1 activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
<LinearLayout
android:id="@+id/ll_main_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正在加载中..." />
</LinearLayout>
</FrameLayout>
2.2 item_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal" >
<ImageView
android:id="@+id/iv_item_icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/ic_launcher"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
android:layout_marginLeft="10dp">
<TextView
android:id="@+id/tv_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="名称"
android:textSize="18sp"/>
<TextView
android:id="@+id/tv_item_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="内容"
android:textSize="18sp"/>
</LinearLayout>
</LinearLayout>
3. 客户端代码实现:
3.1 MainActivity
public class MainActivity extends AppCompatActivity {
private static final int WHAT_REQUEST_SUCCESS = 1;
private static final int WHAT_REQUEST_ERROR = 2;
private ListView iv_main;
private LinearLayout ll_main_loading;
private List<ShopInfo> data;
private ShopInfoAdapter adapter;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT_REQUEST_SUCCESS:
ll_main_loading.setVisibility(View.GONE);
//显示列表
iv_main.setAdapter(adapter);
break;
case WHAT_REQUEST_ERROR:
ll_main_loading.setVisibility(View.GONE);
Toast.makeText(MainActivity.this,"数据加载失败",Toast.LENGTH_LONG).show();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_main = (ListView) findViewById(R.id.iv_main);
ll_main_loading = (LinearLayout) findViewById(R.id.ll_main_loading);
ll_main_loading.setVisibility(View.VISIBLE);
new Thread(){
@Override
public void run() {
try {
String jsonString = requestJson();
data = new Gson().fromJson(jsonString,new TypeToken<List<ShopInfo>>(){}.getType());
adapter = new ShopInfoAdapter(MainActivity.this,data);
handler.sendEmptyMessage(WHAT_REQUEST_SUCCESS);//发请求成功的消息
Log.e("TAG",jsonString);
} catch (Exception e) {
e.printStackTrace();
handler.sendEmptyMessage(WHAT_REQUEST_ERROR);//发送请求失败的消息
}
}
}.start();
}
private String requestJson() throws Exception {
String result = null;
String path = "http://192.168.120.107:8989/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();
connection.disconnect();
result = baos.toString();
} else {
//也可以抛出运行时异常
}
return result;
}
}
3.2 ShopInfoAdapter
public class ShopInfoAdapter extends BaseAdapter{
private Context context;
private List<ShopInfo> shopInfos;
private ImageLoader imageLoader;
public ShopInfoAdapter(Context context, List<ShopInfo> shopInfos) {
this.context = context;
this.shopInfos = shopInfos;
this.imageLoader = new ImageLoader(context, R.mipmap.loading, R.mipmap.error);
}
@Override
public int getCount() {
return shopInfos.size();
}
@Override
public Object getItem(int position) {
return shopInfos.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(context,R.layout.item_main, null);
}
//得到当前行的数据对象
ShopInfo shopInfo = shopInfos.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;
}
}
3.3 ImageLoader
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<String, Bitmap> cacheMap = new HashMap<String, Bitmap>();
/**
* 加载图片并显示
* @param imagePath
* @param imageView
*/
public void loadImage(String imagePath, ImageView imageView) {
//将需要显示的图片url保存到视图上
imageView.setTag(imagePath);
/*
1). 根据url从一级缓存中取对应的bitmap对象
如果有, 显示(结束)
如果没有, 进入2)
*/
Bitmap bitmap = getFromFirstCache(imagePath);
if(bitmap!=null) {
imageView.setImageBitmap(bitmap);
return;
}
/*
2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象
如果有: 显示, 缓存到一级缓存中(结束)
如果没有, 进入3)
/storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg)
*/
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
* loadBitmapFromThirdCache("../b.jpg", imageView)
* loadBitmapFromThirdCache("../f.jpg", imageView)--->imageView.setTag("../f.jpg")
*/
private void loadBitmapFromThirdCache(final String imagePath, final ImageView imageView) {
new AsyncTask<Void, Void, Bitmap>() {
protected void onPreExecute() {
imageView.setImageResource(loadingImageRes);
}
//联网请求得到bitmap对象
@Override
protected Bitmap doInBackground(Void... params) {
//在分线程执行, 可能需要等待一定时间才会执行
//在等待的过程中imageView中的tag值就有可能改变了
//如果改变了, 就不应该再去加载图片(此图片此时不需要显示)
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);
//缓存到二级缓存(分线程)
// /storage/sdcard/Android/data/packageName/files/
String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
// http://192.168.10.165:8080//L05_Web/images/f10.jpg
String fileName = imagePath.substring(imagePath.lastIndexOf("/")+1);// f10.jpg
String filePath = filesPath+"/"+fileName;
bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(filePath));
}
}
connection.disconnect();
} catch (Exception e) {
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) {
// /storage/sdcard/Android/data/packageName/files/
String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
// http://192.168.10.165:8080//L05_Web/images/f10.jpg
String fileName = imagePath.substring(imagePath.lastIndexOf("/")+1);// f10.jpg
String filePath = filesPath+"/"+fileName;
return BitmapFactory.decodeFile(filePath);
}
/**
* 根据图片url从一级缓存中取对应的bitmap对象
* @param imagePath
* @return
*/
private Bitmap getFromFirstCache(String imagePath) {
return cacheMap.get(imagePath);
}
}
3.4 ShopInfo
public class ShopInfo {
private int id; //一定要与json对象字符串中的key的名称要一致
private String name;
private double price;
private String imagePath;
public ShopInfo(int id, String name, double price, String imagePath) {
super();
this.id = id;
this.name = name;
this.price = price;
this.imagePath = imagePath;
}
public ShopInfo() {
super();
}
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;
}
@Override
public String toString() {
return "ShopInfo [id=" + id + ", name=" + name + ", price=" + price
+ ", imagePath=" + imagePath + "]";
}
}