Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android-緩存網絡圖片(MVP模式)學習筆記

Android-緩存網絡圖片(MVP模式)學習筆記

編輯:關於Android編程

訪問網絡圖片是很普遍的事了,在前面的學習中,我也寫過了幾次異步網上請求網絡圖片,但是沒有緩存圖片,那麼我們也都知道,有時候訪問一些經常訪問的網絡圖片,如果不采取緩存的形式,那麼對流量的消耗會非常大,所以,有必要的時候我們可以采取緩存圖片的方式來解決流量消耗問題,下面就通過一個MVP模式的簡單設計來這裡寫代碼片講解一下緩存網絡圖片。

整體的結構如下:

這裡寫圖片描述

首先對於緩存圖片來說,我們需要掌握一下幾個小點:

  • 1. 當圖片沒有緩存的時候,我們就要去通過網絡異步加載圖片
  • 2. 當存在緩存的時候,我們就不能去通過網絡異步加載圖片,直接獲取緩存中的圖片
  • 3. 當保存緩存圖片時如何確定唯一命名規則,由於網絡下載圖片地址是不同的,所以可根據地址命名
  • 4. 考慮到有些模擬器保存到內存卡的緩存中時,出現在cache中找到不緩存的圖片時,我們將圖片地址
  • 中的非單詞字符(除了a-z A-Z 0 - 9) 以外,全部去掉
  • 5. 考慮緩存的內存是否夠用,當緩存不夠用時我們就要刪除部分(這裡我的代碼是刪除全部)已緩存的圖片
  • 6. 考慮在GridView中復用控件時,出現圖片的閃爍已經圖片錯亂問題

model層:

ModelInter.java

//所有的獲取數據功能都抽取到接口中,讓其子類實現
public interface ModelInter {
    void getData(OnCompleteListener listener, String path);
    public interface OnCompleteListener {
        void onComplete(byte[] bytes, String path);
    }
}

ModelImp.java

//實現獲取數據
public class ModelImp implements ModelInter {
    @Override
    public void getData(OnCompleteListener listener, String path) {
        new MyAsyncTask(listener).execute(path);
    }
}

view層:

ViewInter.java

//所有更新界面,以及得到數據的功能
public interface ViewInter {
    void getAdapterData(List data);
}

MainActivity.java

//主界面啟動,展示數據等。
public class MainActivity extends AppCompatActivity implements ViewInter {

    private String path = "http://apis.baidu.com/txapi/mvtp/meinv?num=10";
    private GridView mGrid;
    private MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        new Presenter(this).load(path);
    }

    private void init() {
        mGrid = (GridView) findViewById(R.id.mGrid);
    }

    private void showListData(List data) {
        if (adapter == null) {
            adapter = new MyAdapter(data, R.layout.list_item);
            mGrid.setAdapter(adapter);
        } else {

        }
    }
    //通過presenter代理類為我們提供數據
    @Override
    public void getAdapterData(List data) {
        showListData(data);
    }
}

presenter層:

presenter.java

//為視圖層提供所需要的數據
public class Presenter {
    private ViewInter viewInter;
    private ModelInter modelInter;
    public Presenter(ViewInter viewInter){
        this.viewInter = viewInter;
        modelInter  = new ModelImp();
    }
    //當view需要數據時,調用該方法就可以獲取數據了
    public void load(final String path){
        //傳遞了匿名內部類,實現獲取數據的方法
        modelInter.getData(new ModelInter.OnCompleteListener() {
            @Override
            public void onComplete(byte[] bytes, String path) {
                //通過gson解析數據
                Bean bean = new Gson().fromJson(new String(bytes), Bean.class);
                if(bean != null)
                    viewInter.getAdapterData(bean.getNewslist());
            }
        },path);
    }
}

adapter:

MyAdapter.java

//自定義適配器類,不詳細解說,需要了解的查看我相關知識點即可
public class MyAdapter extends MyBaseAdapter {

    public MyAdapter(List data, int layout) {
        super(data,layout);
    }

    @Override
    public void setData(ViewHolder holder, Bean.NewslistBean t) {
        //初始化上下文
        if(MyUtils.context == null)
            MyUtils.context = holder.getContext();
         //如果不存在緩存,就加載網絡圖片
        if(!MyUtils.isCacheImage(t.getPicUrl())){
            if(MyUtils.isEnoughForCache())//判斷緩存空間是否足夠
                //加載請求網絡圖片
                holder.setImageURL(R.id.mImg,t.getPicUrl());
            else{
                Toast.makeText(MyUtils.context,"空間不足了,緩存不了圖片了,正在請求刪除圖片...",Toast.LENGTH_SHORT).show();
                //刪除已經緩存的圖片
                MyUtils.deletePic(MyUtils.context.getExternalCacheDir());
            }
        }else {
            //這裡可以通過log日志判斷數據是怎樣獲取的
            Log.i("IT_Real", "setData: 直接拿數據了,沒用通過緩存拿數據");
            //得到緩存路徑下的文件
            String fileName = t.getPicUrl().replaceAll("\\W","");
            fileName = fileName + t.getPicUrl().substring(t.getPicUrl().lastIndexOf("."),t.getPicUrl().length());
            //通過文件位置獲取一個Bitmap
            Bitmap bm = BitmapFactory.decodeFile(holder.getContext().getExternalCacheDir() + "/" + fileName);
            //設置圖片
            holder.setImageBitmap(R.id.mImg,bm);
        }
    }
}

MyBaseAdapter.java

public abstract class MyBaseAdapter extends BaseAdapter {
    protected List data;
    protected int layout;
    public MyBaseAdapter(List data,int layout){
        this.data = data;
        this.layout = layout;
    }
    @Override
    public int getCount() {
        return data == null ? 0 : data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = ViewHolder.getHolder(convertView,parent,position, R.layout.list_item);
        setData(holder,data.get(position));
        return holder.getConvertView();
    }
    public abstract void setData(ViewHolder holder,T t);
}

ViewHolder.java

public class ViewHolder {
    private int position;
    private SparseArray array;
    private View convertView;
    private Context context;

    private ViewHolder(ViewGroup parent, int position, int layout) {
        this.position = position;
        this.context = parent.getContext();
        convertView = LayoutInflater.from(parent.getContext()).inflate(layout, null);
        convertView.setTag(this);
        array = new SparseArray<>();
    }

    public static ViewHolder getHolder(View convertView, ViewGroup parent, int position, int layout) {
        if (convertView == null) {
            return new ViewHolder(parent, position, layout);
        } else {
            ViewHolder holder = (ViewHolder) convertView.getTag();
            holder.position = position;
            return holder;
        }
    }

    public  T getView(int viewId) {
        View view = array.get(viewId);
        if (view == null) {
            view = convertView.findViewById(viewId);
            array.put(viewId, view);
        }
        return (T) view;
    }

    public Context getContext() {
        return context;
    }

    public View getConvertView() {
        return convertView;
    }

    public ViewHolder setText(int viewId, String data) {
        TextView tv = getView(viewId);
        tv.setText(data);
        return this;
    }

    public ViewHolder setImageResource(int viewId, int resId) {
        ImageView img = getView(viewId);
        img.setImageResource(resId);
        return this;
    }
    public ViewHolder setImageURL(int viewId, String url){
        final ImageView image = getView(viewId);
        //防止圖片閃爍,每次加載GridView時,設置為空白界面
        image.setImageBitmap(null);
        new MyAsyncTask(new ImageCallBack(image,url,this.context)).execute(url);
        Log.i("IT_Real", "setImageURL: 網絡請求了");
        return this;
    }
    public ViewHolder setImageBitmap(int viewId, Bitmap bm) {
        ImageView img = getView(viewId);
        img.setImageBitmap(bm);
        return this;
    }
}

utils:

MyAsyncTask.java

//異步網絡請求數據
public class MyAsyncTask extends AsyncTask {
    private String path;
    private ModelInter.OnCompleteListener listener;
    public MyAsyncTask(ModelInter.OnCompleteListener listener){
        this.listener = listener;
    }

    /**
    * 異步請求網絡數據
    */
    @Override
    protected byte[] doInBackground(String... params) {
        path = params[0];
        return getBytes(path);
    }
    /**
    * 獲取到數據,然後接口回調傳遞數據
    */
    @Override
    protected void onPostExecute(byte[] bytes) {
        super.onPostExecute(bytes);
        if(listener != null) {
            listener.onComplete(bytes,path);
        }
    }

    /**
    * 網絡請求的數據
    */
    private byte[] getBytes(String path){
        try {
            URL url = new URL(path);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
                connection.setRequestProperty("apikey","百度APIStore的個人apikey,沒有的可以自己去注冊一個,然後獲取美女圖片json數據");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            connection.connect();
            if(connection.getResponseCode() == HttpURLConnection.HTTP_OK){
                BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int len = -1;
                byte[] bytes = new byte[1024 * 8];
                while(-1 != (len = bis.read(bytes))){
                    baos.write(bytes,0,len);
                }
                byte[] result = baos.toByteArray();
                bis.close();
                baos.close();
                return result;
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

MyUtils.java
//所有自己需要實現的功能工具類

public class MyUtils {
    public static  Context context;
    private static StatFs statFs;

    /**
     *  判斷是否存在緩存圖片
     * @param path 圖片路徑
     * @return
     */
    public static boolean isCacheImage(String path){
        String fileName = path.replaceAll("\\W","");
        fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
        File file = new File(context.getExternalCacheDir(),fileName);
        Log.i("IT_Real", "isCacheImage: " + "file is exists" + file.exists() + "  ->" + isMount());
        return isMount() && file.exists();
    }

    /**
     * 判斷內存卡是否具有讀寫權限
     * @return true有,false沒有
     */
    public static boolean isMount(){
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    /**
     * 判斷緩存空間是否足夠
     * @return
     */
    public static boolean isEnoughForCache(){
        if(statFs == null)
            statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
        int availableBlocks = statFs.getAvailableBlocks();
        int blockSize = statFs.getBlockSize();
        //如果可用空間小於10兆,就提示用戶內存不足
        return availableBlocks  * blockSize>= 10 * 1024 * 1024;
    }

    /**
     * 遞歸刪除緩存的文件
     * @param file 文件路徑
     */
    public static void deletePic(File file){
        if(file == null) return;
        if(!file.isFile()){//如果不是文件就遍歷
            File[] files = file.listFiles();
            if(file != null){
                for (File file1 : files) {
                    deletePic(file1);
                }
            }
        }else {
            file.delete();
            Log.i("IT_Real", "deletePic: 刪除了..");
        }

    }
}

callback:

ImageCallBack.java
//圖片回調接口實現類

public class ImageCallBack implements ModelInter.OnCompleteListener {
    private ImageView image;
    private String path;
    private Context context;

    public ImageCallBack(ImageView image, String path, Context context) {
        this.image = image;
        this.path = path;
        this.context = context;
    }

    /**
     * 得到網絡請求圖片後的圖片數據
     * @param bytes
     * @param path
     */
    @Override
    public void onComplete(byte[] bytes, String path) {
        //判斷每次加載的網絡數據是否和當前圖片地址的加載數據,不相同則不設置此圖片,防止圖片數據錯亂
        if (bytes != null && this.path.equals(path)) {
            Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            image.setImageBitmap(bm);
            //開始保存圖片
            saveCache(context.getExternalCacheDir(), bm, path);

        }
    }

    /**
     * 保存緩存的圖片
     * @param cacheDir 緩存的路徑
     * @param bm 
     * @param path 文件的名字
     */
    private void saveCache(File cacheDir, Bitmap bm, String path) {
        //去掉所有非單詞的字符
        String fileName = path.replaceAll("\\W","");
        fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(cacheDir, fileName)));
            bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

整體的功能已經實現了,這個小案例中,我使用了MVP模式、萬能通用適配器,關於MVP模式和萬能適配器,這裡都不做介紹了,不了解的可用查看我博客中的相關知識點,所有涉及到的主要問題都已經注釋了,如果有不懂的可用留言下方,我會在第一時間回復。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved