Android Volley教程

时间:2020-02-23 14:29:23  来源:igfitidea点击:

在此android volley教程中,我们将在应用程序中实现Volley库。
如果您不了解Volley的功能,建议您先阅读本文,然后再继续。

Android Volley

Volley中的网络请求被添加到" RequestQueue"中。
并非每次都创建一个RequestQueue的新实例,而是通过在整个应用程序中创建一个RequestQueue的单个实例来遵循单例模式。

在下面的应用程序中,我们将实现GET和POST StringRequest和JsonObjectRequests。
我们将使用Gson库解析响应并将结果显示在RecyclerView中。

此外,我们将实现CustomRequest并从ImageView中的URL加载图像。

Android Volley Gradle依赖关系

在开始学习本教程的编码方面之前,请在您的build.gradle文件中添加以下依赖项。

compile 'com.android.support:cardview-v7:26.1.0'
  compile 'com.android.support:recyclerview-v7:26.1.0'
  compile 'com.android.support:design:26.1.0'
  compile 'com.google.code.gson:gson:2.8.0'
  compile 'com.android.volley:volley:1.0.0'

Android Volley示例代码

布局– activity_main.xml下面给出了保存着" MainActivity.java"用户界面的" activity_main.xml"布局的代码。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.theitroad.volley.RecyclerViewActivity">

  <Button
      android:id="@+id/btnImageLoader"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignLeft="@+id/networkImageView"
      android:layout_alignStart="@+id/networkImageView"
      android:layout_below="@+id/networkImageView"
      android:layout_marginTop="16dp"
      android:text="LOAD IMAGE" 

  <Button
      android:id="@+id/btnImageRequest"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignBaseline="@+id/btnImageLoader"
      android:layout_alignBottom="@+id/btnImageLoader"
      android:layout_marginLeft="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="16dp"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true"
      android:layout_toEndOf="@+id/imageView"
      android:layout_toRightOf="@+id/imageView"
      android:text="IMAGE REQUEST" 

  <com.android.volley.toolbox.NetworkImageView
      android:id="@+id/networkImageView"
      android:layout_width="100dp"
      android:layout_height="100dp"
      android:layout_alignParentEnd="true"
      android:layout_alignParentRight="true"
      android:layout_alignParentTop="true"
      android:layout_below="@+id/imageView"
      android:layout_marginEnd="16dp"
      android:layout_marginRight="16dp"
      android:layout_marginTop="8dp" 

  <ImageView
      android:id="@+id/imageView"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true"
      android:layout_alignParentTop="true"
      android:layout_marginTop="8dp" 

  <Button
      android:id="@+id/btnGET"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignEnd="@+id/btnPOST"
      android:layout_alignLeft="@+id/btnPOST"
      android:layout_alignRight="@+id/btnPOST"
      android:layout_alignStart="@+id/btnPOST"
      android:layout_centerVertical="true"
      android:text="GET String JSON" 

  <Button
      android:id="@+id/btnPOST"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@+id/btnGET"
      android:layout_centerHorizontal="true"
      android:layout_marginTop="8dp"
      android:text="POST String JSON" 

  <Button
      android:id="@+id/btnCustomRequest"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignEnd="@+id/btnPOST"
      android:layout_alignLeft="@+id/btnPOST"
      android:layout_alignRight="@+id/btnPOST"
      android:layout_alignStart="@+id/btnPOST"
      android:layout_below="@+id/btnPOST"
      android:layout_marginTop="8dp"
      android:text="CUSTOM REQUEST" 

</RelativeLayout>

SingletonRequestQueue.java

package com.theitroad.volley;

import android.content.Context;

import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

public class SingletonRequestQueue {

  private static SingletonRequestQueue mInstance;
  private Context mContext;
  private RequestQueue mRequestQueue;

  private SingletonRequestQueue(Context context) {
      mContext = context;
      mRequestQueue = getRequestQueue();
  }

  public static synchronized SingletonRequestQueue getInstance(Context context) {
      if (mInstance == null) {
          mInstance = new SingletonRequestQueue(context);
      }
      return mInstance;
  }

  public RequestQueue getRequestQueue() {
      if (mRequestQueue == null) {
          mRequestQueue = Volley.newRequestQueue(mContext);
      }
      return mRequestQueue;
  }
}

getInstance()和getRequestQueue()方法分别分别创建SingletonRequestQueue和RequestQueue的实例,并在各处重复使用。

将布局链接到MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

  Button btnGET, btnPOST, btnImageLoader, btnCustomRequest, btnImageRequest;
  NetworkImageView networkImageView;
  ImageView imageView;
  ArrayList<UserList.UserDataList> mUserDataList = new ArrayList<>();
  String BASE_URL = "https://reqres.in";
  String IMAGE_URL = "https://www.android.com/static/2015/img/share/oreo-lg.jpg";
  int numberOfRequestsCompleted;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      btnGET = findViewById(R.id.btnGET);
      btnPOST = findViewById(R.id.btnPOST);
      btnImageLoader = findViewById(R.id.btnImageLoader);
      btnImageRequest = findViewById(R.id.btnImageRequest);
      btnCustomRequest = findViewById(R.id.btnCustomRequest);
      networkImageView = findViewById(R.id.networkImageView);
      imageView = findViewById(R.id.imageView);
      btnGET.setOnClickListener(this);
      btnPOST.setOnClickListener(this);
      btnImageLoader.setOnClickListener(this);
      btnCustomRequest.setOnClickListener(this);
      btnImageRequest.setOnClickListener(this);

      networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
      networkImageView.setErrorImageResId(R.drawable.ic_error);

  }

  @Override
  public void onClick(View v) {
      switch (v.getId()) {
          case R.id.btnGET:
              GETStringAndJSONRequest("2", "4");
              break;
          case R.id.btnPOST:
              POSTStringAndJSONRequest();
          case R.id.btnImageLoader:
              imageLoader();
          case R.id.btnImageRequest:
              imageRequest();
          case R.id.btnCustomRequest:
              customRequest();
              break;
      }
  }
}

有5个按钮用于GET,POST,ImageLoader,ImageRequest,CustomRequest处理。
每个功能都分别在onClickListener中触发。

我们将分别研究每种方法

  • GETStringAndJSONRequest()
  • POSTStringAndJSONRequest()
  • imageLoader()
  • imageRequest()
  • customRequest()

我们将使用www.reqres.in上的REST API。

" UserList.java"类是用于使用Gson序列化响应的POJO类。

UserList.java

package com.theitroad.volley;

import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.List;

public class UserList implements Serializable {

  @SerializedName("page")
  public Integer page;
  @SerializedName("per_page")
  public Integer perPage;
  @SerializedName("total")
  public Integer total;
  @SerializedName("total_pages")
  public Integer totalPages;
  @SerializedName("data")
  public List<UserDataList> userDataList;

  public class UserDataList implements Serializable {

      @SerializedName("id")
      public Integer id;
      @SerializedName("first_name")
      public String first_name;
      @SerializedName("last_name")
      public String last_name;
      @SerializedName("avatar")
      public String avatar;

  }
}

上面的类实现了" Serializable",因为它的一个实例将作为Intent中的Bundle传递

让我们看看封装了Volley Request的MainActivity.java中的Button单击触发的方法。

GETStringAndJSONRequest()

Response.ErrorListener errorListener = new Response.ErrorListener() {
      @Override
      public void onErrorResponse(VolleyError error) {
          if (error instanceof NetworkError) {
              Toast.makeText(getApplicationContext(), "No network available", Toast.LENGTH_LONG).show();
          } else {
              Toast.makeText(getApplicationContext(), error.toString(), Toast.LENGTH_LONG).show();
          }
      }
  };

private void GETStringAndJSONRequest(String page_1, String page_2) {
      mUserDataList.clear();
      numberOfRequestsCompleted = 0;
      VolleyLog.DEBUG = true;
      RequestQueue queue = SingletonRequestQueue.getInstance(getApplicationContext()).getRequestQueue();
      String uri_page_one = String.format(BASE_URL + "/api/users?page=%1$s", page_1);
      final String uri_page_two = String.format(BASE_URL + "/api/users?page=%1$s", page_2);

      StringRequest stringRequest = new StringRequest(Request.Method.GET, uri_page_one, new Response.Listener<String>() {
          @Override
          public void onResponse(String response) {

              VolleyLog.wtf(response, "utf-8");
              GsonBuilder builder = new GsonBuilder();
              Gson mGson = builder.create();
              UserList userList = mGson.fromJson(response, UserList.class);
              mUserDataList.addAll(userList.userDataList);
              ++numberOfRequestsCompleted;

          }
      }, errorListener) {

          @Override
          public Priority getPriority() {
              return Priority.LOW;
          }

      };

      queue.add(stringRequest);

      JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(uri_page_two, null, new Response.Listener<JSONObject>() {
          @Override
          public void onResponse(JSONObject response) {

              VolleyLog.wtf(response.toString(), "utf-8");
              GsonBuilder builder = new GsonBuilder();
              Gson mGson = builder.create();

              UserList userList = mGson.fromJson(response.toString(), UserList.class);
              mUserDataList.addAll(userList.userDataList);
              ++numberOfRequestsCompleted;

          }
      }, errorListener) {

          @Override
          public String getBodyContentType() {
              return "application/json";
          }

          @Override
          public Priority getPriority() {
              return Priority.IMMEDIATE;
          }
      };

      queue.add(jsonObjectRequest);

      queue.addRequestFinishedListener(new RequestQueue.RequestFinishedListener<Object>() {

          @Override
          public void onRequestFinished(Request<Object> request) {
              try {
                  if (request.getCacheEntry() != null) {
                      String cacheValue = new String(request.getCacheEntry().data, "UTF-8");
                     VolleyLog.d(request.getCacheKey() + " " + cacheValue);

                  }
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
              }

              if (numberOfRequestsCompleted == 2) {
                  numberOfRequestsCompleted = 0;
                  startActivity(new Intent(MainActivity.this, RecyclerViewActivity.class).putExtra("users", mUserDataList));
              }
          }
      });

  }

上面代码中要注意的重点:

  • 我们创建了一个ErrorListener实例,该实例将在整个Activity中使用。

  • 我们在RequestQueue中链接了两个请求。

  • 第一个请求是StringRequest。
    在API/api/users中,页面是URL编码参数。
    响应是一个通过Gson序列化的JSONObject。
    我们已将StringRequest的优先级设置为低。
    因此,此请求应最终完成(在服务器快速响应的情况下,优先级将不起作用)。

  • 第二个请求是JsonObjectRequest。
    由于它是GET请求,因此我们将请求正文设置为null(请检查第二个参数)。
    优先级设置为立即。
    因此,JsonObjectRequest应该首先完成。

  • 我们将两个请求返回的列表加入到mUserDataList ArrayList中。

  • addRequestFinishedListener回调侦听器中,我们检查两个请求是否都结束了(通过检查numberOfRequestsCompleted计数器。

  • 此外,在addRequestFinishedListener回调中,我们可以从Cache.Entry中检索响应。

  • 尝试在Requests上将setShouldCache()设置为false,然后在缓存中找到响应。

  • 最后,将从这两个请求中检索到的用户数据列表传递到RecyclerViewActivity.java。
    这是我们在RecyclerView中填充ArrayList的地方。
    让我们看一下RecyclerViewActivity和布局代码。
    Layout– activity_recyclerview.xml
    RecyclerViewActivity.java
    每个RecyclerView行的布局在recyclerview_row.xml中定义。

RecyclerViewAdapter.java
我们已经初始化了一个ImageLoader,它将显示NetworkImageView中每一行的URL中的图像。

LRU缓存用于通过实现ImageCache来缓存图像。
LruCache构造函数中的参数是缓存条目数限制。
POSTStringAndJSONRequest()
此方法将在RequestQueue中链接多个POST请求。

stringRequest –要在StringRequest中发布参数,我们需要覆盖getParams()并将这些参数作为键值对传递。

  • jsonObjectRequest –要在JsonObjectRequest中发布参数,我们将参数传递到JSONObject内,然后在构造函数的第二个参数中进行设置。

  • stringRequestPOSTJSON –要在StringRequest中发布JSON请求正文,我们将覆盖方法getBody()

imageLoader()

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.theitroad.volley.RecyclerViewActivity">

  <android.support.v7.widget.RecyclerView
      android:id="@+id/recyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent" 

</android.support.constraint.ConstraintLayout>

getImageListener()处理显示默认图像的功能,直到收到网络响应为止,此时它将切换为实际图像或者错误图像。
在侦听器内部传递的参数是视图的实例,默认图像可绘制,错误图像可绘制。

imageRequest()

package com.theitroad.volley;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;

public class RecyclerViewActivity extends AppCompatActivity {

  RecyclerView recyclerView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_recyclerview);

      recyclerView = findViewById(R.id.recyclerView);
      recyclerView.setHasFixedSize(true);

      LinearLayoutManager layoutManager = new LinearLayoutManager(this);
      recyclerView.setLayoutManager(layoutManager);
      recyclerView.setItemAnimator(new DefaultItemAnimator());
      recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));

      ArrayList userDataLists = (ArrayList) getIntent().getSerializableExtra("users");

      RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, userDataLists);
      recyclerView.setAdapter(adapter);

  }
}

customRequest()

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.CardView
      android:layout_width="match_parent"
      android:layout_height="60dp">

      <RelativeLayout
          android:layout_width="wrap_content"
          android:layout_height="match_parent">

          <com.android.volley.toolbox.NetworkImageView
              android:id="@+id/imgNetwork"
              android:layout_width="48dp"
              android:layout_centerVertical="true"
              android:layout_height="48dp" 

          <TextView
              android:id="@+id/txtLabel"
              android:layout_toRightOf="@+id/imgNetwork"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_gravity="center"
              android:gravity="center" 

      </RelativeLayout>

  </android.support.v7.widget.CardView>

</RelativeLayout>

我们创建了一个名为GsonRequest的CustomRequest,该序列将响应内部序列化并将其转换为POJO类。
另外,CustomRequest也将标头用作构造函数参数。

让我们看看如何编写GsonRequest.java类。

GsonRequest.java

package com.theitroad.volley;

import android.content.Context;
import android.graphics.Bitmap;
import android.support.v7.widget.RecyclerView;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.CustomRecyclerView> {

  private List<UserList.UserDataList> itemList;

  private RequestQueue mRequestQueue;
  private ImageLoader mImageLoader;

  public RecyclerViewAdapter(Context context, List<UserList.UserDataList> itemList) {
      this.itemList = itemList;
      mRequestQueue = SingletonRequestQueue.getInstance(context).getRequestQueue();
      mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() {
          private final LruCache<String, Bitmap> mCache = new LruCache<>(10);

          public void putBitmap(String url, Bitmap bitmap) {
              mCache.put(url, bitmap);
          }

          public Bitmap getBitmap(String url) {
              return mCache.get(url);
          }
      });
  }

  @Override
  public CustomRecyclerView onCreateViewHolder(ViewGroup parent, int viewType) {
      View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_row, null);
      CustomRecyclerView rcv = new CustomRecyclerView(layoutView);
      return rcv;
  }

  @Override
  public void onBindViewHolder(CustomRecyclerView holder, int position) {

      UserList.UserDataList myData = itemList.get(position);
      holder.txtLabel.setText(myData.first_name + " " + myData.last_name);
      holder.avatar.setImageUrl(myData.avatar, mImageLoader);

  }

  @Override
  public int getItemCount() {
      return this.itemList.size();
  }

  public class CustomRecyclerView extends RecyclerView.ViewHolder {
      TextView txtLabel;
      NetworkImageView avatar;

      CustomRecyclerView(View itemView) {
          super(itemView);
          txtLabel = itemView.findViewById(R.id.txtLabel);
          avatar = itemView.findViewById(R.id.imgNetwork);
      }
  }
}

自定义请求需要覆盖" parseNetworkResponse"和" deliverResponse"这两种方法。

parseNetworkResponse解析原始网络响应。
在上面的代码中,Gson库将其序列化为POJO类。
结果可以在deliverResponse方法中找到。

deliverResponse方法中,我们触发了Response.Listener的onResponse方法的回调。

上面的GsonRequest自定义请求并没有简化和泛化用于在POJO类中存储响应的代码。

下面提供了MainActivity.java类的完整源代码。

private void POSTStringAndJSONRequest() {

      RequestQueue queue = SingletonRequestQueue.getInstance(getApplicationContext()).getRequestQueue();

      VolleyLog.DEBUG = true;
      String uri = BASE_URL + "/api/users";

      StringRequest stringRequest = new StringRequest(Request.Method.POST, uri, new Response.Listener() {
          @Override
          public void onResponse(String response) {
              VolleyLog.wtf(response, "utf-8");
              Toast.makeText(getApplicationContext(), response, Toast.LENGTH_LONG).show();

          }
      }, errorListener) {
          @Override
          public Priority getPriority() {
              return Priority.LOW;
          }

          @Override
          public Map getParams() {
              Map params = new HashMap();

              params.put("name", "Anupam");
              params.put("job", "Android Developer");

              return params;
          }

          @Override
          public Map getHeaders() throws AuthFailureError {
              HashMap headers = new HashMap();
              headers.put("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
              return headers;
          }

      };

      JSONObject jsonObject = new JSONObject();
      try {
          jsonObject.put("name", "theitroad.local");
          jsonObject.put("job", "To teach you the best");
      } catch (JSONException e) {
          e.printStackTrace();
      }

      JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(uri, jsonObject, new Response.Listener() {
          @Override
          public void onResponse(JSONObject response) {
              VolleyLog.wtf(response.toString(), "utf-8");
              Toast.makeText(getApplicationContext(), response.toString(), Toast.LENGTH_LONG).show();

          }
      }, errorListener) {

          @Override
          public int getMethod() {
              return Method.POST;
          }

          @Override
          public Priority getPriority() {
              return Priority.NORMAL;
          }
      };

      StringRequest stringRequestPOSTJSON = new StringRequest(Request.Method.POST, uri, new Response.Listener() {
          @Override
          public void onResponse(String response) {
              VolleyLog.wtf(response);
              Toast.makeText(getApplicationContext(), response, Toast.LENGTH_LONG).show();

          }
      }, errorListener) {
          @Override
          public Priority getPriority() {
              return Priority.HIGH;
          }

          @Override
          public Map getHeaders() throws AuthFailureError {
              HashMap headers = new HashMap();
              headers.put("Content-Type", "application/json; charset=utf-8");
              return headers;
          }

          @Override
          public byte[] getBody() throws AuthFailureError {

              JSONObject jsonObject = new JSONObject();
              try {
                  jsonObject.put("name", "Android Tutorials");
                  jsonObject.put("job", "To implement Volley in an Android Application.");
              } catch (JSONException e) {
                  e.printStackTrace();
              }

              String requestBody = jsonObject.toString();

              try {
                  return requestBody == null ? null : requestBody.getBytes("utf-8");
              } catch (UnsupportedEncodingException uee) {
                  VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", requestBody, "utf-8");
                  return null;
              }
          }

      };
      
      queue.add(stringRequest);
      queue.add(jsonObjectRequest);
      queue.add(stringRequestPOSTJSON);

  }