Saturday, February 28, 2009

How to display a local file in the Android WebView

For some reason WebView is not displaying local files found on sdcard or in other directories of an Android phone. It was doing so prior to the official 1.0 SDK release, but not anymore.
There was another way to display local files - by overriding WebViewClient.shouldOverrideUrlLoading, but since 1.0 SDK it's gone.

Right now community agrees that a relatively easy way to make WebView display local files is through a custom ContentProvider.

It's not quite obvious what needs to be overridden for this specific task. I couldn't find a complete example online. So I decided to share mine.

There are 2 steps
  1. Create new class that extends ContentProvider and override essentially 1 method openFile. Method onCreate can be empty returning true and the rest can just throw UnsupportedOperationException.
  2. Add a provider entry to AndroidManifest.xml.
Provider implementation

Note, that method constructUri is just a utility method that can be used to construct provider specific URI out of a file path.

package com.tourizo.android.content;

import java.io.*;

import android.content.*;
import android.database.*;
import android.net.*;
import android.os.*;

public class LocalFileContentProvider extends ContentProvider {
private static final String URI_PREFIX = "content://com.tourizo.android.localfile";

public static String constructUri(String url) {
Uri uri = Uri.parse(url);
return uri.isAbsolute() ? url : URI_PREFIX + url;
}

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = new File(uri.getPath());
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return parcel;
}

@Override
public boolean onCreate() {
return true;
}

@Override
public int delete(Uri uri, String s, String[] as) {
throw new UnsupportedOperationException("Not supported by this provider");
}

@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("Not supported by this provider");
}

@Override
public Uri insert(Uri uri, ContentValues contentvalues) {
throw new UnsupportedOperationException("Not supported by this provider");
}

@Override
public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
throw new UnsupportedOperationException("Not supported by this provider");
}

@Override
public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
throw new UnsupportedOperationException("Not supported by this provider");
}

}



AndroidManifest.xml entry under application tag

        <provider android:name=".content.LocalFileContentProvider"
android:authorities="com.tourizo.android.localfile"
/>


I hope this was helpful.

Happy hacking! :)

11 comments:

  1. Hi, thanks for sharing! I've been searching everywhere for this information. But I think there is one missing step: how exactly do you load the html file into the webview? webview.loadUrl('???:???') How does it refer to the sdcard? And is there an 8kb limit? I want to show some html documentation which is downloaded from the server and stored in the sdcard. It can change over time, so I don't want to embed it as resources. Strange that one needs to 'hack' to display local html files.

    ReplyDelete
  2. Abe, for an example for how to hook it up with WebView, see

    http://www.techjini.com/blog/2009/01/10/android-tip-1-contentprovider-accessing-local-file-system-from-webview-showing-image-in-webview-using-content/

    I think if your HTML data can have a link like

    <img src=content://com.tourizo.android.localfile/foo.jpg>

    ReplyDelete
  3. Hello, about this, what do I need to change to access files on the internal memory of the phone? Like in the cache folder of my application for instance.. is that possible?

    Thank you

    ReplyDelete
  4. Hello, any one explain me how to use the code in my project . which method i want to call or any one provide sample app code with contentprovider .

    Thanks,

    ReplyDelete
  5. "AndroidManifest.xml entry under application tag" is a little confusing. It better says "AndroidManifest.xml entry inside application tag".

    ReplyDelete
  6. If you just want to read a file you have embbed in you .apk, there is a much simpler way to do it.

    Just add your file in a folder named "assets" in your project
    and you can get your file at the url:
    file:///android_asset/your_file
    sample code here:
    http://developer.android.com/resources/articles/using-webviews.html

    ReplyDelete
  7. final String mimeType = "text/html";
    final String encoding = "utf-8";
    WebView wv;
    try {
    InputStream is = getAssets().open("test.html");
    int size = is.available();
    // Read the entire asset into a local byte buffer.
    byte[] buffer = new byte[size];
    is.read(buffer);
    is.close();
    // Convert the buffer into a string.
    String strContent = new String(buffer);

    wv = (WebView) findViewById(R.id.webview);
    wv.loadData(strContent, mimeType, encoding);
    } catch (IOException e) {
    // Should never happen!
    throw new RuntimeException(e);
    }
    }

    ReplyDelete
  8. Hi,

    thanks for the write-up. We have this method in our code for images and it works. What I did see in the log was that first Android tries to call query() on the content provider. This throws the expected UnsupportedOperationException, but after that everything works fine. I guess Android calls query() first and then openFile(). Is there a way to prevent this? So Android only calls openFile()?

    Cheers

    Marc

    ReplyDelete
  9. how can i display only a string in web view.(not read from file ).

    ReplyDelete