天天看點

Android--三級緩存

例子:從用戶端發送聯網請求來擷取伺服器端清單資料,包括

圖檔

三級緩存:這裡的第三級緩存是用了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發送消息的一些操作。這裡就不展示了。

還有就是,我在測試時遇到一個問題,很重點!!!!也就是為什麼我虛拟機上運作成功了,但是真機上确不行呢????

Android--三級緩存
Android--三級緩存

這一行是這樣子的,看起來沒什麼問題。

最後經過詢問老師,原來是:

targetSdkVersion版本的問題,高版本不允許明文http,除非上https,或者配置網絡安全

之後我就将targetSdkVersion版本調到了25(老師叫我調到27)

之後,就運作成功了!!!!