読者です 読者をやめる 読者になる 読者になる

A Day In The Life

とあるプログラマの備忘録

Javascript 関数の戻り値を Android から取得する方法

Android の WebView を使って Javascript 関数を呼び出すには WebView#loadUrl メソッドを使います。ただしこの方法だと関数の戻り値を取得することができません。
一番簡単で手軽な方法として Javascript の alert 関数を使って WebChromeClient クラスの onJsAlert メソッドから戻り値を受け取る方法がありますが、この方法だと alert 関数が使えなくなりデバッグするときにとても不便だったり、関数の種類が増えると戻り値の文字列解析が必要になったりとイマイチ使えませんでした。

そこで JavascriptInterface を使って Javascript 関数の戻り値を取得するプログラムを作成してみました。
今回作成したプログラムの特徴は以下になります。

  • サーバ側のプログラム修正不要
  • alert 関数を使うことができる
  • 戻り値を取りたい関数が複数存在しても大丈夫

WebView#loadUrl メソッドを使って JavascriptInterface で定義した Javascript 関数を Android 側から呼び出しているところがポイントです。

// android.fooがJavascriptInterfaceのメソッドと連携している
webView.loadUrl("javascript:android.foo(戻り値を取りたい関数の呼び出し)");

ちなみに JavascriptInterface の基本的な使い方は下記のページを参考にしてください。

プログラム

サーバ側で定義した getMessage と getMessage2 関数の戻り値を Android アプリから取得するプログラムです。

サーバ側のプログラム
<html>
<head>
<script type="text/javascript">
getMessage() {
  return 'こんにちは!';
}
getMessage2() {
  return 'こんばんは!';
}
</script>
</head>
<body>
:
</body>
</html>
Android アプリのプログラム
public class HogeActivity extends Activity implements View.OnClickListener {
  private WebView webView;
  private class JavascriptInterface {
    public void foo(String returnValue) {
      Log.d("getMessage関数の戻り値", returnValue);
    }
    public void bar(String returnValue) {
      Log.d("getMessage2関数の戻り値", returnValue);
    }
  }
  @Override
  public void onCreate(Bundle savedInstanceState) {
    :
    : UIの初期化処理省略
    :
    webView = (WebView)findViewById(R.id.webview);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.addJavascriptInterface(new JavascriptInterface(), "android");
    webView.loadUrl("http://www.hogehoge.com");
  }
  @Override
  public void onClick(View v) {
    // getMessage()関数の戻り値取得
    webView.loadUrl("javascript:android.foo(getMessage())");
    // getMessage2()関数の戻り値取得
    webView.loadUrl("javascript:android.bar(getMessage2())");
  }
}

この方法を使えば特定の Javascript 関数が定義されているかアプリ側で調べることも出来ます。

// 関数が定義されていれば true されていなければ false
webView.loadUrl("javascript:android.foo(typeof getMessage == 'function')");

注意点

JavascriptInterface クラスのメソッドは非同期で呼び出されます。JavascriptInterface クラスのメソッド内で UI の操作をしたい場合は Handler#post メソッドを使って UI スレッドで実行する必要があります。

public class HogeActivity extends Activity implements View.OnClickListener {
  private WebView webView;
  private TextView textView;
  private Handler handler = new Handler();
  private class JavascriptInterface {
    public void foo(final String returnValue) {
      handler.post(new Runnable() {
        @Override
        public void run() {
          textView.setText(returnValue);
        }
      });
    }
  }
  @Override
  public void onCreate(Bundle savedInstanceState) {
    : 省略
  }
  @Override
  public void onClick(View v) {
    webView.loadUrl("javascript:android.foo(getMessage())");
  }
}

ページ読み込みエラーが発生すると JavascriptInterface が無効になる

最近わかったことなんですが WebView でページ読み込みエラーが発生する(WebViewClient#onReceivedError が発生する)と JavascriptInterface が無効になるようです。ページ読み込みエラー発生後は JavascriptInterface が動かなくなります。WebViewClient クラスの onPageStarted メソッドのタイミングで addJavascriptInterface メソッド呼び出すことで回避できます。ただし毎回インスタンスを new して生成するとアプリがメモリ食い過ぎて落ちる可能性があるためインスタンス変数をメソッドに渡すようにしてください(実際ページリロードしまくると落ちました)。
※2011年6月1日追記

public class HogeActivity extends Activity implements View.OnClickListener {
  private WebView webView;
  private JavascriptInterface javascriptInterface = new JavascriptInterface();
  private class CustomWebViewClient extends WebViewClient {
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
      super.onPageStarted(view, url, favicon);
      webView.addJavascriptInterface(javascriptInterface, "android");
    }
  }
  private class JavascriptInterface {
    public void foo(String returnValue) {
      Log.d("getMessage関数の戻り値", returnValue);
    }
    public void bar(String returnValue) {
      Log.d("getMessage2関数の戻り値", returnValue);
    }
  }
  @Override
  public void onCreate(Bundle savedInstanceState) {
    :
    : UIの初期化処理省略
    :
    webView = (WebView)findViewById(R.id.webview);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new CustomWebViewClient(this));
    webView.loadUrl("http://www.hogehoge.com");
  }
  @Override
  public void onClick(View v) {
    // getMessage()関数の戻り値取得
    webView.loadUrl("javascript:android.foo(getMessage())");
    // getMessage2()関数の戻り値取得
    webView.loadUrl("javascript:android.bar(getMessage2())");
  }
}

書籍紹介

おかげさまで1万部突破しました。Android の基礎を勉強するのにぴったりです。

本の画像 Google Androidプログラミング入門