一.功能描述:
        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(安卓)Visual Studio配置的相关说
  2. android连接mysql数据库
  3. android 电子签名 手写签名 功能实现
  4. 使用android模拟器需要的设置(环境变量设
  5. Best Practice For Android
  6. android 6.0及以下获取wifi mac地址
  7. Supporting Multiple Screens(支持Androi
  8. Android摄像头编程及注意事项
  9. android 4.0 (Ice Cream Sandwich) 已经开
  10. android content provider