幸福なプログラマ

プログラマは幸福になれる。

非同期処理にはThreadを直接使わずにExecutorServiceを使用する

一個前の記事

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

みたいな感じでThreadクラスのコンストラクタRunnable実装したサブクラスを渡して非同期処理をしようと書いたけれど、以下の観点からおすすめできません。

  • 複数Threadを作成する際、スレッド作成時間がパフォーマンスに影響を与える可能性がある。
  • 作成したThreadがそのまま放置される。

そこで一度作成したスレッドを待機(プール)させ、必要に応じてスレッドを取り出してタスクを割り当て、タスクが完了したらスレッドを再度プールさせる(スレッドを再利用する)スレッドプールという仕組みを活用してパフォーマンスの向上を図ります。

スレッドプールはExecutorクラスのメソッドで作成できます。
これによりExecutorServiceオブジェクトが生成され、スレッドの実行を行います。

ExecutorService executorService = null;
try {
    executorService = Executors.newFixedThreadPool(2);
    executorService.execute(new OneRunnable());
    executorService.execute(new TwoRunnable());
} finally {
    executorService.shutdown();
}

Executors#newFixedThreadPool(int) でスレッドプールを作成します。
executeにRunnableオブジェクトを渡すと自動的にタスクをスレッド意に割り当てて実行されます。
Executorsは主に以下のようなメソッドを持ちます。

メソッド 機能
newFixedThreadPool 引数で指定した数のスレッドを常時保持するスレッドプールを作成。
タスクは空いているスレッドに割り当てられます。
newCachedThreadPool タスクを割り当てる度に新しいスレッドを生成し、そのスレッドを使いまわします。
一定時間使用されなかったスレッドは消滅します。
newScheduledThreadPool タスクを一定時間ごとに実行するスレッドを持つスレッドプールを作成。
newSingleThreadExecutor 1つのスレッドを使いまわすスレッドプールを作成。
割り当てられたタスクは順番に実行されます。

Threadクラスの継承ではなく、Runnableインタフェースを実装したクラスに処理を委譲する

javaで非同期処理を実現する際、以下のようにThreadクラスを継承したサブクラスを作成し

public class MyThread extends Thread {
    @Override
    public void run() {
        // 非同期処理
    }
}

下記のように呼び出すと思います。

MyThread thread = new MyThread();
thread.start();

これはそのまま以下のようにRunnableインタフェースを実装したクラスで置き換えることが可能です。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 非同期処理
    }
}
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

Thread#run()のデフォルト実装がThreadクラスのコンストラクタに渡されたRunnableを実装したクラスのrunメソッドを実行するようになっているため上記のように置き換えが可能となります。

Runnableインタフェースの実装を使うべき理由として以下が上げられます。

  • 継承よりも委譲を使うことでクラス間の独立性が高まる。
  • Threadクラスはスレッド生成、Runnable実装は実行する処理を記述するものとしてそれぞれの責務を分離できる。

AndroidでSQLiteを使用する

データ定義


AndroidでSQLLiteを扱うにはSQLiteDatabaseクラスを使用します。
このSQLiteDatabaseインスタンス生成を簡略化してくれるのがSQLiteOpenHelperです。
SQLiteOpenHelperは抽象クラスであり、DBオープン時、テーブルのバージョンアップ時の実装を追加することができます。

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    public MySQLiteOpenHelper(Context context) {
        // 任意のデータベースファイル名と、バージョンを指定する
        super(context, "pastingcontroller.db", null, 1);
    }

    /**
     * このデータベースを初めて使用する時に実行される処理
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        // テーブルの作成、初期データの投入等を行う。
    }

    /**
     * アプリケーションの更新などによって、データベースのバージョンが上がった場合に実行される処理
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // データの退避、テーブルの再構成等を行う。
    }
}

コンストラクタで任意の名称のデータベースを作成します。
onCreateはこのヘルパクラスのインスタンスを使ってデータベースをオープンする際、コンストラクタで指定されたデータベースが無かった場合に実行されるメソッドです。
テーブルの作成や初期データの投入等を行います。
onUpgradeはこのヘルパクラスのインスタンスを使ってデータベースをオープンする際、バージョンがあがっていた場合に実行されるメソッドです。(ここで言うバージョンはコンストラクタの第四引数のことです。)
テーブルの更新や、それに伴うデータの退避・再投入を行います。

たとえば、onCreateでテーブルを作成する場合、以下のような実装になります。

@Override
public void onCreate(SQLiteDatabase db) {
    db.execSQL(
        "create table sample_table ("
        + "_id  integer primary key autoincrement not null, "
        + "text_column text not null, "
        + "num_column integer not null)" );
}

データ制御


データ操作を行うために、SQLiteDatabaseクラスのインスタンスを生成します。
作成したSQLiteOpenHelperの実装を用いて以下のようにインスタンスを生成します。

SQLiteOpenHelper sqliteOpenHelper = new MySQLiteOpenHelper(getApplicationContext());
SQLiteDatabase db = sqliteOpenHelper.getWritableDatabase();
try {
    // データ操作
} finally {
    db.close();
}

getWritableDatabaseは読み書き両用のインスタンスを生成するのに用います。
読み取り専用のインスタンスが必要な場合はgetReadableDatabaseを使用します。

トランザクション

SQLiteでも意図的に記述することでトランザクション制御が可能です。
ループで大量のInsert等を投げる場合はトランザクション制御を行ったほうが処理が早くなるようです。
参考:SQLiteでINSERTが激しく遅い件

// トランザクション開始
db.beginTransaction();
try {
    /** Insert等のDB操作 */
    
    db.setTransactionSuccessful();
} catch(Exception e) {
    e.printStackTrace();
}
finally {
    // トランザクション終了
    db.endTransaction();
}

beginTransactionトランザクションを開始し、endTransactionトランザクションを終了します。
トランザクション終了前にsetTransactionSuccessfulが呼ばれていればcommitされ、世ベレ帝無ければrollbackされます。なので、処理に成功した後は必ずsetTransactionSuccessfulを呼ぶ必要があります。

データ操作


登録

テーブルへのデータの登録(insert)は以下のように行います。

ContentValues values = new ContentValues();
values.put("text_column", "text");
values.put("num_column", 111);
try {
    db.insert("sample_table", null, values);
} finally {
    db.close();
}

登録に用いるデータははContentValuesオブジェクトに格納します。
insertの第一引数にテーブル名、第三引数に登録したいデータを渡します。
第二引数は少々複雑です。
全ての項目がnull許可のテーブルに対して、空のContentValuesオブジェクトを渡した場合、発行されるSQLは以下のようになると思われます。

INSERT INTO foo;

しかし、SQLiteでは以下のように最低でも1つのカラム名を明記しなければ登録することができません。

INSERT INTO foo (somecol) VALUES (NULL);

上記の仕様を満たすために、第二引数には第三引数が空のオブジェクトだった場合にinsert文に加えるカラム名を渡すことになります。
参考:Insert method of SQLiteDatabase

更新

データの更新(update)は以下のように行います。

ContentValues values = new ContentValues();
values.put("num_column", 222);

String whereClause = "text_column = ?";
String whereArgs[] = new String[1];
whereArgs[0] = "text";

try {
    db.update("sample_table", values, whereClause, whereArgs);
} finally {
    db.close();
}

第一引数がテーブル名、第二引数が更新対象のContentValuesオブジェクト、第三引数がwhere句に指定する条件、第四引数が条件の"?"にバインドする値になります。
第四引数のwhereArgsは文字列の配列です。数値型を条件にしたい場合は

String whereClause = "num_column = 111";

みたいな感じで直接条件文に記述することになります。
参考: Androidが再生産しているSQLインジェクション?

削除

データの削除(delete)は以下のようになります。

String whereClause = "text_column = ?";
String whereArgs[] = new String[1];
whereArgs[0] = "text";

try {
    db.delete("sample_table", whereClause, whereArgs);
} finally {
    db.close();
}

読み取り

データの読み取り(select)は以下のようになります。

String[] columns = {"text_column ", "num_column"};
String selection = "text_column = ?";
String[] selectionArgs = {"text"};
String groupBy = null;
String having = null;
String orderBy = null;
try {
    Cursor cursor = db.query("sample_table", columns, selection, selectionArgs, groupBy, having, orderBy);
    StringBuilder text = new StringBuilder();
    while (cursor.moveToNext()){
        String textColumn = cursor.getString(0);
        int numColumn = cursor.getInt(1);
    }
} finally {
    db.close();
}

第一引数にテーブル名、第二引数に検索対象のカラム名(nullを指定すると全てが対象になります)、第三引数に検索条件、第四引数に検索条件へのバインド値を指定します。
必要に応じてgroup by句、having句、order by句を指定します。

Android4.1以下でRelativeLayout.LayoutParamsの相対位置指定を動的に削除したい

RelativeLayoutの相対位置をプログラム中で動的に削除する際RelativeLayout.LayoutParams#removeRuleを使いたいのだけれどAPIレベルが16以下の場合実装されていないため使うことができない。(NoSuchMethodErrorが発生する。)
RelativeLayout.LayoutParams | Android Developers

以下のようにaddRuleの第二引数に0を渡せば削除することが可能です。

RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) findViewById(id).getLayoutParams();
params.addRule(RelativeLayout.BELOW, 0);
findViewById(id).setLayoutParams(params);

Androidで振動の実装

AndroidManifestに設定を追加して

<uses-permission android:name="android.permission.VIBRATE"/>

以下の1行を書くだけ。

((Vibrator)getSystemService(VIBRATOR_SERVICE)).vibrate(100);

vibrateの引数にミリ秒を渡すことでその時間だけ振動を続けます。

これで震えなかったら当該端末がVibratorに対応していないということ。(Nexus7は対応していなかった。)
対応しているかどうかの判定はVibrator#hasVibrator()で行えます。

ノンデザイナーズ・デザインブック 4つの基本原則

デザインの4つの原則

上手なデザインに共通して見つけることができる4つの基本原則の概要を示す。

1.近接

 関連する項目は近づけてグループ化する。いくつかの項目が互いに近接しているとき、それらは1つの視覚的ユニットとして認識される。
 これにより情報が組織化され、読者に混乱の少ない明快な構造を提供できる。

2.整列

 あらゆる要素にほかの要素との視覚的な関連を持たせる必要がある。
 これにより明快、洗練、新鮮、という印象が生まれる。

3.反復

 色、形、質感、位置関係、線の太さ、書体、サイズ、画像などの視覚的要素を作品全体を通して繰り返す。
 これにより組織化を促進し、一体感を出す。

4.コントラスト

 ページ上の要素が類似するのを避ける。
 もし要素(書体、色、サイズ、線の太さ、形、空きなど)が同一でない場合、はっきり違わせる。
 これにより視覚を引きつけ、読者を読む気にさせる。