Tutorial: WebView
- Simple evaluation
- Evaluation of synchronous Javascript with return value
- Evaluation of asynchronous Javascript to obtain a value (problem)
- Create JavascriptInterface Java class
- Execute Ruby from Javascript
- Passing parameters from Javascript to Ruby
- Return values from Ruby methods to Javascript
- Returning values from Ruby to Javascript
This tutorial demonstrates how to use the Android WebView component to display and interact with local content. Communication between the activity (Ruby) and webview (Javascript) code. Topics covered include:
- Content assets and resources
- Execution of Javascript from activity Ruby
- Execution of synchronous and asynchronous Javascript
- Execution of activity Ruby from Javascript
- Passing and conversion of arguments between Ruby/Java/Javascript
- Returning and conversion of values between Ruby/Java/Javascript
- Tips: Naming, URLs, debugging, scaling content, life-cycle
You should have completed the Setting Up a Ruboto Development Environment tutorial.
This tutorial requires Ruboto version 1.3.0 or greater. Verify version:
$ ruboto --version
1.3.0
If necessary, download and install an up to date version of Ruboto.
Since Android 4.4 KitKat (API 19), the WebView component has been based on the Chromium browser.
http://developer.android.com/guide/webapps/migrating.html
This change upgrades WebView performance and standards support for HTML5, CSS3, and JavaScript to match the latest web browsers.
This tutorial is based upon API 19. Use the Android SDK manager to install API 19:
$ android sdk
Select SDK Platform
under the Android 4.4.2 (API 19)
branch and a system image if an emulator is needed. Install the packages.
The WebView is a view component that can load and display a web page. HTML, CSS, and Javascript are supported.
The Android developer documentation includes a useful guide to web apps and using WebView:
http://developer.android.com/guide/webapps/index.html
WebView API reference:
http://developer.android.com/reference/android/webkit/WebView.html
The documentation describes support classes that provide functionality for the WebView class:
This class is called when something that might impact a browser UI happens, for instance, progress updates and JavaScript alerts are sent here (see Debugging Tasks).
It will be called when things happen that impact the rendering of the content, eg, errors or form submissions. You can also intercept URL loading here (via shouldOverrideUrlLoading()).
Modifying the WebSettings, such as enabling JavaScript with setJavaScriptEnabled().
The WebChromeClient and WebViewClient classes are useful for implementing browser-like functionality. This tutorial uses the WebView to display and interact with local content, it does not cover their usage. This tutorial does utilize the WebSettings class to configure the WebView.
Create a Ruboto project targeting API 19, including JRuby jars:
$ ruboto gen app --package=org.ruboto.webviewtutorial --target=19
$ cd webviewtutorial
$ ruboto gen jruby 1.7.19
(See Tip "Naming issue" for package naming convention.)
Compile and install this default app, verify it runs.
Create or replace these files with contents...
For this tutorial the WebView is in the main activity layout.
Simple Android UI layout file for webview component to fill the activity view.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webview" />
</RelativeLayout>
Assets for use by webview are placed in an assets
directory in the project root. These files may be referenced from the activity with a URL beginning with: file:///android_asset/...
.
HTML file to be initially loaded by webview. Note the source URL for Javascript file specified with a relative path. This works to access other files in the assets
directories.
<html>
<head>
<script src="../js/tutorial.js"></script>
</head>
<body>
<h1>Webview Tutorial</h1>
<div id="tutorial-images" ></div>
<ul id='tutorial-list'> </ul>
</body>
</html>
Javascript file containing functions that will be called from the activity.
function add_item(text) {
var content = document.createTextNode(text);
var item = document.createElement('li')
var list = document.getElementById('tutorial-list')
item.appendChild(content)
list.appendChild(item)
return list.childElementCount
}
function load_image_return_size() {
var image = document.createElement('img')
var images = document.getElementById( 'tutorial-images' )
images.appendChild(image)
image.src = 'file:///android_res/drawable/get_ruboto_core.png'
return { 'width': image.width, 'height': image.height }
}
function remove_image() {
var images = document.getElementById( 'tutorial-images' )
images.removeChild(images.lastChild)
}
Ruboto app activity. Initializes webview and menu of actions that illustrate interaction between the activity and webview.
require 'ruboto/widget'
require 'ruboto/util/toast'
require_relative 'jscallback.rb'
class WebviewtutorialActivity
def onCreate(bundle)
super
set_title 'Webview Tutorial'
setup_webview
end
def on_create_options_menu menu
super
setup_menu menu
true
end
private
def setup_webview
android::webkit::WebView.web_contents_debugging_enabled = true
self.content_view = Ruboto::R::layout::webviewtutorial
@webview = self.find_view_by_id Ruboto::R::id::webview
set = @webview.settings
set.use_wide_view_port = true
set.load_with_overview_mode = true
set.java_script_enabled = true
set.loads_images_automatically = true
set.support_zoom = true # enable zoom
set.built_in_zoom_controls = true # includes pinch gesture
set.display_zoom_controls = false # do not display +/- zoom controls
@webview.load_url "file:///android_asset/html/tutorial.html"
end
def setup_menu menu
menu.add( 'Add item' ).set_on_menu_item_click_listener proc { eval_js_add_item ; true }
menu.add( 'Add item return count' ).set_on_menu_item_click_listener proc { eval_js_add_item_return_count ; true }
menu.add( 'Load image return size' ).set_on_menu_item_click_listener proc { eval_js_load_image_return_size ; true }
menu.add( 'Remove image' ).set_on_menu_item_click_listener proc { eval_js_remove_image ; true }
end
def eval_js_add_item
@webview.evaluate_javascript "add_item('#{Time.now}')", nil
end
def eval_js_add_item_return_count
@webview.evaluate_javascript "add_item('#{Time.now}')", Jscallback.new( self )
end
def eval_js_load_image_return_size
# ensure a cached image does not ruin this example
@webview.clear_cache true
@webview.evaluate_javascript "load_image_return_size()", Jscallback.new( self )
end
def eval_js_remove_image
@webview.evaluate_javascript "remove_image()", nil
end
end
Ruby class implementing Java interface ValueCallback
. Required to obtain a return value when evaluating Javascript in the webview from the activity.
require 'json'
class Jscallback
def initialize context
@context = context
end
def onReceiveValue json
android::util::Log.i "Webviewtutorial", "Jscallback#onReceiveValue: #{json}"
value = JSON.load( json, nil, symbolize_names: true )
@context.toast "Received value: #{value.inspect}"
end
end
To ensure changes to assets are included in package, clean old files before each build:
$ rake clean debug reinstall log
When run the app should display a simple white page with the title "Webview Tutorial" and a menu.
The actions on the activity menu illustrate how to evaluate Javascript in the webview using the WebView method evaluateJavascript
documented here.
Method WebviewtutorialActivity#eval_js_add_item()
in file ./src/webviewtutorial_activity.rb
passes an interpolated string of Javascript code to call function add_item(text)
. There is no intention to handle the return value so no callback object is provided (nil).
def eval_js_add_item
@webview.evaluate_javascript "add_item('#{Time.now}')", nil
end
The Javascript function add_item(text)
adds an item to a list in the HTML page.
function add_item(text) {
var content = document.createTextNode(text);
var item = document.createElement('li')
var list = document.getElementById('tutorial-list')
item.appendChild(content)
list.appendChild(item)
return list.childElementCount
}
Method WebviewtutorialActivity#eval_js_add_item_return_count
in file ./src/webviewtutorial_activity.rb
passes an interpolated string of Javascript code to call function add_item(text)
. The return value will be used so a callback object Jscallback.new( self )
is provided as required by evaluateJavascript
.
def eval_js_add_item_return_count
@webview.evaluate_javascript "add_item('#{Time.now}')", Jscallback.new( self )
end
The Javascript function add_item(text)
returns the size of the list after adding the item.
function add_item(text) {
var content = document.createTextNode(text);
var item = document.createElement('li')
var list = document.getElementById('tutorial-list')
item.appendChild(content)
list.appendChild(item)
return list.childElementCount
}
The callback object (see file ./src/jscallback.rb
) implements Android Java interface ValueCallback
documented here.
Method Jscallback.onReceiveValue(json)
in file ./src/jscallback.rb
accepts a json string provided by the webview containing the return value of the evaluated Javascript function. It is parsed and converted to a Ruby object and displayed in a toast.
def onReceiveValue json
android::util::Log.i "Webviewtutorial", "Jscallback#onReceiveValue: #{json}"
value = JSON.load( json, nil, symbolize_names: true )
@context.toast "Received value: #{value.inspect}"
end
Method WebviewtutorialActivity#eval_js_load_image_return_size
in file ./src/webviewtutorial_activity.rb
passes a string of Javascript code to call function load_image_return_size()
. A callback object is provided as required by evaluateJavascript
because the return value will be used.
def eval_js_load_image_return_size
# ensure a cached image does not ruin this example
@webview.clear_cache true
@webview.evaluate_javascript "load_image_return_size()", Jscallback.new( self )
end
Javascript function load_image_return_size()
in file ./assets/js/tutorial.js
loads an image, adds it to a div, and returns an object (hash) containing the image width and height.
Note the image URL begins with the special absolute path file://android_res/...
because the image is a resource file from the ./res
directory not the ./assets
directory. Despite their relative location in the soure project directory, these locations are handled separately by Android. In this case a relative path ../../res/drawable/get_ruboto_core.png
does not work.
function load_image_return_size() {
var image = document.createElement('img')
var images = document.getElementById( 'tutorial-images' )
images.appendChild(image)
image.src = 'file:///android_res/drawable/get_ruboto_core.png'
return { 'width': image.width, 'height': image.height }
}
When chosen, menu action 'Load image return size' adds an image to the page and displays a toast containing the dimensions of the image. The image loads but the returned dimensions are zero (0). This happens because images are loaded asynchronously and the Javascript function returns before the image has finished loading.
This kind of problem can be difficult to detect. In this case it occurs every time the action is excecuted because the cache is purposefully cleared each time:
# ensure a cached image does not ruin this example
@webview.clear_cache true
If that line is removed, the incorrect return value is only evident for the first image loaded. Subsequent return values are for the cached image. The asynchronous nature of many Javascript operations in webview makes race conditions like this a common problem.
It is tempting to try the quick fix of a short delay before returning, but this approach is detrimental to Javascript application and browser performance and degrades the user's experience. A better solution more in keeping with Javascript's asynchronous nature will be presented later.
WebView supports execution of application code from Javascript via an interface object. The interface object is registered with WebView method addJavascriptInterface
documented here.
For Android API > 16 methods of the interface object must be annotated with Java annotation @JavascriptInterface
to be available for use by Javascript.
Unlike the Ruby callback object provided to the evaluateJavascript
method, the reflection process used by WebView to find these methods when called from Javascript requires that there be an actual Java object instance of a Java class.
We can create a such a Java class to back a Ruby class in Ruboto as follows; for a class Jsi
:
$ ruboto gen subclass java.lang.Object --name=Jsi --method_base=none
Added file /home/gerard/dev/webviewtutorial/src/org/ruboto/webviewtutorial/Jsi.java.
Added file /home/gerard/dev/webviewtutorial/src/jsi.rb.
Added file /home/gerard/dev/webviewtutorial/test/src/jsi_test.rb.
Loading Android API...Done.
Generating methods for Jsi...Done. Methods created: 0
(See Tip "Naming issue" for class naming convention.)
This creates the Java file Jsi.java
and Ruby file jsi.rb
.
Make these changes to files...
Replace with:
// Generated Ruboto subclass with method base "none"
package org.ruboto.webviewtutorial;
import org.ruboto.JRubyAdapter;
import org.ruboto.Log;
import org.ruboto.Script;
import org.ruboto.ScriptInfo;
import org.ruboto.ScriptLoader;
import android.webkit.JavascriptInterface;
public class Jsi extends java.lang.Object implements org.ruboto.RubotoComponent {
public Jsi() {
super();
}
private final ScriptInfo scriptInfo = new ScriptInfo();
public ScriptInfo getScriptInfo() {
return scriptInfo;
}
@JavascriptInterface
public void no_arg() {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "no_arg" );
}
@JavascriptInterface
public void boolean_arg( boolean b ) {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "boolean_arg", b );
}
@JavascriptInterface
public void int_arg( int i ) {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "int_arg", i );
}
@JavascriptInterface
public void string_arg( String s ) {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "string_arg", s );
}
@JavascriptInterface
public void json_arg( String j ) {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "json_arg", j );
}
@JavascriptInterface
public void multiple_arg( String j, String s, int i, boolean b ) {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "multiple_arg", new Object[]{ j, s, i, b } );
}
{
scriptInfo.setRubyClassName(getClass().getSimpleName());
ScriptLoader.loadScript(this);
}
}
Replace with:
require 'json'
class Jsi
attr_accessor :context
def no_arg
@context.toast "no_arg: nil"
"null" # == nil.to_json
end
def boolean_arg b
@context.toast "boolean_arg: #{b}"
"#{b}" # == b.to_json
end
def int_arg i
@context.toast "int_arg: #{i}"
"#{i}" # == i.to_json
end
def string_arg s
@context.toast "string_arg: #{s}"
s # != s.to_json ( s.to_json == "\"#{s}\"" )
end
def json_arg j
obj = JSON.load(j)
@context.toast "json_arg: #{obj.inspect}"
obj.to_json
end
def multiple_arg j, s, i, b
obj = JSON.load(j)
@context.toast "multiple_arg: #{obj.inspect}, #{s}, #{i}, #{b}"
[ obj, s, i, b ].to_json
end
end
Add functions:
function no_arg() {
jsi.no_arg()
}
function boolean_arg(b) {
jsi.boolean_arg(b)
}
function int_arg(i) {
jsi.int_arg(i)
}
function string_arg(s) {
jsi.string_arg(s)
}
function json_arg(j) {
jsi.json_arg(JSON.stringify(j))
}
function multiple_arg(j,s,i,b) {
jsi.multiple_arg(JSON.stringify(j),s,i,b)
}
Add this in the <body>
tag:
<h2>Using addJavascriptInterface</h2>
<div>
<table>
<tr><td><button onClick="no_arg()">no_arg()</button></td></tr>
<tr><td><button onClick="boolean_arg(true)">boolean_arg(true)</button></td></tr>
<tr><td><button onClick="int_arg(123)">int_arg(123)</button></td></tr>
<tr><td><button onClick="string_arg('abc')">string_arg("abc")</button></td></tr>
<tr><td><button onClick="json_arg({'a':1,'b':2})">json_arg({'a':1,'b':2})</button></td></tr>
<tr><td>
<button onClick="multiple_arg({'a':1,'b':2},'abc',123,true)">multiple_arg({'a':1,'b':2},true,123,'abc'</button>
</td></tr>
</table>
</div>
The annotated methods in Jsi.java
illustrate accepting parameters of supported types and passing them to corresponding methods in the Ruby instance of class Jsi
. The creation of a Java object array Object[]{...}
to pass multiple parameters in method multiple_arg
is notable. For more information: JRuby document 'Calling Java from Ruby' explains automatic bi-directional conversion of types between Ruby and Java.
There is little documention of the types supported by WebView for calls from Javascript, those listed above were determined by trial and error. If a complex object must be passed, it may be encoded as JSON and passed as a string. JSON encoding and decoding is illustrated in Javascript functions json_arg
and multiple_arg
and in corresponding Ruby methods Jsi#json_arg
and Jsi#multiple_arg
.
The additional HTML is a table of buttons to call each Javascript function.
To ensure changes to assets are included in package, clean old files before each build:
$ rake clean debug reinstall log
Run the app, click each button in the webview to call a Ruby method. Each method displays a toast indicating the method called and the parameters passed.
Make these changes to files...
Add methods:
@JavascriptInterface
public java.lang.String no_arg_return() {
return (java.lang.String) JRubyAdapter.runRubyMethod( java.lang.String.class, scriptInfo.getRubyInstance(), "no_arg" );
}
@JavascriptInterface
public java.lang.String boolean_arg_return( boolean b ) {
return (java.lang.String) JRubyAdapter.runRubyMethod( java.lang.String.class, scriptInfo.getRubyInstance(), "boolean_arg", b );
}
@JavascriptInterface
public java.lang.String int_arg_return( int i ) {
return (java.lang.String) JRubyAdapter.runRubyMethod( java.lang.String.class, scriptInfo.getRubyInstance(), "int_arg", i );
}
@JavascriptInterface
public java.lang.String string_arg_return( String s ) {
return (java.lang.String) JRubyAdapter.runRubyMethod( java.lang.String.class, scriptInfo.getRubyInstance(), "string_arg", s );
}
@JavascriptInterface
public java.lang.String json_arg_return( String j ) {
return (java.lang.String) JRubyAdapter.runRubyMethod( java.lang.String.class, scriptInfo.getRubyInstance(), "json_arg", j );
}
@JavascriptInterface
public java.lang.String multiple_arg_return( String j, String s, int i, boolean b ) {
return (java.lang.String) JRubyAdapter.runRubyMethod( java.lang.String.class, scriptInfo.getRubyInstance(), "multiple_arg", new Object[]{ j, s, i, b } );
}
Add functions:
function no_arg_return() {
return jsi.no_arg_return()
}
function boolean_arg_return(b) {
return jsi.boolean_arg_return(b)
}
function int_arg_return(i) {
return jsi.int_arg_return(i)
}
function string_arg_return(s) {
return jsi.string_arg_return(s)
}
function json_arg_return(j) {
return jsi.json_arg_return(JSON.stringify(j))
}
function multiple_arg_return(j,s,i,b) {
return jsi.multiple_arg_return(JSON.stringify(j),s,i,b)
}
Replace the <table>
element with:
<table>
<tr>
<td><button onClick="no_arg()">no_arg()</button></td>
<td><button onClick="this.parentElement.nextElementSibling.textContent=no_arg_return()">no_arg_return()</button></td>
<td style="width:8em;border-style:solid;border-width:thin;"></td>
<td><button onClick="this.parentElement.previousElementSibling.textContent=''">Clear</button></td>
</tr>
<tr>
<td><button onClick="boolean_arg(true)">boolean_arg(true)</button></td>
<td><button onClick="this.parentElement.nextElementSibling.textContent=boolean_arg_return(true)">boolean_arg_return(true)</button></td>
<td style="width:8em;border-style:solid;border-width:thin;"></td>
<td><button onClick="this.parentElement.previousElementSibling.textContent=''">Clear</button></td>
</tr>
<tr>
<td><button onClick="int_arg(123)">int_arg(123)</button></td>
<td><button onClick="this.parentElement.nextElementSibling.textContent=int_arg_return(123)">int_arg_return(123)</button></td>
<td style="width:8em;border-style:solid;border-width:thin;"></td>
<td><button onClick="this.parentElement.previousElementSibling.textContent=''">Clear</button></td>
</tr>
<tr>
<td><button onClick="string_arg('abc')">string_arg("abc")</button></td>
<td><button onClick="this.parentElement.nextElementSibling.textContent=string_arg_return('abc')">string_arg_return("abc")</button></td>
<td style="width:8em;border-style:solid;border-width:thin;"></td>
<td><button onClick="this.parentElement.previousElementSibling.textContent=''">Clear</button></td>
</tr>
<tr>
<td><button onClick="json_arg({'a':1,'b':2})">json_arg({'a':1,'b':2})</button></td>
<td><button onClick="this.parentElement.nextElementSibling.textContent=json_arg_return({'a':1,'b':2})">json_arg_return({'a':1,'b':2})</button></td>
<td style="width:8em;border-style:solid;border-width:thin;"></td>
<td><button onClick="this.parentElement.previousElementSibling.textContent=''">Clear</button></td>
</tr>
<tr>
<td><button onClick="multiple_arg({'a':1,'b':2},'abc',123,true)">multiple_arg({'a':1,'b':2},true,123,'abc')</button></td>
<td><button onClick="this.parentElement.nextElementSibling.textContent=multiple_arg_return({'a':1,'b':2},'abc',123,true)">multiple_arg_return({'a':1,'b':2},true,123,'abc')</button></td>
<td style="width:8em;border-style:solid;border-width:thin;"></td>
<td><button onClick="this.parentElement.previousElementSibling.textContent=''">Clear</button></td>
</tr>
</table>
The additional methods in Jsi.java
illustrate calling corresponding Ruby methods to get a return value and returning the value to the Javascript caller. WebView only supports return values of type java.lang.String
. The Javascript caller may need to extract a value from the returned string, JSON parsing is the usual approach.
The Ruby methods in jsi.rb
demonstrate returning non-string value as strings that will be interpreted as JSON expected. Again, Ruby methods Jsi#json_arg
and Jsi#multiple_arg
are the most interesting in this regard.
Note that Ruby method Jsi#string_arg
simply returns a string value, this is handled as a native string by the Javascript caller. JSON parsing will not parse it as a string, it will result in an error. A string is represented in JSON with embedded quotes, eg; "\"abc\""
.
The additional functions in tutorial.js
call corresponding methods in Jsi.java
and expect return value.
The additional HTML in tutorial.html
provides buttons to call the additional Javascript functions and display the returned values in the page.
There are no changes for file jsi.rb
because we are reusing the same methods.
To ensure changes to assets are included in package, clean old files before each build:
$ rake clean debug reinstall log
Run the app. Click the new buttons in the webview to call Ruby methods, display a toast, and display the returned values in the page.
We can now solve the earlier problem of evaluating asynchronous Javascript from Ruby to obtain a value. Make the following changes to files...
Add menu action in method setup_menu
:
menu.add( 'Load image jsi size' ).set_on_menu_item_click_listener proc { eval_js_load_image_jsi_size ; true }
Add method:
def eval_js_load_image_jsi_size
@webview.clear_cache true
@webview.evaluate_javascript "load_image_jsi_size()", nil
end
Add function:
function load_image_jsi_size() {
var image = document.createElement('img')
var images = document.getElementById( 'tutorial-images' )
images.appendChild(image)
image.src = 'file:///android_res/drawable/get_ruboto_core.png'
image.onload = function() {
jsi.image_loaded( image.width, image.height )
}
}
Add method:
@JavascriptInterface
public void image_loaded( int w, int h ) {
JRubyAdapter.runRubyMethod( scriptInfo.getRubyInstance(), "image_loaded", new Object[]{ w, h } );
}
Add method:
def image_loaded w, h
@context.toast "Image size: #{w}x#{h}"
end
The approach here is that the activity uses the WebView evaluateJavascript
method to call Javascript which in turn calls a method of the Javascript interface object registered with addJavascriptInterface
to send a value back to the activity.
The menu action handler eval_js_load_image_jsi_size
calls evaluateJavascript
without a callback object as the return value will not be used.
@webview.evaluate_javascript "load_image_jsi_size()", nil
Javascript function load_image_jsi_size()
adds an image to the page. The Javascript interface object (jsi
) method image_loaded
is called from the onload
event handler which will be executed after the image has loaded and properties are updated.
image.onload = function() {
jsi.image_loaded( image.width, image.height )
}
Finally the javascript interface Java method image_loaded
and corresponding Ruby method Jsi#image_loaded
display a toast containg the image dimensions.
To ensure changes to assets are included in package, clean old files before each build:
$ rake clean debug reinstall log
Run the app. When selected, the new menu action 'Load image jsi size' adds an image to the page and displays a toast containing the correct dimensions for the image.
The Java reflection responsible for finding the JavascriptInterface method called from Javascript does not handle some naming cases as expected. In particular; JavascriptInterface class names with multiple contiguous uppercase letters and package names with multiple uppercase letters may result in Ruby methods not being found. See Ruboto issue 719 for example. To avoid the problem, use this following naming convention:
- The first letter of the JavascriptInterface class name should be the only uppercase letter eg; Myjsi not MyJsi or MyJSI.
- Do not use underscores in the --package name when creating the Ruboto project to simplify the CamelCase for the package portion of the fully qualified class name.
Project assets assets/...
and resources res/...
may be referred to via file:///android_asset/...
and file:///android_res/...
URLs respectively.
This is not well documented, but is mentioned in the Android source for URLUtil.java:
// to refer to bar.png under your package's asset/foo/ directory, use
// "file:///android_asset/foo/bar.png".
static final String ASSET_BASE = "file:///android_asset/";
// to refer to bar.png under your package's res/drawable/ directory, use
// "file:///android_res/drawable/bar.png". Use "drawable" to refer to
// "drawable-hdpi" directory as well.
static final String RESOURCE_BASE = "file:///android_res/";
After initially loading a local page with a file:///android_asset/
URL. Content URLs within the page can use relative paths ../...
to access other local assets.
This is useful for testing HTML and Javascript code in the source project with a browser and tools on a developer system. Webview user interaction and network access may be efficiently developed and tested this way independent of the application compile/install/debug cycle.
Chrome developer tools can be used to debug a WebView on a device running Android 4.4 or later. Chrome developer docs at https://developer.chrome.com/devtools/docs/remote-debugging describe how to configure remote debugging of a webview from a USB connected developer system. The Chrome developer tools can be used to inspect the page and debug Javascript. In addition the page can be displayed and interacted with on the developer system.
Many of the methods to get or set scale for webview are deprecated. The API documentation for getScale() recommends an onScaleChanged() listener in a WebViewClient to track interactive scale changes. Determining the initial scale for a page per the API documentation for setInitialScale() can be challenging. The effects of different device screen densities complicate matters even more.
A simple approach applicable when using a webview to interact with local content is to open the webview initially scaled to display the entire content. This is achieved in this tutorial with the local HTML page not having a viewport tag, and these WebView settings:
def setup_webview
...
@webview = self.find_view_by_id Ruboto::R::id::webview
set = @webview.settings
set.use_wide_view_port = true
set.load_with_overview_mode = true
...
end
A close read of the Android docs for methods setInitialScale(), useWideViewPort(), and setLoadWithOverviewMode() indicates that the WebView should scale to display the entire content in the absence of a viewport tag. This seems to work in practice.
The difficulties of programmatic scaling described above are felt particularly keenly when saving and restoring webview position and scale for device screen orientation changes. Luckily, experimentation indicates that the webview component can handle orientation changes well itself. The position and scale of the content is preserved well enough that a user will not lose their place on the page.
These android:configChanges
activity parameter in file AndroidManifest.xml
configures the application to handle screen orientation changes instead of restarting:
<activity
...
android:name='WebviewtutorialActivity'
android:configChanges='orientation|screenSize'>
...
</activity>
Handling the orientation/screen-size configuration change is described at:
http://developer.android.com/guide/topics/resources/runtime-changes.html