saving-the-android-webview-cache-on-the-sd-card

It could be useful to save the cache of an Android WebView on the SD card (or integrated external memory) especially for devices with a limited amount of internal memory, but how can you do that? Well, it’s simple. You know that some Android browsers already do it and here I’m going to tell you a way to do it in your own app as well. This solution is made to work with Android 2.1 and higher, so I’m not going to use the API that was introduced only in a later version of the OS.

The key is the getCacheDir method of the ContextWrapper class. This is what is used by the cache manager to decide where to store the cache files. The behavior is slightly different for Android 2.1 and Android 2.2 and higher, so we’ll also keep this in mind.

I’ll explain everything going through the source code of the example application that you can download through the link on top of this post. Let’s start with the mainfest file:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
  package="com.devahead.androidwebviewcacheonsd"
  android:versionCode="1"
  android:versionName="1.0">
 
  <uses-sdkandroid:minSdkVersion="7"/>
   
  <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-permissionandroid:name="android.permission.INTERNET"/>
  <uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/>
     
  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:name="com.devahead.androidwebviewcacheonsd.ApplicationExt">
    <activity
      android:name=".MainActivity"
      android:label="@string/app_name">
      <intent-filter>
        <actionandroid:name="android.intent.action.MAIN"/>
        <categoryandroid:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
    <activity
      android:name=".SecondActivity"
      android:label="Second activity"/>
  </application>
 
</manifest>

Here we have the permissions to write to the SD (the external storage) and access the web. We also have a custom Application class extension called ApplicationExt and a couple of activities,MainActivity and SecondActivity.

The ApplicationExt class is where most of the things are done to have the cache on the SD:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
packagecom.devahead.androidwebviewcacheonsd;
 
importjava.io.File;
 
importandroid.app.Application;
importandroid.os.Environment;
 
publicclassApplicationExtextendsApplication
{
  // NOTE: the content of this path will be deleted
  //       when the application is uninstalled (Android 2.2 and higher)
  protectedFile extStorageAppBasePath;
   
  protectedFile extStorageAppCachePath;
 
  @Override
  publicvoidonCreate()
  {
    super.onCreate();
 
    // Check if the external storage is writeable
    if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
    {
      // Retrieve the base path for the application in the external storage
      File externalStorageDir = Environment.getExternalStorageDirectory();
         
      if(externalStorageDir !=null)
      {
        // {SD_PATH}/Android/data/com.devahead.androidwebviewcacheonsd
        extStorageAppBasePath =newFile(externalStorageDir.getAbsolutePath() +
          File.separator +"Android"+ File.separator +"data"+
          File.separator + getPackageName());
      }
   
      if(extStorageAppBasePath !=null)
      {
        // {SD_PATH}/Android/data/com.devahead.androidwebviewcacheonsd/cache
        extStorageAppCachePath =newFile(extStorageAppBasePath.getAbsolutePath() +
          File.separator +"cache");
   
        booleanisCachePathAvailable =true;
         
        if(!extStorageAppCachePath.exists())
        {
          // Create the cache path on the external storage
          isCachePathAvailable = extStorageAppCachePath.mkdirs();
        }
         
        if(!isCachePathAvailable)
        {
          // Unable to create the cache path
          extStorageAppCachePath =null;
        }
      }
    }
  }
   
  @Override
  publicFile getCacheDir()
  {
    // NOTE: this method is used in Android 2.2 and higher
     
    if(extStorageAppCachePath !=null)
    {
      // Use the external storage for the cache
      returnextStorageAppCachePath;
    }
    else
    {
      // /data/data/com.devahead.androidwebviewcacheonsd/cache
      returnsuper.getCacheDir();
    }
  }
}

In the onCreate method we start by checking if the external storage is actually mounted and we can write on it, then we build the base path of the app on the external storage. This path is{SD_PATH}/Android/data/com.devahead.androidwebviewcacheonsd becausecom.devahead.androidwebviewcacheonsd is the package declared in the manifest file and using this path structure makes sure that the entire path and all its content will be automatically deleted by Android 2.2 and higher in case the app is uninstalled avoiding to have garbage files on the SD. Note that this works only in Android 2.2 and higher, while in Android 2.1 the path will not be deleted by the OS so you’ll have to deal with it on your own. The full path for the cache files will be{SD_PATH}/Android/data/com.devahead.androidwebviewcacheonsd/cache, so the base path plus the cache directory. We must also make sure that the path is actually available before using it, so we create all the directories with the mkdirs method in case they don’t already exist.

Now that we have the cache path on the SD, we’re ready to use it and all we have to do is override the getCacheDir method inside our ApplicationExt class. This method is invoked by the cache manager when the application is started so basically we’re saying that the cache will be stored on the SD if it’s available and writeable, while we use the default path in case we’re allowed to use only the internal memory. Android stores all the cache data for our app in the/data/data/com.devahead.androidwebviewcacheonsd/cache directory by default on the internal memory.

We’re done with the implementation for Android 2.2 and higher, but what about Android 2.1? For that version of the OS the cache manager doesn’t use the getCacheDir method of the Applicationcontext, but it uses the one of the Activity context instead. So to make our solution work also with Android 2.1, we must override the getCacheDir method inside our activities.

To immediately understand how it works with Android 2.1, let’s take a look at the activities of the example application. We start with the MainActivity:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical">
 
  <Buttonandroid:id="@+id/startSecondActivityBtn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="Start second activity"/>
 
  <WebViewandroid:id="@+id/webView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>
 
</LinearLayout>

There’s simply a button to start the SecondActivity and a WebView to test our solution. The code forMainActivity looks like this:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
packagecom.devahead.androidwebviewcacheonsd;
 
importjava.io.File;
 
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.webkit.WebView;
importandroid.webkit.WebViewClient;
importandroid.widget.Button;
 
publicclassMainActivityextendsActivityimplementsOnClickListener
{
  protectedWebView webView;
  protectedButton startSecondActivityBtn;
   
  @Override
  publicvoidonCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
     
    webView = ((WebView)findViewById(R.id.webView));
    startSecondActivityBtn = ((Button)findViewById(R.id.startSecondActivityBtn));
 
    // Set the listener
    startSecondActivityBtn.setOnClickListener(this);
     
    // Initialize the WebView
    webView.getSettings().setSupportZoom(true);
    webView.getSettings().setBuiltInZoomControls(true);
    webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
    webView.setScrollbarFadingEnabled(true);
    webView.getSettings().setLoadsImagesAutomatically(true);
 
    // Load the URLs inside the WebView, not in the external web browser
    webView.setWebViewClient(newWebViewClient());
 
    webView.loadUrl("http://www.google.com");
  }
 
  @Override
  protectedvoidonDestroy()
  {
    // Clear the cache (this clears the WebViews cache for the entire application)
    webView.clearCache(true);
     
    super.onDestroy();
  }
   
  @Override
  publicFile getCacheDir()
  {
    // NOTE: this method is used in Android 2.1
     
    returngetApplicationContext().getCacheDir();
  }
 
  @Override
  publicvoidonClick(View v)
  {
    if(v == startSecondActivityBtn)
    {
      Intent intent =newIntent(this, SecondActivity.class);
      startActivity(intent);
    }
  }
}

As you can see, the getCacheDir method inside the activity simply calls the same method in theApplication context, that means that the ApplicationExt method is called. We’ll have to do this for every activity in the app to make sure the cache path is redirected correctly in Android 2.1, but actually in my tests I found that it looks like the cache manager calls the getCacheDir method inside the first activity that initializes a WebView (if MainActivity doesn’t use a WebView, but onlySecondActivity does, then the getCacheDir method of SecondActivity will be called by the cache manager and not the one of MainActivity), anyway I think it’s a good idea to make sure the cache directory is consistent across all the activities of the application.

In MainActivity there’s a button that starts SecondActivity. This is just another activity with aWebView that I used to test the calls to the getCacheDir method in Android 2.1 and see what happens if there’s a WebView also in MainActivity or not. This is the layout of SecondActivity:

?
01
02
03
04
05
06
07
08
09
10
11
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical">
 
  <WebViewandroid:id="@+id/webView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>
 
</LinearLayout>

And this is its source code:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
packagecom.devahead.androidwebviewcacheonsd;
 
importjava.io.File;
 
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.webkit.WebView;
importandroid.webkit.WebViewClient;
 
publicclassSecondActivityextendsActivity
{
  protectedWebView webView;
   
  @Override
  publicvoidonCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second);
     
    webView = ((WebView)findViewById(R.id.webView));
     
    // Initialize the WebView
    webView.getSettings().setSupportZoom(true);
    webView.getSettings().setBuiltInZoomControls(true);
    webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
    webView.setScrollbarFadingEnabled(true);
    webView.getSettings().setLoadsImagesAutomatically(true);
 
    // Load the URLs inside the WebView, not in the external web browser
    webView.setWebViewClient(newWebViewClient());
 
    webView.loadUrl("http://www.google.com");
  }
 
  @Override
  publicFile getCacheDir()
  {
    // NOTE: this method is used in Android 2.1
     
    returngetApplicationContext().getCacheDir();
  }
}

It looks similar to the one of MainActivity and we override the getCacheDir method here as well. The main difference is that in MainActivity the cache of the WebViews is cleared inside theonDestroy method. Remember that whenever you call the clearCache method you’re actually clearing the cache for all the WebViews of your application, not only the one that you used to invoke the method, so you must implement your own logic to clear the cache depending on the structure of your app.

This is all you have to do to write the cache of an Android WebView to the external memory. Remember that you’ll probably have to manage also the case when the external storage is unmounted during your app extecution by detecting the event and maybe giving a warning to the user. I hope you found this post useful and in case you’ve got more suggestions about this topic, feel free to write a comment.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值