いろいろ注意事項はありますが、Android の技術情報は年月で変化して行くので、まずそのへんをしっかり押さえておく必要があります。 年月で変化 1) WebView の JavaScript のアクセスに loadUrl を使ってはいけない ( 厳密に言うと使ってはいけないわけでは無いと思いますが、ブックマークレットのような使い方は後々の為に避けたほうがいいし、evaluateJavascript なら非同期で戻り値を得られるわけですから使う意味もありません。戻り値がいらないなら callback を null にすればいいですし ) Android公式ドキュメント Migrating to WebView in Android 4.4 の翻訳ページ代わりに、新しいメソッドとして evaluateJavascript を JavaScript を非同期で実行させるために使用してください。 (Android 4.4 : API level 19 : KitKat で追加)実際、JavaScript の結果を "文字列" としてイベントで取得できるので、使いやすいです。但し、帰って来た文字列が "(ダブルクォート) で挟まれているので Android 側で取り去る必要があります。 2) WebViewClient の shouldOverrideUrlLoading(WebView view, String url) が非推奨になった (Android 7.0 : API level 24 : Nougat から) 代わりに、shouldOverrideUrlLoading(WebView view, WebResourceRequest request) を使用します (Android 7.0 : API level 24 : Nougat で追加) 一般 WEB では使えない これらが目新しい変化ですが、そもそも WebView では一般の WEB ページを表示させる事を想定していないようで、無闇に使用すると良く解らないトラブルが発生します。 1) opener が使用できない いろいろ調べましたが、良く解りません。どうやら、WebChromeClient を使って自分で代替処理を作る必要があるようです。( 参考 : WebChromeClientで使える関数のまとめ / onCreateWindow ) 2) localStorage を使っているページに注意 setDomStorageEnabled(true) を実行せずに、そのページで localStorage が処理されると、JavaScript 側のメモリがぶっ壊れて、変数がおかしくなりました。 これは、動作しないとかでは無くぶっ壊れるのでバグの部類です。という事は、他のオプションでも何があるか解らないので一般の WEB ページのように自分ではコントロールできないものを表示すべきでは無いと言う事です。ちなにみに、Android の Chrome では正常に動作しています。ただ、キャッシュの操作や履歴の操作等、いずれにしても問題は多くあります。 JavaScript から Android の UI データを取得に注意 JavaScript から Android とのやり取りは、@JavascriptInterface で定義される public なメソッドですが、JavaScript から 呼び出されたそれらのメソッドは、UI スレッドでは無いところが重要です。 特に問題の無い一方通行 JavaScript => Android JavaScript からデータを JSON 文字列で渡せば、たいていの情報を簡単に渡す事ができ、受け取った Android のメソッドでは Google Gson でデシリアライズすれば、runOnUiThread でそのまま画面に対して有効利用できるはすです。 非同期の為、Android で 画面データを return できない しかし、JavaScript の呼び出しで Android が値を返す場合は、UI スレッドへのアクセスが終わる前に戻り地を返す必要が出てきます。結局一度では無理があるので、通常の Android のように リクエストコードを使用して呼び出してもらい、画面データを用意してから Android から JavaScript のメソッドを呼び出すだけにします。 そして、その Android から呼び出された JavaScript 側のメソッド内から、再度 JavascriptInterface を持ったメソッドを呼び出すようにするのがいいでしょう。 実行画面 JAVASCRIPT からデータを取得 (Android 側 : 行28〜行56) JAVASCRIPT からデータを取得ボタンをクリックした結果です。取得するのは、一番下にある文字列で、WebView 内の テキストフィールドです。これは最も単純で、evaluateJavascript を使用して jQuery の文字列を直接渡してフィールドの結果を取得し、イベントで Android 内に戻って来ます。この文字列は ダブルクォート で挟まれているので、元の表示とダブルクォートを取り去った表示を同時に行っています。 JAVASCRIPT へデータを送る 単純に考えてしまうと、JavaScript の引数に文字列を指定すればいいですが、とても煩雑で問題が出そうです。ですから、データを用意した後、いったん『getAndroidUIData』を "request-02" のリクエストで呼び出して、『getAndroidUIData』 内から Android を呼び出す(getRequestData) 事によって、必要な文字列を JavaScript のメソッドの戻り値で取得するようにしています。 Android 側
public class MainActivity extends AppCompatActivity { private WebView webview; private JsonDataList jsonDataList = null; private Gson gson; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); gson = new Gson(); jsonDataList = gson.fromJson("{\"item\": [\n" + " {" + " \"text\": \"\"," + " \"subject\": \"\"," + " \"name\": \"\"," + " \"datetime\": \"\"" + " }]}",JsonDataList.class); // ***************************** // JavaScript からデータを取得 // ***************************** MainActivity.this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditText et; et = (EditText) MainActivity.this.findViewById(R.id.editSubject); jsonDataList.item[0].subject = et.getText().toString(); et = (EditText) MainActivity.this.findViewById(R.id.editText); jsonDataList.item[0].text = et.getText().toString(); // jQuery でそのまま実行 String script = "$('#android_data').val();"; webview.evaluateJavascript(script, new ValueCallback<String>() { @Override public void onReceiveValue(String s) { EditText et; // そのまま表示 et = (EditText) MainActivity.this.findViewById(R.id.editSubject); et.setText(s); // ダブルクォート削除 et = (EditText) MainActivity.this.findViewById(R.id.editText); if (s != null ) { String wk = s; wk = wk.substring(1, wk.length() - 1); et.setText(wk); } } }); } }); // ***************************** // JavaScript へデータを送る // ***************************** MainActivity.this.findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditText et; et = (EditText) MainActivity.this.findViewById(R.id.editSubject); jsonDataList.item[0].subject = et.getText().toString(); et = (EditText) MainActivity.this.findViewById(R.id.editText); jsonDataList.item[0].text = et.getText().toString(); String script = "getAndroidUIData(\"request-02\");"; Log.i("lightbox", script); webview.evaluateJavascript(script,null); } }); webview = (WebView) MainActivity.this.findViewById(R.id.webView); webview.getSettings().setDomStorageEnabled(true); webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); // 必ず必要な JavaScript を有効にする設定 webview.getSettings().setJavaScriptEnabled(true); JavaScriptAccess jsa = new JavaScriptAccess(); // ********************************** // 第二引数の文字列が、WebView の中のページの // JavaScript より使用可能になります // ********************************** webview.addJavascriptInterface(jsa,"myAndroidObject"); // 必ず必要な設定 webview.setWebViewClient(new WebViewClient(){ // 必ず必要な設定 : 常に WebView 内でページを表示する為 @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return false; } }); // 目的のページを表示する webview.loadUrl("https://lightbox.sakura.ne.jp/homepage/demo/data/csvtype/android.html"); } // ********************************** // JavaScript 用クラス // ********************************** private class JavaScriptAccess { @JavascriptInterface public void log(String message) { Log.i("lightbox",message); } @JavascriptInterface public void clear() { webview.clearHistory(); webview.clearCache(true); } // ************************************** // JavaScript から Android へデータを送る // ************************************** @JavascriptInterface public void setAndroid(String json) { Log.i("lightbox","setAndroid"); jsonDataList = gson.fromJson(json,JsonDataList.class); runOnUiThread(new Runnable() { @Override public void run() { EditText et; et = (EditText) MainActivity.this.findViewById(R.id.editSubject); et.setText(jsonDataList.item[0].subject); et = (EditText) MainActivity.this.findViewById(R.id.editText); et.setText(jsonDataList.item[0].text); } }); } // ************************************** // Android 内の データを JavaScript へ戻す // ************************************** @JavascriptInterface public String getAndroid() { Log.i("lightbox","getAndroid"); String result = gson.toJson(jsonDataList.item[0],JsonData.class); return result; } // ************************************** // UI データを JavaScript へ戻す // ************************************** @JavascriptInterface public String getRequestData(String requestCode) { Log.i("lightbox","getRequestData"); String result = ""; if ( requestCode.equals("request-01") ) { result = gson.toJson(jsonDataList.item[0],JsonData.class); } if ( requestCode.equals("request-02") ) { result = gson.toJson(jsonDataList.item[0],JsonData.class); } return result; } // ************************************** // JavaScript から UI データのリクエスト // ************************************** @JavascriptInterface public void requestAndroid(String requestCode) { Log.i("lightbox","requestAndroid"); if ( requestCode.equals("request-01") ) { runOnUiThread(new Runnable() { @Override public void run() { EditText et; et = (EditText) MainActivity.this.findViewById(R.id.editSubject); jsonDataList.item[0].subject = et.getText().toString(); et = (EditText) MainActivity.this.findViewById(R.id.editText); jsonDataList.item[0].text = et.getText().toString(); String script = "getAndroidUIData(\"request-01\");"; Log.i("lightbox",script); // JavaScript からの戻りを必要としない webview.evaluateJavascript(script, null); } }); } } } private class JsonData { String text; String subject; String name; String datetime; } private class JsonDataList { JsonData[] item; } }
JavaScript 側
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> * { font-size:18px; } div { white-space: pre-wrap; word-wrap: break-word; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script> // ************************************** // Android から呼ばれる場合 // console.log は Android 側で表示 // ************************************** if ( typeof myAndroidObject !== 'undefined' ) { window.console.log = function(message){ myAndroidObject.log(message); } } // ************************************** // Android から呼ばれる関数 // ************************************** function getAndroidUIData( requestCode ) { console.log("JavaScript:getAndroidUIData:" + requestCode) var result; if ( requestCode == "request-01" ) { result = myAndroidObject.getRequestData( requestCode ) console.log("JavaScript:getAndroidUIData:result:" + result); var json = JSON.parse(result); $("#android_data").val( json.subject ); } if ( requestCode == "request-02" ) { result = myAndroidObject.getRequestData( requestCode ) console.log("JavaScript:getAndroidUIData:result:" + result); $("#json").text( result ); } return requestCode; } $( function(){ // ************************************** // Android へデータを送る // ************************************** $("#set_android").on("click",function(){ $.get("json.php",function( data ){ if ( typeof myAndroidObject !== 'undefined' ) { // サーバから受け取った application/json データ // を文字列に変換して Android に渡します myAndroidObject.setAndroid(JSON.stringify(data)); } else { // 通常のブラウザではこちらが実行されます console.log(JSON.stringify(data,null," ")); } }) }); // ************************************** // Android から画面データを取得する // ************************************** $("#get_android").on("click",function(){ if ( typeof myAndroidObject !== 'undefined' ) { // サーバから受け取った application/json データ // を文字列に変換して Android に渡します myAndroidObject.requestAndroid("request-01"); } else { // 通常のブラウザではこちらが実行されます console.log("なにも実行しません"); } }); } ); </script> </head> <body> <input type="button" id="set_android" value="Android へデータを送る"> <br> <input type="button" id="get_android" value="Android から画面データを取得する"> <br> <input type="text" id="android_data"> <br> <div id="json"> </div> </body> </html>
画面定義
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.lightbox.webviewjavascript.MainActivity"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/scrollView"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="JavaScript からデータを取得" android:id="@+id/button"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="JavaScript へデータを送る" android:id="@+id/button2"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/editSubject"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/editText"/> <WebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/webView"/> </LinearLayout> </ScrollView> </LinearLayout>
関連する記事 Android で WebView を使ってWEBにあるデータベースのデータを取得する為のクラス JavaScript から Android へアクセス Android : WebView 経由のデータベースアプリケーション
|
【Android Studio 2の最新記事】
- Android : WebView 経由のデータベースアプリケーション
- Android で WebView を使ってWEBにあるデータベースのデータを取得する為のクラス
- シンプル Android Data Binding : Android Studio 2.2 / 古い定義との違いと、以前のプロジェクトでエラーが出る場合の対処
- Android Studio で、ListView アプリケーションを作成するテンプレート
- Android : Data Binding で ListView へのデータ表示を凄く簡単にする
- Android Studio で理解不能なエラーが出た時の対処方法 : Invalidate Caches / Restart
- Android : TabHost のタブに下から上へのアニメーションを設定して、include で同一画面を使用するので 回転しないように AndroidMainfest で設定する
- Android : TabHost 内の各 TabSpec 内にある TextView の 端末回転時における保存と復帰
- Android の 端末回転時の EditText と TextView の違い
- ViewPager 内のイベントで設定した TextView の値を保持する Fragment 処理
- ExpandableListView を使用して、タップした時に明細データ表示する
- カスタム・リストビュー・ダイアログ : DialogFragment 内の ListView を ArrayAdapter でカスタムする
- カスタム・リストビュー・ダイアログ : ダイアログ内の ListView を ArrayAdapter でカスタムする
- AlertDialog.Builder の setItems(int itemsId, DialogInterface .OnClickListener listener) を使用した、ListVie..
- AsyncTask<Params, Progress, Result> の Progress を無しにして、onPostExecute 内の処理を interface を使って MainA..
- javamail-android + AsyncTask でメール送信を行う為のテンプレート
- tools.jar の callHttpGet のテストと include による画面再利用と HttpPost クラスで掲示板書き込み / Android Studio
- Android の Spinner に関するいろいろな実装と知識 / Android Studio
- AsyncTask を継承して、Drawable を取得する専用クラスを作成する : Android Studio
- Android Studio が 2.0 になりました。前からの内容も含めて整理してます。