Android Volley教程
在此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); }