Cdacians

Cdacians
Cdacians

Tuesday 21 August 2012

Android WebView (WebKit) Tutorial


Android WebView (WebKit) Tutorial

Posted August 11th, 2008 by 

Introduction

This document outlines the capabilities and limitations of the Webkit canvas component in Android called WebView. Webkit is the open source browser engine that’s used in Apple’s Safari.

Android 101

To get started with Android, click here.

Limitations

As of M5 SDK, the Webkit canvas/component isn’t very mature. Lots of features/functionality are planned, but aren’t implemented yet. It seems that the Android team spent a lot of time building their browser, but not enough time working on the Webkit component (WebView) that developers can use in their applications. An example of the limitations with the M5 SDK is the following: it’s not easy to intercept events that are generated by the WebView component, things like intercepting loading a URL in your application. By default, the WebView component it’s resources over the web, and you just point it to a URL. However, it is possible to override this default behavior and have the HTML code generated in the device itself, by an application that’s running on the device. However, in the M5 SDK there are severe limitations on what events can be intercepted and what behaviors can be overloaded, which limits how this Webkit component can be customized, and it’s default behaviors changed.

What is possible

Having covered the limitations, there are plenty of things that can be done. Here’s a list:
  1. HTML can be loaded over the network, just like a normal web browser.
  2. HTML can be downloaded over the web, and saved to the Android device. This HTML can then be loaded into the WebView component.
  3. HTML code can be statically created and saved in the APK file (ie, pre-loaded in the application file, in the res/ folder), and it can be loaded in the WebView component on the device itself, without any network connectivity.
  4. It is possible for the HTML code running in the WebView component to reference assets (images, videos, etc. in the assets/ folder) that are stored on the device itself. These assets can be pre-loaded with the APK file.
  5. Also, assets can be downloaded from the network, and then saved to the device itself – these downloaded assets can then be referenced in the HTML, eg: <img src=”/data/data/com.developerlife/test.png”></img>. In this example, the downloaded file is saved in the /data/data/$package_name$ folder on the device.
  6. HTML doesn’t just have to be statically pre-loaded on the device, or downloaded from the network. HTML code can be generated on the device itself, and this can be rendered on the device itself. There are some very interesting possibilities with taking this approach. It’s possible to leverage APIs on the device itself, eg, to get current location and then display it in the WebView component.
  7. It’s possible to intercept various events in the WebView component to overload any of it’s default behaviors. One important behavior to overload is defining what happens when a URL is selected. “Fake” URLs can be generated that are serviced locally, while “normal” URLs can be used to load data from the network. There are lots of other events that can be overloaded to customize what the WebView component does, or what operations are performed when the component is loaded or viewed, etc.

Layout of WebView

It’s best to let the WebView component take up the full space available in it’s parent ViewGroup object. It’s not a good idea to have it wrap, based on it’s size. When you create a WebView component, it doesn’t know it’s size until it loads HTML into it. This is why it’s best to set a size on the WebView component, when placing it into a ViewGroup, and then loading HTML into it. Otherwise there are issues with the component being displayed correctly.

Source code examples

The following example does the following:
  1. When the example is executed in the emulator, the first thing it does is download a PNG file from the Web and saves it to the device. This image is used in HTML that’s displayed in this example.
  2. Some of the assets are pre-loaded in the APK file from assets and resources.
  3. The HTML is loaded from the APK file itself (it’s a pre-loaded asset). When you run the program, you will also encounter a bug in the m5 SDK – when you click on the link, it’s supposed to generate HTML locally, but you will see that it won’t work… this is hopefully going to be fixed in the next SDK release.

Downloading files from the network and accessing them in HTML

Here’s the code that downloads a PNG file in the background when an Activity is started. This is then referenced in HTML that’s then displayed in the WebView component.
@Override
public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  try {

    Log.i(Global.TAG, "System.property(user.dir)=" + System.getProperty("user.dir"));

    // download an image from the web... in the background
    {
      Runnable getImage = new Runnable() {
        public void run() {
          // get the image from http://developerlife.com/theblog/wp-content/uploads/2007/11/news-thumb.png
          // save it here (user.dir/FILENAME)
          // file is saved here on emulator - /data/data/com.developerlife/files/file.png
          try {
            Log.i(Global.TAG, "MainDriver: trying to download and save PNG file to user.dir");
            HttpClient client = new HttpClient();
            GetMethod get = new GetMethod("http://developerlife.com/theblog/wp-content/uploads/2007/11/news-thumb.png");
            client.executeMethod(get);
            byte[] bRay = get.getResponseBody();

            FileOutputStream fos = openFileOutput(Global.FILENAME, Activity.MODE_WORLD_WRITEABLE);
            fos.write(bRay);
            fos.flush();
            fos.close();
            Log.i(Global.TAG, "MainDriver: successfully downloaded PNG file to user.dir");
          }
          catch (Exception e) {
            Log.e(Global.TAG, "MainDriver: could not download and save PNG file", e);
          }

        }
      };
      new Thread(getImage).start();
    }

  }
  catch (Exception e) {
    Log.e(Global.TAG, "ui creation problem", e);
  }

}
Notes on the code:
  1. When saving a file on the device, where does it get saved? If you get the value of the system property – “user.dir” it will tell you where the current user’s folder is on the device. When an app is installed on the device/emulator, Android gives that app it’s own “user folder”. This is where it’s files are going to be saved by default.
  2. “user.dir” resolves to /data/data/$package_name$, where $package_name$ is the name of the package in the AndroidManifest.xml file for the APK file that you are assembling. Here’s an example:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.developerlife">
  3. In this example, HttpClient is used to download a PNG file using HTTP GET, and then save the byte[] of the PNG to a file on the device. The Context class’s openFileOutput() method is used to actually create a FileOutputStream object to save the PNG file with. You just have to pass it:
    1. a filename (which in this case is file.png),
    2. and the “mode”, which in this case is “make it writeable for any app on the device”.
  4. The code that performs this is executed in a background thread, so as not to tied up the UI thread, since this code is executed in the onCreate() method of an Activity. This is just done as an example, in a real app, you would probably have this downloaded via a Service.
Need more help? developerlife.com offers training courses to empower you, and consulting services to enable you.
Now that you’ve seen the code to download a PNG file and save it to the “user.dir”, here’s the HTML that references it. Also, this HTML is stored in the /res/raw/ folder as “content.xml”. I will show you how to load it into a WebView component next.
<html>

  <head>

    <title>test</title>

  </head>

  <body>

    Here's an image
    <img height="42" width="42" src="file:///data/data/com.developerlife/files/file.png"></img>
    loaded from the user.dir folder,
    which was downloaded from the web and saved to the device, when this app started.

  </body>

</html>
Notes on the HTML:
  1. To reference the “file.png” in an img tag, I have to use the file URI, and use /data/data/$package_name$/files/$actual_file$.
  2. After downloading the PNG file, you can run the SDK’s DDMS tool, which has a device filesystem browser, to see where this file actually gets stored.
Here’s the Java code to load this HTML into a WebKit component:
/** put some content into the webview, etc. */
private static void initWebkit(WebView web, WidgetActivity activity) {

  final String mimetype = "text/html";
  final String encoding = "UTF-8";
  String htmldata = "<html><body>boo</body></html>";

  {
    String data = ResourceUtils.loadResToString(R.raw.content, activity);
    if (data != null) htmldata = data;
  }

  web.loadData(htmldata,
               mimetype,
               encoding);

  Log.i(Global.TAG, "created webkit, configured it to intercept URL, and loaded webkit data:" + htmldata);

}
Notes on the code:
  1. The ResourceUtils class (from AndroidUtils.zip) is used to actually expand the content.xml file in resources, and then turn it into a String.

Creating HTML that references resources bundled in the APK file itself

The previous example showed HTML used to reference a file that is downloaded from the network and saved to the filesystem. This example will show you how to access resources that are in the res/ folder of the APK file of the application that’s loading HTML into the WebView component. The file “card.png” is loaded in the res/drawable/ folder. This file is going to be accessed in the img tag in some HTML, which will then be loaded into the WebView component. Here’s the HTML:
<html>

  <head>

    <title>test</title>

  </head>

  <body>

    Here's an image
    <img height="42" width="42" src="file:///android_asset/card.png"></img>
    loaded from the assets folder (preloaded with the app, not a compiled resource).
  </body>

</html>
Notes on the code:
  1. This HTML is saved in “content.xml”, in the res/raw/ folder, similar to the previous example.
  2. To reference an asset from the APK file, use the file URI, and point it to “/android_asset/$filename$”, where the filename is what you want to reference. Unlike the example where the file was downloaded from the network, there’s no need to reference the user.dir in this example.
The Java code to load this HTML into a WebKit component is the same as in the example before.

Intercepting URI selection

Let’s say that you want to create some HTML that has a URI in it, which does not resolve to a network URI. Let’s say this URI should be intercepted by the WebKit component that you’re using, and it should do something that you’ve defined when you click it. Here’s the code to do this:
/** put some content into the webview, etc. */
private static void initWebkit(WebView web, WidgetActivity activity) {

  final String mimetype = "text/html";
  final String encoding = "UTF-8";
  String htmldata = "<html><body>boo</body></html>";

  {
    String data = ResourceUtils.loadResToString(R.raw.content, activity);
    if (data != null) htmldata = data;
  }

  web.loadData(htmldata,
               mimetype,
               encoding);

  web.setWebViewClient(new WebViewClient() {
    @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
      Log.i(Global.TAG, "Panel2Builder... webview URL:" + url);

      if (url.contains("clock")) {
        String html = "<html><body>Date:" + new Date().toString() + "</body></html>";
        view.loadData(html, mimetype, encoding);
        Log.i(Global.TAG, "Panel2Builder intercepting webview url click...");
        return true;
      }
      else {
        Log.i(Global.TAG, "Panel2Builder not processing webview url click...");
        return false;
      }

    }
  });

  Log.i(Global.TAG, "created webkit, configured it to intercept URL, and loaded webkit data:" + htmldata);

}
Notes on the code:
  1. The WebViewClient class is overriden to create something that will intercept any URI selections in the WebView component that you’re using to load HTML into.
  2. In this case, if the URI contains the word “clock” then some HTML is generated and loaded into the WebView component.
Here’s the HTML:
<html>

  <head>

    <title>test</title>

  </head>

  <body>

    <a href="/clock">click here to see date (this doesnt work in m5 sdk)</a>.

  </body>

</html>
The Java code to load this HTML into the WebKit component is the same as before.

No comments:

Post a Comment