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
を読んだ雑な感想
開発するアプリの中でホスト端末の状態を監視し、それに基づいて機能や動作を変更することができるようになります
- 接続が失われたときはバックグラウンド サービスの更新を停止
- 電池残量が低下したときは更新の頻度を下げる
電池残量と充電状態の監視
- 充電時は常に最大出力でok
- 電池駆動 => 更新頻度を下げる
現在の充電状態を特定する
- 現在の充電状態を特定
var ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = context.registerReceiver(null, ifilter)
- バッテリー状態のIntentはsticky IntentなのでBroadcastReceiverいらない
- あれ、
sticky
は非推奨的な話を聞いたがシステムさんはいいのかな
- あれ、
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.
スポーツセンター アプリなら、卓上ホルダー装着時には更新頻度を上げ、カー ホルダー装着時には更新を完全に停止する
- 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で効く
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ヘの接続はバックグラウンドで
- 特にActivityの
もっと効率的に長時間のワーカースレッドを作る方法は
AsyncTask
クラスの利用- 作った
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 ... したら解決しました。
読んではないけど、こういうものもあるんだ、ということで.
http://developer.android.com/intl/ja/guide/components/aidl.html
SwipeRefreshLayoutはCoordinatorLayoutを包める(!)
- SwipeRefreshLayoutはCoordinatorLayoutを包める(!)
- swipeLayout.setProgressViewOffset(true, 30, 250) のようにSwipeRefreshLayoutのIndicatorの出てくる位置を調整できる
- ルートレイアウトをSwipeRefreshLayoutに変えた場合ImageViewはStatusBarが透明でもその背後に描画されない