Kotlin×Data Bindingの問題点と解決法

はじめに

Kotlin Advent Calendar 2016の3日目の記事です。

KotlinとData Binding、両方とも便利ですよね。この記事ではこれらを組み合わせて使った場合に発生するいくつかの問題点と解決法を紹介したいと思います。ググるといくつか情報が出てきますが、断片的であったり、新しい解決法が登場していたりするため、情報を集約して解決法を列挙する形で書いています。

この記事では、

  1. Kotlinから自動生成されたクラスが参照できない
  2. BindingAdapterを使ったカスタムバインディングが使えない

という2つの問題を扱います。

1. Kotlinから自動生成されたクラスが参照できない

これはData Bindingに限った話ではなく、KotlinとAnnotation Processingを組み合わせた場合に発生します。

Data Bindingライブラリを使う場合、xmlで記述したレイアウトをlayoutタグで囲むことで、FooBindingのようなクラスが生成されます。しかし、このFooBindingをKotlinから参照すると正常にビルドが出来ず、以下のようなビルドエラーが表示されると思います。

Error:(7, 35) Unresolved reference: databinding
Error:(19, 27) Unresolved reference: ItemArticleBinding
Error:(40, 35) Unresolved reference: ItemArticleBinding

この記事で詳しくは説明しませんが、KotlinとJavaコンパイル順序が関係しています。以下の記事で詳しい解説があるので、興味のある方はご覧ください。

この問題を解決する方法として、2016年12月現在では2つの方法があります。

generateStubsを有効にする

build.gradleに以下の記述を追加することでKotlinとJavaコンパイル順序に関連した問題が解消されて、KotlinからFooBindingのような自動生成されたクラスが参照できるようになると思います。

kapt {
    generateStubs = true
}

kotlin-kaptを使う

もう1つの解決方法として、同じくbuild.gradleに以下の記述を追加することも解決できます。

apply plugin: 'kotlin-kapt'

しかし、公式ブログには以下のような記述があり、kotlin-kaptはまだ実験段階のようです。この記事を書くにあたって簡単なサンプルを実装した限りは正常に動作しましたが、まだ問題が残っている可能性もありそうです。

The new annotation processing still has known issues and may not be compatible with all annotation processors. You should enable it only if you’ve run into problems with the default kapt annotation processing implementation.

2. BindingAdapterを使ったカスタムバインディングが使えない

Data BindingではTextViewにStringをバインドするといった標準で用意されているバインディングに加えて、カスタムバインディングを実装することができます。よくある例としては、ImageViewにGlideやPicassoなどを使ってURLをバインディングするものだと思います。これをJavaで実装すると以下のようになります。

public class CustomBinder {
    @BindingAdapter("app:imageUrl")
    public static void imageUrl(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url).into(imageView);
    }
}

JavaではBindingAdapterアノテーションを付与したstaticなメソッドとしてカスタムバインディングを実装することができます。これをKotlinで実装すると以下のようになります。

object CustomBinder {
    @BindingAdapter("app:imageUrl")
    fun imageUrl(imageView: ImageView, url: String?) {
        Glide.with(imageView.context).load(url).into(imageView)
    }
}

これでいけるかと思いきや、実行すると以下のようなエラーが発生します。

java.lang.IllegalStateException: Required DataBindingComponent is null in class ItemArticleBinding. A BindingAdapter in com.yuyakaido.android.flow.presentation.binder.CustomBinder.Companion is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static.

一言で要約すると、BindingAdapterはstaticメソッドとして実装してね、という感じです。

この問題を解決する方法はいくつも考えられますが、大きくは2つあります。

JvmStaticアノテーションを付与する

Kotlinにはstaticというキーワードはありませんが、以下のようにJvmStaticアノテーションを付与すると、Javaから見たときにstaticとして見えるようになります。

object CustomBinder {
    @JvmStatic
    @BindingAdapter("app:imageUrl")
    fun imageUrl(imageView: ImageView, url: String?) {
        Glide.with(imageView.context).load(url).into(imageView)
    }
}

これでめでたくカスタムバインディングが動作するはずです。

拡張関数としてカスタムバインディングを実装する

もっとKotlinらしく書くとしたら、拡張関数としてカスタムバインディングを実装してもいいかもしれません。

@BindingAdapter("android:imageUrl")
fun ImageView.imageUrl(url: String?) {
    Glide.with(context).load(url).into(this)
}

上記のようにImageViewクラスにimageUrlという拡張関数を実装すると、以下のようにカスタムバインディングであることを意識せずにバインドできるようになります。

<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:imageUrl="@{article.thumbnail()}"/>

さいごに

この記事ではKotlinとData Bindingを組み合わせて使う場合の問題点と解決策を紹介しました。

また、この記事で紹介したコードは以下のリポジトリで公開しています。興味がある方は覗いてみてください。

それでは、良いKotlin×Data Bindingライフを!

繰り返しのある予定表示に対応したカレンダーを作った

はじめに

Android Advent Calendar 2015の10日目の記事です。

2014年のAndroid Advent Calendarは25日目を担当したので、今年は無難な日付に収まることにしました。今回は繰り返しのある予定表示に対応したカレンダーライブラリを作ったので、その紹介をさせていただきます。

リポジトリ

繰り返しのある予定について

予定の繰り返しをどのように表現するかはRFC2445にて定義されており、一般にRRuleと呼ばれています。RRuleは予定の繰り返し周期や繰り返し曜日などを表現することができ、本ライブラリはそのRRuleを用いた予定の繰り返しに対応しています。例えば、毎週月曜日という繰り返し条件は以下のRRuleで表現されます。

FREQ=WEEKLY;BYDAY=MO

もう少し具体的な例を考えてみます。 予定の開始日時が2015-09-05T10:00:00.000Zで、RRuleがFREQ=WEEKLYとし、予定の展開範囲を2015-09-01T00:00:00.000Zから2015-09-30T23:59:59.999Zとします。この場合は、以下の4つの予定に展開されます。

  • 2015-09-05T10:00:00.000Z
  • 2015-09-12T10:00:00.000Z
  • 2015-09-19T10:00:00.000Z
  • 2015-09-26T10:00:00.000Z

機能紹介

土日の色付け

以下のように土曜日は青、日曜日は赤で表示されます。

f:id:yuyakaido:20171210092914p:plain

予定の表示

予定がある日には以下のようにドットが表示されます。最大2つまで表示され、3つ以上ある場合には右下にプラスマークが付きます。

f:id:yuyakaido:20171210092938p:plain

予定の色分け

予定には任意の色を設定することが出来ます。デフォルトでは赤、オレンジ、黄、緑、ティファニーブルー、ライトブルー、青、紫、ピンク、藍のテーマカラーを用意してあります。詳しくはThemeクラスをご参照ください。

f:id:yuyakaido:20171210092949p:plain

テーマカラー

本ライブラリでは日付セルをタップするとその日付がテーマカラーにもとづいてハイライトされます。ライブラリの初期化時にテーマカラーを渡すことで切り替えが可能です。

f:id:yuyakaido:20171210093001p:plain

繰り返しのある予定の表示

本ライブラリではRRuleをもとに自動的に予定が展開されて表示されます。繰り返しの周期は毎日(FREQ=DAILY)毎週(FREQ=WEEKLY)毎月(FREQ=MONTHLY)毎年(FREQ=YEARLY)に対応しています。例えば、開始時間が2015-08-01T00:00:00.000Zで毎週繰り返し(FREQ=WEEKLY)というRRuleを持った予定と開始時間が2015-08-02T00:00:00.000Zで隔週繰り返し(FREQ=WEEKLY;INTERVAL=2)という2つの予定を設定すると以下のように表示されます。

f:id:yuyakaido:20171210093013p:plain

連日予定の帯表示

複数日にわたる予定の場合には、以下のように帯表示となります。予定が被っている場合には重なって表示されます。

f:id:yuyakaido:20171210093028p:plain

使い方紹介

カレンダーの表示

カレンダー本体はCouplesCalendarFragmentで実装されています。カレンダーを表示するだけであれば以下のようにFragmentTransactionCouplesCalendarFragmentを表示するだけでOKです。

CouplesCalendarFragment fragment = CouplesCalendarFragment.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.main_activity_fragment_container, fragment);
transaction.commit();

予定の表示

以下のように任意のオブジェクトにCouplesCalendarEventインターフェースを実装することでライブラリで表示可能な予定オブジェクトとなります。CouplesCalendarFragmentsetEvents(List<CouplesCalendarEvent> events)にこの予定オブジェクトのリストを渡すことで予定が表示されます。

public class SampleEvent implements CouplesCalendarEvent {

    private Date mStartAt;
    private Date mEndAt;
    private String mRRule;
    private int mEventColor;

    public void setStartAt(Date startAt) {
        mStartAt = startAt;
    }

    @Override
    public Date getStartAt() {
        return mStartAt;
    }

    public void setEndAt(Date endAt) {
        mEndAt = endAt;
    }

    @Override
    public Date getEndAt() {
        return mEndAt;
    }

    public void setRecurrenceRule(String recurrenceRule) {
        mRRule = recurrenceRule;
    }

    @Override
    public String getRecurrenceRule() {
        return mRRule;
    }

    public void setEventColor(int eventColor) {
        mEventColor = eventColor;
    }

    @Override
    public int getEventColor() {
        return mEventColor;
    }

}

CouplesCalendarEventインターフェースには以下の4つの情報を取得するためにメソッドが定義されています。

  • 予定の開始時間
  • 予定の終了時間
  • 予定の繰り返し条件
  • 予定の色

このうちでカレンダーに予定を表示するためには最低限以下の情報が必要になります。

  • 予定の開始時間
  • 予定の終了時間

予定の色を設定しなかった場合はデフォルトカラーが設定され、繰り返し条件(RRule)に何も設定しなかった場合は繰り返しのない予定として扱われます。

RxJavaを使ったAndroidアプリにおける非同期処理

はじめに

RxJava Advent Calendar 2015の2日目の記事です。

最近何かと話題なReactive ExtensionsのJVM向け実装であるRxJava(+RxAndroid)を使って、Androidアプリの非同期処理を実装する方法を紹介します。RxJavaとRxAndroidだけでAndroidの非同期処理を実装可能ですが、RxLifecycleというライブラリも同時に使うことで何かと面倒なActivity/Fragmentのライフサイクルのハンドリングを簡単に行うことが可能になります。今回はRxJava、RxAndroid、RxLifecycleの3つを使う方法についてまとめました。

RxJavaとRxAndroidとRxLifecycleの関係

RxJavaとRxAndroidはそれぞれReactive ExtensionsのJVM実装、Android向け実装です。RxLifecycleはAndroidアプリ開発においてライフサイクルを持ったオブジェクト(ActivityやFragment)を管理するためのものです。RxLifecycleを使わなくてもこの記事と同じことが実現出来ますが、ライフサイクルの管理を自前で行わなければならないため、特に理由がない場合は一緒に導入することをお勧めします。

それぞれのリポジトリはこちらです。

AsyncTaskを使った非同期処理

まず、はじめにAndroid標準のAsyncTaskを使った非同期処理を見てみます。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(20000);
                return null;
            }
        }.execute();
    }
}

このコードはかなり極端な例ですが、上記のコードを実行して画面遷移や画面回転等のActivity破棄/再生成を伴う操作をするとMainActivityがリークするという問題を抱えています。なぜかと言うと、Javaの匿名クラス(ここではAsyncTask)はアウタークラス(ここではMainActivity)への暗黙の参照を持っているからで、上記の例ではAsyncTaskは最低でも20秒は裏で動き続けるため、AsyncTaskから参照されているMainActivityがGCで回収されずにリークが発生してしまいます。

RxJavaを使った非同期処理

public class MainActivity extends RxAppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Album.getAllAlbumsObservable().compose(this.<List<Album>>bindToLifecycle())
            .subscribe(new Action1<List<Album>>() {
                @Override
                public void call(List<Album> albums) {
                    // Do something
                }
            });
    }
}

Album.getAllAlbumsObservable()はRxJavaにおけるObservableを返すと考えてください。

今回のポイントは以下の2点です。

  • RxAppCompatActivity を継承していること
  • compose(this.<List<Album>>bindToLifecycle()) を呼び出していること

まず、RxAppCompatActivityについてですが、これはRxLifecycleが提供しているクラスで、ライフサイクルを管理するための実装が含まれています。次に、RxAppCompatActivityのbindToLifecycle()を呼び出すことでライフサイクルを自動で管理してくれます。RxLifecycleを使わない場合はsubscribeしたObservableをどこかのタイミングで明示的にunsubscribeする必要がありますが、上記のように実装することで明示的なunsubscribeが必要なくなります。つまり、RxLifecycleを使えばunsubscribeをしてないことによるActivity等のリークを防ぐことが可能になります。

おわりに

非同期処理のためにRxJavaとRxAndroidを使うのは本来の使い方からは少しズレていますが、標準のAsyncTaskが色々と問題があることを考えるとこの使い方もアリなのかなと思います。

起動時に読み込まれるActivityを変更する際の注意点

AndroidアプリはAndroidManifest.xmlで起動Activityを設定しますが、既にリリース済みのアプリの起動Activityを変更すると、アップデートしたユーザーがアプリを起動できなくなる場合があります。若干ハマったので原因と対策をメモっておきます。

環境

再現方法

サードパーティ製のホームアプリ等を利用して、ホーム画面にショートカットを配置します。その状態で起動Activityの名前を変更して、上書きインストールします。そして、ホーム画面にあるショートカットをタップすると、「アプリケーションが見つかりません」というエラーが表示されて、アプリを実行することが出来なくなります。

再現確認の取れたホームアプリは以下の2つです。 といっても、これ以外にはNova Launcher 3.3しか試してないですが。。

  • Zeam Launcher 3.1.10
  • ADW.Launcher 1.3.3.9

原因

Activity名を直接指定するタイプ?のショートカットを作るホームアプリを使用してショートカットを作成している場合に、起動Activityの名前を変更してしまうとショートカットが無効になってしまう。

対策

起動Activityに変更を加える場合に、名前は変更しないようにしましょう。

Androidアプリを高速化しよう -ANR編-

はじめに

Android Advent Calendar 2014の30日目の記事です。

25日目では目に見えないボトルネックを探す手法を紹介しましたが、今回はAndroidアプリケーション開発を行う上で避けては通れないANRとその対処法について書きます。

環境

この記事で紹介するソースコードの動作確認は以下の環境で行いました。

また、この記事で紹介するコードは以下のリポジトリにあります https://github.com/yuyakaido/AndroidAdventCalendar2014

ANR (Application Not Responding)

ANRとはメインスレッド上で重たい処理を行った際に表示される警告で、ユーザーには「〜は応答していません」というダイアログとして表示されます。ここで言う重たい処理というのは具体的には以下の通りです。

  • メインスレッド上で5秒以上かかる処理を実行した
  • BroadcastReceiverが10秒以内で終了しない

実験

では、実際にANRを発生させてみます。

アプリに適当なボタンを配置してそれを押すと以下のコードが実行されるようにします。

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

アプリケーションを実行し、ボタンを押してしばらくすると以下のようにANRが発生します。

f:id:yuyakaido:20141230154001p:plain

対処法

上記のサンプルでは原因が明らかですが、実際はコードがもっと複雑で一見するとどこが原因となっているのかを特定するのが困難である場合がほとんどだと思います。ですが、実はANRは端末内の以下のファイルに逐一記録されています。

/data/anr/traces.txt

上記のファイルを確認することでANRの原因を特定することが可能となります。

adbを用いて端末内のtraces.txtをローカルマシンに持ってきます。

adb pull /data/anr/traces.txt ~/

ANRのログはこのファイルの先頭に逐一追加されていきます。 ローカルマシンに持ってきたファイルを開いてみると、以下のように先程の実験で発生させたANRが記録されていました。

----- pid 1219 at 2014-12-30 06:54:15 -----
Cmd line: com.yuyakaido.androidadventcalendar2014

DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

"main" prio=5 tid=1 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 obj=0xa62904b0 self=0xb9109510
  | sysTid=1219 nice=0 sched=0/0 cgrp=[fopen-error:2] handle=-1216953280
  | schedstat=( 81308922 43683273 389 ) utm=6 stm=1 core=0
  at java.lang.VMThread.sleep(Native Method)
  at java.lang.Thread.sleep(Thread.java:1031)
  at java.lang.Thread.sleep(Thread.java:1013)
  at com.yuyakaido.androidadventcalendar2014.MainActivity.performApplicationNotRespondingTest(MainActivity.java:71)
  at com.yuyakaido.androidadventcalendar2014.MainActivity.access$400(MainActivity.java:17)
  at com.yuyakaido.androidadventcalendar2014.MainActivity$5.onClick(MainActivity.java:63)
  at android.view.View.performClick(View.java:4084)
  at android.view.View$PerformClick.run(View.java:16966)
  at android.os.Handler.handleCallback(Handler.java:615)
  at android.os.Handler.dispatchMessage(Handler.java:92)
  at android.os.Looper.loop(Looper.java:137)
  at android.app.ActivityThread.main(ActivityThread.java:4745)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:511)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
  at dalvik.system.NativeStart.main(Native Method)

このようにtraces.txtを参照することでANRの原因を特定することが出来ました。原因が分かってしまえば後は煮るなり焼くなりしてしまいましょう。

さいごに

この年末はAndroidではなく、ひたすらSwiftでiOSアプリを開発しています。決してAndroid開発に嫌気がさしたわけではないですよ?

それではAndroid開発者の皆様、良いお年を!

Androidアプリを高速化しよう

はじめに

Android Advent Calendar 2014の25日目の記事です。

Androidアプリの開発をしていたのがきっかけで彼女が出来たyuyakaidoです。昨日のkaneshinthさんの記事の冒頭にあるように僕はマルチスレッド初心者なので常にシングルスレッドで動作しています。勿論クリスマスイブも。

今回は既存のアプリケーションのボトルネックを探すための手法を紹介していこうと思います。

お品書き

  • StrictMode
    • パフォーマンスに影響を及ぼすコードの検出
  • Traceview
    • パフォーマンス計測ツール
  • その他
    • Viewのネストについて
    • Viewの塗り潰しについて

環境

この記事で紹介するソースコードの動作確認は以下の環境で行いました。

StrictMode

StrictModeはGingerbreadから追加された機能で、アプリケーションのパフォーマンスに影響を及ぼす可能性のあるコードを検出してくれるものです。StrictModeはポリシーという形で何を検出するかを自由に設定することが出来ます。

使用方法

実際にコードを交えつつStrictModeの使用方法を解説していきます。

まず、StrictModeを有効にするためにカスタムApplicationクラスのonCreate()、もしくはアプリケーションのエントリーポイントとなるActivityのonCreate()に以下のコードを追加します。

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectAll()
                .penaltyLog()
                .build());

上記で設定したポリシーに違反するコードが検出された場合はlogcatにその旨が出力されます。新規で作成したプロジェクトに上記のコードを追加して実行した場合には何も出力されないのが確認出来るかと思います。

では、上記で設定したポリシーに違反するコードを書いてみましょう。適当な新規プロジェクトを作成して以下のコードを任意の箇所に記述してください。

String fileName = "sample.txt";
String fileContent = "Hello Android Advent Calendar!!";

File file = new File(Environment.getExternalStorageDirectory(), fileName);

try {
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    fileOutputStream.write(fileContent.getBytes());
    fileOutputStream.close();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

上記のポリシーに違反しているコードを追加してアプリを実行してください。するとlogcatに以下のようなログが表示されるはずです。

12-20 21:08:33.894    5064-5064/com.yuyakaido.androidadventcalendar2014 D/StrictMode﹕ StrictMode policy violation; ~duration=24 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2
            at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1089)
            (省略)
12-20 21:08:33.894    5064-5064/com.yuyakaido.androidadventcalendar2014 D/StrictMode﹕ StrictMode policy violation; ~duration=8 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1
            at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk(StrictMode.java:1063)
            (省略)

上記のログからディスク読み込み・書き込みに関するポリシー違反が検出されたことが分かります。 上記の2つのポリシー違反は以下の2行で発生しています。

  • 読み込み: FileOutputStream fileOutputStream = new FileOutputStream(file);
  • 書き込み: fileOutputStream.write(fileContent.getBytes());

上記はかなり単純な例でしたが、もう少し実践的な例を見てみましょう。 ActiveAndroidでデータをDBに保存して、その保存したデータをDBから取り出す場合を考えてみます。 ActiveAndroidに関しては、20日目のandrohiさんの記事をご参照ください。

まず、保存対象となるモデルクラスを用意します。

@Table(name = "Items")
public class Item extends Model {

    @Column(name = "Name")
    public String name;

}

次に先ほど作成した新規プロジェクトの任意の箇所に以下のコードを記述します。

Item item = new Item();
item.name = "NAME";
item.save();
item = new Select().from(Item.class).executeSingle();

上記のコードを追加してアプリケーションを実行すると、logcatに以下のようなログが出力されるはずです。

12-20 21:18:56.758    5350-5350/com.yuyakaido.androidadventcalendar2014 D/StrictMode﹕ StrictMode policy violation; ~duration=32 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1
            at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk(StrictMode.java:1063)
            at android.database.sqlite.SQLiteStatement.acquireAndLock(SQLiteStatement.java:226)
            at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:84)
            at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:2020)
            at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1960)
            at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:736)
            at android.database.sqlite.SQLiteStatement.releaseAndUnlock(SQLiteStatement.java:273)
            at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:115)
            at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1839)
            at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1712)
            at com.activeandroid.Model.save(Model.java:155)
            at com.yuyakaido.androidadventcalendar2014.MainActivity.ActiveAndroidTest(MainActivity.java:27)
            at com.yuyakaido.androidadventcalendar2014.MainActivity.onCreate(MainActivity.java:21)
            at android.app.Activity.performCreate(Activity.java:4470)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1053)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1934)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1995)
            at android.app.ActivityThread.access$600(ActivityThread.java:128)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4514)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
            at dalvik.system.NativeStart.main(Native Method)
12-20 21:18:56.758    5350-5350/com.yuyakaido.androidadventcalendar2014 D/StrictMode﹕ StrictMode policy violation; ~duration=10 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2
            at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1089)
            at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1678)
            at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1659)
            at com.activeandroid.util.SQLiteUtils.rawQuery(SQLiteUtils.java:106)
            at com.activeandroid.util.SQLiteUtils.rawQuerySingle(SQLiteUtils.java:122)
            at com.activeandroid.query.From.executeSingle(From.java:311)
            at com.yuyakaido.androidadventcalendar2014.MainActivity.ActiveAndroidTest(MainActivity.java:28)
            at com.yuyakaido.androidadventcalendar2014.MainActivity.onCreate(MainActivity.java:21)
            at android.app.Activity.performCreate(Activity.java:4470)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1053)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1934)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1995)
            at android.app.ActivityThread.access$600(ActivityThread.java:128)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4514)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
            at dalvik.system.NativeStart.main(Native Method)

上記のログ内のスタックトレースを見るとDBに書き込む時、DBから読み込む時にポリシー違反が発生していることが分かると思います。

Traceview

TraceviewはAndroid SDKに付属しているパフォーマンス計測のためのツールで、開発中のアプリケーション内で実行されるメソッドの実行時間の計測や時系列の表示、メソッドのコールスタックの可視化等高速化の役に立つであろう様々な計測を行うことが出来ます。 以前はソースコードに計測開始・計測終了を指定するメソッドを埋め込まなければならず、さらに計測結果のファイルが端末内に保存されるためadb pullでホストマシンに引っ張ってくる必要がある等かなり面倒でしたが、Android Studio 1.0.1ではIDE上で計測開始・計測終了時にボタンを押すだけで良くなり、簡単にパフォーマンス計測を行うことが可能になりました。

使用方法

今回は簡単のために画面上に配置されたボタンを押すと以下の処理が走るようにします。

try {
    Thread.sleep(3000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

以下のように画面上に適当なボタンを配置してそのボタンを押すと上記のコードが実行されるようにします。

f:id:yuyakaido:20141221001514p:plain

そして、Android Studio上で以下の画像で示す操作を行うことで簡単にパフォーマンス計測を行うことが出来ます。

f:id:yuyakaido:20141221001350p:plain

  • ①: Android DDMSを開く
  • ②: パフォーマンス計測を行うプロセスを選択(今回はcom.yuyakaido.androidadventcalendar2014を選択)
  • ③: 計測開始
  • ④: 計測終了

上記の操作を行うと以下に計測結果が表示されるはずです。

f:id:yuyakaido:20141221003359p:plain

上半分にはUIスレッド上の処理が時系列順に並んでいます。下半分には全メソッドの実行時間や総実行時間に対する各メソッドの実行時間の割合等が表示されています。

次にTraceviewの実践的な使い方を見ていきましょう。 Traceviewはメソッドのコールスタックを追うことが出来ます。

画面にボタンを追加して、そのボタンを押すと以下の処理が走るようにします。

ActiveAndroid.beginTransaction();
try {
    for (int i = 0; i < 100; i++) {
        Item item = new Item();
        item.name = "NAME";
        item.save();
    }
    ActiveAndroid.setTransactionSuccessful();
} finally {
    ActiveAndroid.endTransaction();
}

上記のコードを追加した上でパフォーマンス計測を行った結果を以下に示します。

f:id:yuyakaido:20141221011747p:plain

処理が開始された時点を矢印で示しています。矢印の少し下を見ると上記の処理が書かれているMainActivity.performActiveAndroidTransaction()が実行されていることが分かるかと思います。そして、このメソッド内ではsaveメソッドが100回呼ばれているので、MainActivity.performActiveAndroidTransactionの下にはModel.save()が並んでいます。

このようにTraceviewを使えばメソッドのコールスタックを辿ることが出来ます。このコールスタックを下からみていき、自分のプロジェクトパッケージに行き当たればその部分が根本原因であると特定することが出来ます。

その他

  • Viewのネストについて

15日目でkonifarさんも触れていますが、Viewのネストはパフォーマンスを低下させる一因になります。一般にLinearLayoutよりもRelativeLayoutを使った方がViewのネストが浅くなります。とはいえ、既存のものを書き換えるのはコスパが悪いので、そこまで神経質に全てを書き換える必要はないと思います。新規で作るレイアウトに関してはネストの深さを意識してみると良いのではないでしょうか。

  • Viewの塗り潰しについて

こちらも同じ方がここで言及しているように無駄にViewの塗りつぶしをするのはパフォーマンスを低下させる一因になります。

おわりに

この記事で紹介したソースコードは以下のリポジトリにアップロードしています。

https://github.com/yuyakaido/AndroidAdventCalendar2014

x86・x64・IA-32・IA-64

x86IA-32って何が違うの?x64ってx86を64ビットに拡張したものだから、x64 = IA-64ということ?

OSをかじったりしていた割にこの辺が曖昧だったので調べてみた。といっても概要だけですが。

x86

一言で言えば、インテルが開発した8086とその後継CPUの命令セットアーキテクチャを指す言葉です。

インテルは1978年に8086、その後細かい派生を無視すると、80186、80286、8038680486という順番でCPUを開発しました。インテルが開発したCPUの名前は連番になっており、連番になっている部分をxで置き換えることで「x86」という名称が生まれました。

ちなみに、8086と80186と80286は16ビットCPU、8038680486は32ビットCPUです。そして、これらのCPUは良い意味でも悪い意味でも8086からの互換性を保っています。

IA-32IA-64

タイトルと順番が前後しますが、おそらくこちらを先に説明した方が分かりやすいため、IA-32IA-64を先に説明します。

インテルは1978年に8086を発売して以来、市場の強い要望により8086と互換性を持つCPUを発売してきたわけですが、そもそも8086は拡張性を意識したアーキテクチャではなく、インテルは苦し紛れの拡張を続けてきました。そこでインテルは8086から脈々と続いてきたx86の系譜を捨て、8086と互換性を持たない次世代の64ビット命令セットアーキテクチャとして「IA-64」を開発しました。IA-64を発表した際に今までの32ビット命令セットアーキテクチャ8038680486が採用している命令セットアーキテクチャ)に「IA-32」という名称を付けました。

インテルとしては当然IA-64を広めたいと考えていたようですが、市場はx86上で築き上げてきた莫大な資産を捨ててまでIA-64に移行したいとは思わなかったようで、現状としてIA-64はあまり普及していないようです。

x64

x64はその名前からも分かるように、x86を64ビット拡張したもので当然ながら8086との互換性を持ち合わせています。x86-64と書いてある場合もありますが、同じものを指します。

x64の開発の経緯を少し補足します。

x86は広く普及し、半導体技術とアーキテクチャの革新と共に性能向上を続けてきましたが、インテルIA-32の性能向上によって自社開発のIA-64との競合を懸念し、あえてx86の64ビット化を行いませんでした。しかし、市場としてはIA-32との互換性を保ちつつそれを64ビット化して欲しいわけです。そこでインテルではなくAMDx86を64ビット化し、AMD64として発表しました。その後にインテルAMD64を自社でもIntel 64として採用しました。AMD 64とIntel 64は微妙に異なる点はありますが、動作的にはほとんど同じものと考えてください。

まとめると、AMD 64とIntel 64等の各社が出している互換64ビット命令セットアーキテクチャを「x64」と言います。

一応確認ですが、x64とIA-64はまったく別物で互換性はありません。

2014年現在で一般用途PCのCPUにおいて一番採用されていると思われるCore iシリーズにはx86、x64の両命令セットが搭載されています。

余談

Ubuntuの64ビットイメージを落とすと、末尾にamd64と書いてある意味がやっと分かりました。

参考サイト