问题
I read the documentation about making an network request activity independent by making a singleton class and passing the application context to it. I implemented it similarly, however I still find that on rotation the app waits for the call again to complete before displaying any data. So what am I doing wrong and how to set it up properly so that the call lasts the lifetime of the application so that it doesn't call every time on orientation change as per the documentation. I know it can be done using loaders or retrofit or okhttp but I wanna know how to achieve it using volley
MainActivity.java
package com.example.imnobody.photosearch;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
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.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ImageGridAdapter imageGridAdapter;
private List<String> imageList;
public static final String URL = "API_HERE";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageList = new ArrayList<>();
//imageList = QueryUtils.extractImages(SAMPLE_JSON_RESPONSE);
GridView gridView = (GridView) findViewById(R.id.gridview);
final TextView emptyTextView = (TextView)findViewById(android.R.id.empty);
gridView.setEmptyView(emptyTextView);
imageGridAdapter = new ImageGridAdapter(MainActivity.this,imageList);
gridView.setAdapter(imageGridAdapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Intent intent = new Intent(MainActivity.this,ImageActivity.class);
intent.putExtra("imageuri",imageList.get(position));
startActivity(intent);
}
});
StringRequest stringRequest = new StringRequest(Request.Method.GET, URL,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
imageList = QueryUtils.extractImages(response); //extract needed things from json
imageGridAdapter.clear();
imageGridAdapter.addAll(imageList);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
emptyTextView.setText("Unknown error occured");
}
});
VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue(stringRequest);
}
}
VolleySingleton.java
package com.example.imnobody.photosearch;
import android.content.Context;
import android.graphics.Bitmap;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import android.support.v4.util.LruCache;
/**
* Created by imnobody on 7/8/17.
*/
public class VolleySingleton {
private static VolleySingleton mInstance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;
private VolleySingleton(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mImageLoader = new ImageLoader(mRequestQueue,
new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap>
cache = new LruCache<String, Bitmap>(20);
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
}
public static synchronized VolleySingleton getInstance(Context context) {
if (mInstance == null) {
mInstance = new VolleySingleton(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
}
回答1:
Well, a couple of points:
You're making a request every time your activity is recreated, in onCreate
. In theory, this isn't necessarily bad if you really need to refresh your data whenever the activity is opened, since it seems that Volley automatically sets a DiskBasedCache
for itself when creating a RequestQueue
with newRequestQueue
(see here).
This means that even though you're making a new request after each orientation change, Volley would fetch you a cached response, instead of hitting the network. By enabling verbose logging you should see when Volley uses the network or the cache to serve a request.
To enable verbose logging, see here: https://stackoverflow.com/a/23654407/2220337
However, the default cache is still just a Disk
cache, which is slower than an in-memory cache. If you need your cache to be faster, you can implement your own in-memory cache by implementing the Cache interface, and then by creating your RequestQueue
as described here, and providing your own custom, in-memory cache in the constructor.
An even better way would be to not make a request at all after an orientation change, and instead rely on onSaveInstanceState
/onRestoreInstanceState
to persist and then restore your data. This way, if a request was already completed, you won't fire a new one when the activity gets recreated.
Instead, you just display the data you saved in onSaveInstanceState
.
public class MainActivity extends AppCompatActivity {
public static final String URL = "API_HERE";
private static final String SAVED_RESPONSE = "SAVED_RESPONSE";
private ImageGridAdapter imageGridAdapter;
private List<String> imageList;
private GridView gridView;
private String savedResponse;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageList = new ArrayList<>();
gridView = (GridView) findViewById(R.id.gridview);
final TextView emptyTextView = (TextView) findViewById(android.R.id.empty);
gridView.setEmptyView(emptyTextView);
imageGridAdapter = new ImageGridAdapter(MainActivity.this, imageList);
gridView.setAdapter(imageGridAdapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Intent intent = new Intent(MainActivity.this, ImageActivity.class);
intent.putExtra("imageuri", imageList.get(position));
startActivity(intent);
}
});
if (savedInstanceState == null) {
//activity was created for the first time, fetch images
getImages();
} else {
//everything in this else branch can be moved in onRestoreInstanceState, this is just a matter of preference
savedResponse = savedInstanceState.getString(SAVED_RESPONSE);
if (savedResponse != null) {
refreshImages(savedResponse);
} else {
//an error occurred when the request was fired previously
((TextView) gridView.getEmptyView()).setText("Unknown error occured");
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SAVED_RESPONSE, savedResponse);
}
private void getImages() {
StringRequest stringRequest = new StringRequest(Request.Method.GET, URL,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
savedResponse = response;
refreshImages(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
savedResponse = null;
((TextView) gridView.getEmptyView()).setText("Unknown error occured");
}
});
VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue(stringRequest);
}
private void refreshImages(String response) {
imageList = QueryUtils.extractImages(response); //extract needed things from json
imageGridAdapter.clear();
imageGridAdapter.addAll(imageList);
}
Please also be mindful regarding the following points:
If you start a request and an orientation change occurs before it completes, you will have memory leaks, and your Activity won't be garbage collected. This is because your
stringRequest
is an anonymous inner class instance that will reference MainActivity implicitly.To circumvent this, I had good results in the past having my Volley requests & responses being managed by an Android service, with the responses being forwarded to the UI through sticky broadcasts. An event bus also works for this purpose.
The broadcasts needed to be sticky in order to not lose a response if it completed while the activity was being recreated, since while being recreated it wasn't registered as a broadcast receiver. By sending sticky broadcasts though, they would persist and allow me to read the data after Android has finished recreating my Activity.
The second approach I mentioned should be fine if your response string is a not-very-large JSON which points to some online images that will later be downloaded. However, if it contains BASE64 enconded images instead, then maybe the default DiskBasedCache from Volley is more appropriate for caching its data.
Hope this helps!
来源:https://stackoverflow.com/questions/45536102/how-to-make-a-volley-request-activity-independent-in-android