Random crap
Alex Kremer's blog

HTML5 Cache, Android WebView

So last post was about HTML5 and WebViews but it didn’t cover offline usage and especially caching.
HTML5 is cool. It can work offline once cached by the browser. The main mechanism for offline HTML5 is the so called Manifest. Basically it’s a file which lists entries (js files, pictures, css, etc.) to be cached by the browser.

To enable cache in Android’s WebView we must do at least the following:

  1. webView.setWebChromeClient(new WebChromeClient() {
  2.       @Override
  3.       public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
  4.                    WebStorage.QuotaUpdater quotaUpdater)
  5.       {
  6.             quotaUpdater.updateQuota(spaceNeeded * 2);
  7.       }
  8. });
  9.  
  10. webView.getSettings().setDomStorageEnabled(true);
  11.  
  12. // Set cache size to 8 mb by default. should be more than enough
  13. webView.getSettings().setAppCacheMaxSize(1024*1024*8);
  14.  
  15. // This next one is crazy. It's the DEFAULT location for your app's cache
  16. // But it didn't work for me without this line.
  17. // UPDATE: no hardcoded path. Thanks to Kevin Hawkins
  18. String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
  19. webView.getSettings().setAppCachePath(appCachePath);
  20. webView.getSettings().setAllowFileAccess(true);
  21. webView.getSettings().setAppCacheEnabled(true);

This will work. Well, this will make your WebView support caching.. doesn’t mean it will actually work.
You can tune cache policies using the following code, even though it’s not required to make it work:

  1. webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);

Now when all the cache is setup we would like to see our HTML5 site working in pure offline mode.. but we don’t!
Why? well.. if it’s not working for you it probably means that the site you load up with the following call is actually redirecting you somewhere else:

  1. webView.loadUrl("http://myHTML5app.com/app/");

If /app/ is resolving to a redirect – you are in trouble, sir.
The Cache mechanism will cache the actual RESULT of the redirect and sure it will map it to the url you are being REDIRECTED to, not the one you originally called. So once you are offline – the cache has no clue about the /app/ url and thus you are simply given the default android “can’t open page” replacement.

There are at least two ways to solve this issue..

  • Get rid of the redirect on the server
  • Re-redirect on android side
  • The first option is bad because you typically don’t want to mess with the working HTML5 site which is working on pure Browsers (iPhone, desktop, etc.).
    To implement the second option I used the following code in the WebViewClient implementation:

    1. @Override
    2. public void onReceivedError(WebView view, int errorCode,
    3.            String description, String failingUrl)
    4. {
    5.     // The magic redirect  
    6.     if( "http://HTML5app.com/app/".equals(failingUrl) ) {
    7.           // main.html is the place we are redirected to by the server if we are online
    8.           mWebView.loadUrl("http://HTML5app.com/app/main.html");
    9.           return;    
    10.     }
    11.     else if( "http://HTML5app.com/app/main.html".equals(failingUrl) ) {    
    12.           // The cache failed – We don't have an offline version to show
    13.  
    14.           // This code removes the ugly android's "can't open page"
    15.           // and simply shows a dialog stating we have no network
    16.           view.loadData("", "text/html", "UTF-8");
    17.           showDialog(DIALOG_NONETWORK);
    18.     }
    19. }

    Good luck!

    Tags: , , , ,

    26 Responses to “HTML5 Cache, Android WebView”

    1. Andrey Says:

      Great blog it’s not often that I comment but I felt you deserve it.

    2. Tom Says:

      Thanks a bunch, this helped me out =)

    3. Rishikesh Says:

      Really awesome
      :D

    4. Leimi Says:

      Hi,

      Great article ;) But even by following it, I can’t manage to enable the HTML 5 manifest in the WebView.
      The default Android browser notices the manifest file when I’m offline, but a WebView doesn’t and directly shows the “no internet connection” message.

      Do you have any idea why? Thanks :)

    5. godexsoft Says:

      Hi Leimi,

      Alas, I don’t remember doing anything special to get the manifest working.. I believe that it should just work if your html5 application works on the stock browser. The only difference might be your android being 2.2 or 2.3 against 2.1 used for this article.. maybe they changed something in newer versions of android.

      Hope you can find a solution soon. When you do – let me know :)
      Cheers,
      Alex

    6. Leimi Says:

      Hmm, that’s weird, the code is done with the Android 2.1 SDK. Well, I’ll try harder and come back here if I find a solution :p

    7. Anand Says:

      It’s a great blog Alex… Thnx

    8. abp_dk Says:

      I faced this problem http://code.google.com/p/android/issues/detail?id=7939 on HTC Desire Api 2.2 since I used


      webView.getSettings().setAppCachePath("/data/data/com.your.package.appname/cache");

      as described in the article. Changing this to


      webView.getSettings().setAppCachePath("/data/data/[packagename]/cache");

      According to packagename in manifest

      This resolved the issue

    9. Novia Says:

      Thanks..

      but in my side, eveything works well except when broswer get new version of cache, always fire ‘Error’ event of appCache and should be fire ‘UpdateReady’ event. I can’t find out the reason. Anybody Help me?

      I am really green in android developing field.

    10. Novia Says:

      Any help is greatly appreciated. :)

    11. derp Says:

      Tack random crap!

    12. Astro Says:

      Thanks!!!!!!

      very very helpful~!! :)

    13. Thorsten Says:

      Instead to hardcode the cache dir it’s suggested to use getCacheDir() from the Context class (resp. getExternalCacheDir()).

    14. Kevin Hawkins Says:

      This has been a tremendous help, so thank you!

      A couple of things. First, since I abhor literal paths coded into applications, I had to find another way to get the app cache path. ;-) I’d recommend this update:

      String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
      webView.getSettings().setAppCachePath(appCachePath);

      Second, the WebChromeClient updates aren’t strictly necessary to make caching work. Of course, if you run into situations where your cache *does* get maxed out, making the WebChromeClient additions is a more robust way to handle that situation. But I was able to get by without it.

      Thanks again!

    15. godexsoft Says:

      Hi Kevin, I’m glad my post helped you out.
      Thanks for the update suggestion. I merged your code into the post :)

      Cheers,
      Alex

    16. Jordan Says:

      showDialog(DIALOG_NONETWORK); has been depreciated

      thanks for all the help!

    17. Laysa Says:

      Thanks! Included this into my code. Hope it works well with Android 4.0

    18. Ryan Gravener Says:

      I was doing this and for the life of me could not figure out why it wasn’t working.

      I magically found this: http://www.w3.org/TR/html5/offline.html#manifests

      It told me that my webserver mimetype for manifest needs to be supported. So if it isn’t working, make sure your content type for your manifest is text/cache-manifest

      you can see by doing curl -I “http://yourmanifestsite/cache.manifest”

    19. Marco Seguri Says:

      Thank you so much!

    20. joe Says:

      Is there a way for me to package a prepopulated cache in my apk? I’d like the app to work the first time even with no connectivity.
      thanks!

    21. Liam Says:

      I had a WebView project that wasn’t working until I put the following lines in:

      String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
      webView.getSettings().setAppCachePath(appCachePath);

      Thanks very much for the info – not sure why those lines are required, but they work.

    22. José Victor Says:

      I need some help here.

      I’ve tried to use the follow code, but it just work on Android Jelly Bean, and I need it work on Android Gingerbread.

      wv = (WebView)findViewById(R.id.webview);

      wv.setWebChromeClient(new WebChromeClient() {
      @Override
      public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,QuotaUpdater quotaUpdater)
      {
      quotaUpdater.updateQuota(spaceNeeded * 2);
      }

      public void onProgressChanged(WebView view, int progress)
      {
      activity.setTitle(“Loading…”);
      activity.setProgress(progress * 100);

      if(progress == 100) {
      activity.setTitle(R.string.app_name);
      }
      }
      });

      wv.getSettings().setDomStorageEnabled(true);
      // Set cache size to 8 mb by default. should be more than enough
      wv.getSettings().setAppCacheMaxSize(1024*1024*8);
      String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
      //wv.getSettings().setAppCachePath(“/sdcard/batema/cache”);
      wv.getSettings().setAllowFileAccess(true);
      wv.getSettings().setAppCacheEnabled(true);
      wv.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);

      wv.getSettings().setJavaScriptEnabled(true);
      wv.getSettings().setBuiltInZoomControls(true);
      wv.getSettings().setLoadsImagesAutomatically(true);
      wv.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

      //wv.getSettings().setAppCachePath(“/data/data/com.teste.android/cache”);

      // Load the url inside the webview, and not in the external browser
      wv.setWebViewClient(new WebViewClient());
      wv.loadUrl(“http://www.google.com”);

      btn_go = (Button)findViewById(R.id.btn_go);
      btn_go.setOnClickListener(new View.OnClickListener(){

      @Override
      public void onClick(View v) {
      Log.d(“tag_teste”,”Clicou”);

      if(cm.getActiveNetworkInfo() != null) {
      Log.d(“tag_teste”,”Com Conexão” + cm.getActiveNetworkInfo().isConnected());
      wv.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
      wv.loadUrl(“http://www.terra.com.br”);
      }
      else {
      Log.d(“tag_teste”,”Sem Conexão” + cm.getActiveNetworkInfo());
      wv.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
      wv.loadUrl(“http://www.terra.com.br”);
      }
      }

      });

      Does anyone have any ideia?

      Thanks.

    23. Kade Hafen Says:

      Thank you

    24. bb Says:

      I have created mvc4 site using html5 cache
      I want that android webview can view mypage offline
      only my phone google chrome can work
      how webview work like google chrome

    25. Noom Says:

      Thank so much
      this 2 lines made my day

      String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
      webView.getSettings().setAppCachePath(appCachePath);

    26. Harry Says:

      Is it the case that any HTTP Request from Java code is also cached? OR its only the URLs loaded from the WebView?

      I need to cache couple of URLs without actually loading the URLs in WebView. Any solution?

    Leave a Comment