Under the Hood of Native Web Apps for Android

Web Development for Mobile Devices is the latest rage. But what if you want an "off-line" web application? No problem!

The HTML

Here is the html used in our application — as you can see, it is really very simple. Despite all the mention of CSS, we are not actually using any for this application! If you want this to be spiffy, you are welcome to download the code and add a style-sheet — our focus here is on making things run under the hood — not worried about the paint today.

<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=0.25,user-scalable=yes" />
<title>Going Native</title>
</head>
<body onload="window.linuxmag.Info('App loaded!');">
<center>
<h3>Going (Web) Native</h3>
<input type="text" id="ib"><br />
<button onclick="window.linuxmag.Info(document.getElementById('ib').value);">Log Info</button>  
<button onclick="window.linuxmag.Error(document.getElementById('ib').value);">Log Error</button><br />
<button onclick="if (window.confirm('End App?')) window.linuxmag.EndApp();">Kill This App</button><br />
<img src="file:///android_asset/lmlogo.jpg">
</center>
</body>
</html>

Aside from the basic html code, the thing to make not of is the Javascript code which includes “window.linuxmag.*”. This code is essentially exercising three new functions we have mapped into the browser’s Javascript environment: Info, Error, and EndApp, all part of the “linuxmag namespace”.

Note also the link to the logo image file — it is using a file:///android_asset based url. The android_asset folder maps to the “assets” folder in the development project — refer to the image above to see the lmlogo.jpg sitting in the assets folder. We will see this url construction used again when we look at the Java code, which is up next.

The Java

The Java code provides three basic items to our application:

  • The Java code makes our application “native” — one of our primary goals here.
  • An instance of a WebView view in our one and only Android Activity, defined in main.xml.
  • Implementation of the “linuxmag” functions demonstrated in the HTML/Javascript shown above.

We will look at the java source file in three sections. First the imports:

package com.msi.linuxmag.goingnative;

import android.app.Activity;
import android.os.Bundle;

import android.webkit.WebView;
import android.webkit.WebSettings;
import android.webkit.WebChromeClient;

import android.util.Log;

We see that we have an import statement for the boiler-plate Activity and Bundle classes. Next we have three import statements for three explicit classes within the android.webkit package. Note that we could also have used a single import android.webkit.* statement to replace those three lines. Lastly, we have an import statement for the Logging facility used by our application.

Now for the MainAct Activity class implementation, which includes a fair number of comments to walk us through what the code is doing.

public class MainAct extends Activity {
    /** Called when the activity is first created. */
    private WebView browser = null;
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // connect to our browser so we can manipulate it
        browser = (WebView) findViewById(R.id.mybrowser);

        // get settings so we can config our WebView instance
        WebSettings settings = browser.getSettings();

        // JavaScript?  Of course!
        settings.setJavaScriptEnabled(true);
        // clear cache
        browser.clearCache(true);

        // this is necessary for "alert()" to work
        browser.setWebChromeClient(new WebChromeClient());

        // add our custom functionality to the javascript environment
        browser.addJavascriptInterface(new MyCoolJSHandler(), "linuxmag");

        // load a page to get things started
        browser.loadUrl("file:///android_asset/index.html");

    }

    final class MyCoolJSHandler () // defined later in this article
}

Here is a quick and dirty run-down of what is happening in the onCreate() method.

  • Call the super class’s onCreate() method [ boiler-plate code]
  • Setup the view with a call to setContentView [ boiler-plate code]
  • Connect an instance of a WebView control to the WebView control defined in the main.xml file, shown at the end of this article
  • Get the settings object
  • Enable Javascript!
  • Clear the cache — very helpful during development
  • Setup our WebChromeClient. This is needed for certain functions like alert and confirm. You may want to check out the online documentation on this class.
  • Ahh, the magic — setup a Javascript interface between the browser and a class we define named MyCoolJSHandler, relating them via the namespace “linuxmag”
  • Load up our statically provided index.html file

Let’s see what goes on in the Javascript handler.

	final class MyCoolJSHandler
	{
		// write to LogCat (Info)
		public void Info(String str) {
			Log.i("GoingNative",str);
		}

		// write to LogCat (Error)
		public void Error(String str) {
			Log.e("GoingNative",str);
		}

		// Kill the app
		public void EndApp() {
			finish();
		}
	}

This class is surprisingly simple — just define functions with a public signature and this function is exposed in the javascript environment, thanks to the earlier call to addJavascriptInterface() on the WebView instance.

A call to window.linuxmag.Info(“some string”) in Javascript is mapped to the Info method of the MyCoolJSHandler class, anonymously created in this example. This implementation just drops a line in the Log with a level of “I” for Info.

The Error and EndApp functions similarly exercise native API calls. However, this could go much further and interact with the Contacts database, the Camera, a raw socket connection, etc. This is a super simplification, but in essence, this is what the folks at PhoneGap are doing — mapping Javascript-accessible code into native Java code.

Let’s wrap up with a couple of necessary xml files that are part of our project. First main.xml, which defines the layout for our Activity. In this case we have a single WebView in a LinearLayout. In a more sophisticated application, this layout might include an “Address Bar” to accompany the WebView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

 <WebView
      android:id="@+id/mybrowser"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:layout_weight="1"
  />
</LinearLayout>

As for all Android native applications, we need an AndroidManifest.xml file. This file provides the linkages and permissions that are required for an application, including a special permissions entry for applications which want to access resources from the Internet. This is required in the event that you want to link to data beyond the local device.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.msi.linuxmag.goingnative"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainAct"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-sdk android:minSdkVersion="5" />
     <uses-permission android:name="android.permission.INTERNET" />
</manifest>

So that’s pretty much it. In honor of our “EndApp” function, wrapped in a window.confirm() in the code, we’ll end with a screen shot of ending the application. I hope you learned something along the way. If this was of interest to you, please let me know.

confirm.png

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62