如何在Android中使Volley Request活动独立? [英] How to make a Volley Request activity independent in android?
问题描述
我阅读了文档,该文档通过以下方式使网络请求活动独立单例类,并将应用程序上下文传递给它.我以类似的方式实现了它,但是我仍然发现在旋转时,应用程序会再次等待调用完成,然后再显示任何数据.因此,我在做什么错了,以及如何正确设置它,以便该调用可以持续应用程序的生命周期,从而使每次文档说明中的方向更改时都不会调用它.我知道可以使用装载机,改造或okhttp来完成,但是我想知道如何使用凌空抽空来实现它
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;
}
}
好几点:
每次重新创建活动时,您都会在onCreate
中发出请求.从理论上讲,如果您在活动打开时确实真的需要刷新数据,这并不一定不好,因为Volley似乎在创建带有RequestQueue
的内容时自动为自己设置了DiskBasedCache
. newRequestQueue
(请参见 https://stackoverflow.com/a/23654407/2220337 >
但是,默认缓存仍然只是Disk
缓存,它比内存中的缓存慢.如果需要更快的缓存,可以通过实现此处,并在构造函数中提供您自己的自定义内存中缓存.
一种更好的方法是在更改方向后完全不发出请求,而是依靠onSaveInstanceState
/onRestoreInstanceState
保留并恢复您的数据.这样,如果请求已完成,则在重新创建活动时您不会触发新请求.
相反,您只显示保存在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);
}
请注意以下几点:
-
如果启动请求,并且在完成请求之前发生方向更改,则将发生内存泄漏,并且不会对活动"进行垃圾收集.这是因为您的
stringRequest
是一个匿名内部类实例,该实例将隐式引用MainActivity.为避免这种情况,我在过去的Volley请求中取得了不错的成绩,响应由Android服务管理,并且响应通过粘性广播转发到UI.事件总线也可以用于此目的.
广播必须保持粘性,以便在重新创建活动时完成响应后不会丢失响应,因为在重新创建活动时未将其注册为广播接收器.通过发送粘性广播,它们可以保留并允许我在Android完成重新创建我的Activity后读取数据.
-
如果您的响应字符串不是很大的JSON(指向一些稍后将下载的在线图像),则我提到的第二种方法应该很好.但是,如果它包含BASE64编码映像,则Volley的默认DiskBasedCache可能更适合于缓存其数据.
希望这会有所帮助!
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;
}
}
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!
这篇关于如何在Android中使Volley Request活动独立?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!