The code on the CacheDispatcher is as below. And focus on that release previous request object to avoid leaking request object when mQueue is drained.
I do not know why. Can anyone tell me the reason?
Thanks in advance.
java
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
request = mCacheQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
request = mCacheQueue.take(); is a blocking call so when the queue is empty(drained) we will still keep reference of the old request while waiting if we didn't do request = null;
this however is not the best way to achieve this behavior and it was changed in the new Volley code to :
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();
Related
see the code below, I put network request and local database write operation together under the same method.
ConcurrentAsyncTask.execute(new Runnable() {
#Override
public void run() {
Pair<String, String> rlt = null;
try {
rlt = sc.createNewDir(repoID, parentDir, dirName);
} catch (SeafException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String newDirID = rlt.first;
String response = rlt.second;
// The response is the dirents of the parentDir after creating
// the new dir. We save it to avoid request it again
mDatabaseHelper.saveDirents(repoID, parentDir, newDirID, response);
}
});
sc.createNewDir() was used to request network request and mDatabaseHelper.saveDirents() was used to write data into database.
I wonder if it breaks the programming rules of AsyncTask.
Waiting for your advice, thanks!
There is nothing wrong with running different background operations in a single AsyncTask, especially when these operations are logically connected - as in your case network operation produces input for database operations, also you always want to store network operation result to database.
I will soon work on a project, which uses a lot of HTTPRequests for mainly JSONs and Images, so I thought it is a good idea to think about caching. Basically I'm looking for a solution for
Start a HTTPRequest with a given lifetime (f.e. 3,6,12 hours)
Check, if that Request is available in the Cache and still valid (lifetime)
If Request is still valid, take it from Cache, otherwise make the Request and save its Response
I found HttpResponseCache class in Android. It is working, however it is not working like I'm expecting.
My test case is an AsyncTask to cache several Images. Code looks like the following:
URL url = new URL(link);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
Bitmap myBitmap;
try {
connection.addRequestProperty("Cache-Control","only-if-cached");
//check if Request is in cache
InputStream cached = connection.getInputStream();
//set image if in cache
myBitmap = BitmapFactory.decodeStream(cached);
} catch (FileNotFoundException e) {
HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
connection2.setDoInput(true);
connection2.addRequestProperty("Cache-Control", "max-stale=" + 60);
connection2.connect();
InputStream input = connection2.getInputStream();
myBitmap = BitmapFactory.decodeStream(input);
}
return myBitmap;
} catch (IOException e) {
e.printStackTrace();
}
Two questions:
I set max-stale=60seconds for testing purposes. However, if I call the same URL 5minutes later, it tells me, it is loading the image from cache. I would assume, that the image is reloaded because the HTTPRequest in the cache is out of date? Or do I have to clean the cache myself?
In my catch block, I have to create a second HttpURLConnection, because I cannot add properties, after I opened an URLConnection (this happens in the connection.getInputStream()?!). Is this bad programming?
After all, I find that HttpResponseCache poorly documented. I came across Volley: Fast Networking, but this seems even less documented, even if it is offering exactly the things I need (Request queuing and prioritization...). What do you use for caching? Any links to libraries, tutorials, are welcome.
UPDATE
I'm not targeting Android versions lower than 4.0 (still maybe intresting for other users?)
Both HttpResponseCache and volley are poorly documented. However, I have found that you
can very easily extend and tweak volley. If you explore source code of volley, especially of: CacheEntry, CacheDispatcher and HttpHeaderParser, you can see how it is implemented.
A CacheEntry holds serverDate, etag, ttl and sofTtl which can represent cache state pretty well, also it has isExpired() and refreshNeeded() methods as convenience.
CacheDispatcher is implemented accurately as well:
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
#Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
One interesting tidbit: If cache is "soft expired", volley will deliver data from local cache immediately, and re-deliver it from server again after some time, for single request.
Finally, HttpHeaderParser does its best to cope to server headers:
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
return null;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
maxAge = 0;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = entry.softTtl;
entry.serverDate = serverDate;
entry.responseHeaders = headers;
So, ensure the server sends proper headers as well as honors etag,time-stamp and cache control headers.
Finally, you can override getCacheEntry() of Request class to return custom CacheEntry make cache behave exactly according to your needs.
Soryy. But why don't you use third-party libs for this? Try to use Volley lib for this. It maintains a cache out of the box and it is async out of the box. It works really good. Tutorials for Volley: one (with caching demonstration), two.
And there is another good one async, caching lib for android with good documentation - Retrofit. Here is Retrofit Caching Example.
And here is their comparison.
To enable caching, all you need to do is just install HTTP response cache at application startup by using below code:
File httpCacheDir = new File(context.getCacheDir(), "http");
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
HttpResponseCache.install(httpCacheDir, httpCacheSize);
Whether the resource need to be fetched from network or cache, is taken care by HttpResponseCache. The age of cache is specified in the response headers of the resource request. For example this image, specifies cache age of 43200 seconds.
You can verify whether the resource is fetched from cache or network, by using following apis:
getHitCount : Number of Http requests which were served by cache.
getNetworkCount : Number of Http requests which were served by network.
Regarding max-stale, you have misunderstood it's purpose. It is used to permit stale cache responses. Here is it's definition from the rfc documentation :
Indicates that the client is willing to accept a response that has
exceeded its expiration time. If max-stale is assigned a value, then
the client is willing to accept a response that has exceeded its
expiration time by no more than the specified number of seconds. If no
value is assigned to max-stale, then the client is willing to accept a
stale response of any age.
Regarding cache control directive only-if-cached, only use it when you need to show something while your application is downloading latest content. So the question of handling new HttpUrlConnection in exception handler does not arise. From the docs:
Sometimes you'll want to show resources if they are available
immediately, but not otherwise. This can be used so your application
can show something while waiting for the latest data to be downloaded.
To restrict a request to locally-cached resources, add the
only-if-cached directive
One suggestion, add finally block, where you release the connection by calling disconnect.
I'm getting an address from the user and i'm using GeoCoder to get the lat/lng.
After getting the lat/lng I need to send a POST request to my server to save the data (using Volley).
I'm running the GeoCoder request in an AsyncTask and I run the post request in the onPostExecute callback.
Is that the right way to do it or is there a better way?
here is my code:
private class GeoCoderAsync extends AsyncTask<String, Void, List<Address>>{
#Override
protected List<Address> doInBackground(String... params) {
String address = params[0];
List<Address> addresses = null;
Geocoder coder = new Geocoder(Favorites.this);
try {
addresses = coder.getFromLocationName(address, 1);
} catch (IOException e) {
e.printStackTrace();
}
return addresses;
}
#Override
protected void onPostExecute(List<Address> result) {
Utils.dismissDialog(progDialog);
if (result != null) {
Address address = result.get(0);
...
//this makes a post request
VolleyHelper.post(request,AppConstants.URL.FAVORITES, getFavoritesSuccess(), getFavoritesError(), progDialog);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else{
Log.d(TAG, "Got Nothing!!!");
}
}
}
I'm pretty sure that your way is the best. As you commented, you need to wait until onPost to use volley so there is no way around it. Anyways, volley uses its own multi threading and so it won't lock anything up.
In general it is best to minimize http requests due to the battery cost from the radio, but in this case it is key to the functionality of your app so there is no way around it.
Btw, for anyone reading this question and curious about volley -http://arnab.ch/blog/2013/08/asynchronous-http-requests-in-android-using-volley/
Hope this helps ;)
I'm running the GeoCoder request in an AsyncTask and I run the post
request in the onPostExecute callback.
Is that the right way to do it
No, because onPostExecute run on main ui Thread. if you do this then UI will freeze until Http post request not completed.
or is there a better way?
Make post request from doInBackground after getting addresses from Geocoder.
I am trying to perform a simple get request using Apache HTTPClient however it seems as if all the code after the HTTPResponse response = client.execute(get); is being skipped. I am not able to access the contents of the response object,they are all null. However when I use debug mode and I explore the object I see all the data. This function is wrapped in an async task so I am wondering the task itself is not waiting on it to be executed or something I am not sure.
Something similar happened here:
Android code after httpclient.execute(httpget) doesn't get run in try (using AsyncTask)
Here is the code.
#Override
public T execute()
{
utils = new GeneralUtils();
if(getURL() == null)
{
throw new NullPointerException("No path specified");
}
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(getURL());
Log.e(TAG,"client created");
if(getHeaders()!=null)
{
Log.e(TAG,"client created");
for(Map.Entry<String,String> header:getHeaders().entrySet())
{
get.addHeader(header.getKey(),header.getValue());
}
}
try
{
HttpResponse response = client.execute(get);
Log.e(TAG,"executed");
if(response==null)
Log.v(TAG,"its null as heell");
Log.v(TAG,response.getStatusLine().getReasonPhrase());
Log.v(TAG,String.valueOf(response.getStatusLine().getStatusCode()));
Log.e(TAG,getURL());
Log.v(TAG,"everything else is dead");
for(Header header:response.getAllHeaders())
{
Log.v(TAG,header.getName()+" "+header.getValue());
}
if(response.getStatusLine().getStatusCode() == 200)
{
if(response.getEntity().getContent()!=null)
{
try
{
if(utils.isExternalStorageWritable())
{
String path = getContext().getFilesDir().getAbsolutePath()+"/"+getFileCategory()+"/" +getAlarmId()+getFileExtension();
media = new File(path);
/**
* if the directory has not being created this function does the creation.
*/
media.mkdirs();
FileOutputStream fileOutputStream = new FileOutputStream(media);
IOUtils.copy(response.getEntity().getContent(),fileOutputStream);
fileOutputStream.close();
Log.e(TAG,media.getAbsolutePath());
return (T)media;
}
return null;
}
catch (ClientProtocolException e)
{
Log.v(TAG,e.getMessage());
}
catch (IOException e)
{
Log.v(TAG,e.getMessage());
}
}
}
}
catch (IOException e)
{
Log.e(TAG, e.getCause().getMessage());
e.printStackTrace();
}
return null;
}
The code is not throwing any exceptions so I am not sure about what's happening.
All the code after the response object does not work. It just returns null, As in as soon as I try to obtain a value from response like so response.getStatusCode(), it seems as if the code goes dead and just returns null.
Why don't you use a library that will handle all these restful connections?
I would recommend a couple:
https://github.com/darko1002001/android-rest-client (this is mine i have to mention it first :). I have built this library for the projects i build. For your case you would supply a parser which will give you an InputStream which you will just save as a file (as you do it now with IO utils). It handles the Asynchronous part of the whole thing and generally gives you a nice way to organize code.
http://square.github.io/retrofit/ - is another one that i have been playing around with. i think it is pretty well made and should be able to do whatever you want with it.
http://java.dzone.com/articles/android-%E2%80%93-volley-library - Volley is a project that came out straight from Google and it was demoed at the last Google IO conference. It handles all the async operations for you as well and enables you to do all these things. One thing that i am not really sure about is whether or not it will enable you to parse the responses in the background thread.
I would strongly suggest for you to use one of these as they might save you a lot of time.
If you do want to continue with your code then i would suggest to first investigate if some of the "if" blocks you have are skipped, use the debugger or add log messages to see if it enters the blocks. Go step by step and see what goes wrong.
I am doing something similar in my project, check out this file:
https://github.com/darko1002001/android-rest-client/blob/master/android-rest-lib/src/main/java/com/dg/libs/rest/client/BaseRestClient.java
I have an application in which there is Google map, location overlays on Google map and a separate thread which send the current location of device to server after every 30 seconds. The problem is that when the thread sends the location to server the screen of device halted until the server respond. Here is the following code,
Global Object
private Handler handlerTimer = new Handler();
In onCreate Method
handlerTimer.removeCallbacks(taskUpdateStuffOnDialog );
handlerTimer.postDelayed(taskUpdateStuffOnDialog , 100);
And here is the taskUpdateStuffOnDialog
private Runnable taskUpdateStuffOnDialog = new Runnable()
{
public void run()
{
try
{
URL url3 = new URL("http://"+ appState.getURL()+"//iLocator/IDForClient.php?reg_no="+ Device_ID[0]);
HttpURLConnection conn = (HttpURLConnection) url3.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String quote = reader.readLine();
while (quote != null)
{
Device_ID = quote.split("\n");
quote = reader.readLine();
bCheckID = true;
}//End While
positionOverlay.setID(Device_ID[0]);
addEvent(Device_ID[0]);
}//End try
catch (Exception e)
{
e.printStackTrace();
Toast.makeText(MainMapActivity.this, "Communication Issue",Toast.LENGTH_LONG).show();
}//End catch
handlerTimer.postDelayed(this, 9000);
}
};
Please tell me what is wrong with my code.
The problem is that, although you're spawning a new Thread, you aren't spawning a new process. Everything you're doing is still in the user interface process, and that's blocking. You can find more information on the topic on developer.android.com.
The quickest and easiest way to get around this is using the IntentService class. It will only allow one HTTP request to be executed at a time, but will take care of all the problems for you.
Try using the AsyncTask for connecting to the Server. See an example here: http://developer.android.com/reference/android/os/AsyncTask.html