I'm building an Android application which uses Volley to load images from the web.
I'm using an LruCache with Volley to cache bitmaps in memory, and this is fine. In addition, I'd like Volley to leverage the built in support for http disk based caching using HttpResponseCache.
I implemented the example in the given link, but I noticed that nothing is being cached in the HttpResponseCache (I checked by sampling the HitCount field in the cache object).
After sniffing around in the Volley source code, I found that Volley manually removes the caching flag when opening a URLConnection:
private URLConnection openConnection(URL url, Request<?> request) throws IOException {
URLConnection connection = createConnection(url);
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false); // <-- Disables the caching
connection.setDoInput(true);
// use caller-provided custom SslSocketFactory, if any, for HTTPS
if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
}
return connection;
}
You can see this code for yourself here: Volley - HurlStack (line 167)
The moment I comment out this line, the HttpResponseCahce works as expected.
My question is: Why does Volley disable the UseCaches flag in all URLConnections, and what is the risk / side-effects of removing this?
Related
Using Glide v4 and OkHttp3, how can I detect a redirection and load another url when it happens?
My usecase: I use the Glide v4 library with OkHttp3 to download pictures in my app. Sometimes when a picture is not available, a redirection is performed by the server to provide another picture instead of the one I originaly wanted. I can see it in my browser because when I request url A, I finally land on url B with the second picture. When that happens I want to instead load url C that is derived from url A (so not a static url).
At the moment I can detect the redirection using an OkHttp3 Interceptor:
public Response intercept(#NonNull Chain chain) throws IOException {
String requestUrl = chain.request().url().toString();
Response response = chain.proceed(chain.request());
String responseUrl = response.request().url().toString();
boolean redirect = !requestUrl.equals(responseUrl);
if (redirect) {
Timber.d("Detected redirection");
}
return response;
}
but then I don't know how to cleanly load url C. I don't see how I can load another url in the interceptor, and if I throw an IOException to handle the error later in a Glide RequestListener it will just result in a GlideException so I can't determine why it was throw.
OkHttp should be automatically redirecting by default and loading the final content. See OkHttpClient.Builder.followRedirects.
https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#followRedirects-boolean-
My own testing suggests this is working, e.g.
$ oksocial 'https://httpbin.org/redirect-to?url=https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg'
For detecting it, I assume your interceptor is registered via addInterceptor instead of addNetworkInterceptor. Is that correct?
It is also worth ensuring the redirect is via a 30X response and not via a HTML refresh.
The solution I ended up with is pretty ugly: the request interceptor I showed in the question raise an IOException when a redirection is detected. I will then use a GlideListener to detect the error, and load the url C. The ugly part is that in the GlideListener I can't determine the cause of the error, it can be a redirection but it can also be a network error or anything else.
A cleaner version of this solution can probably be achieved with a custom OkHttpUrlLoader but it's too much code for my simple usecase.
This is my solution in Glide v4 / OkHttp3
String redirect = response.header("location");
if(redirect != null && !request.url().toString().equals(redirect)) {
// if redirected your code here
}
This may be a bug in Picasso, but I wanted to post to StackOverflow first.
I am getting the error "Received response with 0 content-length" when the responses are being read from the cache from disk. I can reproduce this error every time by
1) Run my app without OKHttp in classpath. Let pictures load
2) Add OkHttp into classpath, I get that error.
I added Picasso source to my project to investigate further. I found out that
1) Turning off caching connection.setUseCaches(false); will bypass the error (since it ignores the cache)
2) I found the switch in the Picasso source where it checks if OkHttp was available
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
okHttpClient = true;
} catch (ClassNotFoundException ignored) {}
and was able to reproduce the bug by hardcoding true, then false between runs.
I want to solve this problem so I can use OKHttp (and provide a viable upgrade for my current users) and all the benefits that come with it. I also have seen this "reading response with no content-length from cache" problem in other cases in my live environment. Once I get into the state with a bad response in cache, the pictures will never show up.
OkHttpClient okHttpClient = new OkHttpClient();
RestAdapter restAdapter = new RestAdapter.Builder().setClient(new OkClient(okHttpClient)).build();
OkHttpDownloader downloader = new OkHttpDownloader(okHttpClient);
Picasso picasso = new Picasso.Builder(this).downloader(downloader).build();
Source: https://stackoverflow.com/a/23832172/2158970
I am trying to download files from a server in android and show progress dialog using code very similar to the answer provided in this thread but i am not able to get content length in HttpURLConnection's getContentLength() method. Content length for all files is -1.
For the same file, i get correct content length in iOS app with NSHTTPURLResponse's expectedContentLength method.
Is there some basic difference in the way these methods fetch the content length for an http connection/response?
EDIT 1:
Tried following few things as suggested some answers and comments.
Set Accept-Encoding header to identity
Fetching the content length as string (from header field Content-Length) and then converting it to long
Tried conn.getContent().toString().length() instead of getContentLength()
None of these worked for me yet.
What baffles me most is i get the content length in iOS but not on android.
EDIT 2:
Heres my iOS and Android code for comparison -
iOS:
NSURLRequest *request = [NSURLRequest requestWithURL:self.url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1200.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
Android:
URL url = new URL(downloadUrlString);
connection = (HttpURLConnection) url.openConnection();
connection .setRequestProperty("Accept-Encoding", "identity");
connection.connect();
The only difference i can see is caching.
So i added following line in android code as well but nothing changed.
connection.setUseCaches(true);
try this:
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn .setRequestProperty("Accept-Encoding", "identity");
conn.connect();
if you just need to content length, can you try
conn.getContent().toString().length()
where conn is the HttpURLConnection object
Root cause for this problem turned out to be Cookies.
I am using a web view in one of the activities in my application. Some cookies are stored by the web view. All other REST api and file download requests work without those cookies however for a particular type of requests, the cookies are necessary.
Apparently, android web view and the connection requests do not share cookies out of the box like iOS. As a result i had to make changes in my code to make sure that the HttpUrlConnection uses WebKit's cookie store. I did it using method described in the accepted answer for this question.
Has anyone released code to show the full HTTP request/response headers, any intermediate redirects, and any cookie data for the Android HttpURLConnection? This would be similar to Firefox Web Console
I roughly know how to write this myself, but 1) it's a non-trivial amount of code 2) it's tricky to get this kind of code to work in all instances. So i'm interested in finding a readymade solution. I know how to tcpdump the emulator, but I'm searching for code to print this information into the Android Log class for really quick runtime debugging.
for header fields
URL url = new URL(str_url);
HttpURLConnection conection = (HttpURLConnection) url.openConnection();
conection.setConnectTimeout(TIMEOUT_SOCKET);
conection.setReadTimeout(TIMEOUT_CONNECTION);
conection.addRequestProperty("Accept-Encoding", "gzip");
RedirectLocations locations = new RedirectLocations();
// here u get all header fields and properties write it in logs
conection.getHeaderFields();
conection.getRequestProperties();
// conection.getOutputStream().write(buffer);
// download the file
InputStream is = conection.getInputStream();
// This is file path were a; quiz data will get saved.
// String file_path = context.getDir(folder,Activity.MODE_PRIVATE).getAbsolutePath();
return unzip(is,save_file_path);
for redirects
link
after u get response, again u ve to look for header fields
There is a handy com.android.volley.NetworkImageView widget extending ImageView. According to a Google presentation, to use it one needs only this in the layout xml file:
<com.android.volley.toolbox.NetworkImageView
and this in the Activity source code:
mImageView.setImageUrl(imageUrl, mImageLoader);
But what if I need to put a cookie or parameter into the request? I've seen this question, but I think this requires modifying volley library. Is there an easier way?
Here you can find a project with different simple examples about using Volley including using cookies and GET/POST parameters.
The proposed solution by Ogre_BGR is not a proper one as he is using Apache's HTTP client. And volley uses Apache's HTTP client only on API 8 and lower as there it is less buggy than URLConnection. But since Gingerbread (2.3) it is recommended to use URLConnection and it is the one that Android's team is maintaining and updating.
I think that a better solution is the one that you've given a link to. It does not modify Volley, it just adds/saves cookies to the ones that Volley already uses. And it is a common thing to extend some of the *Request classes of Volley for quick and easy call of API requests ( see here ). Ogre_BGR's solution completely changes the HttpStack of Volley.
You need to set you xml layout file to
<com.android.volley.toolbox.NetworkImageView...
You are missing "toolbox" in your class name.
As for the cookies, what I did to keep using URLConnetion (which is the default use case for Volley on API > 8) is to implement a custom class that extends HurlStack (Volley's implementation of an HTTP client using URLConnection), and overriding createConnection(URL url), which is the method used to get a connection prior to every request.
I just added my user agent (none is send by default, using HurlStack), and a cookie (also, isn't managed automatically in HurlStack).
Here's my code for the class:
public class CustomHurlStack extends HurlStack {
public CustomHurlStack() {
super();
}
#Override
protected HttpURLConnection createConnection(URL url) throws IOException {
// Create a connection with custom attributes
HttpURLConnection conn = super.createConnection(url);
conn.addRequestProperty("User-Agent", "myUserAgent/1.0");
conn.addRequestProperty("cookie", "myCookie");
return conn;
}
}
This should be a viable solution, and I'm not sure why Volley doesn't include such a configurable class to use, but oh well, you can create your own in just a minute or so, as described above.
private static RequestQueue mQueue;
String userAgent = "volley/0";
HttpStack stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
mQueue = Volley.newRequestQueue(this, stack, 100 * 1024 * 1024);