2014年4月18日金曜日

Google App Engine/J と Titanium MobileでPush Notification

先日人狼オンラインXにPush Notificationを実装したのですが、いくつかハマった箇所があったので書いておきます。

■Google App Engine/J

・GAE/J iPhone用の実装
iPhone用のPush送信はAppleのAPNSというサービスを使います。
APNS配信にはAppleのDeveloper Centerで証明書と秘密鍵を取得する必要がありますが、こちらを参考にしてください。
http://support.titanium-mobile.jp/questions/2187

ここで注意点ですが、GAE/Jではpemファイルではなく、キーチェーンアクセスの自分の証明書から「Apple Development iOS Push Services:***」を右クリックして保存した .p12 ファイルとパスワードだけを使います。
(pemファイルの作成は不要です)
作成した .p12 ファイルを /WEB-INF/ 以下に置きます。

実装にはjavapnsを使いました。
https://code.google.com/p/javapns/

こちらのサンプルコードを参考にしました。
https://github.com/GoogleCloudPlatform/solutions-ios-push-notification-sample-backend-java

こんな感じです。

 private static final String CERTTIFICATE_FILE_NAME = "/WEB-INF/apns_cert.p12";
 static final String CERTIFICATE_PASSWORD = "**************";
 private static byte[] certificateBytes = null;

 static public void sendAPNSMessage(String msg, List devices) {
  PushNotificationPayload payload = PushNotificationPayload.complex();
        try {
            payload.addAlert(msg);
            payload.addSound("default");
        } catch (JSONException e) {
   e.printStackTrace();
        }

  List payloads = new ArrayList();
  for (String device : devices) {
   PayloadPerDevice payloadPerDevice = null;
   try {
    payloadPerDevice = new PayloadPerDevice(payload, device);
    payloads.add(payloadPerDevice);
   } catch (InvalidDeviceTokenFormatException e) {
    log.log(Level.SEVERE, "Error", e);
   }
  }

  PushedNotifications notifications = null;
  try {
   notifications = Push.payloads(getCertificateBytes(),
     CERTIFICATE_PASSWORD, true, // Development:false
            // Production:true
     payloads);
  } catch (CommunicationException e) {
   log.log(Level.SEVERE, "Error", e);
  } catch (KeystoreException e) {
   log.log(Level.SEVERE, "Error", e);
  }

  if ( notifications != null ){
   for (PushedNotification notification : notifications) {
    String iosDeviceToken = notification.getDevice().getToken();
    if (notification.isSuccessful()) {
    } else {
    }
   }
  }
 }

 protected static byte[] getCertificateBytes() {
  if (certificateBytes == null) {
   InputStream certifacteStream = this.getServletContext()
     .getResourceAsStream(CERTTIFICATE_FILE_NAME);
   if (certifacteStream == null) {
    return null;
   }
   ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

   byte[] buffer = new byte[4096];

   try {
    int bytesRead = 0;
    while ((bytesRead = certifacteStream.read(buffer, 0,
      buffer.length)) != -1) {
     outputStream.write(buffer, 0, bytesRead);
    }
   } catch (IOException e) {
    log.log(Level.SEVERE, "Error reading the certificate", e);
   }

   certificateBytes = outputStream.toByteArray();
  }
  return certificateBytes;
 }


・GAE/J Android用の実装
AndroidにはGoogle Cloud Messaging(GCM)を利用します。
GAE/JでGCMを使うために、こちらを参考に gcm-server.jar と json_simple-1.1.jar をプロジェクトに含めてください。
http://intink.blogspot.jp/2012/10/google-cloud-messaging-for-android.html

一つ一つメッセージを送るのならいいのですが、多くのデバイスにまとめて送信しようとすると文字化けします。
https://groups.google.com/forum/#!msg/google-app-engine-japan/CfREYgz9NDc/wPEYvtLgiiAJ
こちらで紹介されている MySenderを利用させて頂きました。

こんな感じです。

 private static final String GCM_API_KEY = "AIzaSyDJmKm3FGjMaqoesUseGme9fLVNYN-ubYE";
 private static final int RETRY_COUNT = 5;
 static public void sendGCMMessage(String msg, List devices) {
  MySender sender = new MySender(GCM_API_KEY);
  Message message = new Message.Builder().addData("message", msg).build();
  try {
   MulticastResult res = sender.send(message, devices, RETRY_COUNT);
   List results = res.getResults();
   for (Result result : results) {
    if (result.getMessageId() != null) {
     String canonicalRegId = result.getCanonicalRegistrationId();
     if (canonicalRegId != null) {
      // same device has more than on registration ID: update
      // database
     }
    } else {
     String error = result.getErrorCodeName();
     if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
      // application has been removed from device - unregister
      // database
     }
    }
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }


■Titanium

・Titanium iPhone用の実装
iPhone用の実装は Titanium.Network.registerForPushNotifications を使えばすごく簡単でした。
調べると方法が出てくると思うので省略します。

・Titanium Android用の実装
Android用は少々やっかいです。
専用のモジュールを利用するのが一番早いので、こちらのモジュールを利用させていただきました
http://iamyellow.net/post/40100981563/gcm-appcelerator-titanium-module

モジュールの組み込みは通常通りですが、 アプリが起動していない時用の gcm.js と、起動している時用の gcm_activity.js をResources直下に置いて、それぞれ自分のやりたいことに合わせて書き換えてください。
あと、なぜか私の環境のTitanium3.2.2だとモジュールのtimodule.xmlと、自分で作ったplatform/android/AndroidManifest.xmlがうまくマージされず権限が足りなかったので注意してください。
(その場合は手動で権限をAndroidManifest.xmlにマージしてください。${tiapp.properties['id']} は自分のアプリのパッケージ名に置き換えます)


ちなみにiPhone版は普通にPushが届くのですが、Androidの場合は30分くらい遅れることが多いです。
なんでだろう…?

0 件のコメント:

コメントを投稿