AndroidでJavaScriptとJavaの連携

ボタンを一列に並べて、画面からはみ出るようだったら折り返すってビューを作りたかったんですが、JavaのViewで実現するのに妙案が浮かばず、じゃあWebViewで出しちゃえばいいじゃない!ってことで、JavaScriptとどうやって連携するのか調べました。

ちなみに何を作ってるのかというとタグを並べて、それを押したらEditTextに文字列を流し込むって画面。はてブアプリをパクr…、参考にして。あれかっこいい。あれもWebViewで実現してるんでしょうか。

で、JSの連携は説明はめんどいので実際のソースコード

まずはlayout

<?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"
  android:focusable="false"
  android:focusableInTouchMode="false"
>

  <TextView
    android:id="@+id/tv"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>
  
  <Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="btn"
    android:onClick="clickBtn"/>
  <WebView
    android:id="@+id/wv"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="false"
    android:focusableInTouchMode="false"/>
</LinearLayout>

難しいところは何一つない。
お次はActivity。importとかは端折った。

public class JsBridgeActivity extends Activity {
  private static final String HOGE_HTML = "file:///android_asset/hoge.html";

  private TextView textView;
  private WebView webView;

  /**
   * WebViewの中で使うJsObj
   */
  public class JsObj{
    private JsBridgeActivity act;
    private Handler handler;

    public JsObj(JsBridgeActivity act){
      this.act = act;
      handler = new Handler();
    }

    /**
     * ボタンクリック時に呼ばれる
     * @param tag
     */
    public void send(final String str){
      //ここで普通にact.setTextValue(str)とかやっても怒られる。
      handler.post(new Runnable() {
        public void run() {
          act.setTextValue(str);
        }
      });
    }
    /**
     * Textの中の値を取得
     * @return
     */
    public String getTextValue(){
      if(textView == null){
        return "";
      }
      String str = textView.getText().toString() == null ? "" : textView.getText().toString();
      return str;
    }

  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    textView = (TextView) findViewById(R.id.tv);
    textView.setText("ほげー");

    webView = (WebView) findViewById(R.id.wv);
  //js使用可能
    webView.getSettings().setJavaScriptEnabled(true);
    //load
    webView.loadUrl(HOGE_HTML);

    JsObj js = new JsObj(this);
    //JavaScriptではandroidでJsObjにアクセスできる
    webView.addJavascriptInterface(js, "android");
  }
  
  /**
   * ボタン押した
   * @param v
   */
  public void clickBtn(View v){
    webView.loadUrl("javascript:fromAndroid('androidからです')");
  }

  /**
   * textに値を入れる
   * @param str
   */
  public void setTextValue(String str){
    if(textView == null) return;
    textView.setText(str);
  }
}

ポイントはJavaScriptで使うオブジェクトを作ってあげることと、JavaScriptからActivityの値を変更したい場合は、handlerを経由してあげなきゃいけない。
handlerについて詳しくは下記。

http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html

で、最後にhoge.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1">
<script type="text/javascript" src="js/jquery-1.4.3.min.js"></script>
<script type="text/javascript">

$(document).ready(function(){
  $("#sendBtn").click(function(){
    android.send($("#input").val());
  });
  
  $("#getBtn").click(function(){
    $("#jText").text(android.getTextValue());
  });
});

function fromAndroid(str){
	$("#jText").text(str);
}

</script>
</head>
<body>
<input type="text" value="" id="input" />
<button id="sendBtn">send</button>

<p id="jText"></p>
<button id="getBtn">get</button>
</body>
</html>

ソース見れば分かるように、

    JsObj js = new JsObj(this);
    webView.addJavascriptInterface(js, "android");

と指定してやるとJavaScript側からJavaのメソッドを叩ける。
そしてJava側からJSを叩く場合はloadUrlの引数にJSを書けばよいらしい。すげーかっこわるい!?

ただ、あんまりレスポンスが良くないような気がします。この程度だったらよいけど、今作ってるタグのUIはタグが押されてから文字列を流し込むまで若干もたつく印象。パクリもとの参考にしたはてブのアプリはレスポンスも素敵なんですが…。アプローチが間違ってるんだろうか。ううむ。