ググってもあんまり有力な記事がなく、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が発行されるという感じのようです。
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を呼ぶとかですかねー
まだどうするのが最適解か考え中です。
難しい、、、
翔泳社
売り上げランキング: 100,308