2010年6月28日月曜日

[Android]SurfaceViewと終了処理

前の記事でSurfaceViewの描画処理が軽いと書きましたが、実際にはViewよりもメモリもCPUパワーも食うけどフレームレートが高くなるよ、が正しいところでしょうか。

携帯アプリだと描画処理にパワーとメモリを食って、キーイベントの割り込みが遅れるなんてこともあったので(かなり前の端末ですが)、描画用スレッドのループにはそれなりのsleepを入れた方が良さそうです。

さて、View#OnDraw(Canvas)で実装していた描画処理をSurfaceViewに変更しました。


【引っかかった点】

・コンストラクタで描画スレッド作ってstart()させちゃダメ
 ⇒surfaceCreated(SurfaceHolder)でやるのが正解。
 (そうやってたつもりでしたが、書く場所間違えてました・・・うっかり?)

・Xperiaの□ボタン押しても、描画スレッドのログが止まらない〜
 ⇒マルチスレッドですものね。
  ゾンビ恐ろしや。
  SurfaceViewをセットしたActivityのOnPause()をオーバーライドして、描画スレッドのループを抜けてスレッド終了させるようにしました。
  こんな感じのメソッドをSurfaceView継承クラスに追加して、OnPause()から呼んでいます。
  このメソッドの後ろにActivity#finish()を記述しています。


public void endLoop() {
synchronized (MainLoop) {
// ループを抜ける
loopflg = false;
}
try{
// スレッドの終了を待つ
MainLoop.join();
} catch( InterruptedException ex ){
Thread.currentThread().interrupt();
}
}

  MainLoop:描画スレッド
  loopflg:trueだとループ、falseでループ脱出
  です。
  他の方の処理を参考にさせて頂きました。

[Android]SurfaceViewとアノテーション

携帯アプリの移植のため、iPhone風にタッチパネルに指を滑らせる(フリックする)と画面が滑って切り替わる演出を作成しようとしました。

「throw Life - ActivityのOpenとCloseをアニメーションさせる」
http://www.adamrocker.com/blog/289/activity_open_close_animation.html

ここを参考にView + Themeによるアニメーションでも出来たのですが、設定がめんどくさい・・・(というかよく理解できていません・・・)

そこで調べてみると、ゲームのような画面描画が頻繁に行われるアプリにはSurfaceViewがいいとありました。
描画処理がViewより軽い模様。
これで携帯アプリと同じようにCanvasに画像をずらして描画して、タッチイベントに合わせてアニメーションしてるように動かせば実現できそうな気がします。

SurfaceViewにRunnableをインプリメントするか、別のThreadクラスにするか。
うーん。
携帯アプリの描画処理がCanvasにRunnableインプリして実装してあるので、とりあえずはRunnableでいきます。

早速、ネットをうろうろして他の方のコードを参考に書いてみました。
すると、親クラスからオーバーライドした surfaceCreated(SurfaceView surface) でいきなりのエラー。
見ると「@Override」をつけるとエラー、はずすとエラーじゃなくなります。

???

これも調べると同じ現象になった方がいらっしゃって、無事解決。
Javaコンパイルのターゲットバージョンを

Java1.5 ⇒ 1.6

と変更したら出なくなりました。
アノテーションというJava1.6で正式実装された機能なのですね。
携帯アプリでJava1.5までしか使ってなかったので知りませんでした。(言い訳)

2010年6月25日金曜日

[Android]文字列描画(訂正)

指定したY座標を文字列の左上に合わせる書き方は


canvas.drawText("hijkLMN", 100, 240 + fm.ascent, mPaint);


ではなくて、


canvas.drawText("hijkLMN", 100, 240 - fm.ascent, mPaint);


みたいです。
でも、微妙にずれる・・・おかしい。

[Android]文字列描画

Viewへの文字列の描画は下のような書き方で出来ました。

private class SampleView extends View {

Private Paint mPaint;

public SampleView(Context context) {
super(context);
mPaint = new Paint();
}

@Override
protected void onDraw(Canvas canvas) {

FontMetrics fm = null;
// 背景は白
canvas.drawColor(Color.WHITE);
// アンチエイリアス有り
mPaint.setAntiAlias(true);
// 描画色は黒
mPaint.setColor(Color.BLACK);

// フォントサイズの指定
mPaint.setTextSize(64);
fm = mPaint.getFontMetrics();
// 文字列リソースから取得する場合
canvas.drawText(getResources().getString(R.string.test), 100, 80, mPaint);
// 直接記述する場合
canvas.drawText("Abcdefg", 100, 160, mPaint);
// 座標位置(Y軸)はフォントのベースラインの値になるので
// 実際の描画位置は少し上になる。
// 座標位値(Y軸)をフォントの上限(Ascent)に合わせたい時は
// 下のように書く。
canvas.drawText("hijkLMN", 100, 240 + fm.ascent, mPaint);
}

FontMetricsのメンバ変数 ascent はフォントのベースラインから上限までの値(負の値)が入っているので、Y座標 + FontMetrics.ascentがフォントの上の部分の座標値になります。
文字列の幅はどうやって取ればいいのだろうと思っていたら、mPaint.getTextWidths(String str, float[] float)なるメソッドが。
これだ!と思ったら戻り値のintに入ったのは文字数・・・。
どうやら2番目の引数の中に各文字の幅が1文字ずつ入るメソッドのようなので、

String str = "Abcdefg";
float[] fl = new float[length];
int width = 0;

mPaint.getTextWidths(str, fl);
for(int i=0; i<length-1; i++) {
width += fl[i];
}

としました。
かっこ悪い気がするけど、他にどうやったら取れるかしら。
mPaintは上のコードのPaintクラスの変数です。

[Android]画面サイズの取得

http://start-android-sdk.blogspot.com/2010/05/android-how-to-get-screen-size-on.html
より。

これで取得できる画面サイズはタイトルバー&ステータスバーを含んだもの。

[Activity継承クラスの場合]
WindowManager windowmanager = (WindowManager)getSystemService(WINDOW_SERVICE);
Display disp = windowmanager.getDefaultDisplay();
int width = disp.getWidth();
int height = disp.getHeight();

[View継承クラスの場合]
Display disp = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).
getDefaultDisplay();
int width = disp.getWidth();
int height = disp.getHeight();

[Android]実行構成でNullPointerException 3

実行構成から構成の設定を行おうとするとNullPointerExceptionが発生する現象について。
おそらく、ではありますが解決方法がわかりました。

現象が発生するプロジェクトと、発生しないプロジェクトがありましたので両者を比較してみました。
すると、大きな違いが・・・

前者はビルドターゲットが「X10」、後者はビルドターゲットが「Android1.6」になっていました。
もしやと思い、前者のビルドターゲットを「Android1.6」にしたところ・・・


エラーが発生しなくなりました。


前に「プロジェクト > クリーン」を実施したら発生した、と書きましたが、あれは勘違いだったようです。
何度かビルドターゲットを「X10」と「Android1.6」にそれぞれ切り替えて確認してみましたが、「X10」にすると必ず発生し、「Android1.6」にすると1度も発生しなかったことから、私の環境ではこれが原因で間違いなさそうです。

しかし、ネットで検索をかけてみても同様の現象が出てる人はいない様子・・・
「Xperia X10」スキン&SDKインストールした時に失敗したのかしら。

2010年6月24日木曜日

[Android]実行構成でNullPointerException

以前からアプリをコンパイルし、Eclipseメニューの

「実行」⇒「実行構成」⇒Androidアプリケーション

を選択すると、NullPointerExceptionが発生していました。

[Error Message]
Error during launch: java.lang.NullPointerException

こんな感じ。
理由はどうやらdrawableフォルダの
・drawable-hdpi
・drawable-mdpi
・drawable-ldpi
が無かった為。
valuesフォルダのように、values-jaフォルダが無くてもデフォルトフォルダだけ用意しておけばいいものだと思っていました。
でも、drawableフォルダだけではエラーになるんですね。

2010年6月23日水曜日

[Android]XperiaのBluetooth電源ON/OFF

前にBluetooth通信の動作確認に使った「BluetoothChat」。
ソースコードを参考にさせていただきつつ、動作を確認していました。

すると、不思議なことにXperiaのBluetooth電源オフ状態でアプリを起動すると、
「Bluetooth is not available」
とToast表示が。
(ソースコードを見ると、Bluetooth非搭載の場合に表示される内容)

設定でBluetooth電源オンにすると表示されません。
XperiaではBluetooth電源オフ状態だとデフォルトのBluetooth Adapterが取得できないのでしょうか。
その後ろにBluetooth電源オフならオンにする処理が入っていますが・・・。

うーん?

[Android]XperiaのBluetoothペアリングの挙動

Androidスマートフォンと「ある機械」をBluetooth接続しようとした時に気になっていること。
それはペアリング情報の保持。

かつて、携帯電話と接続した時に、携帯電話側は過去にペアリングした機器の情報を保持しているにもかかわらず、「ある機械」側が全く保持できない構造だった為、お互いの情報交換やらサービス検索やら毎回やりなおす羽目になったことがありました。
やりなおすだけならいいんですけど、携帯電話側のアプリがBluetooth接続情報をプログラムからいじれない仕組みになっていたので、携帯電話側にペアリング情報が残っていると手動で情報を消さない限り進めなくなるという非常にいけてない仕組みだったので悲惨。

Androidで同じことにならないよね?という確認もこめて、XperiaでBluetoothのペアリングにおける挙動を確認。
esmasuiさんの「backport-android-bluetooth」を使わせていただいてます。

【実験】
Xperia側ではPair情報が保持されていて、「ある機械」側で情報が無い場合。Androidアプリでは画面上に「ある機械」の名前が表示されるのでタップタップ。

【結果】
Xperia側にパスキー入力ダイアログが表示されました。
相手にお前なんか知らないよと言われて、もう1度よろしくお願いしますっていう作法なんでしょうか。
礼儀正しくてステキです。

他機種でも同じ動きをするかどうかが気になります。

Logcatでのログ保存

実機(Xperia)に繋いで、DDMS経由でLogcatログを見る。

そこまではいいんですけど、ログをtxt形式で保存するのはどうすれば?
フロッピーな感じのアイコンは反転したままですし・・・

結論としてはLogcat窓の中の必要な部分をShift+方向キーで選択して、「▽」を押して「Export Selection As Text...」を選んで保存。

とりあえずこれで。

2010年6月2日水曜日

[Androidアプリ]Android1.6でBluetoothを使う

esmasuiさんの「backport-android-bluetooth」とサンプルコード「BluetoothChat」で、Xperiaと携帯電話以外の機器とのBluetooth通信が可能かどうかを確かめようと試みました。

「backport-android-bluetooth」
http://code.google.com/p/backport-android-bluetooth/

Source > Browse のところにサンプルソースがあるようです。
Source path: svn / trunk / backport-android-bluetooth201 / src / com / example / bluetooth / *.java

多分・・・。

ちなみにAndroid2.1のサンプルコード「BluetoothChat」をbackport用に変更したもののようです。
2.1用サンプルの方で最初やってみたんですが、Android1.6 -> 2.1で変更になった点でエラーが出たり、エミュ&実機でVerifyErrorが出たり。
xml関係ではこの部分で大量のエラーが。書き換えればOKでした

match_parent(Android2.1?) ⇒ fill_parent(Android1.x?)

VerifyErrorは原因がわからなかったので、改めて「backport-android-bluetooth」のサンプルコードの方でアプリをビルドして実機で実行。

RuntimeError・・・BluetoothAdapterクラスが無いだと??

「backport-android-bluetooth2.jar」をビルドパスに関連付けただけじゃダメなのかな?
/src以下にbackport.android.bluetoothパッケージを作って、ソースコード一式を置いてみる・・・

android.bluetooth.* 系が全部無いよ、エラー。

あまりに知識が無さ過ぎてどうすれバインダー状態。
/src以下にandroid.bluetoothパッケージも作って、ソースコード一式(以下略)
ここでAIDLなるファイルのことを知りました。
/src以下に拡張子aidlでファイル作っておいたら、/genの下にJavaファイルが出来る模様。

その状態でビルド、実行したら無事Xperia上で動作しました。
原因がわかってないことが多いけど、まずはBluetoothで接続&SPPにて通信が出来るかどうかを確認することが先なので接続相手を探してみる。

[1]手持ちのiPhone・・・iPhoneアプリ側に動作確認できるアプリがない。却下。
[2]手持ちの携帯電話・・・SPP通信用のアプリは入ってないので、SPPの上位プロファイルであるDUNで繋いでみる。
繋がるけどデータは何も飛んでこず。
そりゃそうだ。
[3]会社用の携帯・・・SPP通信用アプリが入っているけど、電池切れ。
充電器が無かったので後日に見送り。
[4]元々繋ごうと思ってた機器・・・よく考えたら、このド本命と繋げられるかどうかが大事なのであった。機器をセットアップして、いざ接続!

--------------------------------------
DEMO-1GOU
0x00012121@9abc ・・・
--------------------------------------
DEMO-1GOU
0x00045678@9abc ・・・
--------------------------------------
DEMO-1GOU
0x00099999@9abc ・・・
--------------------------------------

定期的に飛ばす電文が画面に表示されていきます。
メニューボタンを押して、接続メニューを出すことに気づくまで30分くらいかかったのはご愛嬌。
(それまではXperiaの「設定」からBluetooth接続させようとしてました)

iPhoneのようにiPhone系以外の機器とのSPP通信が制限されてる(?)かもしれないという危惧があったのですが、さすがはAndroid端末Xperia。
そんな制限は特になされていないようですね。

これで安心してBluetooth通信アプリが作れそうです。
問題は山積していますが・・・。
色々間違えていたらすみません。