Android之okHttpClient+handler+LruCache缓存网络图片学习笔记(通用MVP模式)

Android之okHttpClient+handler+LruCache缓存网络图片(通用MVP模式)

上一次我在学习过程中,写了一篇关于缓存网络图片的学习笔记,在那一篇博客中使用的是AsyncTask异步任务请求的方式缓存的,这一次我从学习中,学会了一种新的缓存方法,就是通过LruCache去缓存数据,LruCache是一种内存缓存机制,采用了最近最少LRU算法,这样的效率比直接去判断从本地出数据效率相对更高一点。下面开始贴代码,具体的功能在代码中注释。

整体框架:

这里写图片描述

涉及的类有点多,但是,为了养成使用MVP模式的习惯,也只能拼了。这里只写几个具体有实际操作的类,因为代码会上传,大家可以下载源码查看。

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<MainActivityPresenter,MainActivity> implements MainActivityInter<List<PicBean.NewslistBean>> {

    //请求图片的json地址,这里是百度为我们提供的一个美女图片接口
    private String path = "http://apis.baidu.com/txapi/mvtp/meinv?num=10";
    //自定义的adpter
    private MyListAdapter adapter;
    //显示的数据集
    private List<PicBean.NewslistBean> 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<PicBean.NewslistBean> 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<PicBean.NewslistBean> newslistBeen) {
        data = newslistBeen;
        showListData(data);
    }
}

presenter层:

MainActivityPresenter.java

public class MainActivityPresenter extends BasePresenter<MainActivity, MainActivityModelImp> {

    @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<PicBean.NewslistBean> {
    public MyListAdapter(List<PicBean.NewslistBean> 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<View> 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 extends View> 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<String,Bitmap> {
    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);
                        }
                    }
                });

            }
        });
    }
}

以上就是一些具体的实现。。。上面涉及的知识点不是很多,只要理解了就会觉得很简单了。如果不懂通用Adapter封装的,请点击这里 , 如果不懂通用MVP模式的,请点击这里,这里只是一些具体的实现,还又很多类没有贴上,因为大部分都是一些重复的类,大家想看的下载源码去看吧。

源码下载

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值