ApportableでJavaの機能を使う


> 2015.03.24追記
> 現在はコマンドラインバージョンのApportableはDLできないです。
> 新しいバージョンの SpriteBuilder Android Pluginについてまとめました。
> http://qiita.com/masanorythm/items/215a7dd0a9ee9365079d

### Apportableとは

Objective-Cをクロスコンパイルして
ARMコードに変換しちゃう技術。
x86への変換も目指しているようだ。

cocos2d-xなんかはC++で書いたものを
NDK通してネイティブコードに変換して動いている。
そのC++部分がObjective-Cになったと思えば近い。

有償プランがあり
Androidのネイティブ機能を使った機能を手軽に使えるようになるようだ。
主に課金処理とか
iOSのGameCenterを
AndroidのGoogle Play Game Servicesに対応させたりする
手間を大分減らした形で実装できるようだ。

### どうやって使うの？

Apportableの導入は簡単。
公式サイトにメンバー登録をしたら
curlコマンドが出てくる。
curlコマンドをそのままターミナルにコピペして実行すれば
`~/.apportable` にインストールされる

後はXcodeプロジェクトのルートディレクトリ
(ProjectName.xcodeprojがあるディレクトリ)
で `apportable load` と入力すると
APKが生成されて、実機に転送される。

### Java機能が使えるか試してみる

Apportable自体はcocos2d-iphone専用というわけではないが
cocos2d-iphone + SpriteBuilder + ApportableSDK
といった形での使い方が無難だと思う。

UIKitも色々対応しているが
プランによって制限があったり、対応していないものがあったりするので
UIKitは極力避けて実装した方が開発はスムーズ。

けど、Javaの機能を全く使わないというのは
無理な話なので、無償プランでもBridgeKitを使えば好き放題できる。


まずは、SpriteBuilderでプロジェクトを作成する。

SpriteBuilderの使い方は他の記事で少し触れたので
そっちを参照して下さい。

[参考](http://qiita.com/masanorythm/items/2aac844786255c1bf5c6)


![javatest01.png](https://qiita-image-store.s3.amazonaws.com/0/31990/53701a79-c9be-a34b-4fe9-cdd34634282f.png "javatest01.png")

ルートはこんな感じ。

名前はなんでも良いんだけど
`java`
`java_wrappers`
ディレクトリを作っておく。

javaの方にはjavaコードを入れていく予定。
java_wrappersにはObjective-CからJavaを呼び出す為の
橋渡し役のコードを置いていく。

まずはJavaTest.xcodeprojを開いて
最初に呼び出されるシーンのMainScene.mをいじってく。

なにもメソッドが無い状態だと思うんで
適当に追加

```objc:MainScene.m
- (void)onEnter
{
    [super onEnter];
    
    NSString *platformString = @"iOS";
    
    // ラベル作る
    CCLabelTTF *label = [CCLabelTTF labelWithString:platformString
                                           fontName:@"Helvetica"
                                           fontSize:32];
    // アウトライン設定
    label.outlineColor = [CCColor blackColor];
    label.outlineWidth = 3.0f;
    
    // 位置設定
    label.positionType = CCPositionTypeNormalized;
    label.position = ccp(0.5f, 0.25f);
    
    // ノードに追加
    [self addChild:label];
}
```

iOSシミュレータで起動してみて
iOSってラベルが表示されればOK。

なんか実用的じゃない事やってもしょうがないんで
Android側ではアイコン広告のアスタを表示させてみる。

`jp.maru.mrd-1.2.0.jar`
SDKにある、jarファイル。
先程作ったjavaディレクトリにlibsディレクトリつくってつっこみます。

`java/libs/jp.maru.mrd-1.2.0.jar`

こんな配置。

ソース用のディレクトリも作っておく。
パッケージ名は適当に変えて下さい。

`java/src/com/masanorythm/bridge/`


まずはObjective-CからJavaを呼び出す為のクラスを作ります。
java_wrappersに作る。これはAndroidの時だけ使う事になるので
xcodeプロジェクトに追加しないでも良いです。

![javatest02.png](https://qiita-image-store.s3.amazonaws.com/0/31990/76f2482c-02c5-dbbc-e138-881dda859b34.png "javatest02.png")


```objc:AstaBridge.h

#import <BridgeKit/JavaObject.h>
#import <BridgeKit/AndroidActivity.h>

@interface AstaBridge : JavaObject

- (id)initWithActivity:(AndroidActivity *)activity
              iconSize:(int)size
             mediaCode:(NSString *)mediaCode;

- (void)createAdIcon;
- (void)showAdIcon;
- (void)hideAdIcon;

@end

```

```objc:AstaBridge.m

#import "AstaBridge.h"

@implementation AstaBridge

+ (void)initializeJava
{
    [super initializeJava];

    [AstaBridge registerConstructorWithSelector:@selector(initWithActivity:iconSize:mediaCode:)
                                      arguments:[AndroidActivity className], [JavaClass intPrimitive], [NSString className], nil];

    [AstaBridge registerInstanceMethod:@"createAdIcon"
                              selector:@selector(createAdIcon)];
    
    [AstaBridge registerInstanceMethod:@"showAdIcon"
                              selector:@selector(showAdIcon)];

    [AstaBridge registerInstanceMethod:@"hideAdIcon"
                              selector:@selector(hideAdIcon)];
}

+ (NSString *)className
{
    return @"com.masanorythm.bridge.Asta";
}

@end

```

実装ファイル(.m)は多分よくわからん感じですが
ヘッダについてはよくあるObjCコードと大して変わらないと思います。

```
- (id)initWithActivity:(AndroidActivity *)activity
              iconSize:(int)size
             mediaCode:(NSString *)mediaCode;
```
アクティビティ、アイコンサイズ、メディアコードを指定して
アスタ表示用のクラスを初期化します。

対応するのは

```
[AstaBridge registerConstructorWithSelector:@selector(initWithActivity:iconSize:mediaCode:)
                                  arguments:[AndroidActivity className], [JavaClass intPrimitive], [NSString className], nil];
```
このところです。
initWithActivity:iconSize:mediaCode:を
Java側のコンストラクタとして登録しました。

他の3つはインスタンスメソッドの登録になります。
同じ名前のメソッドをそのまま呼び出しているので簡単になってます。

細かい情報は [BridgeKitReference](http://static.apportable.com/documentation/BridgeKit/html/)

それじゃ、Javaの方いじりましょう。

```Java:java/src/com/masanorythm/bridge/Asta.java
package com.masanorythm.bridge;

import android.app.Activity;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import jp.maru.mrd.IconCell;
import jp.maru.mrd.IconLoader;

public class Asta {
    private static final String TAG = "Asta";
    
    private Activity mActivity;
    private int mIconSize;
    private String mMediaCode;
    private LinearLayout mLayout;
    private IconLoader<Integer> mIconLoader = null;

    public Asta(Activity activity, int size, String mediaCode) {
        mActivity = activity;
        mIconSize = size;
        mMediaCode = mediaCode;
    }

    public void createAdIcon() {

        if (mIconLoader == null) {

            mLayout = new LinearLayout(mActivity);
            mLayout.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
            LayoutParams param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            mActivity.addContentView(mLayout, param);

            mIconLoader = new IconLoader<Integer>(mMediaCode, mActivity);
            for (int i = 0; i < 4; i++) {
                IconCell icon = new IconCell(mActivity, null);
                icon.setTitleColor(0xffffffff);
                icon.addToIconLoader(mIconLoader);
                LayoutParams iconParam = new LayoutParams(mIconSize, mIconSize);
                mLayout.addView(icon, iconParam);
            }
            mIconLoader.setRefreshInterval(30);
        }
    }

    public void showAdIcon() {
        if (mIconLoader != null) {
            mIconLoader.startLoading();
        }
    }
    
    public void hideAdIcon() {
        if (mIconLoader != null) {
            mIconLoader.stopLoading();
        }
    }
}
```

はい。。。Java苦手なんでおかしいとこあるかもですが
指定されたサイズのアイコンを4つ作ってるだけです。

それじゃーObjective-Cから呼び出します。

```objc:MainScene.m
#import "MainScene.h"

#if defined(ANDROID)
#import "AstaBridge.h"
#endif

@interface MainScene()

#if defined(ANDROID)
@property (nonatomic, readwrite, strong) AstaBridge *astaBridge;
#endif

@end

@implementation MainScene

- (void)onEnter
{
    [super onEnter];
    
    NSString *platformString = @"iOS";
    
#if defined(ANDROID)
    platformString = @"Android";
#endif
    
    // ラベル作る
    CCLabelTTF *label = [CCLabelTTF labelWithString:platformString
                                           fontName:@"Helvetica"
                                           fontSize:32];
    // アウトライン設定
    label.outlineColor = [CCColor blackColor];
    label.outlineWidth = 3.0f;
    
    // 位置設定
    label.positionType = CCPositionTypeNormalized;
    label.position = ccp(0.5f, 0.25f);
    
    // ノードに追加
    [self addChild:label];
    
    // アスタアイコン作成
    [self createAdIcon];

    // トグルボタン作成
    CCSpriteFrame *normalFrame = [CCSpriteFrame frameWithImageNamed:@"ccbResources/ccbButtonNormal.png"];
    CCSpriteFrame *hiliteFrame = [CCSpriteFrame frameWithImageNamed:@"ccbResources/ccbButtonHighlighted.png"];
    CCButton *toggleButton = [CCButton buttonWithTitle:@"Show"];
    toggleButton.togglesSelectedState = YES;
    toggleButton.zoomWhenHighlighted = NO;
    toggleButton.preferredSize = CGSizeMake(80, 30);
    [toggleButton setBackgroundSpriteFrame:normalFrame forState:CCControlStateNormal];
    [toggleButton setBackgroundSpriteFrame:hiliteFrame forState:CCControlStateHighlighted];
    [toggleButton setTarget:self selector:@selector(touchToggleButton:)];
    toggleButton.positionType = CCPositionTypeNormalized;
    toggleButton.position = ccp(0.5f, 0.15f);
    [self addChild:toggleButton];
}

- (void)touchToggleButton:(id)sender
{
    if ([sender isKindOfClass:[CCButton class]]) {
        CCButton *button = (CCButton *)sender;
        if (button.selected) {
#if defined(ANDROID)
            [self.astaBridge showAdIcon];
#endif
            [button.label setString:@"Hide"];
        } else {
#if defined(ANDROID)
            [self.astaBridge hideAdIcon];
#endif
            [button.label setString:@"Show"];
        }
    }
}

- (void)createAdIcon
{
#if defined(ANDROID)
    self.astaBridge = [[AstaBridge alloc] initWithActivity:[AndroidActivity currentActivity]
                                                  iconSize:(int)(75 * [CCDirector sharedDirector].contentScaleFactor)
                                                 mediaCode:@"__TEST__"];
    [self.astaBridge createAdIcon];
#endif
}

@end
```

```
#if defined(ANDROID)
#endif
```
この部分はAndroidのみの実行になります。

```
or 

#ifdef ANDROID
#ifdef APPORTABLE

#if defined(APPORTABLE)
```
これでも同じ動きです。
Apportableが他のプラットフォームに対応したりを考えると

```
#if defined(ANDROID)

#elif defined(WINDOWS)

#endif
```
こういう風にできるんで、definedがスマートかもしれないです。

アイコンサイズにcontentScaleFactorをかける事で
iOSとAndroidで同じ見え方になります。

このままだとjavaファイルなどがコンパイル時に読み込まれてないので
動きません。
JavaTest.approj/configuration.json をいじります。
中身はシンプルなjsonになってるんで簡単です。

いじるのはadd_paramsの中です。

```json:configuration.json
  "add_params": {
    "pchs": [],

    "header_paths": [
        "java_wrappers"
    ],
    "flags": [],

    "defines": {},

    "deps": [],

    "sources": [
        "java_wrappers/AstaBridge.m"
    ],

    "sources_glob":[],

    "assets": [],

    "infoplists": [],

    "java_sources": [
        "java/src/com/masanorythm/bridge/Asta.java"
    ],

    "java_sourcepaths": [
        "java/src"
    ],

    "java_libs": [
        "java/libs/jp.maru.mrd-1.2.0.jar"
    ],

    "java_res_dirs": [],

    "libs":[],

    "link_flags":[],

    "modules": [],
  },
```

今回xcodeプロジェクトにjava_wrappersは入れてないので
ヘッダーが見つけられるように`header_paths`にいれときます
同じ理由でsourcesに実装ファイルも。

上記2つは面倒な場合プロジェクトに入れちゃって
ワーニングやら解除しちゃうのも良いかもしれません。

後はjava系のファイル
`java_sources` `java_sourcepaths` `java_libs`
にそれぞれ追加しただけです。

これでxcodeprojがある場所で
`apportable load` を実行してみて下さい。
間違いがなければ動くはず。

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/31990/e418be59-932b-3f83-f691-f33e141b5290.png "ss.png")

実際使う場合は
もう一段階上にクラスをおいて
iOS、Androidへの処理をわけた方が使いやすいでしょう。