AppとWebViewの相互の機能の呼び方、そしてhistory.back()について(iPhone・Android)

history.back()が出来る状況が毎度毎度分からなくなってしまうので、ここにまとめておきます。
また、AppからWebViewの機能を呼ぶ、WebViewからAppの機能を呼ぶということもどうゆう状況だとできるのかもまとめておきます。
むしろこっちのほうが忘れやすい。

history.back()

ローカルHTMLファイルの場合

iPhone 動かない
Android 動く

リモートHTMLファイルの場合

iPhone 動く
Android 動く

なので、history.back()を前提にしているjQueryMobileを使うサイトは、ローカルではなくリモートを参照したほうがいいでしょう。


Titanium.App.fireEvent

ここはTitaniumのfireEventが動く範囲をまとめてみました。

ローカルHTMLファイルの場合

iPhone 動く
Android 動く

リモートHTMLファイルの場合

iPhone 動かない
Android 動かない

iPhoneとWebView

ここからはネイティブの話しになります。

iPhoneでアプリ側からWebViewに対してJavaScriptを実行する方法

「App → WebView」

WebViewをもつViewControllerのヘッダファイルにて、UIWebViewDelegateプロトコルを指定しておく。

@interface ViewController : UIViewController <UIWebViewDelegate>

すると以下のメソッドたちが使えるので、準備しておく。

- (void)webViewDidStartLoad:(UIWebView *)webView {
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    return YES; // Return YES to make sure regular navigation works as expected.}
}

あとはstringByEvaluatingJavaScriptFromStringを呼び出すだけ。

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSString *title = [self.webView stringByEvaluatingJavaScriptFromString:@"$.method()"];
    NSLog(@"title - %@", title);
}

このメソッドの便利なところは、returnがあるところ、後述するがAndroidのほうだとreturnが返ってこないので、少し強引に取得する必要があったりする。

WebViewからiPhoneアプリ側に処理を通知する方法

「WebView → App」

こっちはURLのリクエスト時に、自分宛、つまり自分のアプリ内で処理して欲しいURLを渡して、それを監視している。
結果、ルールにのっとったURLの場合は、適切な処理を呼べば良い。

面白いところは、自分宛のリクエストはreturn NOを返してなかったことにしている。
これをしないと、真っ白な画面に遷移してしまう。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  NSString *requestString = [[request URL] absoluteString];
  NSArray *components = [requestString componentsSeparatedByString:@":"];

  if ([components count] > 1 &&
        [(NSString *) [components objectAtIndex:0] isEqualToString:@"myapp"]) {
    if ([(NSString *) [components objectAtIndex:1] isEqualToString:@"myfunction"]) {
      // ここで何か処理
      NSLog(@"%@", [components objectAtIndex:2]); // param1
      NSLog(@"%@", [components objectAtIndex:3]); // param2
    }
    return NO;
  }

  return YES; // Return YES to make sure regular navigation works as expected.}
}

WebViewからの呼び出し方。

location.href = "myapp:" + "myfunction:" + param1 + ":" + param2;

比較的にシンプルにできるが、ディレクトリトラバーサルなど、セキュリティに気をつける必要がある。


AndroidとWebView

Androidでアプリ側からWebViewに対してJavaScriptを実行する方法

iPhone型と違って、view.loadUrlにjavascriptスキームを渡してもreturnが得られません。
なので、ハックに近いですが、あえてalertを実行させ、そのアラートが出る前のイベントonJsAlertでメッセージから情報を取得します。
そのあとに、result.confirm()をすることで、アラートがなかったことにできるようです。

なかなか面白いですね。

WebView webview = (WebView) findViewById(R.id.webView1);
webview.getSettings().setJavaScriptEnabled(true);

webview.setWebViewClient(new WebViewClient() {
  @Override
  public void onPageFinished(WebView view, String url) {
    view.loadUrl("javascript:alert($.method())");
  }
});
webview.setWebChromeClient(new WebChromeClient() {
  @Override
  public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    try {
      System.out.println("onJsAlert - " + message);

      return true;
    } finally {
      // アラートが出ないようにしている
      result.confirm();
    }
  }
});

webview.loadUrl("http://lab.hisasann.com/appEval/");

WebViewからAndroidアプリ側に処理を通知する方法 – その1

WebViewからAndroidアプリ側に情報を渡すのは以下の感じで、iPhone側とすごく似ていますね。
ただ、Androidのほうは直接JavaScriptコードからAndroid側のアプリをキックできるようです。

それは後述します。

webview.setWebViewClient(new WebViewClient() {
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    String[] components = url.split(":");

    // 特定のURLの場合、ダイアログを表示する等
    if (components[0].length() > 1 && "myapp".equals(components[0])) {
        if ("myfunction".equals(components[1])) {
          System.out.println(components[2]);
          System.out.println(components[3]);

          return true;
        }
    }

    return false;
  }
});

WebViewからの呼び出し方。

location.href = "myapp:" + "myfunction:" + param1 + ":" + param2;

WebViewからAndroidアプリ側に処理を通知する方法 – その2

先ほどのwebview変数にJavascriptInterfaceをセットします。

webview.addJavascriptInterface(new JavascriptAdapter(), "android");

そのアダプターの中で、JavaScriptから呼び出されるメソッドを定義しておきます。

package com.example.sample_webvieweval;

import android.os.Handler;

public class JavascriptAdapter {
  private Handler handler = new Handler();

  // JavaScriptから呼び出されるメソッド
  public void getFromJS(final String s) {
    handler.post(new Runnable() {
        public void run() {
          System.out.println(s);
        }
    });
  }
}

WebViewからの呼び出し方。

// Javaのメソッドを呼び出す
window.android.getFromJS("called getFromJS");

するとこのようにwindowオブジェクトの中にJavascriptInterfaceの名前空間が追加されているので、そこから呼びたいメソッドをコールできます。

すごく簡単!
すごく簡単なんだけど、やっぱりこれもセキュリティをかなり意識しないといけないですね。

参考リンク

本格アプリを作ろう! Androidプログラミングレシピ
Dave Smith Jeff Friesen
インプレスジャパン
売り上げランキング: 13499

TitaniumMobile2.1.2とXCode4.5でハングした場合の対処

ちょっとハマったんですが、このバージョンでTiのRunConfigを開いたり、iOS Deviceで起動をするとXCodeとともにハングしてしまって強制終了するしかなかった。

結論としては、XCodeのCommand Line ToolsUpdateしたら直りました。
そうかーと、たしかにTiはこれを使っていると思うので、ここのバージョンアップが必要でした。

TitaniumMobile2.1.2とXCode4.5でハングした場合の対処

ふだんiOSアプリ作っているときは気にしないですもんね。

気がついたらiPhone5、iOS6対応されてた

  • iOS — Support for iOS 6 and XCode 4.5.
  • iOS — Addition of new API for authorization to access contacts.
  • iOS — iOS 6-specific fixes for issues surrounding labels, text fields, audio and layout.
  • via: Titanium SDK 2.1.3 Is Released « Appcelerator Developer Center

    Titanium SDK 2.1.3 Is Released « Appcelerator Developer Center

    iPhone5で試してみたら、ちゃんと縦長でした!

    気がついたらデフォルトアプリのサイズが7.1MBになってた

    ちょっと前まで5MB近くだったような気がするんですが、iOS6分が増えたからなのかただのSingle Windowなアプリが7.1MBになっていた。
    このサイズはちょっと使うのを悩むレベルですね。

    7.1MBあれば、それ相応なアプリが作れてしまうので、さらに画像を使うと平気で10MB超えてしまう。