I have a recyclerview in wich each row contains an url, and the urls can expire in any moment.
I would like the user to not be forced to click on the url to check if it's not valid but I'd rather change the row's color in that case to notify the user.
This is the method I use to check if the url is still valid:
private boolean isUrlStillValid(String url) {
try {
HttpURLConnection.setFollowRedirects(false);
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("Accept-Encoding", "");
int responseCode = con.getResponseCode();
con.disconnect();
return responseCode == HttpURLConnection.HTTP_OK;
}catch(Exception e) {
return false;
}
}
I initially thought of launching an AsyncTask in the onBindViewHolder method of the recycler view where I check the url validity, but this will open a lot of connections simultaneously every time a row is shown and will cause very bad performance and memory issues I think.
Do you have any tip/suggestions on how to achieve this?
Do you have any tip/suggestions on how to achieve this?
for each url create a runnable and run all of them on fix thread pool with ExecutorService.
for example you can have 5 threads at a time with below code:
ExecutorService executor = Executors.newFixedThreadPool(5);
and also I recommend to save the result somewhere so when the user plays with scrollbar it dose not check the url again and again and again ...
Related
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.
Hello Guys,
I am creating an Android App, which uses the net connection to fetch a simple .php page which simply shows the current visitor counter.
According to the standard, I tried to implement net connection and web page fetching on a different thread using asynctask class, however, i am facing some problems.
Here is the code
public void myClickHandler(View view) {
// Gets the URL from the UI's text field.
String stringUrl = urlText.getText().toString();
//String stringUrl = "android.bisoft.in/index.php";
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
new DownloadWebpageTask().execute(stringUrl);
} else {
textView.setText("No network connection available.");
}
}private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... urls) {
// params comes from the execute() call: params[0] is the url.
try {
return downloadUrl(urls[0]);
} catch (IOException e) {
return "Unable to retrieve web page. URL may be invalid.";
}
}
// onPostExecute displays the results of the AsyncTask.
#Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}private String downloadUrl(String myurl) throws IOException {
InputStream is = null;
// Only display the first 500 characters of the retrieved
// web page content.
int len = 500;
try {
URL url = new URL(myurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
int response = conn.getResponseCode();
Log.d(DEBUG_TAG, "The response is: " + response);
is = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString = readIt(is, len);
return contentAsString;
// Makes sure that the InputStream is closed after the app is
// finished using it.
} finally {
if (is != null) {
is.close();
} }}//Reads an InputStream and converts it to a String. public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException {Reader reader = null;reader = new InputStreamReader(stream, "UTF-8"); char[] buffer = new char[len];reader.read(buffer);return new String(buffer);}}`
When i run the project, (Using an android device emulator), it does not connect to the internet at all, i.e. It does not even show the "No network connection available". error, it simply shows the textview with the default value.
I tried downloading the bluestacks emulator and running it in there, and the same thing happened, just the textview displays without any webpage being fetched or without showing any signs of internet connection activity.
When I searched the net, i came across the fact that there might be a problem with my device emulator's internet connectivity, I am currently developing and testing on a pc, with a wired broadband connection.
I tried doing this
" In eclipse go to DDMS
under DDMS select Emulator Control ,which contains Telephony Status in telephony status contain data -->select Home , this will enable your internet connection ,if you want disable internet connection for Emulator then --->select None
(Note: This will enable internet connections only if you PC/laptop on which you are running your eclipse have active internet connections.) " - some answerer from stackoverflow
but when i opened the ddms.bat from the tools directory of the sdk, it showed me a warning that this batch file was deprecated, i ignored it, and i navigated to emulator control, but I am not even able to select or interact with anything in that tab and all the buttons and textboxes are greyed out.
Thanks in advance for the assistance.
Edit:
I have used the two necessary permissions for the internet connectivity checking and usage
"<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />"
I have tried to run it in a Samsung Galaxy y duos lite, the App runs but the same problem persists, the phone is not connected to the net, so i assume the "no internet connection" message should be displayed, but instead, it just shows the default value of the text field. Any help is greatly appreciated as I am at a wit's end.
This task can be completed much easier using the droidQuery library:
$.ajax(new AjaxOptions().url(stringUrl)
.type("GET")
.dataType("String")
.success(new Function() {
#Override
public void invoke($ droidQuery, Object... params) {
textView.setText((String) params[0]);
}
})
.error(new Function() {
#Override
public void invoke($ droidQuery, Object... params) {
textView.setText("Unable to retrieve web page. URL may be invalid.");
}
}));
Did you write the good permissions the manifest ?
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
You really should try it on a real phone. Emulators are hard to setup for Internet connection.
For my app (supporting Android 2.2+) I have to check HTML-code of a lot (approx 700) of different web-pages and retrieve a single name from each web-page. I have all the URL's stored in an array.
I now use a single Asynctask and iterate over the array with URLs like this:
(snippet from Asynctask's doinbackground)
publishProgress(urls.size());
int a = 0;
for(String code : urls) {
if(!running) return null;
try {
URL url = new URL(code);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
naam_codes.put(readStream(con.getInputStream(), true).get(0), code);
} catch (Exception e) {
running = false;
}
publishProgress(++a);
and readstream being:
BufferedReader reader = null;
ArrayList<String> html = new ArrayList<String>();
try {
reader = new BufferedReader(new InputStreamReader(in, Charset.forName("ISO-8859-1")));
if (snel){
//reading, matching and stuff
}
else {
//other reading, matching and stuff
}
}
} catch (IOException e) {
//pass
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
return null;
}
}
}
return html;
Now my problem is that it has to wait for one download+matching to finish before starting with a new one. It should be possible to speed this up, right? After monitoring for a bit the process doesn't seem to fully use the CPU nor internet-bandwidth(?). Should I instead of iterating inside one Asynctask, iterate on the UI-thread and execute multiple Asynctasks? If so, how?
Multiple AsyncTasks won't take advantage of multiple cores before API 11. After that, you can create one AsyncTask per download/parsing and have them executed parralelly using the executeOnExecutor function with the parameter AsyncTask.THREAD_POOL_EXECUTOR.
From the documentation:
Order of execution
When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of
threads allowing multiple tasks to operate in parallel. Starting with
HONEYCOMB, tasks are executed on a single thread to avoid common
application errors caused by parallel execution.
If you truly want parallel execution, you can invoke
executeOnExecutor(java.util.concurrent.Executor, Object[]) with
THREAD_POOL_EXECUTOR.
If I were you, I would build my own server (Just a CRON task launching a PHP script somewhere + a MySQL database + a PHP script to serve your data) and I would not let the applications do the processing.
Let your server do the 700 downlaods, parse them, store what you need in a database. And then let your applications access your server script which will pick the required info from your database.
Advantages:
Your server has better bandwidth
It has more processing power
Your apps can request whatever data they need instead of downloading & parsing several hundreds of pages.
Inconvenient:
You may induce a little delay in making new data available (depends on your CRON task's execution period & execution time to update the database)
I'm porting some of my Windows Phone 7 apps to Android. When we call services in the WP7 world, the calls are async. We call the service and there is a delegate _completed event that triggers when when the result is returned. Meanwhile we go on about our way.
The java android code pasted below is how I am calling an HTTP service on my cloud server. I developed this code by going through Android tutorials teaching how to call a service in the android world. Apparently, service calls here are synchronus so the instruction starting with InputStream in... doesn't get executed until the result is returned.
Is this how it is supposed to work for Android? If the service does not respond, there is a wait of a couple minutes and then a timeout exception takes place. That's no good. Everything will hang.
What is the reccommended way to call services in android?
Thanks, Gary
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
HttpURLConnection urlConnection = null;
try
{
URL url = new URL("http://www.deanblakely.com/REST/api/products");
urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
String myString = readStream(in);
String otherString = myString;
otherString = otherString + " ";
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
urlConnection.disconnect();
}
}
Is this how it is supposed to work for Android?
Yes. Do HTTP operations in a background thread, such as the one supplied by an AsyncTask.
If the service does not respond, there is a wait of a couple minutes and then a timeout exception takes place. That's no good. Everything will hang.
You can set a socket timeout to be something shorter than "a couple minutes". For example, you can call setReadTimeout() on your HttpURLConnection to specify a timeout period in milliseconds.
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