2013年12月03日

[Cocos2d-x][Android] エラー発生

create_project.pyで作成されたプロジェクトがどのような構成になっているのか確認しようと、ソースファイルを見ていたら…。
「jni/hellocpp/main.cpp」をEclipseで開いたところ、盛大にエラーが発生していました。
どうやらいろいろと定義が見つからない状態の様子。直しましょう。

■Eclipseの環境設定(Cocos2d-xへの参照)

Cocos2d-x開発入門(p29〜)に「Eclipseの設定」という項目がありました。
この項目でCocos2d-xへの参照を設定していました。
Eclipseの「環境設定...」を開き「General→Workspace→LinkedRecources」の「New...」ボタンを押します。
Name:に「COCOS2DX」と入力し、Location:にはCocos2d-xを展開した場所を絶対パスで指定します。
この設定の後、プロジェクトをリフレッシュしたところ、エラーがかなり減りました。
しかし、まだ残っています。

■Eclipseの環境設定(NDKへの参照)

再びEclipseの環境設定を開きます。
今度は「C/C++→Build→Environment」の「Add...」ボタンを押します。
Name:に「NDK_ROOT」、Value:にNDKを展開した場所を指定します。
こちらは相対パスを指定しても怒られません。良いのでしょうか。わからないので、とりあえず絶対パスで指定しました。
続いて「C/C++→Code Analysis」の「Syntax and Semantic Errors」のチェックを外します。
「Apply」ボタンを押したところ、main.cppのエラーがすべて消えました。
発生すべきエラーをもみ消してしまったような印象があるのですが、これで良いのでしょうか?
(このチェックを外すところはプロジェクト毎のプロパティで設定した方が良いかも?)

■他
この設定が抜けていたせいなのかどうかはわかりませんが、これまでClassesやcocos2dxのフォルダが開けませんでした。
一度プロジェクトをEclipseから削除し、再度インポートしなおしたところ、Classesフォルダ内のファイルも開けるようになりました。
良かった良かった。

とりあえず、エラーは消えたので作業を続けます。



posted by t2low at 21:00| Cocos2d-x

2013年12月02日

[Cocos2d-x][Android] 開発環境を構築する

Cocos2d-xを使った仕事をすることになりました。
ということで、まずは開発環境の構築から。

■Cocos2d-xのインストール
以下のサイトからcocos2d-xをダウンロードしてきます。

Cocos2d-x | Cross Platform Open Source 2D Game Engine

僕はこの記事を書いている時点の安定版v2.2.1をダウンロードしました。
ダウンロードしたzipファイルは展開して、「~/cocos2d-x/cocos2d-x-2.2.1」として配置しました。
入門書によるとCocos2d-xは頻繁に新バージョンがリリースされるらしいので、後々切り替えられるようバージョンがわかるようにしておいた方が良いようです。

あと、~/.bash_profileに以下を追加しておきます。
# cocos2d-x 設定
export COCOS2DX_ROOT=~/cocos2d-x/cocos2d-x-2.2.1

■Android NDKのインストール
以下のサイトからAndroid NDKをダウンロードしてきます。Revision 9bをダウンロードしました。

Android NDK | Android Developers

ダウンロード完了後、「~/android-ndk/android-ndk-r9b」に展開しました。
~/.bash_profileに以下を追記します。
# Android NDK 設定
export NDK_ROOT=~/android-ndk/android-ndk-r9b


■プロジェクトの作成
各OS向けに作成する方法もあるようですが、とりあえずはクロスプラットフォーム向けのプロジェクトを作成してみます。
$ cd $COCOS2DX_ROOT/tools/project-creator
$ python create_project.py
Usage: create_project.py -project PROJECT_NAME -package PACKAGE_NAME -language PROGRAMING_LANGUAGE
Options:
-project PROJECT_NAME Project name, for example: MyGame
-package PACKAGE_NAME Package name, for example: com.MyCompany.MyAwesomeGame
-language PROGRAMING_LANGUAGE Major programing lanauge you want to used, should be [cpp | lua | javascript]

Sample 1: ./create_project.py -project MyGame -package com.MyCompany.AwesomeGame
Sample 2: ./create_project.py -project MyGame -package com.MyCompany.AwesomeGame -language javascript

create_project.pyというスクリプトでクロスプラットフォームなプロジェクトを作成できるようです。
引数なしで実行すると使い方が表示されます。
各引数にパラメータを与えて実行します。
$ python create_project.py -project MyCocos2dx -package com.example.mycocos2dx -language cpp 
proj.ios : Done!
proj.android : Done!
proj.win32 : Done!
proj.winrt : Done!
proj.wp8 : Done!
proj.mac : Done!
proj.blackberry : Done!
proj.linux : Done!
proj.marmalade : Done!
New project has been created in this path: $COCOS2DX_ROOT/projects/MyCocos2dx
Have Fun!

■プロジェクトをEclipseにインポートする
まずはCocos2d-xのライブラリプロジェクトをインポートします。
Eclipseを起動し、[File]-[Import...]から[Existing Android Code Into Workspace]を選択して、以下のAndroid用プロジェクトを選択します。

$COCOS2DX_ROOT/cocos2dx/platform/android/java

このとき「Copy projects into workspace」のチェックが付いているとうまくいかないようです。
続いて、同様に作成したプロジェクト(Android用のもの)をインポートします。

$COCOS2DX_ROOT/projects/MyCocos2dx/proj.android

■ビルド・実行
C++コードのビルドはコマンドラインから行うそうです。
プロジェクト(Android用)のディレクトリに移動して、ビルド用スクリプトを実行します。

$ cd $COCOS2DX_ROOT/projects/MyCocos2dx/proj.android
$ ./build_native.sh

コンパイルが完了したら、Eclipseから実行します。

device-2013-12-02-171310.png

うごきました。

今回の環境構築は以下の2冊の書籍を参考にさせてもらいました。





posted by t2low at 21:00| Cocos2d-x

2013年11月30日

[Android] IntentServiceを継承したServiceをBroadcastReceiverの中に書く

IntentServiceという便利なクラスがあります。
バックグラウンドで動作してくれて、処理が終わったら勝手に終了してくれるService(という認識)です。
BroadcastReceiver契機で時間のかかる処理を行いたいときに使うと便利ですね。

今回は特定のBroadcastReceiverからしか呼ばれない処理だっため、以下のようにIntentServiceを書いてみました。
public class HogeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// ... なんらかの処理

context.startService(new Intent(context, FugaService.class));
}

public static class FugaService extends IntentService {
public FugaService() {
super("FugaService");
}

@Override
protected void onHandleIntent(Intent intent) {
// ... バックグラウンドで行いたい処理
}
}
}


AndroidManifest.xmlはこんな感じ。
<receiver android:name=".HogeReceiver" />
<service android:name=".HogeReceiver$FugaService" />


ここでのBroadcastReceiverとServiceは2つで1つな感じなので、こんな感じで書いた方がつながりがわかって良いかな、と思った次第。
IntentServiceのクラスを外側にしても良いかもしれない。
BroadcastReceiver内に非同期処理が書ければこんな書き方しなくて済むのですけどね。

posted by t2low at 21:00| Android

2013年11月29日

[Android] android.intent.action.PACKAGE_〜

BroadcastReceiverで受信できる「PACKAGE_〜」のアクションについて調べてみました。
おこなったのは以下のIntentFilterを設定したBroadcastReceiverで受信したActionをログに表示するというもの。
Androidのソースを読んで詳細に調べたわけでなく、動作を確認しただけです。

<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_CHANGED" />
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<action android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<action android:name="android.intent.action.PACKAGE_INSTALL" />
<action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<action android:name="android.intent.action.PACKAGE_VERIFIED" />

<data android:scheme="package" />
</intent-filter>


確認した端末は以下の3つ。括弧内はAndroidのバージョンです。
・HTC Desire (2.3.3)
・HTC Evo 3D (4.0.3)
・HTC J One (4.2.2)

以下が「操作」とその時に受信した「Broadcast」です。

--------------------------------
■インストール
android.intent.action.PACKAGE_ADDED

■アップデート
android.intent.action.PACKAGE_REMOVED
android.intent.action.PACKAGE_ADDED
android.intent.action.PACKAGE_REPLACED

■アンインストール
android.intent.action.PACKAGE_REMOVED
android.intent.action.PACKAGE_FULLY_REMOVED (4.0以降のみ)

■強制停止
android.intent.action.PACKAGE_RESTARTED

■データ削除
android.intent.action.PACKAGE_RESTARTED
android.intent.action.PACKAGE_DATA_CLEARED

■無効化/有効化 (4.0以降のみ)
android.intent.action.PACKAGE_CHANGED (4.0以降のみ)

■SDカードへ移動/携帯端末へ移動
(なし)
--------------------------------

アップデートはADDEDもREMOVEDも受信するのがめんどくさいですね。
インストール/アップデート/アンインストールで何らかの処理を行いたい場合、単純にADDEDやREMOVED毎に処理を行ってしまうと、アップデート時には2回処理されてしまいます。
アップデート時は受信したIntentのExtraに「android.intent.extra.REPLACING」というキーで「true」が詰められているようなので、それを使って判断するのが良さそうです。
(Intentに EXTRA_REPLACING として定義されています)
あと「SDカードへの移動/携帯端末へ移動」も何かしらBroadcastされるかと思ってましたが何も来ませんでした。


また、以下はそれぞれのアクションがどのAPIレベルで追加されたかの一覧です。

android.intent.action.PACKAGE_ADDED (Lv1)
android.intent.action.PACKAGE_CHANGED (Lv1)
android.intent.action.PACKAGE_INSTALL (Lv1) (deprecated in Lv14)
android.intent.action.PACKAGE_REMOVED (Lv1)
android.intent.action.PACKAGE_RESTARTED (Lv1)
android.intent.action.PACKAGE_DATA_CLEARED (Lv3)
android.intent.action.PACKAGE_REPLACED (Lv3)
android.intent.action.PACKAGE_FIRST_LAUNCH (Lv12)
android.intent.action.PACKAGE_FULLY_REMOVED (Lv14)
android.intent.action.PACKAGE_NEEDS_VERIFICATION (Lv14)
android.intent.action.PACKAGE_VERIFIED (Lv17)


いくつか確認できてないActionがあります。
FIRST_LAUNCHなんて受信できたら何かおもしろそうなことが出来そうな気がしますが、今回確認した範囲では受信しなかったです。
何かpermissionでも必要なんでしょうか。

どの操作で、どのBroadcastが届くのか、また調べるのがめんどくさいのでメモとして残しました。
今回はここまで。

タグ:android
posted by t2low at 21:00| Android

2013年11月28日

[Android] SQLのUPDATEで更新された行数を取得する

AndroidのSQLiteDatabaseクラスで用意されているupdate()では無理そうなSQLを発行する必要がありました。
ContentProviderのupdate()内で呼ぶために、UPDATEで変更された行数を知らなければなりません。
しかし、SQLiteDatabaseクラスのexecSQL()は戻り値を返しません。
UPDATEを行う前に同条件で該当行数を調べておくべきでしょうか。

少し調べてみたところ、SQLiteStatement#executeUpdateDelete()を使えば変更された行数を返してくれるようです。
SQLiteStatementはSQLiteDatabase#compileStatement()で取得することが出来そうです。
しかし、このメソッドが追加されたのはAPI Level 11でした。
残念ながら現在開発中のアプリはAPI Level 9から対応させる予定のため、このメソッドは使えません。

もう少し調べてみたところ、SQLにchanges()というメソッドがあるようです。
UPDATEを実行した後、続けてchanges()のSQLを発行すれば良さそうです。
public static int changes(SQLiteDatabase db) {
Cursor cursor = null;
try {
cursor = db.rawQuery("select changes();", null);
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return -1;
}

上記のようなメソッドを作成し試してみたところ、期待した結果が返ってきました。
(僕が知らないだけで標準のクラスやメソッドで出来たりするんだろうか…?)
とりあえずはこのメソッドを使っていこうと思います。


タグ:android SQLite
posted by t2low at 21:00| Android

2013年11月27日

[Android] IME以外からパスワードを入力させるときでも、文字がわかるようにする

パスワードを入力する画面を作っています。
この画面ではアプリが表示するキーボードを使うため、IMEは使いません。
キーボードが押される度に以下のようなコードを実行していました。
textView.setText(textView.getText() + moji);

この方法でも文字の追加は出来ますが、入力した文字がわかりません。
マスクされた状態で表示されてしまうのです。
通常であればパスワード入力欄は*や●でマスクされる前に1秒程度文字を確認できる時間があります。

特別な処理が必要かと考えましたが、なんてことはありませんでした。
TextViewに文字を追加するためのメソッドが用意されていました。
textView.append(moji);

これで期待通りの動きになりました。

今までTextViewの操作はほとんどgetText()/setText()で行っていたので、append()の存在に気付きませんでした。
思い込みはダメですね。


posted by t2low at 21:00| Android

2013年11月26日

[Android] 壁紙を取得する

Activityに壁紙を取得するメソッドがあったような気がする、ということで、Eclipseの補完機能を頼りにとりあえず使ってみました。
Drawable wallpaper = getWallpaper();

取得できました。ばんざーい。
一応、リファレンスを見てみます。

Context | Android Developers

API Level5でdeprecatedになっていました。
(Eclipseでは警告が出ないのは何故でしょう…?)
何にしても、WallpaperManagerを使った方が良いようです。
WallpaperManager wpm = WallpaperManager.getInstance(getApplication());
Drawable wallpaper = wpm.getDrawable();
// または
Drawable wallpaper = wpm.getFastDrawable();

これでも問題なく取得できました。
壁紙の取得は簡単ですね。
posted by t2low at 21:00| Android

2013年11月25日

[Android] DialogFragmentの呼び出し元で結果を知るには

ダイアログ内のユーザが入力した情報を、ダイアログを呼び出したFragment側で知る必要が出てきました。
つまり、コールバックする仕組みが欲しいと思ったのです。
public class HogeDialogFragment extends DialogFragment {
// ... HogeDialogFragmentの実装

public interface Callback {
void onFuga();
}
}

ダイアログは上記のようにDialogFragmentを継承して実装しています。
そのため、コンストラクタやメソッドを使ってCallbackを渡すのはよろしくなさそう(※)です。
(※システム側でFragmentが再生成されたときに云々という話がありますね)
他に良い方法があるか調べてみましたが、どうもそういった仕組みはないようです。

いろいろと調べて、これしかないかなぁ、というのが以下のような方法

1.9. DialogFragment 実装例 − Kojionilk

android - Callback to a Fragment from a DialogFragment - Stack Overflow

ただ、この方法だと呼び出す側のクラス(ActivityかFragment)がCallbackを実装しなければならないんですよね。
public class HogeHogeFragment extends Fragment 
implements HogeDialogFragment.Callback {
// ...HogeHogeFragmentの実装
}

うーん、うーん…。なんだかイマイチな…。でも、これしか方法がなさそうな…。うーん…。
posted by t2low at 21:00| Android

2013年11月22日

[Android] AlertDialogのgetButton()がnullを返す

DialogのOKボタン(Positiveボタン)の長押しを判定したくて、以下のようなコードを書きました。
Builder builder = new Builder(getActivity());
builder.setTitle("title").setMessage("message");
builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
}
});

AlertDialog dialog = builder.create();
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});


これを実行すると、NullPointerExceptionが発生してアプリが異常終了しました。
11行目のdialog.getButton()でnullが返却されているためです。
Webを検索してみると以下の情報が見つかりました。

Issue 6360 - android - AlertDialog getButton() return null - Android Open Source Project - Issue Tracker - Google Project Hosting

どうもDialogを表示してからでないとnullになってしまうようです。
ソースコードを読んでみるとDialogのonCreate()を通らないと中のViewが生成されないようですね(たぶん)。
Dialogが表示されたところでOnLongClickListenerをセットすることにしました。


Builder builder = new Builder(getActivity());
// ... 省略 ...

AlertDialog dialog = builder.create();
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button positiveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
}
});


多少めんどくさいですが、これなら大丈夫でした。
posted by t2low at 21:00| Android

2013年11月21日

[Eclipse] override

継承元のクラスのメソッドをoverrideしたい場合、どのようにメソッドを書いていますか?
僕はこれまで、Eclipseのメニューから「Source→Override/Implement Methods...」を選んで、Eclipseに生成してもらっていました。
Windowsを使っていた頃は、Altキーを使ってキーボードだけで操作できていたのですが、Macを使うようになってからマウス(タッチパッド)を使わなければいけなくなり面倒に感じていました。
Macではキーボードショートカットを設定すれば良いのは知っていましたが、今回書きたいのはそこではありません。

いつものようにEclipseでコードを書いていて、ふと気づきました。
クラス内でoverrideしたいメソッド名を書き始めてから、コード補完を行うとそのメソッドの足りない記述を補ってくれるんですね!
override_completion.png
これは知りませんでした。
大変便利です。さすがEclipseですね。すごい。
いやー、コードを書くのが捗ります。

posted by t2low at 21:00| Eclipse