Unityで2Dゲーム系アプリを作るときのメモ

iOSで60FPS出す方法

Is it possible to get above 30 FPS on an iOS device? – Unity Answers

Androidだとデフォルトで60FPSが出ているんですが、iOSで試すとFPSが30しかでません。
これだと、アニメーションの速度などに差異が出てしまうので、同じにしたいもんです。

Application.targetFrameRate = 60;

これだけでOK!

UnityアプリをiOSで書きだしてビルドするとdSYMのところですんごい時間かかる

  • XCodeのプロジェクト設定ページからBuild Settingsタブを選択
  • dSYMでBuild Options項目を検索
  • DWARF with dSYM fileからDWARFに変更
  • via: UnityアプリをiOSに書き出す時間をdSYMを無効化して早くする – The jonki

    これで解決!
    1分以上かかっていたビルドが数秒になります。

    ただ、XCode上から設定するので、これ以降はUnityはappendでビルドしないといけないかもです。

    prime31のEtceteraTwoの在処が分からない

    LocalNotificationのスケジューリングをしようとすると、EtceteraプラグインだけではダメでTwoのほうが必要になります。
    AndroidのほうはなぜかEtceteraだけでいけるんですが、iOSは2個必要みたい。

    このtwoプラグインはAssetStoreにありません。
    なので、以下のURLから購入することができます。
    prime[31] – the best Unity native code plugins

    ただ、LocalNotificationをただ通知したいだけなら、iOS・Android含めたコードは、

    #if UNITY_IPHONE
            LocalNotification l = new LocalNotification();
            l.applicationIconBadgeNumber = 1;
            l.fireDate = System.DateTime.Now.AddSeconds(10);
            l.alertBody = "hello world";
            NotificationServices.ScheduleLocalNotification(l);
    #elif UNITY_ANDROID
            EtceteraAndroid.scheduleNotification( 10, "Notiifcation Title", "The subtitle of the notification", "Ticker text gets ticked", "my-other-special-data" );
    #endif
    

    こんな感じでもいいかなーと思います。
    LocalNotificationのためだけにtwoで$65払うのもちょっとね。

    PlayerPrefs

    ちょいと値を保存したいとき用に使える。

    Unity – Scripting API: PlayerPrefs

    複数のシーンをマージしてロードする

    テラシュールウェア [Unity3D]ロードの待ち時間を短くする(その4/Application.LoadLevelAdditive)
    このあたりを読むとApplication.LoadLevelAdditiveはかなりオーバーヘッドがあるようで、あまりシーンの分割をしすぎるのは危険のよう。

    開発する人数が多いとシーンの分割はしたくなるが、ある程度最小限のほうがよさそう。

    今回はUnity3D – Unity3Dで、体感ロード時間を減らす – Qiitaを参考にApplication.LoadLevelAdditiveAsyncを採用してみた。

    ただし、非同期で読み込むので、読み込みが完了したかが分からなかったので、以下のようにあるオブジェクトがFindできたらLoadしたとみなす処理を書いた。

    Application.LoadLevelAdditiveAsync ("Game");
    
    StartCoroutine (ObjectChecker (0.1f, () => {
        Debug.Log ("OnLoad---------------------------------");
    }));
    
    IEnumerator ObjectChecker (float secs, Action callback)
    {
        while (GameObject.Find(ConstantsUI.GAME_CAMERA) == null) {
            yield return new WaitForSeconds (secs);
        }
    
        callback ();
    }
    

    このあたりもっと良い方法がないかなー

    NGUIについて

    2Dのゲームアプリを作るため、NGUIを使ってみました。
    はじめの数日はハマることが多いですが、手に馴染んでくるとこれなしでは2Dアプリは作れないという印象。

    NGUIのレンダリングコントロール

    少しやっかいだったのが、レンダリングの順番をどうコントロールするか。

    UIPanelのRenderQでStartAtにしてここでコントロールした。
    NGUIはどうもRenderQueueが3000番から始まるようで、ここを知っていればレンダリングのコントロールは用意です。

    NGUIのコントロールとUnityの3Dコントロール、たとえばパーティクルとかを一緒に使う場合は、以下のようなコードをパーティクルにつけて、3001とかになるようにすればパーティクルが手前にレンダリングされます。

    ■参考リンク

    Webデザイナーが覚えるUnity その2 NGUIの基礎知識 | GREE Creators Blog
    【Unity】NGUIを使ってボタンを実装する。 | albatrus.com
    ゲームは初心者にやさしく: NGUI 描画順について
    NGUIではないオブジェクトをNGUIの手前に描画する方法 – 太郎Work
    ウダサンコウボウ: [Unity]NGUIのDepth設定
    ウダサンコウボウ: [Unity]NGUIのDepth設定その2(複数Atlas)

    NGUIのアニメーションをプログラムから指定する

    TweenPosition tp = TweenPosition.Begin (gameObject, 0.5f, new Vector3 (0, 0, 0));
    tp.tweenGroup = 0;
    tp.from = new Vector3 (transform.localPosition.x, transform.localPosition.y, transform.localPosition.z);
    tp.method = UITweener.Method.EaseInOut;
    tp.style = UITweener.Style.Once;
    tp.AddOnFinished (new EventDelegate (OnShowOverlayEnd));
    tp.Play (true);
    

    ■参考リンク
    開発メモ 「NGUIのまとめ(スクリプトから動的動作)」 – シヴァのブログ
    NGUI TweenPostion.. – Unity Answers
    NGUI: Next-Gen UI kit: TweenPosition Class Reference
    【Unity】NGUIのTweenを利用してみる | albatrus.com

    NGUIのイベントたち

  • void OnHover (bool isOver) – マウスポインタが重なったときに呼ばれます。タップは×。
  • void OnPress (bool isDown) – マウスによりクリック(押下時と離脱時の2回)が発生したときによばれます。タップも○。
  • void OnClick() — 上と同じですが、離脱時の1回だけ呼ばれるパターン。
  • void OnDoubleClick () — ダブルクリックされたとき。
  • void OnSelect (bool selected) –選択メニューで便利?OnClickと同じだけど、こっちは他のものが選択されるまでは2度目の選択時には呼ばれません。
  • void OnDrag (Vector2 delta) – OnPress(true) と OnPress(false)の間に起きた移動を取得。タップ○。
  • void OnDrop (GameObject drag) – ドロップ時(OnPress(false)のタイミング)にドロップされた(下にある)オブジェクト側で呼ばれる。引数には、ドロップした(上にある)オブジェクトが渡される。
  • void OnTooltip (bool show) – Sent after the mouse hovers over a collider without moving for longer thantooltipDelay, and when the tooltip should be hidden. Not sent on touch-based devices.
  • void OnScroll (float delta) マウスのホイールが動いた時によばれる
  • void OnKey (KeyCode key) キーボードが押されたときによばれる
  • via: NGUIでイベントを取得する | Unity3Dのプラグインによる開発

    こちらのサイトにはほんと助けられました。
    どうゆうイベントがいつ呼ばれるのかまったく分かりませんでした。

    動画をネイティブのプレイヤーでフルスクリーンで再生する

    Handheld.PlayFullScreenMovie ("main_movie.mp4", Color.black, FullScreenMovieControlMode.Full);
    

    たったのこれだけ、ただUnity上で実行するのではなくあくまでもネイティブのプレイヤーを表示するので、
    使いどころとしては、何かチュートリアル的な動画などを再生するときかなー

    Unity – Scripting API: Handheld.PlayFullScreenMovie
    Handheld.PlayFullScreenMovie on Android | Unity Community

    Unity4入門   最新開発環境による簡単3Dゲーム製作
    浅野 祐一 荒川 巧也 森 信虎
    ソフトバンククリエイティブ
    売り上げランキング: 1,332

    AndroidのAndroid In App Billingで「アイテムが見つかりませんでした。」の解決方法

    現象について

    UnityでAndroid In App Billing Pluginを使ってアプリ内課金をためす方法
    で、UnityからAndroidの課金のやり方を解説したのですが、ある時から、

    「購入しようとしたアイテムが見つかりませんでした。」

    というエラーが出て、いっこうに課金テストすることができませんでした。

    UnityのPluginが出していたエラーは以下のような感じ。

    You have not queried your inventory yet so the plugin does not have the required information to protect you from coding errors.
    CANNOT fetch sku type due to either inventory not being queried or it returned no valid skus.

    FileBasedKeyValueStore.delete: Attempt to delete ‘lkajsdflkasdlkfjaldkfjaldjflkd’ failed!

    BasicNetwork.performRequest: Unexpected response code 500 for https://android.clients.google.com/fdfe/preparePurchase

    ググると時間がたつと使えるようになるよ。といった記事もあり、待ってみましたがいっこうにダメ!

    さらに調べてみると、公式にアナウンスがあったよう。

    テスト課金、ライセンシング、APK 拡張ファイルのダウンロード 以前はこれらの機能を APK がドラフトの状態でもご利用いただけましたが、最近の変更により、今後は APK が公開されている状態である必要があります。アルファ版もしくはベータ版 APK として公開することでテストが可能で、一般公開する必要はありません。詳細については、Android Developers サイトをご確認ください。

    via
    Android デベロッパー ヘルプ

    つまり、課金の仕組み変わって、ドラフト状態だと課金のテストができないよーというもの。

    Testing In-app Billing | Android Developers
    にもDraft Apps are No Longer Supportedという文章がありました。

    これはハマるよー!わからないよー!

    解決策

    Google Play アルファ版機能を利用してアプリをテストする -でじうぃき
    「購入しようとしたアイテムが見つかりませんでした。」アプリ内課金の実機テストが出来なくなった時の対処法 | 日本VTR実験室

    こちらの記事を読んでもらえれば解決します。

    • Google Playでアルファにapkをアップロードする
    • アルファのテスターのリストを管理をクリックし、テストユーザーを含むGoogleグループを追加する
    • アプリを公開する、キャプチャなどの画像・国・規約に同意などなど
    • 公開したあと(この公開はアルファ公開なので一般にはDLできない)再度テスターのリストを管理をクリックするとURLが書いてあるので、Android端末からアクセスする
    • テストユーザーに含めたGoogleグループに所属しているユーザーならアプリのDLが出来るようになっている
    • アプリがDL出来るようになるまで少し時間が掛かります
    • またすでにUnityから同アプリを入れている場合はアンインストールしておきましょう
    • DLアプリを起動し、課金してみるとやっと念願の課金画面がでてきます

    と、かなり面倒になってしまいました。
    とくに面倒なのが、アプリを修正するたびに課金の確認をする場合はapkのアップロードが必要なところですね。

    この仕様変更は、どうも調べてると5月のどっかでされたようで、確かにその時期からなんかうまくいかないなーと思っていました。

    Unityでgree/unity-webviewを使ってLandscapeでWebViewを表示するときの注意点

    UnityでWebViewを表示しようと思ってググるとgreeのこのプラグインがよく使われているので、試してみました。
    gree/unity-webview · GitHub

    ざっくりとサンプルを書くとこんな感じです。

    using UnityEngine;
    using System.Collections;
    
    public class WebviewSample : MonoBehaviour {
      public string url;
      WebViewObject webViewObject;
    
      // Use this for initialization
      void Start () {
        webViewObject = (new GameObject("WebViewObject")).AddComponent<WebViewObject>();
        webViewObject.Init((msg) => {
          Debug.Log(msg);
        });
        webViewObject.LoadURL("http://google.com/");
        webViewObject.SetMargins(0, 0, 0, 100);
        webViewObject.SetVisibility(true);
      }
    
      // Update is called once per frame
      void Update () {
    
      }
    }
    

    あとは、AndroidManifest.xmlに以下を追加し、変更点を加える。

    インターネットを使えるようにする。

    <uses-permission android:name=”android.permission.INTERNET”/>
    

    これをしないとWebView内のタッチがDOM側に通知されない。

    <meta-data android:name=”unityplayer.ForwardNativeEventsToDalvik” android:value=”true” />
    

    iOSでlandscape時に横幅いっぱいにWebViewが表示されない

    この問題を修正したプルリクがあり、あれちゃんと対応されているなーと思っていたんですが、
    Merge pull request #13 from endavid/fix/iOSLandscape · 3343201 · gree/unity-webview
    どうもunity-webview.unitypackageがこのコードを含んだ状態になっていないようで、これをいくらimportしてもうまくいかなくてハマりました。

    以下のふたつのファイルをimport後に上書きするとこの問題は解消されます。

    1. unity-webview/plugins/WebViewObject.cs at master · gree/unity-webview
    2. unity-webview/plugins/iOS/WebView.mm at master · gree/unity-webview

    参考リンク

    unity-webviewを使ってサイトを表示してみました!! 〜i-tai.net〜
    Unity上でWebViewを表示する | 代打、俺
    Unity で WebView を表示してみた。 | Lonely Mobiler

    Unity4ゲームコーディング 本当にゲームが作れるスクリプトの書き方
    浅野 祐一 荒川 巧也
    SBクリエイティブ
    売り上げランキング: 9,521

    UnityでAndroidのPushNotification(GCM)を実装する方法

    ググってもあんまり有力な記事がなく、AssetStoreを探してみても良さそうなのがなくまだまだUnityでのPushNotificationはこれからなのかなーという印象を受けました。

    過去にネイティブでやったときのコードはgithubにまとめてあるので、そちらをご覧ください。
    PushNotification-iOSandAndroid/Android at master · hisasann/PushNotification-iOSandAndroid

    そんな中、試してみようかと感じた記事がこちら。
    Android PNS for Unity Games | Games2win Developers
    iOS版の記事もあり、そちらはPrime31のAssetを使っていて、良さそう!と思いました。

    Asset Store – Android Etcetera PluginこれのiOS版にはPushNotificationのコードが入っていますが、Android版にはない様子。
    でも↑の記事の中でつかつているunitypackageの中に少しPrime31のコードもあったので、そちらを使うで十分そう。

    事前にSender Id、PushNotification用のIdを作成する

    よくやる作業なので、ここでの説明は割愛。

    この作業はGoogle Developers Consoleで行います。
    ProductIdというのができていたのですが、そちらではPushNotificationがうまく動かず、Project Numberを使うといけました。

    PushNotification用のunitypackageをimportする

    GoogleCloudMessaging-Modified.unitypackage – Google Drive
    こちらからDLし、Unityにインポートしておきます。

    AndroidManifest.xmlを作成する

    ↑のunitypackageを入れると、prime[31]のエディター拡張がインストールされるので、それを使うのが便利です。
    メニューからprime[31] → Generate AndroidManifest.xml Fileこれを実行すると、

    Assets/Plugins/Android

    の下に、AndroidManifest.xmlファイルが作成されます。
    すでにある場合はマージしてくれるようです。

    一応貼っておくとこんな感じ。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest
        xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.unity3d.player"
        android:installLocation="preferExternal"
        android:theme="@android:style/Theme.NoTitleBar"
        android:versionCode="1"
        android:versionName="1.0">
        <supports-screens
            android:smallScreens="true"
            android:normalScreens="true"
            android:largeScreens="true"
            android:xlargeScreens="true"
            android:anyDensity="true"/>
    
        <application
        android:icon="@drawable/app_icon"
            android:label="@string/app_name"
            android:debuggable="true">
            <activity android:name="com.prime31.UnityPlayerNativeActivity" android:screenOrientation="portrait"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
                <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
                <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
            </activity>
    
            <receiver
                android:name="com.prime31.GCMBroadcastReceiver"
                android:permission="com.google.android.c2dm.permission.SEND">
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
                    <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
                    <category android:name="PACKAGE_NAME"/>
                </intent-filter>
            </receiver>
    
        <meta-data android:name="com.prime31.GoogleCloudMessagingPlugin" android:value="UnityPlayerActivityProxy"/>
      </application>
    
      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
      <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
      <permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
      <uses-permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE"/>
    
    </manifest>
    

    重要な箇所は、

    <receiver
        android:name="com.prime31.GCMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
            <category android:name="PACKAGE_NAME"/>
        </intent-filter>
    </receiver>
    

      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
      <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
      <permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
      <uses-permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE"/>
    

    ここ。

    PACKAGE_NAMEというのが3箇所あります。
    ここを自分のUnityProjectのBundleIdと同じにします。

    はじめ、1個目のPACKAGE_NAMEしか見えていなく、ここだけ直してビルドしていたらいっこうに通らなくてハマりました。
    そんときのエラーはこちら。
    Android SDK Manifest file error: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED – Stack Overflow

    demoのシーンを使ってPushNotificationを試してみる

    demoにシーンがあるので、そちらを表示しておく。
    EventListenerとuiというGameObjectがあるので、そのソースを表示する。

    GoogleCloudMessagingEventListener.cs

    大事なメンバはnotificationReceivedEventメソッドとregistrationSucceededEventメソッドです。
    元記事のサンプルをもとに書くとこんな感じです。

    using UnityEngine;
    using System;
    using System.Text;
    using System.Collections;
    using System.Collections.Generic;
    
    public class GoogleCloudMessagingEventListener : MonoBehaviour
    {
    #if UNITY_ANDROID
      private string regURL;
      private const string host = "test.com/add/";
    
      //PNS ID provided by the publishing team
      private const string pnsID = "7";
    
      //PNS Environment = should be 'production' in the release build
      private const string pnsEnvironment = "sandbox";
    
      //APP version
      private const string pnsAppVersion = "1.0";
    
      void OnEnable()
      {
        // Listen to all events for illustration purposes
        GoogleCloudMessagingManager.notificationReceivedEvent += notificationReceivedEvent;
        GoogleCloudMessagingManager.registrationSucceededEvent += registrationSucceededEvent;
        GoogleCloudMessagingManager.unregistrationFailedEvent += unregistrationFailedEvent;
        GoogleCloudMessagingManager.registrationFailedEvent += registrationFailedEvent;
        GoogleCloudMessagingManager.unregistrationSucceededEvent += unregistrationSucceededEvent;
      }
    
    
      void OnDisable()
      {
        // Remove all event handlers
        GoogleCloudMessagingManager.notificationReceivedEvent -= notificationReceivedEvent;
        GoogleCloudMessagingManager.registrationSucceededEvent -= registrationSucceededEvent;
        GoogleCloudMessagingManager.unregistrationFailedEvent -= unregistrationFailedEvent;
        GoogleCloudMessagingManager.registrationFailedEvent -= registrationFailedEvent;
        GoogleCloudMessagingManager.unregistrationSucceededEvent -= unregistrationSucceededEvent;
      }
    
    
    
      void notificationReceivedEvent( Dictionary<string,object> jsonDict )
      {
        Debug.Log( "notificationReceivedEvent" );
        Prime31.Utils.logObject( jsonDict );
    
        int badgeNum = int.Parse(jsonDict["badge"].ToString());
        string openURL = jsonDict["open_url"].ToString();
        if(!string.IsNullOrEmpty(openURL))
        {
          Application.OpenURL(openURL);
        }
        GoogleCloudMessaging.setBadge(badgeNum);
        GoogleCloudMessaging.cancelAll();
        showBadge();
      }
    
    
      void registrationSucceededEvent( string registrationId )
      {
        Debug.Log( "registrationSucceededEvent: " + registrationId );
    
        StringBuilder urlString = new StringBuilder ();
        urlString.AppendFormat('?registration_id={0}',WWW.EscapeURL(registrationId));
        urlString.AppendFormat('?app_id={0}',WWW.EscapeURL(pnsID));
        urlString.AppendFormat('?app_version={0}',WWW.EscapeURL(pnsAppVersion));
        urlString.AppendFormat('?apns_env={0}',WWW.EscapeURL(pnsEnvironment));
    
        regURL = urlString.ToString ();
        Debug.Log("regURL: " + regURL);
    
        StartCoroutine ("RegDeviceForPush");
      }
    
    
      void unregistrationSucceededEvent()
      {
        Debug.Log( "UnregistrationSucceededEvent" );
      }
    
      IEnumerator RegDeviceForPush ()
      {
        WWW w = new WWW ("http://" + (host + regURL), UTF8Encoding.UTF8.GetBytes (regURL));
        yield return w;
      }
    
    #endif
    }
    

    GoogleCloudMessagingUI.cs

    重要な箇所は、

    GoogleCloudMessaging.register( "1234567890" );  // sender id
    

    string pushIOApiKey = "lkajdsflkajsdflkajsdflkjadsflkj";  // push key
    

    です。

    using UnityEngine;
    using System;
    using System.Net;
    using System.Collections;
    using System.Collections.Generic;
    using System.Security.Cryptography.X509Certificates;
    using System.Net.Security;
    using Prime31;
    using System.Text;
    using System.Security.Cryptography;
    
    
    public class GoogleCloudMessagingUI : Prime31.MonoBehaviourGUI
    {
    #if UNITY_ANDROID
      private string _registrationId;
    
      void Start()
      {
        // listen for successful registration so we can save off the registrationId in case we want to use it for Push.io registration.
        GoogleCloudMessagingManager.registrationSucceededEvent += regId =>
        {
          Debug.Log("regId: " + regId);
          _registrationId = regId;
        };
      }
    
    
      void OnGUI()
      {
        beginColumn();
    
        if( GUILayout.Button( "Check for Notifications" ) )
        {
          GoogleCloudMessaging.checkForNotifications();
        }
    
    
        if( GUILayout.Button( "Register" ) )
        {
          // replace this with your sender ID!!!
          GoogleCloudMessaging.register( "1234567890" );
        }
    
    
        if( GUILayout.Button( "Unregister" ) )
        {
          GoogleCloudMessaging.unRegister();
        }
    
    
        if( GUILayout.Button( "Cancel All Pending Notifications" ) )
        {
          GoogleCloudMessaging.cancelAll();
        }
    
    
        endColumn( false );
      }
    #endif
    }
    

    アプリを起動して、Registerボタンを押すと、以下のようにLogCatに出力されます。

    04-30 15:02:50.225: I/GCM(9377): GCM config loaded
    04-30 15:02:50.346: I/Prime31-GCMReceiver(17557): recieved broadcast. message type from bundle is null
    04-30 15:02:50.346: I/Prime31-GCMReceiver(17557): received a null message type so we arent interested. aborting
    04-30 15:02:50.406: I/Unity(17557): registrationSucceededEvent: lkajsdflkjasdflkjadsf;lkjadslfkjasd;lfkjalkjasdfl;kjasdf;lkjasdf;lkjasdf
    04-30 15:02:50.406: I/Unity(17557):
    04-30 15:02:50.406: I/Unity(17557): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 53)
    04-30 15:02:50.406: I/Unity(17557): regId: lkajsdflkjasdflkjadsf;lkjadslfkjasd;lfkjalkjasdfl;kjasdf;lkjasdf;lkjasdf
    04-30 15:02:50.406: I/Unity(17557):
    04-30 15:02:50.406: I/Unity(17557): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 53)

    ここのregistrationSucceededEventの中でregisterIdが取れるので、PushNotificationを配信するサーバーに登録しましょう。

    割りと書くコードも少なく、すっきり書けるのでこの方法が僕の中でいまのところ安定しています。
    これからいいAssetとかが見つかれば、そちらの記事をまた書こうかと思います。

    追記:

    タイミングの話し

    UnityでiOSのPushNotification(APNS)を実装する方法 | ひささん日記こちらにも書いたのですが、PushNotificationを受け取るタイミングでひとつ試してなかったのがありました。
    それが、アプリがバックグラウンドにいるときにPushNotificationを受け取って、その後にホーム画面のアプリアイコンをタップした場合にPushNotificationが来ていたかどうかを確認する方法です。
    長いですね。

    結論から言うと、おそらくこの場合のPushNotificationの確認はできません。
    調べたことを順に書いておきます。

    prime[31] – Unity plugin documentationここを見てみると、checkForNotificationsというメソッドがあることに気が付きました。
    書いてあるコメントにもなんだかそれっぽいことが書かれていたので、ここを解析していきました。

    googlecloudmessagingplugin.jarの中にあるclassファイルを全部解凍してみて、見てみると、
    GCMBroadcastReceiverというクラスがありました。
    この中で、

    private void sendNotification(Context context, Bundle bundle, String jsonPayload)
    {
        String launchClassName = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()).getComponent().getClassName();
        ComponentName comp = new ComponentName(context.getPackageName(), launchClassName);
        Intent notificationIntent = (new Intent()).setComponent(comp);
        notificationIntent.putExtra("notificationData", jsonPayload);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0x10000000);
        NotificationManager noteManager = (NotificationManager)context.getSystemService("notification");
        android.support.v4.app.NotificationCompat.Builder noteBuilder = new android.support.v4.app.NotificationCompat.Builder(context);
        noteBuilder.setContentIntent(pendingIntent);
        noteBuilder.setAutoCancel(true);
        noteBuilder.setSmallIcon(context.getApplicationInfo().icon);
        noteBuilder.setContentTitle(context.getApplicationInfo().loadLabel(context.getPackageManager()));
        if(bundle.containsKey("message"))
            noteBuilder.setContentText(bundle.getString("message"));
        else
            noteBuilder.setContentText("");
        String tickerText = "Push Notification Received (default tickerText)";
        noteBuilder.setDefaults(1);
        if(bundle.containsKey("message"))
            tickerText = bundle.getString("message");
        noteBuilder.setTicker(tickerText);
        noteBuilder.setWhen(System.currentTimeMillis());
        noteManager.notify(101, noteBuilder.build());
        Log.i("Prime31-GCMReceiver", "notification posted");
    }
    

    ここが、PushNotificationを受けたときに、メッセージを表示している箇所です。
    notificationIntent.putExtraで必要な情報をIntent経由で渡すんだろうなーという感じです。

    そして、GoogleCloudMessagingPluginクラスの中に、該当のクラスがあるので、ここで取得しているんだろうなーという印象。

    public void checkForNotifications()
    {
        Bundle intentExtras = _lastIntent == null ? getActivity().getIntent().getExtras() : _lastIntent.getExtras();
        if(intentExtras != null && intentExtras.containsKey("notificationData"))
            receivedNotification(intentExtras.getString("notificationData"));
    }
    

    とくに何かバグがありそうな気配もないですし、以前は有料?としてAssetStoreで売られていたAssetなので、そんな不備がある可能性は低い。

    そこでprime31のgithubに書かれている、送る側のコードに問題があるんじゃないかと思い、自分のコードは使わず、Simple PHP script showing how to send an Android push notification. Be sure to replace the API_ACCESS_KEY with a proper one from the Google API’s Console page. To use the script, just call scriptName.php?id=THE_DEVICE_REGISTRATION_IDこのコードを使いました。

    それでもやはりバックグラウンドで受けて、フォアグラウンドになったあとにcheckForNotificationsしてもうんともすんとも言わず。

    そのときのログに出ていたこのCANCELLEDが問題なのかなーとググってみても解決策はなし。

    05-02 20:56:00.291: W/GCM/DMM(9377): broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg=hoge.hoge (has extras) }

    そこで再度、sendNotificationメソッドの中を見ていたら、PendingIntentというのを使っているのに気がついた。

    あ!
    あああ!

    android初心者プログラミング: PendingIntentによると。

    例えば、Notificationに使う場合は、
    通知バーをタップしたタイミングでIntentが発行されるという感じのようです。

    via: android初心者プログラミング: PendingIntent

    PushNotificationを受けて、通知バーよりタップされた場合にPendingIntent経由でextraDataが登録され、そのあとにcheckForNotificationsを実行するとPushNotificationで来た情報が取得できます。
    なんという限定的!

    これを回避するために、Broadcastする方法がありますが、このプラグインでは採用されていません。

    Intent intent = new Intent(ConstantsUtil.DISPLAY_MESSAGE_ACTION);
    intent.putExtra(ConstantsUtil.GCM_EXTRA_BADGE, badge);
    context.sendBroadcast(intent);
    

    解決策としては、アプリ起動時に毎回PushNotificationがあったかどうかを確認するAPIを呼ぶとかですかねー
    まだどうするのが最適解か考え中です。

    難しい、、、

    Androidの絵本 スマートフォンアプリ開発を始める9つの扉
    株式会社アンク
    翔泳社
    売り上げランキング: 100,308

    UnityでAndroid In App Billing Pluginを使ってアプリ内課金をためす方法

    prime31Asset Store – Android In App Billing PluginがIABを使うときによさそうなので、このAssetの使い方をメモしていきます。

    Google Play Developer Consoleにアプリを登録する

    アプリをいったん仮に登録しないと、アイテム画面に行けないのでまずはこちらをアップします。

    こちらからgmailアカウントをもとに登録していきます。
    すべてのアプリ – Google Play Developer Console
    $25かかってしまうのですが、これをしないとアプリ内課金の仕組みが使えません。

    間違えやすいのですが、Google Developers Consoleではないよ。

    ここでUnityからapkファイルを作成して、アップする必要があるのですが、公開用の署名が付いていないのとアップ時にエラーになってしまいます。
    Unityではこの鍵を作るところもサポートしてくれています。

    BuildSettings → AndroidのPlayerSettingsを開く。
    Publushing SettingsでCreate New Keystoreにチェックを入れる。
    BrowseKeystoreでKeystoreの保存場所を選択する。
    Passwordを入力する。
    AliasプルダウンからCreateNewKeyを選択する。
    Aliasには仮にtestと入力しました。
    Passwordを入力する。
    CreateKeyで作成する。
    作成したあとに、Aliasで今作成したAliasを選択する。

    via 【Unity】keystoreの生成 Android – Litのプログラムとかアプリ開発とか。

    では、Buildしてapkファイルを作成し、Consoleにアップしましょう!
    ちなみに、製品版にアップしてもただちに公開されるわけではないので大丈夫です。

    他にベータ版・アルファ版とアップ先がありますが、ここでは製品版にアップしてみました。

    Google Play Developer Consoleに課金用のアイテムを登録する

    アプリ内アイテムで新しいアイテムを追加をクリックすると、
    以下から選ぶようにいわれます。

    1. 管理対象の商品
    2. 管理対象外の商品
    3. 定期購入

    この違いは、以下のサイトから引用させていただきます。

    1.はアカウント単位で1回購入できるアイテム(電子書籍とか)。appleでいうNon-consumable
    2.は同一アカウントで何回も購入できるアイテム(回復系アイテムとか)。appleでいうConsumable
    3.これはappleのAuto-renewable subscriptionsかな?

    via: エンジニアブログはじめました: アンドロイドでアプリ内課金(v3)のテスト環境

    今回は魔法石のように何度でも買えるようにするために、管理対象外の商品を選択します。

    アイテムを作成したら、プルダウンから有効にしておきます。
    これを忘れると、いっこうにアイテム課金ができません。(ぼくはこれで結構ハマった!)

    UnityでAndroid In App Billing Pluginを使ってみる

    Android In App Billing Pluginをimportするとdemoがあるので、そのシーンに移動する。

    以下、[Unity][Android] (In App Billing ver3版) prime31 Android IAB Pluginを使用したアプリ内課金の実装 : West Hill 開発メモこちらの記事を参考に、必要そうな処理も追記しています。

    GoogleIABEventListener.cs

    そもそもHierarchyに追加したgameObjectが破棄されることがあるのか調査できていないのですが、これを追加。

    void Start ()
    {
        // このオブジェクトが破棄されないようにする
        DontDestroyOnLoad (gameObject);
    }
    

    IABUIManager.cs

    ConsoleのサービスとAPIから、ライセンスキーをinitの中に記述します。
    アプリケーションが終了した場合にunbindされるようにしておきます。

    void OnApplicationQuit() {
        Debug.Log("OnApplicationQuit");
        GoogleIAB.unbindService();
    }
    

    以下のボタンのところを追加したアイテムIDに変更しておきます。
    注意:消費系のアイテムの場合、GoogleIAB.purchaseProductしたあとにGoogleIAB.consumeProductをしないと再度買うことができません。

    if( GUILayout.Button( "Purchase Test Product" ) )
    {
        GoogleIAB.purchaseProduct( "magic_stone" );
    }
    
    
    if( GUILayout.Button( "Consume Test Purchase" ) )
    {
        GoogleIAB.consumeProduct( "magic_stone" );
    }
    

    ただ、IAB側の問題なのか調査できていないのですが、このボタンを順番に押した時に、GoogleIAB.consumeProductが失敗する場合がありました。
    ログを見ながら押していったのですが、GoogleIAB.consumeProductするのが速すぎたのかな。

    なので、GoogleIABEventListener.csの購入が成功したイベントハンドラでGoogleIAB.consumeProductするようにしてみました。
    実際にはこうゆうコードでやることになるのかな。

    // 購入が正常に終わると呼ばれる
    void purchaseSucceededEvent( GooglePurchase purchase )
    {
        Debug.Log( "purchaseSucceededEvent: " + purchase );
        GoogleIAB.consumeProduct(purchase.productId);
    }
    

    引数のGooglePurchaseに購入レシート情報が入っています。こちらを見て、何を買ったのかをアプリケーション・サーバーに通知するのかな。

    テスト課金って書いてあってもなんか怖いねw

    参考リンク

    AndroidBilling – 開発資料
    Google Play In-App Billing テスト方法 | Androidアプリ開発の紆余曲折
    android/アプリ内課金 – 初心者エンジニアの簡易メモ
    エンジニアブログはじめました: アンドロイドでアプリ内課金(v3)のテスト環境

    見れていないのですが、
    In-App Purchasing Unity Plugin for Android Tutorial – YouTube
    こちらの動画も参考になるかも。
    自前でやっているっぽいですが。

    iPhone&Androidアプリ内課金プログラミング完全ガイド (Smart Mobile Developer)
    佐藤 航陽 加藤 勝也 瀬戸 健二 日高 正博
    翔泳社
    売り上げランキング: 182,508

    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