2012/02/29

Android:SQLiteのジャーナルファイル


SQLiteのロールバックジャーナルファイル

トランザクション更新処理を開始すると、トランザクションをロールバックするための
ジャーナルファイルが作成されます。
ジャーナルファイルは「データベース名-journal」という名前で作成されます。
# ジャーナルファイルは、データ更新中に外部からの参照アクセスへ応答するための
# 元データとしても使用されます。

アプリ実行中等にアプリデータ領域のdatabasesフォルダを見てみます。
root@android:/data/data/yuki.test/databases # ls -l
ls -l
-rw-rw---- app_145  app_145      5120 1980-01-06 05:34 test.db
-rw-r--r-- app_145  app_145         0 1980-01-06 05:34 test.db-journal

test.db-journalというファイルが作成されていますね。
トランザクションをコミットしたりロールバックすると、このファイルは削除あるいは無効
となります。

場合によっては、ジャーナルファイルの削除がパフォーマンスに悪影響を及ぼすケースが
あります。
そのため、ジャーナルファイルの削除には複数のモードが用意されています。
※Androidアプリがジャーナルモードを指定できるかは未調査

データベースのジャーナルモードを調べてみます。
調べたいデータベースをsqlite3シェルモードで開き、下記コマンドを発行します。
sqlite> PRAGMA journal_mode;
PRAGMA journal_mode;
truncate

自機ではtruncateが取得できました。取得できるモード一覧は下記です。
  • DELETE:トランザクション終了時にジャーナルファイルを削除する
  • TRUNCATE:トランザクション終了時にジャーナルファイルサイズを0にする
  • PERSIST:トランザクション終了時にヘッダを無効にして使用不可にする
  • MEMORY:ジャーナルファイルをメモリ上に保存する
  • OFF:ジャーナルファイル機能を使用しない(トランザクションのロールバック不可)

「journalファイルが削除されない」と言ったケースでは、ジャーナルモードを取得して
みましょう。
モードTRUNCATEであれば、正常にトランザクション終了している場合はファイルサイズが
0になっているはずです。

以上です。
2012/02/27

Android:引数はthisか?getApplicationContextか?ActivityとApplicationの違い


AndroidにはContextを引数にとるAPIが数多く存在しています。
例えばArrayAdapterのコンストラクタArrayAdapter<T>(Context, int)があります。

このコンストラクタ。Activityで使用する場合、第一引数へはthisとしてActivityContextを指定すべきでしょうか?
それともthis.getApplicationContext()としてApplicationContextを指定すべきでしょうか?

ContextはAndroidアプリを作成している場合によく使用されるクラスですが、その役割や
詳細は下記のような理由から不透明な部分が多いです。
  • Contextがアプリケーションに関する様々な情報へのインタフェースであり、非常に広
    範囲で使用されている
  • 外部、あるいはシステム側で使用されるインタフェースであり、アプリ自身でContext
    を直接操作するというシーンが極稀である
  • Contextの情報はシステムが適切に構築・処理してくれるため、アプリ開発者はContext
    の内部情報にそれほど興味を持つ必要がない

Context周辺には落とし穴があります。
例えば、下記のようなアラートダイアログを生成するコートはよく見かけると思います。
シンプルなリストを持つアラートダイアログです。
■コード1
public class MyActivity extends Activity {
// 省略
    @Override
    protected Dialog onCreateDialog(int id) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("test");
        ArrayAdapter<String> adapter
                = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        adapter.add("item");
        builder.setAdapter(adapter, null);
        builder.setPositiveButton("OK", null);
        return builder.create();
    }
// 省略
}

このActivityを定義したAndroidManifest.xmlは下記です。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="yuki.test"
        android:versionCode="1"
        android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="14" />
    <application
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Holo" >
        <activity
                android:label="@string/app_name"
                android:name=".MyActivity"
                android:theme="@android:style/Theme.Holo.Light" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

このコードは下記のようなダイアログを生成します。何の変哲もないただのダイアログです。




次に、ソースコードを下記のように変更してみます。
■コード2
public class MyActivity extends Activity {
// 省略
    @Override
    protected Dialog onCreateDialog(int id) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("test");
        ArrayAdapter<String> adapter
                = new ArrayAdapter<String>(this.getApplicationContext(),  //★
                        android.R.layout.simple_list_item_1);
        adapter.add("item");
        builder.setAdapter(adapter, null);
        builder.setPositiveButton("OK", null);
        return builder.create();
    }
// 省略
}

★の箇所を変更しました。
コード1ではArrayAdapterの第一引数Contextにthis。つまりActivityインスタンスを指
定しました。
コード2ではArrayAdapterの第一引数Contextにthis.getApplicationContext。つまり
ApplicationContextを指定しました。

何か変化するのか?コードを実行して結果を確認してみます。



視認できませんが、リスト項目の文字色が黒→白に変化しました。

AndroidManifest.xmlの内容を見直してみましょう。
・ApplicationのテーマはTheme.Holo
・MyActivityのテーマはTheme.Holo.Light
となっています。
# Theme.Holoは文字色:白/背景色:黒の組み合わせ。
# Theme.Holo.Lightは文字色:黒/背景色:白の組み合わせがデフォルトです

このことから、ArrayAdapterが描画するTextViewの文字色は第一引数のContextが持つテーマ
に依存していることがわかります。
Contextはアプリリソース(ここではテーマ)にアクセスするために利用されているという
ことです。
また、ContextインスタンスであってもActivityContextと、ApplicationContextでは
動作の異なるケースがあることがわかりました。

「一部の色がおかしい」「一部にテーマが適用されない」といった現象はContext
が原因である可能性も考慮した方がよさそうですね。


次に、AlertDialog.Builderコンストラクタの引数にApplicationContextを渡してみましょう。
■コード3
public class MyActivity extends Activity {
// 省略
    @Override
    protected Dialog onCreateDialog(int id) {
        AlertDialog.Builder builder
                = new AlertDialog.Builder(this.getApplicationContext());  //★
        builder.setTitle("test");
        ArrayAdapter<String> adapter
                = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        adapter.add("item");
        builder.setAdapter(adapter, null);
        builder.setPositiveButton("OK", null);
        return builder.create();
    }
// 省略
}

★の箇所を変更しました。
コード1ではBuilderの第一引数ContextにActivityを指定していました。
コード3ではBuilderの第一引数Contextにthis.ApplicationContextを指定しました。
両方ともContextクラスを継承したインスタンスです。

コードを実行して結果を確認してみます。

ログ:
01-06 05:52:22.276: E/AndroidRuntime(4578): Caused by:
   android.view.WindowManager$BadTokenException:
     Unable to add window -- token null is not for an application

ApplicationContextでは適切なWindowTokenが得られないためエラーが発生しました。
このことから、ContextインスタンスであってもActivityContextと、ApplicationContext
には違いがあり、APIによってはエラーとなることがわかりました。


Contextについて少し触れたところで、Contextの詳細に一歩踏み込んでみます。

●Context概要
Contextはアプリケーションのグローバル情報へアクセスするためのインタフェースです。
アプリケーションのグローバル情報とは...
・パーミッション
・アプリリソース情報
・アプリ情報(コンポーネント名、プロセス名、タスクアフィニティ、テーマ等)
・ファイルリソース(DBを含む)
等々です。

Contextを取得することでこれらのグローバル情報へアクセスすることが可能になります。
他アプリのContextを経由して画像リソースを参照したり、パーミッションの有無をチェ
ックすることが可能となります。
ウィジェットコンポーネントがコンストラクタの引数でContextを求めるのはリソースへ
の参照を得るためです。
また、アクティビティの起動、ブロードキャスト、インテントの受信等の操作が可能とな
ります。

Context関連の型階層は簡略化すると下記のようになっています。

Context
└ ContextWrapper
    ├ Application
    ├ ContextThemeWrapper
    | └ Activity
    └ Service

●Contextのライフサイクル
ApplicationContextはアプリケーションのライフサイクルと連動し、ActivityContextは
アクティビティのライフサイクルと連動しています。
つまり、2つはどちらもContextインスタンスではありますが、中身は全く異なるものに
なります。

ContextのライフサイクルについてはDeveloperページのgetApplicationContextが参考に
なります。
以下要約です。
BroadcastReceiverを登録するregistReceiverを使用するケースにおいて...
・ActivityContextを使った場合
レシーバはActivityと関連付けて登録されます。システムはActivityが破棄される
前に登録したBroadcastReceiverがunregistReceiverされることを期待します。
もしunregistReceiverされなかった場合は、フレームワークがこれをクリーンナップし、
エラーログを出力します。

・ApplicationContextを使った場合
レシーバはApplicationと関連付けて登録されグローバルな状態となる。
そのためフレームワークがこれをクリーンアップすることがなく、unregistReceiverを
忘れると容易にリークしてしまいます。
参考:http://developer.android.com/reference/android/content/Context.html#getApplicationContext()

Contextを渡す対象のAPIは、ActivityかApplicationどちらが良いのかについては、
ライフサイクルを意識することも重要であることが上記からわかります。


●Context保持によるメモリリーク問題
Contextに関するメジャーな問題としてメモリリークがあります。
前述からもわかるようにContextへの参照はActivityへの参照でもある可能性があります。

下記のようにActivityContextを長期間保持するようなコードは簡単にメモリリークを引
き起こす可能性があります。
private static Context mContext;
// ...
mContext = activityInstance;
ActivityContextはActivityのライフサイクルと連動していることを思い出してください。
private staticなmContextは、これを保持するActivityのライフサイクルが終了したとして
も、mContextによるActivityへの参照は破棄されず、メモリリークが発生します。

Contextへの参照を使用する場合に留意するべき点は下記となります。
  • Contextの参照は関連するContextのライフサイクルに合わせる
    (ApplicationContextならApplicationライフサイクルに、ActivityContextであれば
    Activityのライフサイクルに合わせること)
  • 可能であればApplicationContextの使用を考える
  • Activity内部に非staticなインナークラスの作成は極力避ける。
  • 可能であればActivityへの参照は弱参照を用いる

Context参照がリークした場合、Activityとそれに紐付くインスタンス(特にViewやBitmap等)
が諸々リークするため、予想以上に大量のメモリリークとなる恐れがあります。
場合により、ガベージコレクタによるインスタンス回収が間に合わずOutOfMemory例外が
発生することもあります。(ガベージコレクタはメモリリーク保証しない)

Contextにまつわるメモリリーク問題のより詳細な情報は下記が参考になります。
http://developer.android.com/resources/articles/avoiding-memory-leaks.html


●getBaseContextについて
getBaseContext()については、ドキュメントが見当たらず詳細については掴めません
でした。
他サイトで「グーグルエンジニアが使用しないほうが良いと言っていた」という記事は見
つけましたが、根拠までは記載されていませんでした。

少し調べた結果、android.app.Dialogクラスで下記のようなコードを発見しました。
/**
* @return The activity associated with this dialog, or null if there is no associated activity.
*/
private ComponentName getAssociatedActivity() {
    Activity activity = mOwnerActivity;
    Context context = getContext();
    while (activity == null && context != null) {
        if (context instanceof Activity) {
            activity = (Activity) context;  // found it!
        } else {
            context = (context instanceof ContextWrapper) ?
                ((ContextWrapper) context).getBaseContext() : // unwrap one level
                null;                                         // done
        }
    }
    return activity == null ? null : activity.getComponentName();
}

これから推測するに、Contextは多重にラッピングされており、getBaseContext()はそれを
1つずつ剥がす(アンラップする)操作になるようです。
# Activityのクラス階層をみると2重ラップされているのかな...?

getBaseContextについて、まだまだ不明点が多いですが必要に迫られない限りは
ActivityContextかApplicationContextを使用したほうが無難そうです。

以上です。

2012/02/26

uses-sdkタグを宣言する場所に注意


AndroidOSバージョンに対するアプリケーションの互換性を示すためにuses-sdkが使用さ
れます。uses-sdkはAndroidManifest.xml上で宣言されます。
参照:http://www.techdoctranslator.com/android/guide/manifest/uses-sdk-element

注意しないといけないのは、uses-sdkを記載する位置。
applicationタグよりも後で宣言すると予想外の動作になる場合があります。

NG:
<manifest>
  <application ...>
  </application>

  <uses-sdk ... /> <!-- applicationタグより後にuses-sdkを宣言 -->
</manifest>

OK:
<manifest>
  <uses-sdk ... /> <!-- applicationタグより前にuses-sdkを宣言 -->

  <application ...>
  </application>
</manifest>

Android4.0でハードウェアアクセラレータの使用ON/OFFを指定できるようになりました。
ハードウェアアクセラレータはデフォルトONですが、実装的に言えばtargetSdkのバージ
ョンがICS以上であればデフォルトONとなります。

注意が必要なのは、アプリケーション情報を記載したAndroidManifest.xmlのパース処理
は先頭からシーケンシャルに行われるということです。
つまり、uses-sdkより先にapplicationタグがある場合、uses-sdkの設定が読み込まれる
前にapplication情報が構築されてしまいます。

NGのパターンではハードウェアアクセラレータはデフォルトOFFになります。
原因となるロジックは下記です。

applicationタグが見つかった場合にコールされるPackageParser.parseApplicationメソッド
(PackageParserはAndroidManifest.xmlをパースし、アプリケーション情報を構築するクラス)

boolean hardwareAccelerated = sa.getBoolean(
    com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
    owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);

targetSdkVersionはuses-sdkタグが見つかった場合の下記ルートで処理されます。

PackageParser.parsePackage()
...
    } else if (tagName.equals("uses-sdk")) {
...

AndroidManifest.xmlのパースは先頭からシーケンシャルに行われますので、uses-sdkよ
り前にapplicationタグが見つかった場合、targetSdkVersionの値がまだ初期化されてい
ない状態となりますのでhardwareAcceleratedの値はfalseとなり、アプリケーションや
アクティビティに設定されるハードウェアアクセラレータ値はOFFとなります。

これらのことから、uses-sdkタグは必ずapplicationタグよりも前に定義するようにしましょう。

以上です。
2012/02/25

AsyncTaskLoaderのまとめ


■AsyncTaskLoaderの概要

AsyncTaskを提供する抽象的なLoaderです。
AsyncTaskLoaderはWorkerとしてのAsyncTask(ロードタスク)を内部にもつLoaderです。

AsyncTaskLoaderを継承した実装サンプルは下記のページにあります。
http://developer.android.com/intl/ja/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.html

CursorLoaderはAsyncTaskLoaderを継承したクラスとなります。
http://developer.android.com/reference/android/content/CursorLoader.html

LoaderとLoaderManagerについての詳細は下記を参照
http://yuki312.blogspot.com/2012/02/loadermanager.html
http://yuki312.blogspot.com/2012/02/loadermanagerloadercallbacks.html
http://yuki312.blogspot.com/2012/02/loader.html

AsyncTaskLoaderはThrottle機能を提供します。
Throttleは連続でクエリ発行された際の実行頻度を調整する弁の役割を持ちます。
Throttleを使用した簡単なサンプルは下記です。
http://developer.android.com/intl/ja/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html


■AsyncTaskLoaderの主なメソッド群
●onLoadInBackground()
  • ローダタスクのワーカースレッド上から呼び出されます。
    ワーカースレッド上で実行されるonLoadInBackgroundから直接、結果データをクライア
    ントへ返却すべきではありません。
    結果の返却はメインスレッド上で呼び出されるdeliverResult()内で行います。
    メインスレッドに結果を返却したい場合はdeliverResultをオーバーライドすることで
    それが可能になります。
  • onLoadInBackgroundはロード処理の結果を返す必要があります。
  • onLoadInBackgroundは、サブクラスレスポンスのためにloadInBackgroundをコールします。


●loadInBackground()
  • onLoadInBackground()を参照


●cancelLoad()
  • 現在のロードタスクをキャンセルしようとします。
    詳細についてはAsyncTask.cancel(boolean)を参照。
    http://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)
  • ロードタスクはバックグラウンドで動作しているため、キャンセル操作は即時反映され
    ません。AsyncTaskのそれと同じです。
  • 実行中のロードタスクの場合、この要求によってロードがキャンセルされます。
    もし、ロード中に他のロード要求があったとしてもロードが完了するまで保持されます。
  • ローダタスクの処理が完了すると、タスクの状態はクリアされます。
  • ロードをキャンセルできなかった場合はfalseを返します。
    ロードが既に完了していた、あるいは、まだstartLoadingがコールされていない場合で
    もfalseを返します。
  • このメソッドは常にメインスレッドから実行する必要があります。


●onCanceled()
  • ローダタスクがそのタスクを完了する前にキャンセルされた場合にコールされます。
    ここでキャンセルされたタスクに関係するデータを破棄することができます。


●setUpdateThrottle()
  • Throttleはクエリ発行頻度を調整する為の弁の役割を持ちます。
  • 引数delayMSは発行頻度を決めるミリ秒単位の調整値です。
    タスクの完了(onLoadInBackground)から次のローダタスクが開始されるまでの最小の
    待ち時間を指定できます。



■AsyncTaskLoaderを継承したLoaderCustomのシーケンス
AsyncTaskLoaderを継承した例としてLoaderCustomが参考になります。
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.html

下記はLoaderCustomのクライアントとLoaderManagerとの連携部分のシーケンス図となります。

1.タスクロードの開始まで(表示拡大

2.タスクロードの完了から通知まで(表示拡大

3.タスクロードの完了から通知まで(表示拡大



■AsyncTaskLoaderの状態遷移
AsyncTaskLoaderの状態遷移は通常下記になります。
AsyncTaskLoaderの開始前はReset状態となります。
状態遷移はユーザのAsyncTaskLoaderの拡張方法次第で変化し得ることに注意が必要です。



以上です。
2012/02/23

LoaderのAPIまとめ

■Loaderの概要
  • 非同期のデータロードを行うための雛形を提供します。
  • Loaderは、アクティブの間は対象となるデータセットソースを監視し、変更があった場
    合にクライアントへ通知を行います。


Loaderは非同期のデータロードを実行するための抽象クラスで、独自に拡張することが可
能ですが、直接Loaderクラスを継承することは推奨されていません。
代わりに、Loaderを継承したAsyncTaskLoaderを継承することができます。
AsyncTaskLoaderを継承した一般的な具象クラスはCursorをデータセットに持つCursorLoaderです。

参考:
 Loaderを継承した簡単なサンプル
 http://developer.android.com/intl/ja/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.html

注意:Loaderを使用するクライアントは、Loaderを使用する際にはメインスレッドからコールする
必要があります。
例えば、AsyncTaskLoaderはロード処理をバックグラウンドスレッドで行いますが、ローダ
の呼び出しや、結果の受信スレッドはメインスレッドである必要があります。


■Loaderの主なメソッド群

●startLoading()
  • Loaderを開始します。
  • LoaderManager経由でLoaderを管理している場合、関連するActivity/Fragmentが開始さ
    れる時(onStart)、このメソッドはLoaderManagerによって自動的に呼び出されます。
  • 結果のロードが完了し、通知の準備ができればメインスレッドのコールバック
    (Loader.onLoadComplete)が呼び出されます。
    ただし、前回のロードが完了済みかつ有効である場合は即コールバックが呼び出されます。
    コールバック通知はstopLoadingを呼び出すことで一時停止することができます。
  • Loaderはデータセットソースに対するObserver(ForceLoadContentObserver)を内包しており、
    データセットソースの変更検知もLoaderが行ってくれます。
    そのため、Loaderを使用する場合はデータの監視を自身で行う必要はありませんが、
    CursorAdapterとLoaderを併用する場合には注意が必要で、
    CursorAdapter(android.content.Context, android.database.Cursor, int)コンストラ
    クタのflagには0をセットし、FLAG_AUTO_REQUERYまたはFLAG_REGISTER_CONTENT_OBSERVER
    はOFFしない(コンテンツ監視しない)ようにする必要があります。
  • startLoadingを呼び出すと、Loaderの内部状態が更新されます。
    具体的には 開始状態=true / リセット状態=false / abandon状態=false
    となります。(それぞれの状態はisStarted(), isReset(), isAbandoned()で取得可)
    startLoadingは状態更新後、サブクラスレスポンスのためにonStartLoading()をコールします。
  • このメソッドを直接コールするとLoaderの管理に影響がでるため避けるべきです。
  • このメソッドは常にメインスレッドから実行する必要があります。



●onStartLoading()
  • サブクラスはLoader開始時の実装をここで行います。
  • このメソッドはstartLoading()経由でコールされることが前提となります。
    クライアントが直接このメソッドをコールすることは推奨されません。
  • このメソッドは常にメインスレッドから実行する必要があります。



●stopLoading()
  • Loaderを停止し、startLoading()が呼ばれるまでデータの更新通知も停止します。
  • LoaderManager経由でLoaderを管理している場合、関連するActivity/Fragmentが停止
    される時、このメソッドはLoaderManagerによって自動的に呼び出されます。
  • 実装においては、この時点でデータセットを無効にするべきではありません。
    クライアントはLoaderが最後に通知したデータ結果を自由に使用できるようにするべき
    です。
  • Loaderが停止している間は、データセットが更新されてもそれがクライアントに通知さ
    れることはありません。
    ただし、Loaderが再開された時に正しい値(takeContentChangedの戻り値)を保証するた
    めに、データセットの監視は続けられます。
  • stopLoadingを呼び出すことで、Loaderの内部状態が更新されます。
    具体的には 開始状態=false となります。(それぞれの状態はisStarted()で取得可)
    stopLoadingは状態更新後、サブクラスレスポンスのためにonStopLoading()をコールします。
  • このメソッドを直接コールするとLoaderの管理に影響がでるため避けるべきです。
  • このメソッドは常にメインスレッドから実行する必要があります。



●onStopLoading()  
  • サブクラスはLoader停止要求時の実装をここで行います。
  • このメソッドはstopLoading()経由でコールされることが前提となります。
    クライアントが直接このメソッドをコールすることは推奨されません。
  • このメソッドは常にメインスレッドから実行する必要があります。



●abandon()
  • LoaderManager経由でLoaderを再起動(restartLoader)した際にLoaderManagerから自動的
    にコールされるメソッドです。
  • abandonはreset()の前にコールされます。
    Loaderはabandon状態になっても現在のデータを保持し続けますが、それが更新された
    としてもユーザに通知することはありません。
  • abandonを呼び出すことで、Loaderの内部状態が更新されます。
    具体的には abandon状態=true となります。(それぞれの状態はisAbandon()で取得可)
    abandonは状態更新後、サブクラスレスポンスのためにonAbandon()をコールします。
  • このメソッドを直接コールするとLoaderの管理に影響がでるため避けるべきです。



●onAbandon()
  • サブクラスはLoaderが不要となった時の実装をここで行います。
  • Abandon状態はLoaderが不要となった状態を指します。
    リセットされる時はonResetが呼ばれる為、onAbandonがコールされたときのLoaderは
    start→resetの中間的な状態であると言えます。
  • Abandon状態であるLoaderにクライアントは興味を持つことがありません。
    (おそらくクライアントは既に新しいLoaderを扱っています)
    そのため、AbandonなLoaderはユーザに通知を送ってはいけません。
    しかし、Loader.onReset()が処理されるまでLoaderはユーザに通知したデータを有効な
    状態に保ち続ける必要があります。
  • このメソッドはabandon()経由でコールされることが前提となります。
    クライアントが直接このメソッドをコールすることは推奨されません。
  • LoaderがAbandon状態かどうかはisAbandonedで取得可能です。



●reset()
  • このメソッドはLoaderを破棄する場合にLoaderManagerから自動的に呼ばれるメソッド
    です。resetによりLoaderは破棄されます。
  • このメソッドが再度コールされることはありません。
    Loaderはこの時点で解放できる全てのリソースを解放する必要があります。
    しかし、resetの後でstartLoadingが呼ばれることもあり、その際には再び実行可能で
    ある必要があります。
  • resetを呼び出すことで、Loaderの内部状態が更新されます。
    具体的には 開始状態=false / リセット状態=true / abandon状態=false /
    データソース変更状態=false となります。
    (それぞれの状態はisStarted(), isReset(), isAbandoned(), takeContentChanged()で
    取得可)
  • resetはLoaderの状態更新前に、サブクラスレスポンスの為にonReset()をコールします。
  • このメソッドを直接コールするとLoaderの管理に影響がでるため避けるべきです。
  • このメソッドは常にメインスレッドから実行する必要があります。



●onReset() 
  • サブクラスはLoaderのリセット要求時の実装をここで行います。
  • このメソッドはreset()経由でコールされることが前提となります。
    クライアントが直接このメソッドをコールすることは推奨されません。
  • このメソッドは常にメインスレッドから実行する必要があります。



●forceLoad()
  • Loaderによるロードを強制します。
  • startLoading()とは異なり、以前にロードしたデータは全てキャンセルされて、
    新しいものに置き換えられます。
  • forceLoadは、サブクラスレスポンスのためにonForceLoad()をコールします。
  • 通常、Loaderが開始状態(isStarted)の場合にこれを呼び出すべきです。
  • このメソッドは常にメインスレッドから実行する必要があります。



●onForceLoad()
  • サブクラスは強制ロード要求時の実装をここで行います。
  • このメソッドは常にメインスレッドから実行する必要があります。



●onContentChanged()
  • ForceLoadContentObserverがデータセットソースの変更を検知したときにコールされる
    メソッドです(ContentObserver.onChangeに相当)
  • デフォルトでは、Loaderが開始済みの場合にforceLoadをコールし、開始していない場合
    はtakeContentChangedがtrueを返すようにフラグを設定します。
  • このメソッドは常にメインスレッドから実行する必要があります。



●takeContentChanged() 
  • Loaderの停止中にデータセットに変更があったかどうかを取得できます。
    変更があった場合はtrueが返され、フラグ(変更状態)はリセットされます。



●deliverResult()
  • クライアントへロード結果を送信します。
  • このメソッドはサブクラスからコールされることを想定しており、この処理はメインス
    レッド上で実行する必要があります。



●isStarted()
  • Loaderが開始状態かどうかを返します。
  • Trueである場合はstartLoadingが呼び出されており、まだstopLoadingまたはresetが
    コールされていない状態であることを意味します。



●isAbandoned()
  • このLoaderが不要となったかどうかを返します。
  • abandon状態のLoaderは現在のデータを保持しますが、それが更新されたとしてもユーザ
    に通知しません。



●isReset()
  • Loaderがリセット状態かどうかを返します。
  • Trueである場合はLoaderがまだ1度も開始されていない状態、またはresetが既にコー
    ルされている状態などがあります。


以上です。

LoaderManager.LoaderCallbacksインタフェースのAPIまとめ

LoaderはAndroid3.0より追加された機能です。

LoaderCallbacksはクライアントがLoaderManagerと双方向にやり取りするためのコール
バックインターフェイスです。
例えば、onCreateLoader()コールバックメソッドはLoaderManagerからのLoader生成の要
求で、ここでLoaderをインスタンス化して新しいローダを返します。

このコールバックはActivity/FragmentがLoaderManager経由でLoaderを使用する場合に
必要です。各コールバックはメインスレッドで実行される必要があります。

●onCreateLoader(id, args)
  • LoaderManagerから指定されたIDに紐付くLoaderインスタンスの生成要求です。
    ここで、データのロード準備ができたLoaderインスタンスを返却します。


●onLoadFinished(loader, data)
  • Loaderのデータロード完了通知です。
  • このコールバックはActivity.onSaveInstanceState()より後に呼ばれる可能性があるこ
    とに注意してください。
    例えば、このメソッド内でFragmentTransactionのコミットを行うと例外が発生します。
    ※FragmentManager.openTransactionについて
     http://developer.android.com/intl/ja/reference/android/app/FragmentManager.html
  • このコールバックはLoaderの持つデータが解放される前に呼び出されます。
    この時点で使用している古いデータは削除すべきですが、Loaderがそのデータを管理
    しているため、ここでデータを解放してはいけません(Cursorのclose等)
  • Loaderはアプリケーションがそのデータを使用しないと分かると自動で削除・解放して
    くれます。CursorAdapterとLoaderを併用している場合、CursorAdapter.changeCursor(cursor)メソッドは既存のcorsorをクローズしようとするので注意が必要です。
  • Adapterに紐付くカーソルの更新は、これをcloseしないswapCursor(Cursor)を使用する
    必要があります。mAdapter.swapCursor(data)がその例です。
  • ※swapCursorについて
     http://developer.android.com/reference/android/widget/CursorAdapter.html#swapCursor(android.database.Cursor)
  • ※サンプルプログラム
     http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html


●onLoaderReset(loader)
  • 作成したLoaderのリセット通知です。これによりロードしたデータが使用できなくなります。
    アプリケーションはこのタイミングで必要なデータ参照の削除を行います。
    mAdapter.swapCursor(null);がその例です。


以上です。
2012/02/21

Logcatを使用してスタックトレースを出力する方法

スタックトレースをログ出力する方法です。
RuntimeException e = new RuntimeException("here");
e.fillInStackTrace();
Log.w(TAG, "Called doStart when already started: " + this, e);
出力結果は下記
W/List(16167): yuki: ItemListFragment{412b96f0 #1 id=0x7f050003}
W/List(16167): java.lang.RuntimeException: here
W/List(16167): at yuki.fragment.page.ItemListFragment.onActivityCreated(ItemListFragment.java:53)
W/List(16167): at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:847)
W/List(16167): at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1032)
W/List(16167): at android.app.BackStackRecord.run(BackStackRecord.java:622)
W/List(16167): at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1382)
W/List(16167): at android.app.Activity.performStart(Activity.java:4474)
W/List(16167): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1929)
W/List(16167): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
W/List(16167): at android.app.ActivityThread.access$600(ActivityThread.java:123)
W/List(16167): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1147)
W/List(16167): at android.os.Handler.dispatchMessage(Handler.java:99)
W/List(16167): at android.os.Looper.loop(Looper.java:137)
W/List(16167): at android.app.ActivityThread.main(ActivityThread.java:4424)
W/List(16167): at java.lang.reflect.Method.invokeNative(Native Method)
W/List(16167): at java.lang.reflect.Method.invoke(Method.java:511)
W/List(16167): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
W/List(16167): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
W/List(16167): at dalvik.system.NativeStart.main(Native Method)



2012/02/20

LoaderManagerのAPIまとめ

LoaderManagerについて、ポイントとなるメソッドの概要をまとめました。

Activity.getLoaderManager
  • 複数のLoaderを管理するLoaderManagerのインスタンスを取得します。
    LoaderManagerは各Activity/Fragment毎に1つ割り当てられ、何度getLoaderManager
    をコールしても同じLoaderManagerインスタンスが返却されます。
    Activity/Fragmentのインスタンスが異なれば返却されるLoaderManagerのインスタンス
    は異なるので、LoaderのID重複を心配する必要はありません。
  • LoaderManagerはgetLoaderManagerメソッドの初回コール時に生成されます。
  • LoaderManagerのライフサイクルはActivity/Fragmentにより管理され、
    そのライフサイクルはActivity/FragmentのonStart時に開始されます。
    ただし、onStart時にLoaderManagerが初期化されていない(つまりgetLoaderManagerが
    まだ呼ばれていない)時はLoaderManagerは正しく動作しません。
  • getLoaderManagerメソッドは遅くともActivity/Fragment.onStartより前にコールしていな
    いと、LoaderManagerがLoaderを正しく管理してくれません。
    # 画面回転時等のLoaderManager.onRetainで内部エラーが発生し、期待通りに動作しなく
    # なります。



LoaderManager.initLoader
  • 指定したIDでLoaderを初期化・生成します。
    ここで指定するIDはLoaderの識別子として使用され、Loaderが作成される時は
    Loader.onCreateLoaderがコールバックされます。
    initLoaderは指定のLoaderが初期化されることを保証します。
  • 指定したIDに紐づくLoaderがすでに存在している場合、Loaderは初期化されません。
    ただし、Loaderへのコールバックが指定されたインスタンスで上書きされます。
    この時、引数のargsは無視されます
  • initLoaderは関連するActivity/Fragmentの生成時(Activity.onCreate/Fragment.onCreateActivity)
    に使用されるべきメソッドです。
  • LoaderManagerはconfiguration changeによって破棄・生成されず再利用されます。
    LoaderManagerは既に存在するLoaderの再利用を可能としています。
    そのため、Activityがconfiguration changeにより再生成されてonCreateでinitLoaderを
    コールしても、対象のLoaderをLoaderManagerが既に保持しているのでLoaderを生成する
    onCreateLoaderが再度呼ばれることはありません。
  • 注意しなければならないのは、Loaderが再利用される場合は引数argsは無視されるということです。



LoaderManager.restartLoader
  • 引数のIDに関連付けられているLoaderを再生成します。
    既存のLoaderは必要に応じてキャンセル/停止/破棄されます。
    生成されるLoaderは引数argsを持つ新たなLoaderとして生成されます。
  • # # # # 下記詳細不明 (Loaderが実行中か、実行済みかでLoaderを制御している模様)
    ただし、指定のIDに紐づくLoaderが複数作成されていて、まだそれが満了していない場合、
    新たにLoaderを作成しても、既存のLoaderが満了するまでこれを起動できません。
  • restartLoaderで指定したIDに紐づく過去のLoaderは無効となり、それらのLoaderの
    コールバックは以降通知されなくなります。



LoaderManager.destroyLoader
  • 引数のIDに紐づいたLoaderを停止・破棄します。
  • 破棄対象のLoaderがデータをユーザへ通知していた場合は、onLoadFinished→
    onLoaderResetがコールされます。



以上です。
2012/02/13

画面の向き:ORIENTATION_SQUARE


画面向きを定義した値の中にはORIENTATION_LANDSCAPEやORIENTATION_PORTRAITがあります
が、ORIENTATION_SQUAREという値も定義されています。
これは縦、横といった向きではなく「正方形」を表す値です。

正方形であるため、縦、横といった画面の向きはありません。
あまり見かけることのない値ですが、i'm Watchのようにディスプレイが正方形の場合に
利用されます。
 I'm watch→http://www.imwatch.it/jp-en/

正方形ということで"向き"という概念が無いためか、画面を90度回転してもorientation
のconfigChangeは呼ばれないようです。

画面が正方形の場合は、getResouces().getConfiguration().orientationの値を参照する
ことでORIENTATION_SQUAREが取得できます。

参考:Android developers

以上です。
2012/02/12

Android:FragmentとActivityのライフサイクルコールバック順序

fragmentとactivityのコールバック順序をメモ。
※fragmentはActivityから読み込むレイアウトリソース内で定義。

●アプリ起動→HOMEキー押下
  fragment.onAttach
  ↓
  fragment.onCreate
  ↓
  fragment.onCreateView
  ↓
  activity.onCreate
  ↓
  fragment.onActivityCreated
  ↓
  activity.onStart
  ↓
  fragment.onStart
  ↓
  activity.onResume
  ↓
  fragment.onResume
  ↓
  fragment.onPause
  ↓
  activity.onPause
  ↓
  fragment.onStop
  ↓
  activity.onStop

●バックグラウンドから復帰時
  activity.onRestart
  ↓
  activity.onStart
  ↓
  fragment.onStart
  ↓
  activity.onResume
  ↓
  fragment.onResume

●アプリ起動→バックキー押下でアプリ終了
  fragment.onAttach
  ↓
  fragment.onCreate
  ↓
  fragment.onCreateView
  ↓
  activity.onCreate
  ↓
  fragment.onActivityCreated
  ↓
  activity.onStart
  ↓
  fragment.onStart
  ↓
  activity.onResume
  ↓
  fragment.onResume
  ↓
  fragment.onPause
  ↓
  activity.onPause
  ↓
  fragment.onStop
  ↓
  activity.onStop
  ↓
  fragment.onDestroyView
  ↓
  fragment.onDestroy
  ↓
  fragment.onDetach
  ↓
  activity.onDestroy


下記Developerサイトより


以上です。

2012/02/11

Android.mk:includeモジュール


Android.mkファイルのinclude記述で指定するモジュールの概要。

記載例)
 include $(CLEAR_VARS)

・$(CLEAR_VARS)
 LOCAL変数を初期化します。
 それぞれのモジュールはLOCAL変数の使用前に一度実行する必要があります。

・$(BUILD_PACKAGE)
 Androidアプリケーションを作成するためのpackage.mkファイルをインクルード

・$(BUILD_STATIC_JAVA_LIBRARY)
 静的Javaライブラリを作成するためのstatic_java_.ibrary.mkファイルをインクルード

・$(BUILD_JAVA_LIBRARY)
 Javaライブラリを作成するためのjava_library.mkファイルをインクルード

・$(BUILD_SHARED_LIBRARY)
 共有ライブラリ(.so)を作成するためのshared_library.mkファイルをインクルード

・$(BUILD_EXECUTABLE)
 実行ファイルを作成するためのexucutable.mkファイルをインクルード

・$(BUILD_DROIDDOC)
 javadocを作成するためのdroidoc.mkをインクルード

以上です。

2012/02/08

Android:画面密度によるリソースのスケーリング

解像度にあったサイズを割り出すには下記の比率を覚えておくと便利です。
3:4:6:8
左から ldpi, mdpi, hdpi, xhdpi となります。

mdpiで100pxの画像をhdpiでも同じ見た目に仕上げたい場合は
4:6=100:x
4x=600
として x=150px が求められます。

もう少し掘り下げてみます。

開発者はどのプラットフォームでも同じ動作をするようにアプリを作成する必要があります。
UIについても同様に「特定の端末でレイアウトが崩れる」といった問題を避けなければい
けません。
今回はマルチ画面対応を実現するために欠かせない「画面密度」「解像度」についてです。

・画面密度
画面の物理的な領域内におけるピクセル数。
通常dpi(ドット数/インチ dot per inch)と呼ばれます。
画面密度の低い(lowな)画面は、画面密度の高い(highな)画面と比べて、領域あたりのピ
クセル数が少なくなります。
Androidは、画面密度の程度をlow, medium, high, extra highの4つに分類しています。
それぞれの画面密度は下記です。
  • low dpi(ldpi):120dpi
  • mdpi(medium dpi):160dpi
  • hdpi(high dpi):240dpi
  • xhdpi(extra high dpi):320dpi

・解像度 
画面の物理ピクセルの総数。
PC等でもよく見る1280x768がこれに当たります。

マルチ画面をサポートするにあたって、画面密度と解像度は非常に重要です。
対象とするユーザが持つ端末の画面密度と解像度はバラバラです。
同じ画像をA端末で表示した場合とB端末で表示した場合とでは見た目に差がでる場合が
あります。

どの画面密度と解像度でも同じような見た目にしたい場合は、密度非依存pxを
使用します。密度非依存pxは単位dpとして知られます。
dpは画面密度に依存しない依存しないサイズです。
160dpi(mdpi)の画面では1pxに相当し、240dpi(hdpi)の画面では1.5pxに相当します。
dpでサイズを指定すると、画面密度が変化しても見た目は変化しないため、各画面密度
におけるスケーリングを透過的に扱うことができます。

●dp→px変換、px→dp変換
変換は簡単です。
Androidでは160dpiが画面密度の基準として扱われるため1dp=1pxとなります。
ldpi~xhdpiまでの画面密度比は3(ldpi):4(mdpi):6(hdpi):8(xhdpi)になります。
100pxは各画面密度では何dpになるのかはmdpiを基準に相似比で考えます。
  • 100dpはldpiで何pxか? 3:4=x:100となり75px
  • 100dpはmdpiで何pxか? 4:4=100:xなので100px
  • 100dpはhdpiで何pxか? 4:6=100:xとなり150px
  • 100dpはxhdpiで何pxか? 4:8=100:xとなり200px

px→dp変換も同じです。
mdpiで100pxの場合、xhdpiで同じ見た目のサイズとするには
4:8=100:xとなり200pxのサイズを指定する必要があります。

各画面密度におけるdpサイズとpxサイズの関係は下記の図がわかりやすいです。



●解像度による画像サイズ
画像ファイルのサイズは静的ですので、上記で述べたように同じ画像をhdpiとxhdpiで描
画すると見た目のサイズが変わってきます。
これも画面密度の違いにより、画像がスケーリングされるためです。

スケーリングは画像にとっては大敵で、画像が拡大されてしまうと粗く見えてしまいます。
そのため、画像は各解像度毎に用意する必要あります。
用意するべき画像のサイズの計算も3:4:6:8の相似比を利用します。

画像サイズ100px×100pxの見た目を画面密度毎に統一したい場合、
  • ldpiでは、3:4=x:100となり75px×75pxの画像をdrawable-ldpiに配置
  • mdpiでは、4:4=100:xとなり100px×100pxの画像をdrawable-mdpiに配置
  • hdpiでは、4:6=100:xとなり150px×150pxの画像をdrawable-hdpiに配置
  • xhdpiでは、4:8=100:xとなり200px×200pxの画像をdrawable-xhdpiに配置
となる。

マルチ画面のサポートとスケーリングについてはDeveloperサイトが参考になります。
http://developer.android.com/guide/practices/screens_support.html

以上です。

2012/02/07

Android.mk:ビルド用環境変数


Android標準で定義されている環境変数は、用途別にプレフィックスが決まっています。
下記一覧です。

●LOCAL_
 各アプリ(モジュール)で使用する変数。
 アプリ開発者が主に使用する変数となります。
 ※変数のクリアはinclude $(CLEAR_VARS)

●INTERNAL_
 ビルドシステム内で使用する変数。
 アプリ開発者はこの変数の使用を控える必要があります。

●PRIVATE_
 各モジュールへのコマンド実行時に使用する変数。
 アプリ開発者はこの変数の使用を控える必要があります。

●PRODUCT_
 端末全体に関する設定で使用する変数。
 アプリ開発者はこの変数の使用を控える必要があります。

●HOST_ / TARGET_
 ホストorターゲットビルドで使用する変数。
 アプリ開発者はこの変数の使用を控える必要があります。

●BUILD_
 Make時のテンプレート用変数。

以上です。