图片的三级缓存

图片的三级缓存:

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 + "]";
	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值