Wednesday, June 8, 2011

Adding Local Images to An Android Webview

This tutorial demonstrates how to download a web page from the internet, and then modify that web page to use local graphics resources before displaying to the user.

Why Use Local Images?

Perhaps you want your app to dynamically load data from the internet, but you frequently want to display the same images. Using local images can be a good way to cut down on the amount of bandwidth needed for an application and could improve performance.

Before We Start

If you haven't all ready, please take a look at the previous tutorial, displaying a static web page in android.This tutorial will be building off of that.


Creating a Download Helper Class

Rather than just load a http url into the webview, we will want to download the source ourselves, and then modify it so it contains references to our local images.

I'm not going to go into detail about how the AndroidDownloadHelper class works in this tutorial. The source is pasted below.

package com.androiddom.webview;


import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;

import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;

public class AndroidDownloadHelper {

 public static final int BUFFER_SIZE = 2000;

 public static String downloadText(String url) throws MalformedURLException,
   ProtocolException, IOException, Exception {
  StringBuilder strBuild = new StringBuilder();

  // Open the connection
  InputStream in = null;
  in = openHttpConnection(url);

  // Verify that the stream was created correctly
  if (in == null) {
   return "";
  }

  // Read the input
  InputStreamReader reader = new InputStreamReader(in);
  int charsRead = 0;
  char[] inputBuffer = new char[BUFFER_SIZE];
  while ((charsRead = reader.read(inputBuffer)) > 0) {
   strBuild.append(String.valueOf(inputBuffer, 0, charsRead));
  }

  return strBuild.toString();
 }

 public static BitmapDrawable downloadDrawable(String url)
   throws MalformedURLException, ProtocolException, IOException,
   Exception {
  BitmapDrawable bd = null;

  InputStream in = null;
  in = openHttpConnection(url);

  // Verify that the stream was created correctly
  if (in == null)
   return null;

  bd = new BitmapDrawable(BitmapFactory.decodeStream(in));

  return bd;
 }

 public static InputStream openHttpConnection(String urlString)
   throws MalformedURLException, ProtocolException, IOException,
   Exception {

  // Initialize the variables
  InputStream in = null;
  int response = -1;

  // Build a url object
  URL url = new URL(urlString);
  URLConnection urlConn = url.openConnection();

  // Verify that we have an http connection
  if (!(urlConn instanceof HttpURLConnection))
   throw new Exception("Only HTTP Connections allowed");

  // Set some connection parameters
  HttpURLConnection httpConn = (HttpURLConnection) urlConn;
  httpConn.setAllowUserInteraction(false);
  httpConn.setInstanceFollowRedirects(true);
  httpConn.setRequestMethod("GET");
  httpConn.connect();

  // Check the response
  response = httpConn.getResponseCode();
  if (response == HttpURLConnection.HTTP_OK) {
   in = httpConn.getInputStream();
  }

  return in;
 }

}

Create a Page to Download

I created a special page on this blog for this tutorial http://www.androiddom.com/p/smiley-man-test-page.html If you click on this link you will see the words IMG1 at the bottom. Our Android App is going to look for those words, and then replace it with the picture of the smiley man from the previous tutorial.

Download the Page

The code below will download the WebPage to a single String. From there we can manipulate the data before we pass it off to the WebView.

try {
 String webPageString = AndroidDownloadHelper.downloadText("http://www.androiddom.com/p/smiley-man-test-page.html");

} catch (Exception e) {
 e.printStackTrace();
}

Replace the Token

We create a StringBuilder to store the modified web page. Then we look for the IMG1 tag, replace it with code to reference our asset, and load the new webpage into the WebView.

StringBuilder builder = new StringBuilder();
String replaceToken = "IMG1";

// Find the position of the token
int index = webPageString.indexOf(replaceToken);

// Copy up to the token
builder.append(webPageString.substring(0, index));

// Append our local image
String imageLocation = "file:///android_asset/smileyguy.png";
builder.append("<img src=\"" + imageLocation + "\" />");

// Skip past the token and copy the rest of the page
builder.append(webPageString.substring(index+replaceToken.length()));

// Load the data
webview.loadDataWithBaseURL("fake://seeJavaDocForExplanation/",builder.toString(),"text/html","UTF-8", "")

Why Is There That Fake://... as the Base URL

Because it has to be, even though it means nothing. This is what the JavaDoc for that function says.

Note for post 1.0. Due to the change in the WebKit, the access to asset files through "file:///android_asset/" for the sub resources is more restricted. If you provide null or empty string as baseUrl, you won't be able to access asset files. If the baseUrl is anything other than http(s)/ftp(s)/about/javascript as scheme, you can access asset files for sub resources.
And You're Done

This tutorial demonstrates the basic idea behind using the WebView to fetch an external page but display with local graphics. It should also be noted that the images don't have to be assets in your project. You could also load an image from the SD card.

Screenshot of the App running


The Full Source

The full source for the Activity is listed below.

package com.androiddom.webview;


import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.webkit.WebView;

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        WebView webview = (WebView) findViewById(R.id.webview);
        
        // webview.loadUrl("file:///android_asset/testpage.html");
        try {
   String webPageString = AndroidDownloadHelper.downloadText("http://www.androiddom.com/p/smiley-man-test-page.html");
   StringBuilder builder = new StringBuilder();
   String replaceToken = "IMG1";
   
   // Find the position of the token
   int index = webPageString.indexOf(replaceToken);
   
   // Copy up to the token
   builder.append(webPageString.substring(0, index));
   
   // Append our local image
   String imageLocation = "file:///android_asset/smileyguy.png";
   builder.append("<img src=\"" + imageLocation + "\" />");
   
   // Skip past the token and copy the rest of the page
   builder.append(webPageString.substring(index+replaceToken.length()));
   
   // Load the data
   webview.loadDataWithBaseURL("fake://seeJavaDocForExplanation/",builder.toString(),"text/html","UTF-8", "");
   
  } catch (Exception e) {
   e.printStackTrace();
  }
       
    }

6 comments:

  1. Good information. Which is more efficient and effective. Storing images the way you have here or storing it in sqlite?

    ReplyDelete
  2. I guess I'm not sure which is more efficient. I do not know enough about the inner workings of SQLite.

    Personally, I would probably go with storing the image files on the file system. These separate files would be easy to create, open, and delete.

    And I would guess that I would probably run into less problems when working with them. Debugging SQLite errors can sometimes be more difficult.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hi,

    I'm trying to use getUrl() to replace the 'downloadtext' value so it always apples to the current URL - but I'm a big noob and having problems. Can anyone advise how i can do this?

    ReplyDelete
  5. Not working :(

    try {
    String currentURL = webView.getUrl();
    String webPageString AndroidDownloadHelper.downloadText(currentURL);

    ReplyDelete
  6. what if i want to display image according to screen sizes.. i.e if i want to display html image from drawables..

    ReplyDelete