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

Android之okHttpClient+handler+LruCache緩存網絡圖片學習筆記(通用MVP模式)

編輯:關於Android編程

上一次我在學習過程中,寫了一篇關於緩存網絡圖片的學習筆記,在那一篇博客中使用的是AsyncTask異步任務請求的方式緩存的,這一次我從學習中,學會了一種新的緩存方法,就是通過LruCache去緩存數據,LruCache是一種內存緩存機制,采用了最近最少LRU算法,這樣的效率比直接去判斷從本地出數據效率相對更高一點。下面開始貼代碼,具體的功能在代碼中注釋。

整體框架:

這裡寫圖片描述

涉及的類有點多,但是,為了養成使用MVP模式的習慣,也只能拼了。這裡只寫幾個具體有實際操作的類,因為代碼會上傳,大家可以下載源碼查看。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="model層">model層:

MainActivityModelImp.java

public class MainActivityModelImp implements MainActivityModelInter {
    /**
     * 處理具體的數據,
     * @param path json地址
     * @param listener presenter中回調接口
     */
    @Override
    public void getData(String path,OnCompleteNetDataListener listener) {
        //這裡發送一個json數據請求
        MyNetUtils.getNetData(path,listener, GloableInterface.JSON);
    }
}

view層:

MainActivity.java

public class MainActivity extends BaseActivity implements MainActivityInter> {

    //請求圖片的json地址,這裡是百度為我們提供的一個美女圖片接口
    private String path = "http://apis.baidu.com/txapi/mvtp/meinv?num=10";
    //自定義的adpter
    private MyListAdapter adapter;
    //顯示的數據集
    private List data;
    //ListView控件
    private ListView mList;
    //ListView布局的item布局id
    @LayoutRes
    private int layoutId;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        event();
        //請求數據
        basePresenter.load(path);
    }

    private void event() {
    }

    /**
     * 初始化控件或對象
     */
    private void init() {
        layoutId = R.layout.list_item;
        data = new ArrayList<>();
        mList = (ListView)findViewById(R.id.mList);
        //文件管理的布局
        MyFileUtils.context = MainActivity.this;
    }

    /**
     * 向adapter提供數據,設置ListView的數據
     * @param data
     */
    private void showListData(List data){
        if(adapter == null){
            adapter = new MyListAdapter(data,layoutId);
            mList.setAdapter(adapter);
        }else{
            data.addAll(data);
            adapter.notifyDataSetChanged();
        }
    }

    /**
     * 關聯MainActivityPresenter
     * @return
     */
    @Override
    public MainActivityPresenter getPresenter() {
        return new MainActivityPresenter();
    }

    /**
     * presenter返回的數據
     * @param newslistBeen 數據集
     */
    @Override
    public void showData(List newslistBeen) {
        data = newslistBeen;
        showListData(data);
    }
}

presenter層:

MainActivityPresenter.java

public class MainActivityPresenter extends BasePresenter {

    @Override
    public MainActivityModelImp createModel() {
        return new MainActivityModelImp();
    }

    /**
     * 處理view的請求,獲取model的數據返回給view
     * @param path
     */
    public void load(String path) {
        model.getData(path, new MainActivityModelInter.OnCompleteNetDataListener() {
            @Override
            public void onCompleteNetData(byte[] bytes, String path) {
                if (bytes != null) {
                    PicBean picBean = new Gson().fromJson(new String(bytes), PicBean.class);
                    getView().showData(picBean.getNewslist());
                }else{
                    Log.i("IT_Real", "onCompleteNetData: 數據沒有訪問到");
                }
            }
        });
    }
}

callback包:

OnLoadImage.java

public class OnLoadImage implements MainActivityModelInter.OnCompleteNetDataListener{
    private ImageView imageView;
    public OnLoadImage(ImageView imageView){
        this.imageView = imageView;
    }
    @Override
    public void onCompleteNetData(byte[] bytes, String path) {
        //避免圖片對應位置請求錯誤。出現一閃一閃的圖片或者,連續不間斷顯示圖片的問題
        if(bytes != null && imageView.getTag().equals(path)){
            Bitmap bm = BitmapFactory.decodeByteArray(bytes,0,bytes.length);
            //TODO 緩存圖片、
            //添加到內存緩存中
            MyLruCache.getInstance().put(path.replaceAll("\\W",""),bm);
            //緩存到本地
            MyFileUtils.saveCache(imageView.getContext().getExternalCacheDir(),bm,path);
            //設置圖片數據
            imageView.setImageBitmap(bm);
        }
    }


}

adapter包:

MyListAdapter.java

public class MyListAdapter extends MyBaseAdapter {
    public MyListAdapter(List data, @LayoutRes int layoutId) {
        super(data, layoutId);
    }

    /**
     * 具體的設置item數據操作
     * @param holder ViewHolder持有對象
     * @param picBean 數據集
     */
    @Override
    public void setData(ViewHolder holder, PicBean.NewslistBean picBean) {
        //獲取圖片對應的標題
        String title = picBean.getTitle();
        //獲取圖片的地址
        String path = picBean.getPicUrl();
        //去掉非法的字符,然後作為文件名保存到本地
        String fileName = path.replaceAll("\\W","");
        //設置標題
        holder.setText(R.id.mTvTitle, title);
        //通過內存緩存機制去獲取Bitmap數據
        Bitmap bm = MyLruCache.getInstance().get(fileName);
        //自定義一個標記,看看數據是從內存緩存中拿到的還是從本地拿到的
        String tag = "從內存緩存中拿了數據";
        //如果內存緩存中沒有數據
        if (bm == null) {
            //先看看本地內存中有沒有數據,如果有就直接從本地拿數據
             if(MyFileUtils.isCachedToLocal(path)){
                 tag = "從本地拿了數據";
                 fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
                 bm = BitmapFactory.decodeFile(MyFileUtils.context.getExternalCacheDir() + "/" + fileName);
             }else{//否則就去網絡請求數據
                 if(MyFileUtils.isEnoughSpace())//判斷內存空間是否足夠,足夠就去請求數據,然後緩存
                     holder.setImageURL(R.id.mImage, path);
                 else{//空間不足則刪除一些已經緩存的圖片
                     Toast.makeText(MyFileUtils.context,"空間不足了,緩存不了圖片了,正在請求刪除圖片...",Toast.LENGTH_SHORT).show();
                     MyFileUtils.deletePic(holder.getConverView().getContext().getExternalCacheDir());
                 }
             }

        }
        //不為空,就直接去設置圖片
        if(bm != null){
            Toast.makeText(MyFileUtils.context, tag, Toast.LENGTH_SHORT).show();
            holder.setImageBitmap(R.id.mImage,bm);
        }
    }
}

ViewHolder.java

public class ViewHolder {
    private View converView;
    private int position;
    private Context context;
    private SparseArray views = new SparseArray<>();

    public ViewHolder(Context context, int position, @LayoutRes int layoutId) {
        if (context == null)
            this.context = context;
        this.position = position;
        converView = LayoutInflater.from(context).inflate(layoutId, null);
        converView.setTag(this);
    }

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

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

    /**
     * 通過圖片地址請求圖片數據
     * @param viewId 設置圖片控件的id
     * @param path 請求圖片的地址
     * @return 返回ViewHolder,為了鏈式操作
     */
    public ViewHolder setImageURL(int viewId, String path) {
        final ImageView imageView = getView(viewId);
        imageView.setImageBitmap(null);
        //每個id控件對應一張圖片的地址,只有一一對應才會請求網絡,避免了不必要的網絡請求
        imageView.setTag(path);
        //請求網絡數據
        MyNetUtils.getNetData(path, new OnLoadImage(imageView), GloableInterface.IMAGEURL);
        return this;
    }

    /**
     * 通過Bitmap設置圖片
     * @param viewId 圖片控件id
     * @param bm 圖片數據
     * @return
     */
    public ViewHolder setImageBitmap(int viewId, Bitmap bm) {
        ImageView imageView = getView(viewId);
        imageView.setImageBitmap(bm);
        return this;
    }

    public static ViewHolder getViewHolder(View convertView, Context context, int position, @LayoutRes int layoutId) {
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder(context, position, layoutId);
        } else {
            holder = (ViewHolder) convertView.getTag();
            holder.position = position;
        }
        return holder;
    }

    public View getConverView() {
        return converView;
    }
}

utils包:

MyFileUtils.java

public class MyFileUtils {
    //上下文布局
    public static Context context;
    //獲取指定位置存儲狀態的類對象
    private static StatFs statFs;
    //私有化該對象,外部不能創建該實例,大量的操作只需要通過靜態方法獲取即可
    private static MyFileUtils myFileUtils = new MyFileUtils();

    private MyFileUtils(){

    }

    /**
     * 為外部提供一個獲取實例的方法
     * @return
     */
    public static MyFileUtils getInstance(){
        return myFileUtils;
    }

    /**
     * 判斷本地內存中是否有當前文件名的緩存路徑
     * @param path 圖片的地址,將圖片地址去掉非法字符作為文件名
     * @return
     */
    public static boolean isCachedToLocal(String path){
        String fileName = path.replaceAll("\\W","");
        fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
        File file = new File(context.getExternalCacheDir(),fileName);
        return isMount() && file.exists();
    }

    /**
     * 是否有足夠的空間
     * @return
     */
    public static boolean isEnoughSpace(){
        if(statFs == null){
            statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
        }
        int availableBlocks = statFs.getAvailableBlocks();
        int blockSize = statFs.getBlockSize();
        return availableBlocks * blockSize >= 10 * 1024 * 1024;
    }

    /**
     * 判斷是否判斷內存卡是否具有讀寫權限,是不是掛載狀態
     * @return
     */
    public static boolean isMount(){
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }
    /**
     * 遞歸刪除緩存的文件
     * @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();
        }

    }
    /**
     * 保存緩存的圖片
     * @param cacheDir 緩存的路徑
     * @param bm
     * @param path 文件的名字
     */
    public static 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();
        }
    }
}

MyLruCache.java

/**
 * 內存緩存類,這個類使用了最近最少算法來操作緩存
 */
public class MyLruCache extends LruCache {
    private static MyLruCache myLruCache = new MyLruCache((int)Runtime.getRuntime().maxMemory()/1024/8);
    public static MyLruCache getInstance(){
        return myLruCache;
    }

    /**
     * 緩存的大小,默認給他分配最大空間的1/8即可
     * @param maxSize
     */
    private MyLruCache(int maxSize) {
        super(maxSize);
    }


    /**
     * 返回每個緩存對象
     * @param key 存放的key
     * @param value 對應的圖片數據
     * @return
     */
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;//返回大小  KB單位
    }
}

MyNetUtils.java

/**
 * 網絡請求工具
 */
public class MyNetUtils {
    //創建okHttpClient對象
    private static OkHttpClient client = new OkHttpClient();
    //創建一個運行在主線程的handler
    private static Handler handler = new Handler(Looper.getMainLooper());
    private MyNetUtils(){

    }

    /**
     * 獲取網絡數據
     * @param path
     * @param listener
     * @param flag
     */
    public static void getNetData(final String path, final MainActivityModelInter.OnCompleteNetDataListener listener, String flag){
        //創建一個builder實例
        Request.Builder builder = new Request.Builder();
        Request request = null;
        if("json".equals(flag)){
            //設置對應的請求地址,header
            request = builder.url(path).get().addHeader("apikey","自己申請的百度apiStore中的APIKEY").build();
        }else if("imageURL".equals(flag)){
            request = builder.url(path).get().build();
        }
        //開始請求數據
        client.newCall(request).enqueue(new Callback() {

            /**
             * 當服務器中斷,或者出現不可避免的故障,不能正常訪問服務器,執行該方法
             *
             * @param call
             * @param e
             */
            @Override
            public void onFailure(Call call, IOException e) {

            }

            /**
             * 請求成功,或者404等錯誤都會執行這個方法
             * @param call
             * @param response
             * @throws IOException
             */
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //獲取到請求的字節數組,這個請求是在異步任務中請求的
                final byte[] bytes = response.body().bytes();
                //通過handler將數據回調到主線程
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if(listener != null){
                            listener.onCompleteNetData(bytes,path);
                        }
                    }
                });

            }
        });
    }
}

以上就是一些具體的實現。。。上面涉及的知識點不是很多,這裡只是一些具體的實現,還又很多類沒有貼上,因為大部分都是一些重復的類,大家想看的下載源碼去看吧。

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