woshidan's loose leaf

ぼんやり勉強しています

Realmのmigration中に「java.lang.IllegalArgumentException: Field already exists in 'Table': column」的なエラーで意味も無くはまった話

Realmで下記のようなmigrationのコードを書いていまして、よし、久しぶりにmigration前のバージョンからの移行処理を書こうか、と思ったところ、

public class Migration implements RealmMigration {
    @Override
    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
        RealmSchema schema = realm.getSchema();

        if (oldVersion == 0) {
            schema.create("Book")
                    .addField("name", String.class, FieldAttribute.REQUIRED) /* .addField("ownerLogin", String.class,  FieldAttribute.PRIMARY_KEY) */
                    .addField("price", int.class);

            oldVersion++;
        }
    }
}

Caused by: java.lang.IllegalArgumentException: Field already exists in 'Book': priceみたいなエラーが出て一向に進まないわけです。

うわー、何かふんじゃった? でもこのエラーしらべてもしらべても出て来ないんだけどって汗がね、めっちゃ出てくる。

というか、前のバージョンから結構でかい変更があるわけで、バージョンを切り替える1ビルド事に3分はかかりますので、一回試すだけで10分弱かかるんですね。

超あせるわけですよ。

もうね、鳥肌立ってくるというか。まあ、帰って熱はかったらがっつり風邪引いてたんですけど。

それで、復活してから見直してたらね、実際のコードは下記のような感じだったわけです。

public class Migration implements RealmMigration {
    @Override
    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
        RealmSchema schema = realm.getSchema();

        if (oldVersion == 0) {
            schema.create("Book")
                    .addField("name", String.class, FieldAttribute.REQUIRED) 
                    .addField("price", int.class)
                    .addField("price", int.class); // 2つめ!!!
            oldVersion++;
        }
    }
}

なるほど!! 来週も順調に仕事が進むね、やったねうぉしだん。

元気出していきましょう!!

なぜ、久しぶりに移行処理をする前に気づかなかったかといいますと、移行後の状態のコードでは、 Realmのインスタンスを下記のように「新しいSchemeバージョン」を指定して取得するようにしておりまして、 この場合、先ほどの oldVersion == 0 の処理は通ってないみたいなんですね。

RealmConfiguration newConfig = new RealmConfiguration.Builder(getApplicationContext())
                .schemaVersion(1)
                .build();

realm = Realm.getInstance(newConfig);

それで、ここからが悪いんですが、どうせなら一度のmigrationで追加した方が安心かな、と Realm migration怖い症候群により、oldVersion == 0 のところに後からさらに必要になった fieldの追加処理を書いていた、と。

でも、schemeバージョンが新しく指定されており、現行のrealmファイルと齟齬が無ければ、 migrationを走らせるように言ってくる例外は発生しないんですね。

要するに、移行後のschemeバージョンを指定して開発している間は、oldVersion == 0 のところのコードが走っていなかったので、追加分の間違いに気づくのが遅れたと。

なんか、少しだけ整理しきれていないのですが、こんな感じの気がします。

書いたタイミングから例外が投げられるタイミングが遠かったため、ただのタイポの類に何が起こったか、と思いました。怖っ!

電池消費量の最適化を読んだ

http://developer.android.com/intl/ja/training/monitoring-device-state/index.html

を読んだ雑な感想

開発するアプリの中でホスト端末の状態を監視し、それに基づいて機能や動作を変更することができるようになります

  • 接続が失われたときはバックグラウンド サービスの更新を停止
  • 電池残量が低下したときは更新の頻度を下げる

電池残量と充電状態の監視

http://developer.android.com/intl/ja/training/monitoring-device-state/battery-monitoring.html#DetermineChargeState

  • 充電時は常に最大出力でok
  • 電池駆動 => 更新頻度を下げる

現在の充電状態を特定する

  • 現在の充電状態を特定
    • var ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = context.registerReceiver(null, ifilter)
    • バッテリー状態のIntentはsticky IntentなのでBroadcastReceiverいらない
    • int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1)...
      • 充電中か等も調べられる

充電状態の変化を監視する

  • 下記のactionを受け付けるintent_filterを持つreceiverの宣言をManifestに書きます
    • action android:name="android.intent.action.ACTION_POWER_CONNECTED", action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"
  • 上記のbroascastReceiverでIntentから充電状態を抽出します

現在の電池残量を特定する / 電池残量の大きな変化を監視する

  • 現在の電池残量を特定する => できるよ、ただ継続的に監視すると監視する方が電池食うよ
  • 電池残量の大きな変化のみを監視できるよ
    • LOWとかOKAYとかついているactionだよ
  • 端末の充電という動作が開始するのは、端末がホルダーにセットされるのと同時
    • 現在のホルダーの状態を特定し、端末のホルダー装着状態の変化を監視する

ホルダーの装着状態とタイプの特定と監視

  • 車載用や家庭用のホルダー
    • デジタル/アナログ
    • だが主に電力供給されるよね
      • ホルダーの種類を見て、アプリが利用されているタイミングと合わせて動作状態を設定できたらいいね
        • cf. スポーツセンター アプリなら、卓上ホルダー装着時には更新頻度を上げ、カー ホルダー装着時には更新を完全に停止する
    • ホルダー装着状態もsticky Intent
  • ホルダーのタイプは4つで
    • カー/卓上/ローエンド卓上/ハイエンド卓上

接続状態の特定と監視

  • インターネット接続の有無
    • ConnectivityManager
  • インターネット接続のタイプを特定する
    • モバイル データ接続は重い傾向があるので、Wifiを松等
  • 接続状態の変化を監視する => "android.net.conn.CONNECTIVITY_CHANGE" のintent filter

オンデマンドでのブロードキャスト レシーバ操作

  • 梅: 監視対象の状態ごとにBroadcastReceiverを作成する
  • 竹: 実行時にブロードキャスト レシーバをオンまたはオフにするというもの
    • => PackageManager

感想

  • 電池残量と充電状態の監視は実際ユーザーとして実感がある感じだった
  • インターネット接続はいま有無くらいしか対応できてないな
  • とにかくシステム情報はIntentで飛んでくるのでBroadcastReceiverでキャッチする

Improving Layout Performanceを読んだ

http://developer.android.com/intl/ja/training/improving-layouts/index.html

を読んだ雑なメモ。

Optimizing Layout Hierarchies

http://developer.android.com/intl/ja/training/improving-layouts/optimizing-layout.html

  • 基本的なレイアウトを使うことがもっとも効率的という誤解

    • しかしそれぞれのウィジェットやレイアウトは追加すると初期化/配置/描写を必要とする
    • LinearLayoutのネスト => どんどんViewの階層が深くなる
      • layout_weightを使ったらそれぞれの子要素は2回大きさの計算がなされる(onMeasure()?)
      • ListViewやGridViewで効く
  • Hierarchy ViewerLayoutoptの使い方

Inspect Your Layout

  • Hierarchy Viewerでレイアウトを分析できる
    • レイアウトツリーの表示
  • ツリーに三つの○(下図1)
    • 描写にかかった時間を下図2のように表示してくれる
  • LayoutWeightのLinearLayoutを止めてRelativeLayoutになって軽い!という話

こちらも読みたい

http://developer.android.com/intl/ja/tools/performance/hierarchy-viewer/index.html

Re-using Layouts with

  • カスタムビューよりレイアウトの再利用の方が簡単かもよ、という話
    • ここではパフォーマンスとかは触れられてない

Use the include Tag

  • The root View should be exactly how you'd like it to appear in each layout to which you add this layout.
  • includeタグではandroid:layout_*属性を上書き可能
  • 何か上書きしたらandroid:layout_height and android:layout_widthも指定

Use the merge Tag

  • Root Viewを無視して子要素の身を入れたい => mergeタグ

Loading Views On Demand

http://developer.android.com/intl/ja/training/improving-layouts/loading-ondemand.html

滅多に使わない重いViewをViewStubでstubしちゃう??

ViewStubはその要素がinflateしなさい!!っていわれたときだけ、android:layoutで指定した属性をimportするっぽい

<ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

一度VISIBLEにするか、inflateしたらView階層からは外れるんだって。inflateされたViewのRootViewのidはandroid:inflatedIdっぽい

ViewStubはincludeと違って、mergeタグについてはサポートしていないので注意

Making ListView Scrolling Smooth

http://developer.android.com/intl/ja/training/improving-layouts/smooth-scrolling.html

ListViewをスムーズにスクロールさせるコツはUIスレッドで重い処理をしないこと

ViewHolderパターンを使って、findViewByIdの回数を減らそう??

感想

  • 恩恵を受けやすいのでListViewやRecyclierViewの要素から最適化する
  • Hierarchy Viewerは使う前にこちらも読む
  • ViewStubははじめて知った

Keeping Your App Responsiveを読んだ

http://developer.android.com/intl/ja/training/articles/perf-anr.html

上記を読んだ雑なメモ。

  • システムはアプリケーションが一定以上の時間応答しないとANRダイアローグを出して、そのアプリケーションを閉じるかどうか尋ねて、ユーザーにアプリを閉じる選択肢を与える
  • システムがANRを出さないように設計すべき

ANRが起こる条件

  • UIスレッドでネットワーク通信やゲームの表示用オブジェクトの生成等をやるとx
  • 時間がかかりそうな処理はUIスレッドで行わない
    • ワーカースレッドを作ってそちらでほとんど行う
  • これによって、UIスレッド(ユーザーインターフェースイベントループ*1を動かしている)を稼動状態にして、システムを凍結させるのを防ぐ
  • このようなスレッド処理はクラス単位で行われる => 応答性はクラス単位の問題だと考えることが多い(普通はメソッド単位で考えるが)

  • Androidでは、アプリケーションの応答性はActivity ManagerとWindow Manager system servicesで監視されている。

  • AndroidがANRを表示する条件は下記の2つの条件を検出したとき
    • キータッチなどの入力イベントに対して5秒以内に反応がないとき
    • BroadcastReceiverの処理が10秒以内に終らなかった時

ANRを避ける方法

  • インプットイベントやインテントを受け取るチャンスをアプリにあげないようなコード(通常、何も指定しないとすべての処理をUIスレッドで行うが、そんなコード)
  • UIスレッドで動くメソッドは小さくしよう

    • 特にActivityのonCreate()onResume()といったライフサイクルメソッドで行うセットアップ処理は可能な限り小さくしよう
      • いまはバックグラウンドで用意するための処理を呼び出し、みたいな感じだっけか
    • 画像の整形やネットワーク/DBヘの接続はバックグラウンドで
  • もっと効率的に長時間のワーカースレッドを作る方法は AsyncTaskクラスの利用

    • AsyncTaskクラスを継承して、doInBackground()をオーバーライドする
    • 進捗の変化については、(doInBackground()内から)publishProgress()メソッドが使える
      • これは同じクラスに書いたonProgressUpdateメソッドを呼び出す
    • onProgressUpdateはUIスレッドで動く
    • 処理が終了した時は、同じクラスのonPostExecuteメソッドが呼ばれる
  • 作ったAsyncTaskのクラスのジョブの実行は下記のような感じ。
new DownloadFilesTask().execute(url1, url2, url3);
  • もっと複雑な場合は、自分自身のThreadクラスを作るか、HandlerThreadクラスを利用する。
  • そうする場合、あなたはスレッドの優先順位をProcess.setThreadPriority()メソッドでセットし、THREAD_PRIORITY_BACKGROUNDの値を指定する必要がある。
    • そうしないとUIスレッドより優先してバックグラウンド処理が行われ、別スレッドに書いたのにANRが起こるとかある。
  • ThreadやHandlerThreadクラスを実装するとき、それらの完了までUIスレッドをブロックしないようにする(=つまり、Thread.wait()Thread.sleep()は呼ばない)
  • メインスレッドをブロックするかわりにメインスレッドはそれらのスレッドの完了時のpost backを受け取るHanlderを提供する
  • メインスレッドでネットワークの処理等を書いてないかを調べるStrictモードがあるらしい

応答性を強化する

  • ユーザーがあれ?と思うのは0.1~0.2s
  • バックグラウンドジョブに時間がかかる場合ProgressBarなどで進捗を示す
  • 計算はワーカースレッドで
  • 初期化処理に時間がかかるならスプラッシュが面を入れてはどうでしょう
  • SystraceやTraceviewといったパフォーマンスツールを使ってみよう

感想

  • ANRの条件は知ってた
  • なんだか、AsyncTaskの書き方に親しみが出てきた
  • 余裕が出てきたら、SystraceやTraceviewといったパフォーマンスツールを使ってみたい
英語
sluggish 動きののろい

*1:ちょっと分かりきってない単語だ

それっぽい名前なのに `Theme_Material_Dialog` は普通のダイアローグに使えない(使わない?)

Material Designっぽいのをやりたいと思ってTheme_Material_Dialog, ThemeOverlay_Material_Dialogをスタイルに指定すると 全画面で出てくるぞ! 気をつけろ...

Android Studioをアップデートしたら<interface declaration>,<parcelable declaration>, AidlTokenType.import or AidlTokenType.package expcted got 'wrap_content'

Android Studioをアップデートしたら表題のエラーが出てImageViewのlayout_widthが指定できなくなりました。

世界には他にもこう言う人がいたらしく、下記のStackOverFlowを真似して、File > Invalid Caches / Restart ... したら解決しました。

stackoverflow.com

読んではないけど、こういうものもあるんだ、ということで.

http://developer.android.com/intl/ja/guide/components/aidl.html

SwipeRefreshLayoutはCoordinatorLayoutを包める(!)