2013年12月13日

[Android] Fragmentの中でFragmentを使う

Fragmentは機能をまとめておけて便利ですね。
少し前に、Fragmentの中でもFragmentを利用できることを知りました。
getChildFragmentManager()でFragmentManagerを取得すれば良いようです。
以下はサンプルです。

MainActivity.java
public class MainActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Fragment fragment = NestFragment.newInstance(0);
getSupportFragmentManager().beginTransaction().add(R.id.content, fragment).commit();
}

public static class NestFragment extends Fragment {
private static final String ARG_ID = "id";

public static NestFragment newInstance(int id) {
Bundle args = new Bundle();
args.putInt(ARG_ID, id);

NestFragment fragment = new NestFragment();
fragment.setArguments(args);
return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.main, null);

int id = getArguments().getInt(ARG_ID);
if (id < 5) {
Fragment fragment = NestFragment.newInstance(id + 1);
getChildFragmentManager().beginTransaction().add(R.id.content, fragment).commit();
}
((TextView) view.findViewById(R.id.text)).setText(Integer.toString(id));

int colorId = ((id & 1) == 1) ? android.R.color.black : android.R.color.white;
view.setBackgroundColor(getResources().getColor(colorId));

return view;
}
}
}


layout/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEXT"
android:textColor="#888" />

<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" >
</FrameLayout>

</LinearLayout>

表示するとこんな感じになります。(こんな使い方はしませんが)

device-2013-12-13-200829.png

posted by t2low at 23:00| Android

2013年12月12日

[Android] onLoadFinished()でDialogFragmentを表示する

データを読み込んで、ある条件に一致したらダイアログを表示しようと考え、以下のようなコードを書きました。
CursorLoaderを使ってデータを読み込んでいます。
条件を満たしていたら、onLoadFinished()でダイアログを表示します。
public class MainActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getApplication(), HOGE_URI, null, null, null, null);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// .. 何らかの処理 ..

DialogFragment fragment = new MyDialogFragment();
fragment.show(getSupportFragmentManager(), "dialog");
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
});
}
}

しかし、このコードを実行すると以下の例外が発生し、アプリがクラッシュしてしまいました。

java.lang.IllegalStateException: Can not perform this action inside of onLoadFinished

どうやら、onLoadFinished()内でダイアログを表示してはいけないようです。
UIスレッド以外でUIの操作をしてはいけないという制限によるものなのかと、ダイアログ表示部分を以下のように書き換えてみました。
runOnUiThread(new Runnable() {
@Override
public void run() {
DialogFragment fragment = new MyDialogFragment();
fragment.show(getSupportFragmentManager(), "dialog");
}
});

これもダメでした。同じ例外が発生してクラッシュしてしまいます。
インターネット上で検索してみると、Handlerを使えばうまくいくらしいことがわかりました。
以下のようにします。
handler.post(new Runnable() {
@Override
public void run() {
DialogFragment fragment = new MyDialogFragment();
fragment.show(getSupportFragmentManager(), "dialog");
}
});

これなら動きました。

イマイチよくわかりません。
時間があれば、もう少し調べてみたいところですが、今はちょっと忙しいのでここまで。

posted by t2low at 22:00| Android

2013年12月11日

[Android] キーがintのMapについて

以下のようなコードを書くと…
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(0, "ZERO");
map.put(1, "ONE");
map.put(2, "TWO");

Lintさんに怒られます。
「Use new SparseArray<String>(...) instead for better performance」
SparseArrayを使え、ということですね。↓こんな感じ。
SparseArray<String> map = new SparseArray<String>();
map.put(0, "ZERO");
map.put(1, "ONE");
map.put(2, "TWO");


また、調子に乗って、以下のように値がIntegerのSparseArrayを書くと…
SparseArray<Integer> map = new SparseArray<Integer>();
map.put(0, 10);
map.put(1, 100);
map.put(2, 1000);

やっぱりLintさんに怒られます。
「Use new SparseIntArray(...) instead for better performance」
SparseIntArrayを使えということですね。
SparseIntArray map = new SparseIntArray();
map.put(0, 10);
map.put(1, 100);
map.put(2, 1000);

少しでもアプリを良くしようとがんばってるLintさんステキです。

posted by t2low at 22:00| Android

2013年12月09日

[Android] Bitmapを回転する

アプリ内で画像(Bitmap)を回転しなければいけなくなりました。
カメラを使ったアプリを作ったときに一度実装したのですが、覚えていなかったので結局また調べて実装することに…。

Bitmap#createBitmap (Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) | Android Developers

使うのは↑このメソッドですね。
最後の引数のfilterは、変形する時にメソッド内部でPaint#setFilterBitmap()に渡す値のようです。
通常はtrueにしておくのが良いでしょう。
Bitmap orgBmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

Matrix matrix = new Matrix();
matrix.postRotate(90);

Bitmap rotatedBmp = Bitmap.createBitmap(orgBmp, 0, 0, orgBmp.getWidth(), orgBmp.getHeight(), matrix, true);

こんな感じ。
回転しかしないならmatrix#setRotate()でも良いですね。
以上です。

posted by t2low at 22:00| Android

2013年12月05日

[Android] Android4.4搭載端末で操作動画を撮影する

Android4.4搭載端末であれば画面の録画ができると聞いて試してみました。

Android Debug Bridge | Android Developers

「adb shell」後に「screenrecord [ファイル名]」と実行すれば良いようです。
止めるには「Ctrl-C」ですね。
$ adb shell
shell@grouper:/ $ cd storage/sdcard0/tmp/
shell@grouper:/storage/sdcard0/tmp $ screenrecord test.mp4
^C
shell@grouper:/storage/sdcard0/tmp $ exit
$ adb pull /storage/sdcard0/tmp/test.mp4 ~/Documents/

保存したファイルは「adb pull」で取得出来ます。
Playストアに載せる動画を撮るのが簡単になりましたね。すごい。

タグ:android ADB
posted by t2low at 22:00| Android

2013年12月04日

[Cocos2d-x][Android] animations.plistの配置場所が原因でエラー

Cocos2d-xを始めてから間もないというのに、iOSでは動いて、Androidでは動かないという状況が発生してしまい、調査に疲れました。
結論から言うと、原因はanimations.plistの配置場所でした。

僕は普段はAndroidアプリを開発しているので、開発環境としてはEclipseに慣れています。
しかし、Cocos2d-xで作られたアプリをAndroid実機で動作させるのは少しめんどくさいです。
シェルスクリプトを動かした後で、Eclipseでのビルドを行わなければなりません。
(もっと効率よくビルドする方法もあると思いますが、まだ初心者なので調べておりません)
そのため、コードを書くのはEclipse上で行い、動作の確認はXcodeからiOSシミュレータを起動して行うようになりました。
そんなこんなでしばらくiOSシミュレータでしか動作を確認していなかったのです。

久しぶりにAndroid実機で動作を確認してみたところ、アプリがクラッシュするではありませんか。
おかしいと思いiOSシミュレータで動作を確認すると、こちらは正常に動きます。
まだデバッグ方法もわからない状態なので、CCLog("...")を各所に埋め込んで異常が発生している箇所を特定しました。
結果、animations.plistが読み込めていないことがわかりました。

animations.plistは「Resources/animations/animations.plist」として配置していました。
コード上では以下のように読み込んでいました。
	CCAnimationCache *cache = CCAnimationCache::sharedAnimationCache();
cache->addAnimationsWithFile("animations.plist");

iOSで動かすときはXcodeで明示的に参照を行っていたので、これでも正常に動いたようです。
Androidで動かすときは明示的にどうこうしないので、ファイルの場所がわからず実行時にクラッシュしていたようです。
配置場所を「Resources/animations.plist」とすることで、どちらの環境でも正常に動きました。

階層を設けてもAndroid/iOSともに同じコードで動かす方法もあるような気がしますが、そこは未調査でございます。

posted by t2low at 22:00| Cocos2d-x

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