2016年5月2日月曜日

Include GooglePlayServices aar library to the Titanium Module

On Android development, there is a limit of methods 64k.

If you want to include GooglePlayServices, it easy to reach the limit.
Basically using Gradle on the Android Studio, it is common to build GooglePlayServices partially, but if you are making Titanium Module, it's a bit difficult to include that.

At first install Android SDK, see the folder $SDK_ROOT/extras/google/m2repository/com/google/m2repository/com/google/android/gms/

Maybe you want to include some of these libraries. You must include
play-services-base and play-services-basement.

For example use play-services-base.

Open the folder inside that, ok, use 8.4.0 folder. open it.

Copy play-services-base-8.4.0.aar to the other folder and rename .aar to zip.
and extract that.
We use only classes.jar file and res folder. Rename classes.jar to play-services-base-8.4.0.jar

Try these steps to the another libraries you want to include.


Next, open  Appcelerator Studio and create new Module Project for GooglePlayServices.

Open the module folder and copy all .jar file we created the first step to the lib folder.

Copy all res folder to the platform folder inside the module folder as well.
(if there are multiple libs merge res folders instead of overwrite)

Ok, it's ready. run build.xml with Ant Build and include the module to your project.


2014年4月20日日曜日

Google App Engine/JでBackendsをModulesに移行

2014年3月13日でBackends APIがdeprecatedになったので、Modulesに移行することにしました。

しかし情報がほとんどなくて苦労しました。
もし間違っている点があればご指摘ください。

BackendsをModulesに移行する手段は一応Googleの公式文書があります。
https://developers.google.com/appengine/docs/java/modules/converting

私の環境はこんな感じです
・Mac OS X 10.9.2
・Eclipse + Google App Engine plugin
・Backendsは3つ。cron + TaskQueueで定期的に稼働

■アプリのModule化
まずはアプリ自体を公式文書にしたがってModule化します。
1. Appのrootにある war フォルダを default にリネーム
2. rootに新しく META-INF フォルダを作成
3. 作成した META-INF フォルダの中に、appengine-application.xml と application.xml を作成

appengine-application.xml の例
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<appengine-application xmlns="http://appengine.google.com/ns/1.0">
<application>appname</application>
</appengine-application>

application.xml の例
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/application_5.xsd" version="1">
<description>App Description</description>
<display-name>App Display Name</display-name>
<module>
<web>
<web-uri>default</web-uri>
<context-root>default</context-root>
</web>
</module>
</application>

4. defaultフォルダ - WEB-INF - appengine-web.xml に以下を追加
<application>appname</application>
<version>2</version>
<module>default</module> ← 追加

5. AppEngineのDashboard - Application Settingsを開き、「Performance Settings Migration for Modules」のところにある appengine-web.xml のところからscallingに関する部分をコピーして、先ほどのappengine-web.xmlにペースト
<application>appname</application>
<version>2</version>
<module>default</module>
<automatic-scaling> ← ペースト
<max-idle-instances>10</max-idle-instances>
</automatic-scaling>
「Migrate Settings」ボタンはまだ押さないでください。

6. Eclipseの設定を変更
まずEclipseを開かずに、エディタでrootにある .classpath を開き、war を default に一括置換して閉じます。
その後Eclipseでプロジェクトを開きます。
プロジェクトのプロパティ Google - Web Application の WAR directory を default に変更します。
Java Build Path の一番下の Default output folder: の war を default に変更します。

以上でアプリ自体のModule化の準備ができました。

■BackendsのModule化
いよいよBackendsのModule化に入ります。
1. アプリのrootにBackendsと同じ名前のフォルダを作る。これがModuleフォルダになります。
2. それぞれのModuleフォルダに WEB-INF フォルダを作成。
3. WEB-INFフォルダの中に appengine-web.xml と web.xml を作成

appengine-web.xml の例
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>appname</application>
<module>mymodule</module>
<version>1</version>
<threadsafe>true</threadsafe>
<instance-class>B2</instance-class>
<basic-scaling>
<max-instances>5</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
</appengine-web-app>
web.xml の例
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>mymodule</servlet-name>
<servlet-class>jp.yourapp.MyModuleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>mymodule</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

4. root - META-INF - application.xml に Moduleに関する記述を追加
<module>
<web>
<web-uri>default</web-uri>
<context-root>default</context-root>
</web>
</module>
<module> ← 追加
<web>
<web-uri>mymodule</web-uri>
<context-root>mymodule</context-root>
</web>
</module>
設定が終了したら、これまでの backends.xml は削除してください。

5. queue.xml にModule用の設定を追加。TaskQueueからBackendsを起動していた場合は queue.xml にtargetを指定します。
<queue>
<name>myqueue</name>
<target>mymodule</target> ← 追加
<rate>5/s</rate>
<bucket-size>5</bucket-size>
<retry-parameters>
<task-retry-limit>1</task-retry-limit>
</retry-parameters>
</queue>

6. cron.xml の URLを変更。urlはモジュールのrootになるので、/ に変更しました。
<cron>
<url>/</url> ← 変更
<target>mymodule</target>
<description>update village</description>
<schedule>every 1 minutes</schedule>
<timezone>Asia/Tokyo</timezone>
</cron>

7. プログラム内からTaskQueueに追加していた場合はModuleを使うように変更
どのModuleでqueueを実行するかは、queue.xmlのtargetで指定します。
[変更前]
Queue queue = QueueFactory.getQueue("myqueue");
TaskOptions to = TaskOptions.Builder.withParam("param1", "aaa").param("param2", "bbb");
to.header("Host", BackendServiceFactory.getBackendService().getBackendAddress("mybackends"));
queue.add(to);
[変更後]
Queue queue = QueueFactory.getQueue("myqueue");
TaskOptions to = TaskOptions.Builder.withUrl("/").param("param1", "aaa").param("param2", "bbb").method(Method.POST);
queue.add(to);

8. ここまで設定できたらEclipseでプロジェクトを選択し、クリーン&ビルドします。
そしてこれはこの方法が正しいかわからないのですが、default - WEB-INF - classes フォルダのシンボリックリンクを、それぞれのModuleの WEB-INF フォルダに作成します。
(libも必要?)

ここまででModule化の作業は完了です。

■デプロイ
それでは完成したものをデプロイしましょう。

1. appengineのDashboard - Application Settings - Performance Settings Migration for Modulesで、「Migrate Settings」ボタンを押してアプリをModuleに移行します。
この作業は元に戻せないので注意してください。

2. 古いBackendsを削除
Migrateが終わると、Versions の中に古いBackendsがVersion化されてると思うので、これをDeleteしてください。

3. Eclipseからは正しくデプロイ出来ないので、アプリケーション - Eclipse - plugin - com.google.appengine.eclipse.sdkbundle_1.9.x - appengine-java-sdk-1.9.x - bin の中にある appcfg.sh を使います。
実行権限がない場合があるので、binの中のshにはすべて実行権限をつけてください。(できればPATH指定も)

ターミナルでアプリのrootの一つ上の階層に移動し、以下のようにしてデプロイします。(appnameはrootフォルダの名前)

appcfg.sh update appname


以上です。

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分くらい遅れることが多いです。
なんでだろう…?

2013年12月3日火曜日

ANEで関数を使う

ANE(ActionScript Native Extensions)を作っててハマったのでメモ。

Mac用のANEを作ろうと思ってネットの情報を調べてみたのだけど、どこも基本の関数の作り方までしか書いてないようです。

ANEを作るくらいだから、みんなそれぞれネイティブのAPIを使っていろいろ凝ったことをやりたいんだろうけど、そんなときにFlash側から呼ばれる関数の中から自分で作った関数を呼ぼうとすると実行時にシンボルが見つからなくて落ちると思います。

これで2日くらい悩んでいろいろネットで情報を探したんですが見つかりませんでした…

で、結論から言うと自分で作った関数は実行時にインスタンス化されていないので実体がありません。

なので手っ取り早くするならstatic関数にすればOK。

どこかに書いておいて欲しかった…