I am using WebViews in an Android app, and I need to prevent the WebViews from caching.
Unfortunately it seems like this seemingly simple goal is nearly impossible to achieve. The solution I have resorted to use is to execute webview.clearCache(true) in the onPageFinished event so that the cache is cleared each time a page is loaded. There are some issues...
I have noticed that as the cache grows it becomes very time consuming for the clearCache method to execute. Sometimes if you execute clearCache and then switch to a different Activity that contains different webview, that webview will not load for a few seconds because it is still waiting on the previous clearCache operation to finish.
What's worse is that execution time of subsequent calls to clearCache does not seem to decrease after the cache has been already cleared. If the call to clearCache takes 3 seconds to complete and then I immediately call clearCache a second time, then I would expect the second call to clearCache to complete almost immediately. But that is not what I'm experiencing; I'm experiencing that the second call to clearCache still take approximately 3 seconds.
Has anyone else experienced this? Is there any way to improve performance? Waiting 2-3 seconds for a webview to load (from the local filesystem) is horrible.
EDIT:
Here is my best alternative to actually clearing the cache. It more or less works but it's sort of flaky and I'm not 100% happy with it (written in Mono c#):
public class NoCacheWebClient : WebViewClient
{
string previous;
public override void OnPageStarted(WebView view, string url, Android.Graphics.Bitmap favicon)
{
base.OnPageStarted(view, url, favicon);
if (!string.Equals(previous, url))
{
previous = url;
view.Reload(); //re-load once to ignore cache
}
else
{
previous = null;
}
}
}
1) Try using setAppCacheEnabled and setAppCacheMaxSize to limit the cache size to very little , lower cache size will result in faster cleanup.
Ex: wv.getSettings().setAppCacheMaxSize(1);
OR
2) If you don't need the cached data then simply set setCacheMode(WebSettings.LOAD_NO_CACHE); , which means
"Don't use the cache, load from the network", even though data is cached.
In-short, simply ignore the cached data, android will take care of it.
OR
3) you can also try the below code for no-caching,
Note: this is only available for Android API 8+
Map<String, String> noCacheHeaders = new HashMap<String, String>(2);
noCacheHeaders.put("Pragma", "no-cache");
noCacheHeaders.put("Cache-Control", "no-cache");
view.loadUrl(url, noCacheHeaders);
OR
4) Clear the cache every-time whenever page load finishes.
Something like this in the WebViewClient.
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.clearCache(true);
}
OR
5) You can try deleting whole cached database at once.
context.deleteDatabase("webview.db");
context.deleteDatabase("webviewCache.db");
This might give a bit faster result, hope so.
As you are going to the next activity finish the previous activity. So that you can free all memory occupied by that activity.
Hope this helps.
Related
i am using Picasso as image loading library in my android project.
I am using it in one fragment.At first it take some time to load profile image but when i open some other fragment and then come back to the same fragment.It again takes the same amount of time to load the same image.I think it is not caching image in the memory.
Below is my java code:
Picasso.with(getContext()).load(str).fetch(new Callback() {
#Override
public void onSuccess() {
Picasso.with(getContext()).load(str).placeholder(R.drawable.user).fit().into(cv);
}
#Override
public void onError() {
TastyToast.makeText(getActivity(),"Unable to load profile image.", TastyToast.LENGTH_SHORT,TastyToast.ERROR).show();
}
});
You have use it like this
Picasso.with(getContext()).load(str).networkPolicy(NetworkPolicy.OFFLINE).fetch(new Callback() {
#Override
public void onSuccess() {
//Picasso.with(getContext()).load(str).placeholder(R.drawable.user).fit().into(cv); don't need to use it here
}
#Override
public void onError() {
TastyToast.makeText(getActivity(),"Unable to load profile image.", TastyToast.LENGTH_SHORT,TastyToast.ERROR).show();
}
});
try to take off the callback in order to know if the image is loaded, instead use the code below that automatically change the image in case of error
Picasso.with(getAplicationContext()).load("image to load").placeholder("image to show while loading").error("image in case of error").into(view);
Note the context of the first parameter: Picasso's cache still alive only if the context still alive, so if you put the ApplicationContext in the first parameter, the context and the cache keap alive even if the activity is changing. otherwise if you put getActivityContext() in the first parameter, the cache keap alive untill the activity hes been destroy
if your app is using internet connection to load image then every time you come back or go to your image containing fragment it will download image from internet so better you should store images in your app database and try to use thumbnails instead of actual images if possible it will take less time to get load
When I enter an activity I'm calling this method:
private void cacheImagesAndLoadToMemory() {
for (City city : cities) {
Picasso.with(this).load(city.getImageUrl()).tag("fetch_images").fetch();
}
}
This fetches around 200 images which equates to around 45MB of data. Then I attach a fragment to this activity but when I leave the fragment I want the requests for the 200 images to be cancelled. So I have this code set up.
#Override
public void onDestroy() {
super.onDestroy();
Picasso.with(getActivity()).cancelTag("fetch_images");
}
But the fetch requests are not being cancelled. I have a bandwidth monitor on my status bar and can see that data keeps being pulled until all 200 images have been cached. Not sure what I'm doing wrong.
Seems to be a well known bug, cf. Github bug #1205.
Unfortunately Picasso project does not seem to move forward lately.
Problem: I've a X amount of ImageViews that I'm adding dynamically like this:
for (int i=2; i < result.size(); i++) {
// instantiate image view
ImageView mImageView = new ImageView(this);
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mImageView.setBackgroundResource(R.drawable.selectable_background_theme);
mImageView.setOnClickListener(this);
// download image and display it
mImageLoader.get(result.get(i), ImageLoader.getImageListener(mImageView, R.drawable.ic_logo, R.drawable.ic_action_refresh));
// add images to container view
mLlDescContent.addView(mImageView);
}
What want to be able to click on the image and display it in another activity in full screen. I have read about a couple of ways such as passing the Uri or passing the actual Bitmap as a byte array.
Question: How do I get the Uri or the actual Bitmap I downloaded with Volley ImageLoader. The LruCache I'm using an BitmapLruCache I found here: Android Volley ImageLoader - BitmapLruCache parameter? . Can someone help me with this or any idea to accomplish my goal.
I tried this after the above code and nothing:
Bitmap mBitmap = VolleyInstance.getBitmapLruCache().getBitmap(result.get(2));
mIvAuthorImg.setImageBitmap(mBitmap);
Edit: If i re-request the image with:
mImageLoader.get(result.get(i), ImageLoader.getImageListener(mImageView, R.drawable.ic_logo, R.drawable.ic_action_refresh));
the image is loaded from the cache, BUT if I try to access the image straight from the cache with:
Bitmap mBitmap = VolleyInstance.getBitmapLruCache().getBitmap(result.get(2));
mIvAuthorImg.setImageBitmap(mBitmap);
the image don't load. I want to be able to manipulate the image, such as size an stuff before a pass it to the next activity.
Volley depends on your implementation of a cache for successful efficient caching.
The constructor for the ImageLoader takes in an ImageCache which is a simple interface in Volley to save and load bitmaps.
public ImageLoader(RequestQueue queue, ImageCache imageCache)
A quote from the Javadoc of ImageCache interface:
Simple cache adapter interface. If provided to the ImageLoader, it will be used as an L1 cache before dispatch to Volley. Implementations must not block. Implementation with an LruCache is recommended.
Darwind is right. If you request an image and it is present in the cache it will be loaded from the cache and not from the web. This should be the case for you since you're loading and presenting an image, which if clicked should be displayed from the cache in your new activity.
You say it's not working, perhaps your implementation isn't optimized for your use case. What kind of cache are you using? Do you have one centralized RequestQueue and ImageLoader as is recommended by the Volley team?
Take a look at this question, which isn't exactly the same as yours, yet could be helpful to you. It has a simple LRU cache implementation.
Hope that helps!
Edit:
The point of Volley is not to worry about implementation details. You want an image? it will load it for you the best and fastest way possible (from memory and if it's not there via network). That's exactly the way you should look at it. Retrieving the cache and then looking in it is not the right approach IMO.
Now, if you want to manipulate the bitmap, you have a few options, the best IMO being to implement your own Image Listener pass it to the get() method instead of the default one.
Something like this:
public class MyImageListener implements ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
// handle errors
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
//
// manipulations
//
// assuming mView is a reference to your ImageView
mView.setImageBitmap(bitmap);
} else {
// display placeholder or whatever you want
}
}
}
from the Javadoc:
The call flow is this:
Upon being attached to a request, onResponse(response, true) will be invoked to reflect any cached data that was already available. If
the data was available, response.getBitmap() will be non-null.
After a network response returns, only one of the following cases will happen:
onResponse(response, false) will be called if the image was loaded.
or
onErrorResponse will be called if there was an error loading the image.
There's a bug (I'm 70% sure it's a bug) in volley currently where if a cache-expiry isn't specified (say, if you're getting an image from an S3 bucket where you have a never-expires setting), you'll always redownload from the network.
You could get around this by checking out HttpHeaderParser, and changing the relevant bits (this isn't totally crazy, as you have to include the volley source code anyway) to:
// 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);
} else if (serverExpires == 0) {
softExpire = Long.MAX_VALUE;
}
Then you could just pass the Uri to the activity opening the thing as a parameter. This solution has the enviable property that if something goes wrong and something happens to the image between launching the activity and displaying the bitmap, you'll still redownload it and things will work appropriately. That'll probably never/seldom happen, but it's nice to know correctness is preserved.
I've run into this weird error, where some images get cached as usual and some don't, any idea why?
Both images do get displayed and memory cached just fine, but when offline some display error image.
For example, this works fine:
http://cs4381.vk.me/u73742951/a_58a41ac2.jpg
However, this does not: http://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Android_robot.svg/220px-Android_robot.svg.png
Both work fine displaying and memcaching but the second doesn't get displayed from disk cache, although I think I see it being saved, as app says it has 12kB cache in the system settings
Edit
I checked out a clean copy of Volley and it does the same thing. Its definatelly a bug...
From what Ive found out its that images do get cached, but Bitmap cachedBitmap = mCache.getBitmap(cacheKey); always returns null, so the cache says it doesnt have the bitmaps and then proceedes to download it again, and fail when offline, weird
The reason you're not getting any hits is because the default behavior in Volley for disk caching is dependent on the HTTP headers of the element you're requesting (in your case, an image).
Check the volley logs and see if you get the "cache-hit-expired" message - that means that the image was cached but it's TTL is expired as far as the default disk cache is concerned.
If you want the default settings to work, the images must have a Cache-Control header like max-age=??? where the question marks indicate enough seconds from the time it was downloaded.
If you want to change the default behavior, I'm not sure, but I think you have to edit the code a bit.
Look at the CacheDispatcher class in the Volley source.
Hope that helps.
A quick and dirty way:
private static class NoExpireDiskBasedCache extends DiskBasedCache
{
public NoExpireDiskBasedCache(File rootDirectory, int maxCacheSizeInBytes)
{
super(rootDirectory, maxCacheSizeInBytes);
}
public NoExpireDiskBasedCache(File rootDirectory)
{
super(rootDirectory);
}
#Override
public synchronized void put(String key, Entry entry)
{
if (entry != null)
{
entry.etag = null;
entry.softTtl = Long.MAX_VALUE;
entry.ttl = Long.MAX_VALUE;
}
super.put(key, entry);
}
}
I'm developing a small app that reads in specific html-pages, re-formats them and then shows them in a WebView. If I run my code in the GUI thread, the performance hit is close to negligible compared to simply letting the WebView show the original html-page. But if I'm a good boy and do like I'm told, I'm supposed to use an AsyncTask to run the code in the background so as not to freeze up the GUI during those 3-5 seconds my code does its job. Problem is... if I do so, the code takes more than 10 times as long to finish. A page takes 60+ seconds to show, which is unacceptable.
Tracking down the problem, TraceView shows me that my AsyncTask is (at default priority) run in roughly 10 ms chunks, around 4 times per second. I need to set my thread priority to MAX_PRIORITY to get close to acceptable loading times, but even then it takes 3-4 times longer than when I run in the GUI thread.
Am I doing something wrong, or is this just the way it works? And must it work this way...?
Here's compilable code as requested:
package my.ownpackage.athome;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class AndroidTestActivity extends Activity
{
WebView webview;
//...
private class HelloWebViewClient extends WebViewClient
{
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
AndroidTestActivity.this.fetch(view, url);
return true;
}
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// To allow to connect to the web and pull down html-files, reset strict mode
// see http://stackoverflow.com/questions/8706464/defaulthttpclient-to-androidhttpclient
if (android.os.Build.VERSION.SDK_INT > 9)
{
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
// webview init etc...
fetch(webview, "http://www.example.com");
}
// This one calls either the AsyncTask or does it all manually in the GUI thread
public void fetch(WebView view, String url)
{
//** Use these when run as AsyncTask in background - SLOW!
//** Takes 30+ seconds at default thread priority, with MAX_PRIORITY 15+ seconds
// AsyncTask<Void, String, String> fx = new FilterX(url, view, this);
// fx.execute(); // running as AsyncTask takes roughly ten times longer than just plain load!
//** Use these when not running as AsyncTask - FAST! takes ~5 seconds
FilterX fx = new FilterX(url, view, this);
fx.onPreExecute();
final String str = fx.doInBackground();
fx.onPostExecute(str);
}
}
class FilterX extends AsyncTask<Void, String, String>
{
WebView the_view = null;
// other stuff...
FilterX(final String url, final WebView view, final Activity activity)
{
the_view = view;
// other initialization
// same code in both cases
}
protected void onPreExecute()
{
// same code in both cases
}
protected String doInBackground(Void... v)
{
// same in both cases...
return new String(); // just to make it compile
}
protected void onPostExecute(final String string)
{
the_view.loadUrl(string);
// same in both cases...
}
}
To run exactly the same code in my FilterX class when run as AsyncTask as when run on the GUI thread, I stripped all ProgressBar stuff, and then I get the following timings:
30+ seconds to load a page at default thread priority
15+ seconds to load a page at MAX_PRIORITY
5+ seconds to load a page when run in the GUI thread
You're not the only one observing this behaviour. The slowdown by factor 10 is probably a result of Android using a Linux cgroup (scheduling class) for threads of priority BACKGROUND or below. All these threads have to live with 10% CPU time altogether.
The good news is you don't have to live with the Thread priority settings from java.lang.Thread. You can assign your Thread a pthread (Linux thread) priority from the definitions in android.os.Process. There, you not only have Process.THREAD_PRIORITY_BACKGROUND, but also constants to adjust the priority a bit.
Currently, Android uses the background thread cgroup for all threads with priority THREAD_PRIORITY_BACKGROUND or worse, and THREAD_PRIORITY_BACKGROUND is 10 while THREAD_PRIORITY_DEFAULT is 0 and THREAD_PRIORITY_FOREGROUND is -2.
If you go for THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE (aka 9) your thread will be lifted out of the background cgroup with the 10% limitation, while not being important enough to interrupt your User Interface threads too often.
I believe there are background tasks which need a bit of computational power but which are at the same time not important enough to de facto block the UI (by consuming too much CPU in a separate thread) and Android currently has no obvious priority to assign to these, so in my view, this is one of the best priorities you can assign to such a task.
If you can use a HandlerThread it's easy to achieve:
ht = new HandlerThread("thread name", THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE);
ht.start();
h = new Handler(ht.getLooper());
If you want to go with AsyncTask, you can still do
protected final YourResult doInBackground(YourInputs... yis) {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE);
...
}
but be aware that the underlying implementation may reuse the same Thread object for different tasks, for the next AsyncTask, or whatever. It seems that Android simply resets the priority after doInBackground() returns, though.
Of course, if your UI really consumes CPU and you want more power for your task at the same time, taking it away from the UI, you can set another priority, maybe up to Process.THREAD_PRIORITY_FOREGROUND.
AsyncTask runs at a lower priority to help making sure the UI thread will remain responsive.
Despite the performance hit, you do want to do this in the background. Play nice, and others will play nice with you.
Since I don't know what this is for, I can't suggest an alternative. My first reaction was that it's odd that you're trying to reformat HTML on a phone device. It's a phone, not a quad-core with oodles of RAM. Is it possible to do the reformatting in a web service and display the result on the phone?
u need to call final String str = fx.execute. you should not call doinbackground directly from ui thread.