安卓网络编程——Volley(网络通信框架)

Volley

引子

Σ(っ °Д °;)っ重要的话终究还是讲了三遍...Android主要有三种形式实现网络编程

传送门?

HttpURLConnection:https://blog.csdn.net/nishigesb123/article/details/89328097

Apache HTTP Client:https://blog.csdn.net/nishigesb123/article/details/89352227

前文已经提到了两种,当然今天并不是要讲最后的那种,而是介绍一个优秀的网络通信框架——Volley,学习了HttpURLConnection和Apache HTTP Client之后,虽然已经能够实现大部分网络编程的需求,但是显然它们还是有些复杂,而它们的使用率也是居高不下,重复使用比比皆是。为了解决这个矛盾,就有了Volley(当然不只是Volley还有其他的)试图去简化网络通信操作。

概述

  • Volley是在2013年Google I/O大会上推出来一个网络通信框架
  • Volley除了上面提到的“简单易用”之外,在性能方面也比较出色。
  • 它的设计目标:去处理数据量不大,但通信频繁的网络操作。
  • 它的缺点:对于大数据量的网络操作,比如下载文件,Volley的表现就会非常糟糕。

功能

  • JSON,图像等的异步下载
  • 网络请求的排序(scheduling)
  • 网络请求的优先级处理
  • 缓存
  • 多级别取消请求
  • 和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)

下载

项目地址:https://github.com/google/volley

git克隆:

git clone https://android.googlesource.com/platform/frameworks/volley

mvnrepository上也可以找到,算曲线救国嘛?

https://mvnrepository.com/artifact/com.mcxiaoke.volley/library


然后Android Studio并不需要特地去搞到Jar...

之前文章提到了通过Android Studio直接添加Gson依赖的步骤,步骤就不重复了,可以看到volley也是可以搜的到的


StringRequest

RequestQueue

RequestQueue是一个请求队列对象

创建RequestQueue对象:

RequestQueue mQueue = Volley.newRequestQueue(context);
  • 它可以缓存所有的HTTP请求, 然后按照一定的算法并发地发出这些请求。
  • RequestQueue内部的设计就是非常合适高并发的
  • 我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的
  • 基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。 

出处:https://blog.csdn.net/guolin_blog/article/details/17482095(包括?许多内容的出处)


要发出一条HTTP请求,需要创建一个StringRequest对象:

发送一个字符串的请求

StringRequest stringRequest = new StringRequest('http://ww.baidu.com,new Response.Listener<String>() { 
    public void onResponse(String response) { Log.d("TAG", responsel);}},
    new Response.Errorlistener() {
        public void onErrorResponse(VolleyError error) {
            Log.e("TAG", error.getMessage(), error); 
        }
    };

 发出一条POST请求

StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorlistener){
    protected Map getParams() throws AuthFailureError { 
        Map<String,String> map = new HashMap<String,String>();
        map.put("params1", "value1");
        map.put("params2", "value2");
        return map;
    }
};  

StringRequest的构造函数需要传入三个参数:

  1. 目标服务器的URL地址
  2. 服务器响应成功的回调
  3. 服务器响应失败的回调

将StringRequest对象添加到RequestQueue

mQueue.add(stringRequest);

涉及网络,所以也需要必要的网络权限。

<uses-permission android:name="android.permission.INTERNET"/>

GET方式案例

需要一个按钮,配置对应点击事件

package com.example.a4_16volley;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

public class MainActivity extends AppCompatActivity {
    RequestQueue queue=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建一个请求队列
        queue = Volley.newRequestQueue(this);
    }

    //发送一个字符串的请求
    public void StringRequest(View view){
        String url="https://blog.csdn.net";
        //创建一个字符串请求 参数(请求方式,URL,响应的回调接口,错误的回调接口)
        StringRequest request=new StringRequest(Request.Method.GET,
                url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }
}

效果如?

输出信息:

POST方式案例 

包括部分上面案例的代码,同样需要一个按钮。

package com.example.a4_16volley;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {
    RequestQueue queue=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建一个请求队列
        queue = Volley.newRequestQueue(this);
    }

    //发送一个字符串的请求
    public void StringRequest(View view){
        String url="https://blog.csdn.net";
        //创建一个字符串请求 参数(请求方式,URL,响应的回调接口,错误的回调接口)
        StringRequest request=new StringRequest(Request.Method.GET,
                url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }
    //发送一个带参数的POST请求
    public void sendParansPostString(View view){
        //需要准备一个服务器...
        String url = "http://10.0.2.2:8080/contact/android";
        StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        }){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String,String> params=new HashMap<>();
                params.put("username","admin");
                params.put("password","admin");
                return params;
            }
        };
        queue.add(request);
    }
}

效果如?

客户端:

服务器端:


JsonRequest

如果接到的数据的Json,可以用JsonRequest去创建一个请求。

  • 类似于StringRequest,JsonRequest也是继承自Request类的。
  • JsonRequest是一个抽象类,无法直接创建它的实例
  • 但JsonRequest有两个直接的子类JsonObjectRequest和JsonArrayRequest。

这部分没有合适的API,就不演示了,代码中示范的API数据实际上是string类型的...

参考代码:

    //发送一个JsonRequest
    public void sendJsonRequest(View view) {
        //毒霸天气的API,最后的数字0表示本地
        String url = "http://weather.123.duba.net/static/weather_info/0.html";

        /***
         * //请求参数对象封装为为JSONObject 放在JsonObjectRequest的jsonRequest对象,就是下面null的位置
         * //        JSONObject jsonObject=new JSONObject();
         * //        try {
         * //            jsonObject.put("param","value");
         * //        } catch (JSONException e) {
         * //            e.printStackTrace();
         * //        }
         */

        //参数(请求方式,URL,请求参数,响应的回调接口,错误的回调接口)
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }

ImageRequest

ImageRequest是一个图片请求对象,它继承自Request<Bitmap>,所以请求得到的结果是一个bitmap。

步骤类似的

  1. 创建RequestQueue对象。

  2. 创建ImageRequest对象。

  3. 将ImageRequest对象添加到RequestQueue里面。

ImageRequest的构造函数接收六个参数:

第一个参数就是图片的URL地址,这个没什么需要解释的。

第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。

第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。

第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。

第六个参数是图片请求失败的回调。

出处:https://blog.csdn.net/UUUUUltraman/article/details/89329891#t3

案例

需要注意的是,为了显示测试效果,ImageRequest搭配了一个ImageView,请读者自行添加并注册组件。

//发送一个ImageRequest
    public void sendImageRequest(View view){
        String url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
        ImageRequest request=new ImageRequest(url, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                imageView.setImageBitmap(response);
            }
        }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }

效果如?  (用黑洞会被告侵权吗 {{{(>_<)}}} 233

ImageLoader

除了ImageRequest,Volley还内置了一种加载网络图片的实现办法——ImageLoader

  • ImageLoader的内部也是使用ImageRequest来实现的,但ImageLoader不再是继承自Request
  • ImageLoader要比ImageRequest更加高效,不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。

使用步骤稍有不同:

基本案例 

  • 首先,RequestQueue当然还是要的(但是实际上是作为ImageLoader的参数)
  • 然后创建一个ImageLoader对象这个差不多,也不难理解
  • 有差别的地方在于需要一个ImageListener对象
  • 最后也不需要再queue.add,而是通过ImageLoader的get()方法加载图片。
    //使用ImageLoader加载网络图片
    public void ImageLoader(View view){
        String url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
        //新建一个ImageLoader对象
        ImageLoader imageLoader=new ImageLoader(queue, new ImageLoader.ImageCache() {
            //对图片进行缓存
            @Override
            public Bitmap getBitmap(String url) {
                return null;
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {
            }
        });

        //获取一个ImageListener对象
        //getImageListener()方法接收三个参数
        // 第一个参数指定用于显示图片的ImageView控件
        // 第二个参数指定加载图片的过程中显示的图片
        // 第三个参数指定加载图片失败的情况下显示的图片
        ImageLoader.ImageListener listener=ImageLoader.getImageListener(imageView,R.mipmap.ic_launcher,R.mipmap.ic_launcher);
        //加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
        imageLoader.get(url,listener,500,500);
    }

效果如下? (ImageView共享的上一个案例的,URL也没有改)

点击后?

实现一个带缓存的ImageCache

前文提到ImageLoader要比ImageRequest更加高效,上面的案例无法体现ImageLoader的优越性。

对代码进行一定的修改,我们自己写一个BitmapCache

    //使用ImageLoader加载网络图片
    public void ImageLoader(View view) {
        String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
        //新建一个ImageLoader对象
        ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
        //获取一个ImageListener对象
        //getImageListener()方法接收三个参数
        // 第一个参数指定用于显示图片的ImageView控件
        // 第二个参数指定加载图片的过程中显示的图片
        // 第三个参数指定加载图片失败的情况下显示的图片
        ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
        //加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
        imageLoader.get(url, listener, 500, 500);
    }

    //创建BitmapCache实现ImageCache接口
    private class BitmapCache implements ImageLoader.ImageCache {
        private LruCache<String, Bitmap> cache;
        //最大缓存大小
        private int maxCache = 10 * 1024;

        public BitmapCache() {
            cache = new LruCache(maxCache);
        }

        @Override
        public Bitmap getBitmap(String url) {
            return cache.get(url);
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            cache.put(url, bitmap);
        }
    }

效果的话,和?的案例是一样的,就不再演示截图了。

主要是性能上的优化(第一次网络加载,第二次及以后读取缓存,而上一个案例,每点击一次都会进行加载)


NetworkImageView

万万没想到,Volley加载图片还有一种方式(Volley是有多喜欢加载图片...)——NetworkImageView

  • NetworkImageView是一个自定义控件
  • NetworkImageView继承自ImageView的
  • NetworkImageView具备ImageView控件的所有功能
  • NetworkImageView在ImageView的基础之上加入了加载网络图片的功能
  • NetworkImageView能够在内部自动的完成图片压缩工作,因此在内存占用方面是最优的。

实现步骤

既然是类似ImageView的组件,一开始的配置自然和ImageView差不多

    <com.android.volley.toolbox.NetworkImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/networkImageView"
        app:layout_constraintTop_toBottomOf="@id/ImageLoader"
        />

布局文件配置完毕后需要注册组件,?下面两句放哪里就不用多说了吧

private NetworkImageView networkImageView;


networkImageView = findViewById(R.id.networkImageView);

一般来讲然后直接在下面接上相关设置语句就算是配置完成了,优雅一点可以封装成一个方法

networkImageView.setDefaultImageResId(默认的);
networkImageView.setErrorImageResId(失败的);
networkImageView.setImageUrl("yoururl",imageLoader);

然后参数需要一个,imageLoader的话可以参考上面的案例的相关内容

本文完整代码

放一个这篇文章的完整代码,最下面就是networkImageView的代码

package com.example.a4_16volley;

import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {
    RequestQueue queue = null;
    private ImageView imageView;
    private NetworkImageView networkImageView;
    private String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建一个请求队列
        queue = Volley.newRequestQueue(this);
        imageView = findViewById(R.id.imageView);
        networkImageView = findViewById(R.id.networkImageView);
        networkImageView();
    }

    //发送一个字符串的请求
    public void StringRequest(View view) {
        String url = "https://blog.csdn.net";
        //创建一个字符串请求 参数(请求方式,URL,响应的回调接口,错误的回调接口)
        StringRequest request = new StringRequest(Request.Method.GET,
                url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }

    //发送一个带参数的POST请求
    public void sendParansPostString(View view) {
        //需要准备一个服务器...
        String url = "http://10.0.2.2:8080/contact/android";
        StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        }) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> params = new HashMap<>();
                params.put("username", "admin");
                params.put("password", "admin");
                return params;
            }
        };
        queue.add(request);
    }

    //发送一个JsonRequest
    public void sendJsonRequest(View view) {
        //毒霸天气的API,最后的数字0表示本地
        String url = "http://weather.123.duba.net/static/weather_info/0.html";

        /***
         * //请求参数对象封装为为JSONObject 放在JsonObjectRequest的jsonRequest对象,就是下面null的位置
         * //        JSONObject jsonObject=new JSONObject();
         * //        try {
         * //            jsonObject.put("param","value");
         * //        } catch (JSONException e) {
         * //            e.printStackTrace();
         * //        }
         */

        //参数(请求方式,URL,请求参数,响应的回调接口,错误的回调接口)
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                System.out.println(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }

    //发送一个ImageRequest
    public void sendImageRequest(View view) {
        ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                imageView.setImageBitmap(response);
            }
        }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error);
            }
        });
        queue.add(request);
    }


/*    //使用ImageLoader加载网络图片
    public void ImageLoader(View view) {
        String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
        //新建一个ImageLoader对象
        ImageLoader imageLoader = new ImageLoader(queue, new ImageLoader.ImageCache() {
            //对图片进行缓存
            @Override
            public Bitmap getBitmap(String url) {
                return null;
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {
            }
        });

        //获取一个ImageListener对象
        //getImageListener()方法接收三个参数
        // 第一个参数指定用于显示图片的ImageView控件
        // 第二个参数指定加载图片的过程中显示的图片
        // 第三个参数指定加载图片失败的情况下显示的图片
        ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
        //加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
        imageLoader.get(url, listener, 500, 500);
    }*/


    //使用ImageLoader加载网络图片
    public void ImageLoader(View view) {
        //新建一个ImageLoader对象
        ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
        //获取一个ImageListener对象
        //getImageListener()方法接收三个参数
        // 第一个参数指定用于显示图片的ImageView控件
        // 第二个参数指定加载图片的过程中显示的图片
        // 第三个参数指定加载图片失败的情况下显示的图片
        ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
        //加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
        imageLoader.get(url, listener, 500, 500);
    }

    //创建BitmapCache实现ImageCache接口
    private class BitmapCache implements ImageLoader.ImageCache {
        private LruCache<String, Bitmap> cache;
        //最大缓存大小
        private int maxCache = 10 * 1024;

        public BitmapCache() {
            cache = new LruCache(maxCache);
        }

        @Override
        public Bitmap getBitmap(String url) {
            return cache.get(url);
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            cache.put(url, bitmap);
        }
    }


    //networkImageView
    private void networkImageView() {
        networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
        networkImageView.setErrorImageResId(R.mipmap.ic_launcher);
        networkImageView.setImageUrl(url,new ImageLoader(queue,new BitmapCache()));
    }
}

效果的话如?,因为启动Activity就调用了,所以会直接在对应位置显示图片

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云无心鸟知还

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值