- Layered functionality
- Graceful degradation
WebView
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WebView mWebView = new WebView(this); setContentView(mWebView); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.loadUrl("file:///android_asset/hello.html"); mWebView.addJavascriptInterface( new JavaScriptBridge(this), "MyAndroid"); }
private class JavaScriptBridge { private Context mContext; public JavaScriptBridge(Context context) { mContext = context; } public void showToast(String msg) { Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); } }
<html> <head> <script type="text/javascript"> function sayHello() { if (typeof MyAndroid === 'undefined') { alert('Hello'); } else { MyAndroid.showToast('Hello'); } } </script> </head> <body> <a href="javascript:sayHello();">Say hello</a> </body> </html>
<html> <head> <script type="text/javascript"> function pickContact() { if (typeof MyAndroid === 'undefined') { return; } else { MyAndroid.pickContact(); } } </script> </head> <body> <label id="emailLabel" onClick="pickContact();">Email</label> <input id="email" type="text" /> </body> </html>
private class JavaScriptBridge { // Constructor omitted public void pickContact() { Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); startActivityForResult(intent, R.id.request_code_pick_contact); } }
res/values/ids.xml
<resources> <item name="request_code_pick_contact" type="id" /> </resources>
protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) { return; } switch (requestCode) { case R.id.request_code_pick_contact: fillEmail(data); break; } }
private void fillEmail(Intent data) {
String id = String.valueOf(ContentUris.parseId(data.getData()));
Cursor cursor = getContentResolver().query(
Email.CONTENT_URI, null,
Email.CONTACT_ID + "=?",
new String[]{ id }, null);
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(Email.DATA);
String email = cursor.getString(index);
mWebView.loadUrl("javascript:fillEmail('" + email + "')");
} else {
Toast.makeText(this, R.string.email_error, Toast.LENGTH_SHORT).show();
}
cursor.close();
}
function fillEmail(email) { var input = document.getElementById('email'); input.value = email; }
<label id="emailLabel" onClick="pickContact();">Email</label> <input id="email" type="text" />
function init() { if (typeof MyAndroid === 'undefined') { return; } // Make the email label look like a link in Android var label = document.getElementById('emailLabel'); label.style.color = '#00f'; label.style.textDecoration = 'underline'; }
<body onLoad="init()"> <label id="emailLabel" onClick="pickContact();">Email</label> <input id="email" type="text" /> </body>
<body> <label>Photo</label> <input id="photoData" type="hidden" /> <div> <img id="photo" style="display: none;" /> </div> <input type="file" value="Pick photo" onClick="return pickPhoto();" /> </body>
<script type="text/javascript"> function pickPhoto() { if (typeof MyAndroid === 'undefined') { return true; // Let the browser handle it } MyAndroid.pickPhoto(); return false; } </script>
<label>Photo</label> <input id="photoData" type="hidden" /> <img id="photo" style="display: none;" /> <input type="file" value="Add photo" onClick="return pickPhoto();" />
public void pickPhoto() { Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, R.id.request_code_pick_photo); }
protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) { return; } switch (requestCode) { case R.id.request_code_pick_photo: fillPhoto(getPhotoFromGallery(data)); break; } }
private Bitmap getPhotoFromGallery(Intent data) { Uri selectedImage = data.getData(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query( selectedImage, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String path = cursor.getString(columnIndex); cursor.close(); return BitmapFactory.decodeFile(path); }
private void fillPhoto(Bitmap bitmap) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] byteArray = stream.toByteArray(); String encoded = "data:image/png;base64," + Base64.encodeBytes(byteArray); mWebView.loadUrl("javascript:fillPhoto('" + encoded + "');"); }
String
is the only way to pass data to javascriptBase64
encodes binary data in an ASCII stringfunction fillPhoto(data) { var photoData = document.getElementById('photoData'); photoData.value = data; var photo = document.getElementById('photo'); photo.src = photoData.value; photo.style.display = photoData.value == '' ? 'none' : 'block'; }
<input id="photoData" type="hidden" /> <img id="photo" style="display: none;" />
protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mWebView.saveState(outState); } protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mWebView.restoreState(savedInstanceState); }
public class MainActivity extends Activity { private WebView mWebView = null; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); init(); setContentView(mWebView); } private void init() { if (mWebView != null) { return; } mWebView = new WebView(this); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.loadUrl("file:///android_asset/index.html"); mWebView.addJavascriptInterface( new JavaScriptBridge(this), "MyAndroid"); } }
<activity android:label="@string/app_name" android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation" >
public boolean onKeyDown(int keyCode, KeyEvent event) { // Check if the key event was the Back button and if there is // history if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) { mWebView.goBack(); return true; } // If it wasn't the Back key or there's no web page history, bubble // up to the default system behavior (probably exit the activity) return super.onKeyDown(keyCode, event); }
WebView
has access to your native calls!WebView
private void init() { // Other init omitted mWebView.loadUrl( "http://www.sqisland.com/talks/progressive-webview/sample"); mWebView.setWebViewClient(new MyWebViewClient()); } private class MyWebViewClient extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { if (Uri.parse(url).getHost().equals("www.sqisland.com")) { // This is my web site, OK to load page return false; } // Otherwise, the link is not for a page on my site, so launch // another Activity that handles URLs Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); return true; } }
WebView
if (typeof MyAndroid === 'undefined') {
doSomethingInBrowser();
} else {
MyAndroid.doSomething();
}