2014/10/16

Android:知っておきたい標準API part.1

Intro

Android標準APIの中で知っておくと便利なものがいくつかある.
3rdParty製ライブラリを使うのもいいけれど, 標準で事足りるならそうしたい.
紹介するAPIについてはSupportLibraryを含んでおり, Javaパッケージは対象外.

Useful

LocalBroadcastManager

android.support.v4.content.LocalBroadcastManager
URL: http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html

Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent):

  • You know that the data you are broadcasting won’t leave your app, so don’t need to worry about leaking private data.
  • It is not possible for other applications to send these broadcasts to your app, so you don’t need to worry about having security holes they can exploit.
  • It is more efficient than sending a global broadcast through the system.

自プロセス内に閉じた, ローカルなBroadcastIntentを投げたい場合に使える. LocalBroadcastManagerを使うことの利点は次の通り.

  • 自プロセスに閉じたBroadcastIntentなのでPrivateデータを送信しても安心
  • 他のアプリケーションがBroadcastIntentを偽ることができないのでエクスプロイト対策にもなる
  • SystemにBroadcastIntentを投げさせるよりも効率的である

StickyなBroadcastIntentが投げられない等, いくつかの制約はある.
(LocalBroadcastManager自体のコードは極めてシンプルなので自分で拡張したほうが早いかも)

Usage

// Receiver登録
LocalBroadcastManager.getInstance(this).registerReceiver(receiver,
      new IntentFilter("custom-action-name"));

// Receiver解除
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);

// LocalBroadcast送信
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

LruCache

android.support.v4.util.LruCache<K, V>
URL: http://developer.android.com/reference/android/support/v4/util/LruCache.html

Static library version of LruCache. Used to write apps that run on API levels prior to 12. When running on API level 12 or above, this implementation is still used; it does not try to switch to the framework’s implementation. See the framework SDK documentation for a class overview.

LruCache本体がAPI Level12から追加された. API Level12より過去のバージョンでLruCacheを使用したい場合にこのAPIを使用する. 本クラスを使用する場合, API Level12以降でもこちらが参照される(FWでスイッチされるというようなことはない)

Usage

TechBoosterで詳しく紹介されています. TechBooster - LruCacheを使ってメモリキャッシュを実装する

メモリを圧迫しやすいBlobなデータをいい感じ(Least Recently Used)に管理してくれる.
参照されなくなったデータからメモリ解放したい場合に適している.

TimingLogger

android.util.TimingLogger
Added in API level 1
URL: http://developer.android.com/reference/android/util/TimingLogger.html

A utility class to help log timings splits throughout a method call. Typical usage is:

A区間からB区間までのタイミング(処理時間)を計測するのを支援するクラス.

TimingLogger timings = new TimingLogger(TAG, "methodA");
// ... do some work A ...
timings.addSplit("work A");
// ... do some work B ...
timings.addSplit("work B");
// ... do some work C ...
timings.addSplit("work C");
timings.dumpToLog();

The dumpToLog call would add the following to the log:

このコードを実行すると次のようなログを吐く.

D/TAG     ( 3459): methodA: begin     
D/TAG     ( 3459): methodA:      9 ms, work A     
D/TAG     ( 3459): methodA:      1 ms, work B     
D/TAG     ( 3459): methodA:      6 ms, work C     
D/TAG     ( 3459): methodA: end, 16 ms

参考:Android:TimingLoggerで処理間隔をログ出力する

AtomicFile

android.support.v4.util.AtomicFile
URL: http://developer.android.com/reference/android/support/v4/util/AtomicFile.html

Static library support version of the framework’s AtomicFile, a helper class for performing atomic operations on a file by creating a backup file until a write has successfully completed.

ファイル書き込み前にバックアップファイルを作成し, 書き込みに失敗した場合はロールバックすることで, ファイル操作をアトミックなものにするヘルパクラス.

Atomic file guarantees file integrity by ensuring that a file has been completely written and sync’d to disk before removing its backup. As long as the backup file exists, the original file is considered to be invalid (left over from a previous attempt to write the file).

AtomicFileは整合性を保証するためにファイルの書き込みが完了し, ディスクと同期するまでバックアップファイルを削除しない. バックアップファイルが残っている限り, オリジナルファイルは無効と判断する(前回のファイル書き込み処理から残っている状態)

  • オリジナルファイル名:作業対象ファイル
  • バックアップファイル名:オリジナルのバックアップ(オリジナルファイル名 + “.bak”で管理)

Atomic file does not confer any file locking semantics. Do not use this class when the file may be accessed or modified concurrently by multiple threads or processes. The caller is responsible for ensuring appropriate mutual exclusion invariants whenever it accesses the file.

AtomicFileは単純なR/Wを想定している. ロックセマンティクスを提供していないため, マルチにアクセスされる可能性がある場合は自前でアクセス制御すること. また, AtomicFileによる操作中に(バックアップファイルも含めて)割り込まないように注意すること.

Usage

AtomicFile f = new AtomicFile(new File(...));
FileOutputStream o = null;
try {
    o = file.startWrite();  // バックアップファイル作成
    // TODO: ファイル出力処理
    f.finishWrite(o);       // コミット
} catch (IOException e) {
    f.failWrite(o);         // ロールバック
}

IntentService

android.app.IntentService
Added in API level 3
URL: http://developer.android.com/reference/android/app/IntentService.html

IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

IntentServiceはIntentによるリクエストを要求に応じて非同期処理するServiceクラス. クライアントはstartService(Intent)でリクエストを投げる. IntentServiceはworker threadでこれを受けて, 処理が全て完了すると自身で停止する.

This “work queue processor” pattern is commonly used to offload tasks from an application’s main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

work queue processorパターンはアプリケーションのメインスレッドからタスクをオフロードするために使用されるが, これを簡略化したのがIntentServiceである. IntentServiceを使用する場合はonHandleIntent(Intent)を実装する.

All requests are handled on a single worker thread – they may take as long as necessary (and will not block the application’s main loop), but only one request will be processed at a time.

全てのリクエストは single worker threadで処理される. 処理には時間がかかるかもしれない(が, アプリケーションのメインスレッドはブロックされない), しかしリクエストは1つずつ処理される.

Developer Guides
For a detailed discussion about how to create services, read the Services developer guide.

public class MyIntentService extends IntentService{
    public MyIntentService(){
        super("MyIntentService");  // 文字列はworker threadの名前に使われる
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "Intentの処理内容を記載");
    }
}

Messenger

android.os.Messenger
Added in API level 1
URL: http://developer.android.com/reference/android/os/Messenger.html

プロセス間の双方向通信をサポートするService形態にはAIDLとMessengerが存在する.
AIDLとMessengerの主な違いは下記.

AIDL

  • サービスホスト(以降ホスト)は.aidlを作成し, サービスクライアント(以降クライアント)はこれを取り込む必要がある.
  • クライアントからのリクエストはBinderThread経由で, 全てのリクエストが非同期通信となる.
  • ホストは必要に応じてクライアントからのリクエストをスレッドセーフに扱う必要がある.
  • .aidlに変更があった場合, クライアントも合わせてこれを更新する必要がある.

Messenger

  • ホストとクライアント間の通信はHandler-Messageの仕組みで実現されているため.aidlが不要.
  • クライアントからのリクエストはHandler経由で通知され, 全てのリクエストが同期通信となる.
  • ホストとクライアント間のメッセージはMessageオブジェクトで表現される.

Messengerはクライアントからのリクエストをシングルスレッドで処理するため, 基本的にスレッドセーフで動作する. また通信メッセージがMessageオブジェクトで表現されるため.aidlファイルも不要.
外部公開APIに変更があっても, .aidlファイルをクライアントに配布する必要がないため, Service側でMessageの規約(プロトコル)を更新するだけで済む(互換性に注意)

AIDLでは外部公開APIはメソッドとして定義されるが, Messengerでは”Messageオブジェクトの組み立て方”でこれを表現する必要がある.
クライアントはMessengerと通信するためにメッセージの組み立て方を理解する必要がある. (アプリローカルで使用されるHandler-Messageのそれと同じ)

参考: Android:Messengerの基本

// クライアントサイド
public class MessengerClient extends Activity {
    private Messenger mServiceMessenger;
    private Messenger mSelfMessenger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mSelfMessenger = new Messenger(new ResponseHandler());
        mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mServiceMessenger = new Messenger(service);
            }
        };
        bindService(new Intent("yuki.test.messenger.START"),
                mConnection, Service.BIND_AUTO_CREATE);

        ((Button)findViewById(R.id.button1)).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mServiceMessenger != null) {
                            try {
                                Message msg = Message.obtain(null, 1);
                                msg.replyTo = mSelfMessenger;
                                mServiceMessenger.send(msg);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
    }

    private class ResponseHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.e("yuki", "handle response=" + msg);
        }
    }
}

// ホストサイド
public class MessengerService extends Service {
    private Messenger mServiceMessenger;

    @Override
    public void onCreate() {
        super.onCreate();
        mServiceMessenger = new Messenger(new RequestHandler());
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mServiceMessenger.getBinder();
    }

    private class RequestHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.e("yuki", "handle request=" + msg);

            if (msg.replyTo != null) {
                try {
                    msg.replyTo.send(Message.obtain()); // send response.
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

DatabaseUtils

android.database.DatabaseUtils
Added in API level 1
URL: http://developer.android.com/reference/android/database/DatabaseUtils.html

データベースとCursorに関するユーティリティメソッドを提供する.

DatabaseUtils.dumpCursor

Cursorの内容をダンプするユーティリティ. dumpCursor後にCursorのpositionは変化しない.

Cursor c = this.getContentResolver().query(
        Browser.BOOKMARKS_URI, new String[]{"_id", "title"},
        null, null, null);
DatabaseUtils.dumpCursor(c);
結果:
I/System.out(2944): >>>>> Dumping cursor 
I/System.out(2944): 0 {
I/System.out(2944):    _id=1dumpCurrentRow (Cursor cursor)
I/System.out(2944):    title=Bookmarks
I/System.out(2944): }
I/System.out(2944): 1 {
I/System.out(2944):    _id=2
I/System.out(2944):    title=Android Developers
I/System.out(2944): }
I/System.out(2944): 2 {
I/System.out(2944):    _id=3
I/System.out(2944):    title=Google
I/System.out(2944): }
I/System.out(2944): <<<<<

DatabaseUtils.concatenateWhere

WHERE句を構築するユーティリティ. 半角スペースや括弧の実装負担を減らせる.

DatabaseUtils.concatenateWhere("criteria1", "criteria2");
結果:
(criteria1) AND (criteria2)

引数1と2は, それぞれ括弧で囲まれた上でAND条件結合される. 片方がnullあるいは空白である場合はもう片方の引数がそのまま返却される.

DatabaseUtils.queryNumEntries

指定されたテーブルに含まれる行数を調べる.

DatabaseUtils.queryNumEntries(db, "test");
結果:
 4 // testテーブルには4レコード存在しているとする

内部ではSELECT count(*)でカラムをカウントしている.

DatabaseUtils.sqlEscapeString

手動でクエリを構築する際にはエスケープ処理に注意する必要がある.
指定される文字列のエスケープ処理にはsqlEscapeStringメソッドが便利.

DatabaseUtils.sqlEscapeString("'");
結果:
 ''''

指定される文字列中にシングルクォートがあるとこれをエスケープする. エスケープ文字列はシングルクォートで囲まれる.

DatabaseUtils.getSqlStatementType

getSqlStatementTypeはSQL文字列のステートメント種別を返却する. 返却される種別は次の定数で定義されている.

  • STATEMENT_SELECT
  • STATEMENT_UPDATE
  • STATEMENT_ATTACH
  • STATEMENT_BEGIN
  • STATEMENT_COMMIT
  • STATEMENT_ABORT
  • STATEMENT_PRAGMA
  • STATEMENT_DDL
  • STATEMENT_UNPREPARED
  • STATEMENT_OTHER
DatabaseUtils.getSqlStatementType("SELECT * FROM bookmarks;");
DatabaseUtils.getSqlStatementType("PRAGMA user_version;");
DatabaseUtils.getSqlStatementType(";");
DatabaseUtils.getSqlStatementType("INSERT SELECT;");
結果:
 1(STATEMENT_SELECT)
 7(STATEMENT_PRAGMA)
 99(STATEMENT_OTHER)
 2(STATEMENT_UPDATE)

4番目の結果を見るとわかるように厳密な解析結果ではない. 単純な解析(頭3文字で判定)しかしてくれないので注意.

以上.