2012/05/24

Android:キーイベントを追跡する

QWERTYキー等のハードキーを備える端末は少ないですが、需要があるため今後も発売され
続けるでしょう。
しかし、Androidの設計はハードキー操作よりもタッチ操作に重きを置いています。
このことが、ハードキーを意識したアプリ開発を難しくしています。

今回はキーイベントのアップ/ダウンについてです。

キーのアップとダウンは対とならないケースがあります。
下記のようなケースが考えられます。

ケース1:
 手順)
  アプリ起動前にキーAを押下し続ける>タップでアプリ起動>起動後にキーアップ
 結果)
  アプリにはキーアップのみが通知される。

ケース2:
 手順)
  アプリ起動中にキーAを押下し続ける>続けてバックキーを押下する
 結果)
  キーアップしていないのにキーAのキーアップが通知される

キーアップとダウンが対になる前提でコーディングするのは危険です。
上記のようなケースを判断したい場合はKeyEventクラスのAPIを使用します。

●ケース1の判断
キーイベントはトラッキング(追跡)することが可能です。
onKeyDownでevent.startTrackingを実行することでキーイベントの追跡が開始されます。
public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
    if (event.getRepeatCount() == 0) {
        event.startTracking();
    }
}
RepeatCountをチェックしているのは、押下し続けているキーのイベントがアプリ起動後
にも配信されるためです。
RepeatCountが0であるということは、今まさにキー押下されたことを意味します。

続けて、onKeyUpでキーイベントが追跡されている状態かどうかを判断します。
public boolean onKeyUp(int keyCode, KeyEvent event) {
    event.isTracking();
}
ここでisTrackingがtureを返せば、このキーイベントが自アプリ内でのキーダウンを契機
に開始されたことを意味します。


●ケース2の判断
ユーザが実際にキーアップしていなくてもonKeyUpが呼ばれます。
本当にユーザがキーアップしたイベントかどうかはevent.isCanceledで確認可能です。
event.isCanceled()
キーAを押し続けた状態で、BackキーやHomeキーを押下(あるいはタップ)した場合に、
キーイベントはキャンセルされてisCanceledがtureを返します。


●ユーザがアプリ内でキーダウン⇒アップしたかどうかを判断したい場合
キーAがアプリ内でキーダウン⇒キーアップされたかどうかを判断するコードです。
public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_A && event.getRepeatCount() == 0) {
        event.startTracking();
    }
....
}

public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_A && event.isTracking()
            && !event.isCanceled()) {
        // catch it!
    }
}
以上です。
2012/05/18

Android:ピクセルフォーマットを意識した高速化テクニック

画像描画でよく取り上げられるピクセルフォーマット。
ピクセルフォーマットはピクセル色の表現形式です。
http://developer.android.com/reference/android/graphics/PixelFormat.html

AndroidでもWindowやBitmapなど描画関係のクラスでピクセルフォーマットを指定できます。
http://developer.android.com/reference/android/view/Window.html#setFormat(int)

アプリパフォーマンスとピクセルフォーマットにどのような関係があるのか?
端的に言えば、下記のような関係にあります。
  • 透過効果を... ⇒ 使用しないフォーマット > 使用するフォーマット
  • ピクセル毎の色情報が... ⇒ 少ないフォーマット > 多いフォーマット

よりシンプルなピクセルフォーマットの方が早いのです。
また、メモリ効率の面で有利なフォーマットもあります。

代表的なピクセルフォーマットについてもう少し深く追っていきます。
ピクセルフォーマットには下記の種類が指定できます(代表的なものを抜粋)
  • RGB_565
  • RGBX_8888
  • RGBA_8888

R(Red)G(Green)B(Blue)A(Alpha)によるピクセルフォーマット指定です。
例えば、写真等の画像はRGB_888で十分表現可能です。
RGB_888はR,G,B各色8bitの情報量で色を表現するフォーマットです。
もちろん、RGB_888以外のフォーマットも存在します。

例えば「透明度を指定できるフォーマット」や、「色数は減るがより軽量であるフォーマ
ット」、「GPUアーキテクチャを考慮したフォーマット」等様々です。
それぞれのフォーマットの長所・短所を知り、最も力を発揮できるシーンで利用すればア
プリのパフォーマンスを向上することができるでしょう。

●RGB_565
画像は粗くなりますが、色情報量を減らすことでメモリ効率の向上が期待できます。
緑が6bitで赤青が5bitずつである理由は、人間の視覚が緑に対して敏感なためです。

●RGBX_8888
24bitトゥルーカラー(RGB)に付加bit(X:8bit)を加えているため、描画速度の向上が期待
できます。
付加bitについて触れておくと、
グラフィクスアクセラレータチップのメモリ接続バスは128bitが主流です。
しかし、RGB_888(24bit)の場合、接続バスへの色情報転送時に"余り"が出てしまいます。
(128 / 24 = 5…8)
そこで、RGB_888に8bitを付加することで転送効率が増し、描画速度が向上します。
ただし、1ピクセルあたりの色情報が増えるためメモリ使用量が増える短所があります。

●RGBA_8888
24bitトゥルーカラー(RGB)にαチャネル領域の8bitを加えたフォーマットです。
これにより、半透明な画像を表示することが可能になります。


高品質な画像を表示する場合にRGB_565は不向きです。
しかし、アイコン等の簡単な画像を表示する場合はRGB_565の品質で十分でしょう。

品質は確保しつつ、高速描画を実現したい場合はRGBX_8888の使用を検討します。
ただし、メモリ使用量が増加する傾向にあるので注意が必要です。

透明度の表現が必要な場合はαチャネル領域を持つフォーマット(RGBA_8888等)を指定す
る必要があります。

以上です。

2012/05/12

Android:キーガードはActivityではなくViewであることの影響

キーガードはFrameworkが表示しています。
キーガードはActivityではなくViewです。

パワーキー押下でスクリーンOFFするとActivityはonPauseします。
再度パワーキー押下でスクリーンONするとキーガードが表示されます。

一見すると、Activityはキーガードの裏にまわってonPauseしたかのように見えます。
しかし、実際にはスクリーンOFFの影響でActivityはonPauseしています。

続けてスクリーンONするとキーガードが表示された状態になります。
しかし、キーガードはViewなのでスクリーンOFF直前にフォアグラウンドであったActivity
のonResumeがキーガードの解除無しに呼ばれます。

これは、フォアグラウンドActivityでも画面上に表示されていないケースの一例です。
(他にもNotificationを引き出している場合やRecentTasks表示時など)

アプリによっては"画面表示されたタイミングを知りたい"というケースがあります。
前述のとおり、onResumeではこの要求を満たすことができません。
この問題を解決する方法として
Activity.onWindowFocusChanged(boolean)
があります。

また、キーガードが表示されているかどうかを取得したい場合は
KeyguardManager.inKeyguardRestrictedInputMode ()
を利用することが可能です。

参考:
http://developer.android.com/reference/android/app/KeyguardManager.html#inKeyguardRestrictedInputMode()
2012/05/10

Android:SparseArrayでパフォーマンスを向上する

Mapインターフェイスの利用を決定する前にSparseArrayの利用を検討します。

SparseArrayのキーはint型に限定されています。
そして、SparseArrayはint型のキーを持つMap<Integer, Object>と置換可能です。

使用例:
SparseArray<String> sparseArray = new SparseArray<String>();
sparseArray.put(1, "value");
String str = sparseArray.get(1);

Mapインターフェイスと比べ、SparseArrayは汎用性を犠牲にする代わりに高いパフォーマンス
を実現しています。

Map<Integer, Object>では、キーの指定にInteger⇒Objectへのキャストを要します。
しかし、キーがint型固定のSparseArrayであればキャストの必要が無い分、パフォーマン
スの面で有利です。

Map<Integer, Object>をSparseArrayに置換する方法は、静的解析ツールのLintでも勧め
られる方法の1つです。

参考:http://developer.android.com/reference/android/util/SparseArray.html
2012/05/08

Android:ハードウェアアクセラレーション

Android3.0からサポートされたハードウェアアクセラレーションについて覚書。

より優れた2Dレンダリングはハードウェアアクセラレーション(以降HWアクセラレーション)
機能を使用することで実現できます。

HWアクセラレーションを有効にすると、Viewのcanvas描画処理でGPUを利用するようにな
ります。
HWアクセラレーションの有効or無効は下記の粒度で指定が可能です。
  • Application
  • Activity
  • Window
  • View

各コンポーネント毎にHWアクセラレーションを設定する例)
  • Application
<application ...="" android:hardwareaccelerated="true">
  • Window
getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  • View
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

ただし、HWアクセラレーションは全ての2D描画をサポートしていません。
下記はサポートされていないAPIです。
  • Canvas
    •  clipPath()
    •  clipRegion()
    •  drawPicture()
    •  drawPosText()
    •  drawTextOnPath()
    •  drawVertices()
  • Paint 
    •  setLinearText()
    •  setMaskFilter()
    •  setRasterizer()

また、カスタムViewや描画処理を独自に実装していると
  • 例外が発生する
  • 描画が反映されない
  • 描画される内容が不正
といった悪影響を及ぼす可能性があります。
下記はHWアクセラレーションを有効にすることで異なる動作となるAPIです。
  • Canvas 
    • clipRect(): XOR, Difference and ReverseDifference は無視される。
             3D変換は、クリップ領域に対して適用されない。
    • drawBitmapMesh(): 色配列は無視される。
    • drawLines(): アンチエイリアスはサポートされない。
    • setDrawFilter(): セットはできるが無視される。
  • Paint 
    • setDither(): 無視される。
    • setFilterBitmap(): フィルタリングは常にONされます。
    • setShadowLayer(): テキストのみ動作します。
  • ComposeShader 
    • 異なった型のシェーダーを含むことしかできない。
    • ComposeShaderはComposeShaderを含む事ができない。

問題を解決するためには、setLayerType(View.LAYER_TYPE_SOFTWARE, null)を呼び出して、
影響を受けている部分のHWアクセラレートをオフにします。

ViewあるいはCanvasがHWアクセラレートされているかを判定する場合は下記を使用します。
  • View.isHardwareAccelerated()
  • Canvas.isHardwareAccelerated()

以上です。