一.功能描述:
        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;}}};@Overrideprotected 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() {//联网请求得到jsonStringtry {String jsonString = requestJson();Log.i("TAG",jsonString);//解析成Listdata = 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);}@Overridepublic int getCount() {return data.size();}@Overridepublic Object getItem(int position) {return data.get(position);}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic 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);//得到当前行的子ViewTextView 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对象@Overrideprotected 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();//发请求读取返回的数据并封装为bitmapint responseCode = connection.getResponseCode();if(responseCode==200){InputStream is = connection.getInputStream();//图片文件流//将is封装为bitmapbitmap = 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 blocke.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 stubreturn 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;}@Overridepublic String toString() {return "ShopInfo [id=" + id + ", name=" + name + ", price=" + price+ ", imagePath=" + imagePath + "]";}}



更多相关文章

  1. android studio实现视频图片轮播功能
  2. Android(安卓)应用软件开发(十四)WIFI
  3. Glide框架V3版本和V4版本区别
  4. android 网络编程--URL获取数据/图片
  5. java泛型操作复习,以及讲解在android中使用的场景
  6. 三种自定义漂亮的Android(安卓)SeekBar的方法
  7. Android(java)学习笔记89:泛型概述和基本使用
  8. android 腾讯微博分享功能
  9. android Fragment 与 Fragment 之间传参(对象)

随机推荐

  1. android 中 padding与margin的区别
  2. Android逆向之旅---Android中的sharedUse
  3. Android音频开发(6):使用 OpenSL ES API(上)
  4. “史上最强Android木马”现身?360手机安全
  5. android游戏引擎初探
  6. ios8.0正式版推送 苹果教大家如何从Andro
  7. Android要走路还很长
  8. android eclipse 真机调试
  9. 【摘录】android 屏幕分辨率问题
  10. Android内存管理机制之一:lowmemory kille