書籍転載:[iOS/Android対応]HTML5ハイブリッドアプリ開発[実践]入門(7)
JavaScriptからネイティブの機能を呼び出す方法 ― addJavascriptInterface方式(前編)
Apache Cordova/Adobe PhoneGapによるハイブリッドアプリ内での、JavaScriptとネイティブとの通信の仕組みを解説する。書籍転載の7本目(「Part 2《実践編》 第11章 JavaScriptとネイティブとのブリッジ」より)。
オープンソースのフレームワーク「Apache Cordova」(Adobe版:「 PhoneGap」)を用いると、HTML5でiOSとAndroid向けのアプリをまとめて作成できます。この連載記事(=書籍転載)の第1回~第6回で、その開発方法を一通り解説しています。また、第7回(=今回)からは、「JavaScriptコード」と「iOS/Andoridネイティブ機能」をつなぐ仕組みを説明しています。
書籍転載について
ご注意
本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。
前回までは「第4章 Cordovaを用いたアプリ開発の流れ」を説明しました。今回からは「第11章 JavaScriptとネイティブとのブリッジ」を説明します。
ハイブリッドアプリ内では、JavaScriptからその端末のOSネイティブの機能を呼び出すことができます。なぜこのようなことができるのでしょうか? この章では、ハイブリッドアプリ内でのJavaScriptとネイティブとの通信の仕組みを解説します。
■
HTML5ハイブリッドアプリでは、通常のHTMLには存在しないJavaScript APIを用いて端末ネイティブの機能を呼び出すことができます。例えば、Cordovaフレームワークではリスト11.1のようなJavaScriptから端末のカメラ撮影機能を呼び出すことができます。
document.addEventListener("deviceready", function() {
// 端末のカメラ撮影機能を呼び出す
navigator.camera.getPicture(function(imageData) {
var image = document.getElementById('myImage');
image.src = "data:image/jpeg;base64," + imageData;
}, function(message) {
alert('エラーが発生しました: ' + message);
}, {
quality : 50
});
}, false);
|
通常のブラウザやWebView上で実行されるJavaScriptでは呼び出せない機能を、なぜHTML5ハイブリッドアプリの中で実行されるJavaScriptでは呼び出せるのでしょうか。
これは、PhoneGapなどのハイブリッドアプリを開発するためのフレームワークがこっそりその窓口を用意しているからです。フレームワークが提供しているJavaScriptとネイティブとの橋渡しの仕組みは、HTMLとネイティブのブリッジと呼ばれます。驚くことに、このHTMLとネイティブのブリッジにはいくつかの方式が存在します。さらにそれらの方式には、それぞれ利点と欠点があります。
この章では、ハイブリッドアプリ開発フレームワークの内部で利用されているブリッジの基本的な仕組みを解説します。具体的には、各方式の実装方法からその特徴、セキュリティ上の懸念事項、OSのバージョンによる利用の可否、AndroidとiOSのプラットフォーム間での違いを詳細に説明していきます。HTML5ハイブリッドアプリの仕組みの裏側を知りたい人や、HTML5ハイブリッドアプリを開発するためのフレームワークを開発したい人を対象とした章です。
11.1 JavaScriptからネイティブの機能を呼び出すいくつかの方法
前述したとおり、JavaScriptからネイティブの機能を呼び出すにはいくつか方法があります。それぞれの方法に特定の名前が付けられているわけではありませんが、この章ではそれらに便宜的に以下のような名前を付けています。
- addJavascriptInterface方式
- カスタムURLスキーム方式
- JsAlert方式
- ローカルHTTPサーバ方式
ここには、既存のハイブリッドアプリフレームワークであるPhoneGap(Cordova)やTriainaなどの内部で実際に利用されている方法も含まれています。
11.2 ネイティブブリッジに必要とされる要件
あらかじめHTML5ハイブリッドアプリのJavaScriptとネイティブとのブリッジに必要とされる要件を以下にまとめておきます。
- JavaScript側からネイティブのコードを呼び出せるようになっている必要がある。
JavaScriptにはない機能を呼び出すのに必要である。 - JavaScript側から呼び出されたネイティブのコードは、そのレスポンスをJavaScript側に返す必要がある。これも、ネイティブのAPIの結果をJavaScript側にも伝える場合があるので必要である。
- XSSなどが発生した場合の被害を最小限に抑えるために、ネイティブで公開できる範囲を調整できる必要がある。不要な情報や機能をJavaScript側に公開してはならない。
- HTML5ハイブリッドアプリのHTMLからはネイティブの機能を呼び出せるが、信頼できないHTMLからの呼び出しは制限する必要がある。
最初の2つは、HTML内のJavaScriptからネイティブに対して命令を投げたり、ネイティブからHTML内のJavaScriptに対して命令を返したりすることができなければならないという要件です。ネイティブの機能を呼び出すのに当然の要件です。
それに対して、下の2つは若干わかりづらい要件ですが、基本的にはセキュリティのための要件です。HTML5ハイブリッドアプリ内で読み込む可能性のあるHTMLには、端末のアプリのパッケージ内に格納されている信頼できるHTMLと、外部にある信頼できないHTMLの2種類があります。さらに、信頼できるHTML内でもバグによるXSSなどのセキュリティ脆弱性がある場合もあります。
信頼できないHTMLを読み込んだ際に攻撃を受けないように、アプリの外部のHTMLを読み込んだ際には、あらかじめブリッジを提供しないようにしなければなりません。また、信頼できるHTMLの中にXSSがあって攻撃を受けた場合にも、その被害を最小限に抑えなければいけません。
ブリッジを提供するHTMLに制限を付けたり、ブリッジで公開する機能を制限する要件が必要なのは、JavaScriptから発するネイティブへの命令リクエストが必ずしもセキュアなものではないという事情があります。
Cordovaなどのハイブリッドアプリフレームワークでは、これらの4つの要件を満たすために複数のブリッジの方法を組み合わせた上でJavaScriptとネイティブとのブリッジを提供しています。というのも、各ブリッジの方法には一長一短があり、単一の方法だけでは前述の要件を満たせないからです。
さて、ここからはそれぞれの方法の解説に移っていきます。まず基本的なブリッジの方法から解説していきます。その後、複数のブリッジの方法を組み合わせたブリッジの方法を解説します。ハイブリッドアプリフレームワークで実際に利用されているブリッジは後者となります。
11.3 addJavascriptInterface方式
addJavascriptInterface方式では、AndroidやWebViewクラスに標準で備わっているJavaScriptとネイティブとのブリッジ機能を利用します。
これは、Androidで利用できる単純で実装も簡単なブリッジの方法です。しかし、後述するセキュリティ上の懸念があり、HTML5ハイブリッドアプリの実装にこのブリッジを全面的に利用することはできません。
11.3.1 実装
WebViewのインスタンスに対してaddJavascriptInterfaceメソッドを呼び出すと、JavaScriptとJavaとで相互に通信できるJavaScriptオブジェクトを作成できます。
JavaScript側からは、その作成されたオブジェクトに対してメソッドを呼び出すことで、ネイティブ側の機能を利用できます(リスト11.2)。
package com.example.sandbox;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.widget.Toast;
public class JSInterfaceSandbox extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
setContentView(webView);
// JavaScriptを有効にする
webView.getSettings().setJavaScriptEnabled(true);
// JavaScript側へオブジェクトを追加する
webView.addJavascriptInterface(new JSHandler(), "Bridge");
// assetsディレクトリ以下のindex.htmlを読み込む
webView.loadUrl("file:///android_asset/index.html");
}
// JavaScriptから利用できるオブジェクトのクラス
class JSHandler {
public void doSomething() {
Toast.makeText(
JSInterfaceSandbox.this,
"DO SOMETHING",
Toast.LENGTH_LONG
).show();
}
}
}
|
addJavascriptInterfaceメソッドの第2引数では、オブジェクトを登録するためのJavaScript側のグローバル変数の名前を指定できます。ここでは、'Bridge'と指定しているので、JavaScript側ではBridgeグローバル変数を通じてこのオブジェクトにアクセスできます。
リスト11.2のコードを呼び出したWebViewで読み込んだHTML側のJavaScriptからは、リスト11.3のようなコードでJSHandlerオブジェクトのメソッドを呼び出すことができます。
JavaScript側からは、グローバル変数のBridgeオブジェクトのメソッドを呼び出しています。
その結果、Java側のJSHandlerクラスのメソッドが呼び出されます。
<!-- assets/index.html -->
<html>
<head>
<script type="text/javascript">
Bridge.doSomething();
</script>
</head><body></body></html>
|
Android用のプロジェクトとしてこのコードを実行すると、JavaScript側からBridgeオブジェクトのdoSomethingメソッドが呼び出されます。その結果、AndroidのToastが画面に表示されます。
元々ブリッジ用に提供されているAPIだけあって、少ない行数のコードで簡単にブリッジが提供できます。
11.3.2 値の受け渡し
addJavascriptInterfaceメソッドで追加したオブジェクトのメソッドに対して、JavaScriptからパラメータを渡したり、Javaのメソッドから値を返したりすることができます。このとき、JavaScriptからJavaのオブジェクトへ渡された値や、JavaのオブジェクトからJavaScriptへ返される値は、自動的に相互変換されます。ただし、受け取れるのは文字列、数値、配列など、JavaとJavaScriptに共通する基本的な値のみで、JavaScriptオブジェクトの受け渡しはできません。
パラメータとしてJavaScriptのオブジェクトを渡すことができないので、実際に利用する場合は、JSON形式を用いて値の受け渡しを行うとよいでしょう。
リスト11.4のコードでは、JavaScriptからパラメータを渡すときにいったんJSON.stringify関数を用いてオブジェクトから文字列に変換し、パラメータを渡します。返ってきた値も、JSON.parse関数を用いて文字列からJavaScriptのオブジェクトに復元して受け取ります(リスト11.5)。
class JSHandler {
public String plus(String json) {
JSONObject params = new JSONObject(json);
JSONObject result = new JSONObject();
result.setInt("result", params.getInt("a", 0) + params.getInt("b", 0));
return JSONObject.toString();
}
}
|
<script type="text/javascript">
var obj = JSON.parse(Bridge.plus(JSON.stringify({
a : 5,
b: 9
}))); // {result : 14}が返る
</script>
|
11.3.3 Java側ではスレッドセーフにする
addJavascriptInterfaceメソッドで追加されたオブジェクトのメソッドは、アプリケーションのメインスレッドではなく、WebViewの持つバックグラウンドスレッドで呼び出されます。そのため、スレッドセーフにする必要があります。
Androidでは、UIを構成するビューやアクティビティを別のスレッドから操作することはできません。addJavascriptInterfaceメソッドで追加したオブジェクトのメソッドでUIを操作するときは、リスト11.6のようにContextオブジェクトのrunOnUiThreadメソッドを利用して、アプリケーションのメインスレッドで処理を実行します。
package com.example;
import android.content.Context;
class MyCustomHander {
public Context context;
public void doSomething() {
context.runOnUiThread(new Runnable() {
@Override
public void run() {
// ビューやアクティビティを操作する処理を入れる
}
});
}
}
|
■
次回は「addJavascriptInterface方式(後編)/ネイティブからJavaScriptへ値を渡す」を説明します。
久保田光則(くぼたみつのり)
東京都在住。アシアル株式会社に所属するUI/UXデザイナー兼ソフトウェアエンジニア。社内では,HTML5ハイブリッドアプリの開発に多数関わる。優れたデザインとエンジニアリングを両立したオーバークオリティなアプリケーションの開発を実現するために日々,頑張る。
アシアル株式会社(あしあるかぶしきがいしゃ、Asial Corporation)
PHPなどのサーバサイドの技術と,PhoneGapなどのスマートフォン関連を中心とした開発を手がける技術ベンチャー。HTML5ハイブリッドアプリをブラウザ上で開発できるMonacaや,PhoneGapの日本語情報を配信するPhoneGap Fanなどのウェブサービスを手がける。
※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
6. Android向けのCordovaプラグインを実装する
アプリの一部をネイティブで記述するには、プラグインの実装が必要。そこでAndroid向けにCordovaプラグインを実装する方法を解説。今回で「Cordovaアプリ開発の基礎」に関する部分の転載完了。
7. 【現在、表示中】≫ JavaScriptからネイティブの機能を呼び出す方法 ― addJavascriptInterface方式(前編)
Apache Cordova/Adobe PhoneGapによるハイブリッドアプリ内での、JavaScriptとネイティブとの通信の仕組みを解説する。書籍転載の7本目(「Part 2《実践編》 第11章 JavaScriptとネイティブとのブリッジ」より)。
8. addJavascriptInterface方式(後編)/ネイティブからJavaScriptへ値を渡す
Apache Cordova/Adobe PhoneGapによるハイブリッドアプリ内での、JavaScriptとネイティブとの通信の仕組みを解説。書籍転載の8本目。
9. カスタムURLスキーム方式/iOSで特定のページの読み込みを制限する
JavaScriptからネイティブ側へ命令を投げる方法(Android&iOS対応)を紹介。またiOSでセキュリティのために、特定のドメインのページの読み込みを制限する方法も紹介。書籍転載の9本目。