例子:從用戶端發送聯網請求來擷取伺服器端清單資料,包括
圖檔
三級緩存:這裡的第三級緩存是用了AsyncTask
個人了解三級緩存:
1,一級緩存是将圖檔資源加載進記憶體,能保證在使用時加載過的圖檔,快速滑動時,不需要加載
2,二級緩存是将圖檔寫入硬碟,即使關了機,也是儲存到了本地
3,三級緩存是去伺服器端讀取對應的圖檔
在ListView使用三級緩存會存在圖檔閃動的bug
原因:
converView被複用
解決:
1,每次getView()都将圖檔的url儲存ImageView上:imageView.setTag(imagePath)
2,在分線程中準備請求伺服器加載圖檔之前,比較加載圖檔的url與ImageView中儲存的最新圖檔的Url是否同一個,
如果是,繼續執行加載遠端圖檔
如果不是,目前加載的圖檔的任務不應該再執行
3,在主線程準備顯示圖檔之前,比較加載到圖檔的url與ImageView中儲存的最新圖檔的url是否同一個
如果不是同一個,不需要顯示此圖檔
如果相同,顯示
/*
用于加載圖檔,并顯示的類
*/
public class ImageLoader {
private Context context;
private int loadingImage;
private int errorImage;
public ImageLoader(Context context, int loadingImage, int errorImage) {
this.context = context;
this.loadingImage = loadingImage;
this.errorImage = errorImage;
}
private Map<String, Bitmap> cacheMap=new HashMap<String,Bitmap>();
public void loadIamge(String imagePath, ImageView imageView) {
//将需要顯示的圖檔URL儲存到imageView身上
//getTag()相當于辨別
//為了檢測視圖是否被複用
imageView.setTag(imagePath);
//一級緩存
/*
如果有,顯示
如果沒有,進入二級緩存
*/
Bitmap bitmap=getFristCache(imagePath);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
//二級緩存
/*
如果有,顯示,并儲存在一級緩存
如果沒有,進入三級緩存
*/
bitmap=getSecondCasche(imagePath);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
cacheMap.put(imagePath,bitmap);
return;
}
//三級緩存
/*
沒有顯示錯誤,
有則儲存進一二級緩存,并顯示
*/
loadingImageFromThirdCache(imagePath,imageView);
}
/*
根據圖檔url從三級緩存中取對應的bitmap對象
*/
private void loadingImageFromThirdCache(final String imagePath, final ImageView imageView) {
new AsyncTask<Void,Void,Bitmap>() {
@Override
protected void onPreExecute() {
imageView.setImageResource(loadingImage);
}
//聯網請求得到Bimap對象
//在分線程中執行,可能需要等待一定時間才會被執行
//在等待過程中imageVIew得tag值就可能改變了
//如果改變了,就不應該再去加載(此圖檔此時不需要顯示)
@Override
protected Bitmap doInBackground(Void... voids) {
//得到連接配接
Bitmap bitmap=null;
try {
/*
在準備請求伺服器圖檔前,判斷是否需要加載
也就是我們為每張圖檔設定辨別,但是當我們快速滑動時
*/
@SuppressLint("WrongThread")
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();
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 filePath = context.getFilesDir().getAbsolutePath();
String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1);
filePath=filePath+"/"+fileName;
//第一個參數是圖檔的格式,png,jpeg這些,點進去就有了。第二個參數是壓縮,也就是這個圖檔原本的大小,然後壓縮成百分之幾,。第三個參數是二級緩存的檔案輸出流
bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(filePath));
}
}
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {//從聯網請求圖檔到得到圖檔對象需要哦一定時間,視圖可能被複用,不需要顯示
String newImagePath= (String) imageView.getTag();
if(newImagePath!=imagePath){
return;
}
if(bitmap==null){
//如果沒有,顯示錯誤圖檔
imageView.setImageResource(errorImage);
}else{//如果有,顯示
imageView.setImageBitmap(bitmap);
}
}
}.execute();
}
/*
根據圖檔url從二級緩存中取對應的bitmap對象
*/
private Bitmap getSecondCasche(String imagePath) {
// /data/data/packageName/files/
String filePath = context.getFilesDir().getAbsolutePath();
String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1);
filePath=filePath+"/"+fileName;
return BitmapFactory.decodeFile(filePath);
}
/*
根據圖檔url從一級緩存中取對應的bitmap對象
*/
private Bitmap getFristCache(String imagePath) {
return cacheMap.get(imagePath);
}
}
在這個事例中,我們在BaseAdapter中調用了這個方法:
public class ItemBaseAdapter extends BaseAdapter{
private ImageLoader imageLoader;
public ItemBaseAdapter(){
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_main,null);
}
ShopInfo shopInfo=data.get(position);
ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_item);
TextView name=(TextView)convertView.findViewById(R.id.name_item);
TextView price=(TextView)convertView.findViewById(R.id.price_item);
name.setText(shopInfo.getName());
price.setText(shopInfo.getPrice()+"元");
String imagePath = shopInfo.getImagePath();
//根據比對路徑動态請求服務加載圖檔并顯示
imageLoader.loadIamge(imagePath,imageView);
return convertView;
}
}
}
伺服器Servlet代碼(用Json傳回相關的資料):
@WebServlet("/ShopInfoServlet")
public class ShopInfoServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//擷取所有商品資訊
List<ShopInfo> list=new ArrayList<ShopInfo>();
list=getAllShops(request);
//轉換為json字元串
String s = new Gson().toJson(list);
System.out.println(s);
//将資料寫向用戶端
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(s);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/*
傳回所有商品資訊對象集合
*/
private List<ShopInfo> getAllShops(HttpServletRequest request){
//準備一個空集合
List<ShopInfo> list=new ArrayList<ShopInfo>();
//得到image的真實路徑
String imagesPath=getServletContext().getRealPath("/image");
//建立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();
//System.out.println(imageName);
//商品名稱
String name=imageName.substring(0,imageName.lastIndexOf("."))+"商品名稱";
//System.out.println(name);
//圖檔路徑
String imagePath="http://"+request.getLocalAddr()+":"+request.getLocalPort()
+"/"+request.getContextPath()+"/image/"+imageName;
System.out.println(imagePath);
//圖檔價格
float price =new Random().nextInt(20)+20;
ShopInfo info=new ShopInfo(id,name,price,imagePath);
list.add(info);
}
return list;
}
}
其他代碼也就是布局,還有視圖,handler發送消息的一些操作。這裡就不展示了。
還有就是,我在測試時遇到一個問題,很重點!!!!也就是為什麼我虛拟機上運作成功了,但是真機上确不行呢????
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL4FERNJTRq5keRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZwpmLyUzM1IDOxkTMwIjMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
這一行是這樣子的,看起來沒什麼問題。
最後經過詢問老師,原來是:
targetSdkVersion版本的問題,高版本不允許明文http,除非上https,或者配置網絡安全
之後我就将targetSdkVersion版本調到了25(老師叫我調到27)
之後,就運作成功了!!!!