UnityでAndroidNativePluginを作るときのお作法

AndroidNativePluginの作り方

ググると、いろんな記事がありそれぞれやり方が違ったので、なるべく新しい記事や動画を見て作ってみました。
以下の動画がシンプルで一番参考になったので、こちらとおり作ってみます。

eclipseでAndroid Application Projectを作成。
package名はあとあとUnityから呼び出すときに重要なので、それっぽく入力しておく。

targetなどのバージョンはこちらを読むと理解できる。
[Android] Androidアプリ開発における Target SDK, Minimum Required SDK, Compiler SDK の違い

Finishでプロジェクト作成。

メニューのデバッグボタンをクリックし、Android Applicationを選択、繋がっているAndroidデバイスを選んでOK。
Package Explorerからプロジェクトを右クリックし、Propertiesを選択。
PropertiesウィンドウでAndroidを選択、右のペインでIs Libraryにチェックを入れる。

すると上の画像のようにjarファイルが出来上がってる。

では、実際にJavaクラスを作成していく。
該当のパッケージに、Classを追加する。
ここでは、Hogeクラスを作成。

package com.test.androidnativeplugin;

import android.util.Log;

import com.unity3d.player.UnityPlayer;

public class Hoge {

  // 引数あり
  public void FuncA(final String msg) {
    Log.d("Android", "call FuncA");
    Log.d("Android", msg);
  }

  // 戻り値あり
  public String FuncB(final String msg) {
    Log.d("Android", "call FuncB");
    return "Back" + msg;
  }

  // callbackあり
  public void FuncC(final String gameObjName, final String msg) {
    Log.d("Android", "call FuncC");
    Log.d("Android", gameObjName);
    // 第一引数:GameObject名、第二引数:メソッド名
    UnityPlayer.UnitySendMessage(gameObjName, "onCallBack", msg);
  }
}

使いそうな3パターンを作ってみた。

ここからは、Unity上での作業。
以下のようにディレクトリを作成する。

Assets/Plugins/Android

ここにbinディレクトリにあるjarファイルを置く。

次は、

Assets/Plugins

に、Androidのメソッドを呼び出すクラスを作成する。

using UnityEngine;
using System.Collections;

public class HogeAndroidPlugin : MonoBehaviour
{
    public static void CallFuncA (string msg)
    {
#if UNITY_ANDROID
        AndroidJavaObject obj = new AndroidJavaObject("com.teamlab.androidnativeplugin.Hoge");
        obj.Call("FuncA", msg);
#endif
    }

    public static string CallFuncB (string msg)
    {
        string returnVal = null;
#if UNITY_ANDROID
        AndroidJavaObject obj = new AndroidJavaObject("com.teamlab.androidnativeplugin.Hoge");
        returnVal = obj.Call<string>("FuncB", msg);
#endif
        return returnVal;
    }

    public static void CallFuncC (string msg)
    {
#if UNITY_ANDROID
        AndroidJavaObject obj = new AndroidJavaObject("com.teamlab.androidnativeplugin.Hoge");
        obj.Call ("FuncC", "Main", msg);
#endif
    }
}

それぞれのメソッドの中には、Android上での実行かどうかのプリプロセッサを書いている。
iOS側のプラグインを書く場合にも、同様にifで実行環境ごとに分けるのがいいだろう。

ここまででプラグインの準備は完了だ。
あとは、これを呼び出す処理をテスト的に書いてみよう。

EmptyなGameObjectを作成し、MainというC#クラスをアタッチする。

using UnityEngine;
using System.Collections;

public class Main : MonoBehaviour
{
    void OnGUI ()
    {
        if (GUI.Button (new Rect (0, 0, 100, 100), "Push Me")) {
            // FuncA
            HogeAndroidPlugin.CallFuncA (" call CallFuncA ");

            // FuncB
            string returnVal = HogeAndroidPlugin.CallFuncB (" call CallFuncB ");
            Debug.Log ("Unity: " + returnVal);

            // FuncC
            HogeAndroidPlugin.CallFuncC (" call CallFuncC ");
        }
    }

    public void onCallBack (string msg)
    {
        Debug.Log ("Call From Native. (" + msg + ")");
    }
}

特にFuncCは使うシチュエーションがありそうだが、使いすぎると保守がしにくくなりそうだ。
UnityからAndroidに処理がわたり、Androidで処理が終わったタイミングでUnityのコードをCallbackする。(でも便利!)

ここまでで、Androidでjarを作成し、AndroidのNativePluginをUnityから呼び出すコードが書けたわけだ。
あとはBuild & Runを実行するのみ!

ビルドが完了すると実機でUnityで作ったアプリが起動しているはず。

ひとつ問題なのが、デバッグがし難いという点。
eclipseでLogCatビューを表示しておいて、Unityというtagの部分を発見するとちゃんとログがでている。

04-16 16:33:21.374: D/Android(11900): call FuncA
04-16 16:33:21.374: D/Android(11900): call CallFuncA
04-16 16:33:21.384: D/Android(11900): call FuncB
04-16 16:33:21.384: I/Unity(11900): Unity: Back call CallFuncB
04-16 16:33:21.384: I/Unity(11900):
04-16 16:33:21.384: I/Unity(11900): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 53)
04-16 16:33:21.384: D/Android(11900): call FuncC
04-16 16:33:21.384: D/Android(11900): Main
04-16 16:33:21.394: I/Unity(11900): Call From Native. ( call CallFuncC )
04-16 16:33:21.394: I/Unity(11900):
04-16 16:33:21.394: I/Unity(11900): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 53)

参考リンク

Unity3D – Androidネイティブ開発のすすめ – Qiita

実践 Android Developer Tools
実践 Android Developer Tools
posted with amazlet at 14.04.16
Mike Wolfson
オライリージャパン
売り上げランキング: 43,217

iOSのWebViewで外部リンクをネイティブブラウザで開く場合の注意点

以下のメソッド内で外部リンクかどうか判断し、外部リンクの場合はネイティブブラウザを開くようにできるのですが、iOSのWebViewはややこしいことにiframeのリクエスト時もここに入ってきてしまいます。

  - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:  (NSURLRequest *)request navigationType:  (UIWebViewNavigationType)navigationType {-

これだと計測用のiframeのリクエストもこのメソッド内に入るので、外部URLだった場合にネイティブブラウザで開くという挙動を入れると、このiframeのリクエスト時にも反応してしまいます。

そこで方法としては、クリックした場合にのみ外部リンクかどうかを判断するようにするという方法があります。

シンプルなサンプルですが、以下のような雰囲気。

  NSString *absoluteUrl = request.URL.absoluteString;

  // 外部リンクをクリックした場合には、デバイスのデフォルトブラウザを起動する
  NSRange range = [absoluteUrl rangeOfString:@'domain'];
  if (range.location == NSNotFound) {
      if (navigationType == UIWebViewNavigationTypeLinkClicked) {
          [[UIApplication sharedApplication] openURL:request.URL];
          return NO;
      }
  }

そして、WebView側のコードに以下のようにクリックイベントをfireするコードで置き換えます。
もしもアンカーリンクなどをクリックする場合は以下のコードは不要ですが、何かしらJavaScriptでURLを作ってlocation.hrefに入れている場合には置き換えが必要になります。

  $("#hoge")[0].dispatchEvent(click());

  function click(){
    var event = document.createEvent("HTMLEvents");
    event.initEvent('click', true, false);
    return event;
  };

AndroidのWebViewはiframeなどはshouldOverrideUrlLoadingメソッドの中に入ってこないので、楽なのですが、
iOSは結構ややこしいですね。

Push通知をまとめてみた(PushNotification) – iOS and Android

hisasann/PushNotification-iOSandAndroid · GitHubにあげておきました。
iOSとAndroidはディレクトリを分けて各ファイルを置いておきました。

プロジェクト自体は重いのでpushしていないです。
README.mdに基本的なことを記載。

iOS

PushNotification-iOSandAndroid/iOS at master · hisasann/PushNotification-iOSandAndroid · GitHub

release用のPush通知証明書ファイルでためす場合は、AdHocとして入れればOK!

Android

PushNotification-iOSandAndroid/Android at master · hisasann/PushNotification-iOSandAndroid · GitHub

はじめてのAndroidアプリ開発―Android4対応版 (TECHNICAL MASTER)
山田 祥寛
秀和システム
売り上げランキング: 10,811

WebViewのCookieをネイティブ側のリクエストで使う方法(iPhone・Android)

iPhone版

CookieをiPhone側でセット/削除する方法 – 風日記
iphone – Where are an UIWebView’s cookies stored? – Stack Overflow
を見た。

iPhone側はすごく簡単で、WebViewでリクエストを投げると、それだけでASIHTTPRequest(ネイティブ)のリクエストと共有されていた。
Cookieの内容を確認するにはNSHTTPCookieStorageを使うとすぐに見ることができる。

サンプル・コード

NSHTTPCookie *cookie;
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [cookieJar cookies]) {
    NSLog(@"%@", cookie);
    if ([@"lab.hisasann.com" isEqualToString:cookie.domain]) {
        NSLog(@"name - %@", cookie.name);
        NSLog(@"value - %@", cookie.value);
    }
}

gist

iPhoneでWebViewのCookieを共有する方法 — Gist


Android版

Androidのほうはやや複雑、WebViewのリクエストが完了するイベントで、すでにインスタンス化してあるHttpClientに対して、Cookieをaddしないといけない。
これをしないと、HttpClient(ネイティブ)のリクエストにCookieが乗ってくれない。

サンプル・コード

webview.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
  System.out.println("onPageFinished - " + url);
  super.onPageFinished(view, url);

  // Cookieの保存が予想されるURLの場合
  if (url.indexOf("http://lab.hisasann.com/") > -1) {
      // WebViewのCookieを取得
      String cookie = CookieManager.getInstance().getCookie(url);
      System.out.println("cookie - " + cookie);
      String[] cookies = cookie.split(";");
      for (String keyValue : cookies) {
    keyValue = keyValue.trim();
    String[] cookieSet = keyValue.split("=");
    // Cookieを作成
    BasicClientCookie bCookie = new BasicClientCookie(cookieSet[0], cookieSet[1]);
    bCookie.setDomain("lab.hisasann.com");
    bCookie.setPath("/");
    // CookieStoreを取得
    CookieStore store = httpClient.getCookieStore();
    // Cookieを追加
    store.addCookie(bCookie);
      }
  }
    }
});

Cookieの内容を確認するにはCookieStoreを使うとすぐに見ることができる。

サンプル・コード

CookieStore store = httpClient.getCookieStore();
List<Cookie> cookies = store.getCookies();
for (Cookie cookie : cookies) {
    // クッキーの設定
    if ("lab.hisasann.com".equals(cookie.getDomain())) {
  // クッキーを再設定
  System.out.println(cookie.getName() + "=" + cookie.getValue());
  break;
    }
}

つまり、ひとつのHttpClientインスタンスにCookieをセットしているので、複数のHttpClientをnewする場合は、それぞれにCookieを入れないとダメっぽい。

gits

AndroidでWebViewのCookieを共有する方法 — Gist

どうでもいいことだが

iPhoneの

NSURL *url = [NSURL URLWithString:@"http://lab.hisasann.com/cookie/setcookie.php"];

http:/でも動いた。つまりスラッシュが一個足りない!

Androidではダメでした。

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