Androidアプリテスト用のエミュレータ設定メモ
なんかPC買い替えるたびに同じようなこと調べてるのでメモ。
アプリ仕様
- minSdk=21, targetSdk=29.
- orientation は Portrait で固定。
- 横サイズは 320dp-411dp で正常表示されること。
エミュレータ設定
Name Device System Image ------------------------------------------------------------------------------------------------ w320dp-api21 : Nexus 5 4.95" 1080x1920 xxhdpi / Lollipop 21 x86_64 Android 5.0(Google APIs) w320dp-api29 : Nexus 5 4.95" 1080x1920 xxhdpi / Q 29 x86_64 Android 10.0(Google Play) w411dp-api21 : Google Pixel 5.0" 1080x1920 420dpi / Lollipop 21 x86_64 Android 5.0(Google APIs) w411dp-api29 : Google Pixel 5.0" 1080x1920 420dpi / Q 29 x86_64 Android 10.0(Google Play)
RxJava の subscribe の例外ハンドリングが適用されない例外への対応メモ
RxJava の subscribe メソッドの例外ハンドリングを記述していてもアプリが落ちるケースがあるのでとりあえずメモ。
source: git@github.com:beyondseeker/chrono3_rx_grobal_error_handling.git
subscribe() による例外ハンドリングをすり抜ける例
下記のようなコードを Android アプリの中で実行した場合、subscribe メソッドの例外ハンドリングでは対応できず、アプリが落ちる。
/** * subscribe() による例外ハンドリングをすり抜ける例 */ @Test fun example1() { val disposable = CompositeDisposable() disposable.add( Completable .fromCallable { // この sleep 中に disposable.dispose() が呼ばれ、このスレッドが interrupt され java.lang.InterruptedException // がスローされるが、既に dispose されているので subscribe() にてこの例外をハンドリングできない。 Thread.sleep(200) } .subscribeOn(Schedulers.newThread()) .subscribe({ onSuccess(disposable) }, ::onError) ) Thread.sleep(100) disposable.dispose() Thread.sleep(300) }
例外はこんな感じで io.reactivex.exceptions.UndeliverableException がスローされてます。
Exception in thread "RxNewThreadScheduler-1" io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.InterruptedException: sleep interrupted at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367) at io.reactivex.internal.operators.completable.CompletableFromCallable.subscribeActual(CompletableFromCallable.java:42) at io.reactivex.Completable.subscribe(Completable.java:2309) at io.reactivex.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.run(CompletableSubscribeOn.java:64) at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578) at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66) at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.objectfanatics.chrono3.ExampleUnitTest$example1$1.call(ExampleUnitTest.kt:26) at com.objectfanatics.chrono3.ExampleUnitTest$example1$1.call(ExampleUnitTest.kt:14) at io.reactivex.internal.operators.completable.CompletableFromCallable.subscribeActual(CompletableFromCallable.java:36) ... 11 more
subscribe() による例外ハンドリングをすり抜けた例外をキャッチする例
下記のコードは、例外を握りつぶしている。
Androidアプリの場合は RxJavaPlugins#setErrorHandler() を Application#onCreate() 辺りで実行しておけばいいんでしょうかね。
/** * subscribe() によるハンドリングをすり抜た例外を catch する例 */ @Test fun example2() { RxJavaPlugins.setErrorHandler { throwable -> println("RxJava Global Error: ${throwable.message}") } example1() } private fun onSuccess(disposable: Disposable) { println("onSuccess: isDisposed = ${disposable.isDisposed}") } private fun onError(error: Throwable) { println("onError: ${error.message}") }
dispose された時点以降は onSuccess も onError も呼ばれる必要はないというのであれば、特に問題なさそうな気もする。
SnapHelper のサンプルを作成してみた
先頭もしくは末尾の item をキリの良い位置で IDLE にするための SnapHelper を Horizontal と Vertical 用に作成してみました。
SnapHelperの本家ドキュメントはこちら
StartSnapHelper の GitHub 上のソースはこちら
サンプルアプリのレポジトリ:git@github.com:beyondseeker/chrono1_StartSnapHelper.git
package com.objectfanatics.infra.androidx.recyclerview.widget import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.OrientationHelper import androidx.recyclerview.widget.RecyclerView.LayoutManager import androidx.recyclerview.widget.RecyclerView.NO_POSITION /** * This is a [LinearSnapHelper] tries to show the first or last visible item completely. */ class StartSnapHelper : LinearSnapHelper() { override fun calculateDistanceToFinalSnap(layoutManager: LayoutManager, targetView: View): IntArray = when { layoutManager.canScrollHorizontally() -> intArrayOf(distanceToLeft(layoutManager, targetView), 0) layoutManager.canScrollVertically() -> intArrayOf(0, distanceToTop(layoutManager, targetView)) else -> throw newException(layoutManager) } override fun findSnapView(layoutManager: LayoutManager): View? { with(layoutManager as LinearLayoutManager) { val firstVisibleItemPos = findFirstVisibleItemPosition() if (isEmpty(firstVisibleItemPos) || isLastItemCompletelyVisible()) { return null } val firstVisibleItem = findViewByPosition(firstVisibleItemPos) getOrientationHelper(layoutManager).run { // Visible size of firstVisibleItem. val visibleSizeOfFirstVisibleItem = getDecoratedEnd(firstVisibleItem) // Size of firstVisibleItem. val wholeSizeOfFirstVisibleItem = getDecoratedMeasurement(firstVisibleItem) return when { // Visible size of firstVisibleItem is greater than zero and greater than or equals to half of the whole size. visibleSizeOfFirstVisibleItem > 0 && visibleSizeOfFirstVisibleItem >= wholeSizeOfFirstVisibleItem / 2 -> firstVisibleItem // Visible size of firstVisibleItem is zero or less than half of the whole size. else -> findViewByPosition(firstVisibleItemPos + 1) } } } } companion object { private fun horizontalHelper(layoutManager: LayoutManager): OrientationHelper = OrientationHelper.createHorizontalHelper(layoutManager) private fun verticalHelper(layoutManager: LayoutManager): OrientationHelper = OrientationHelper.createVerticalHelper(layoutManager) // The left of the view including its decoration, margin and padding. private fun distanceToLeft(layoutManager: LayoutManager, targetView: View): Int = horizontalHelper(layoutManager).run { getDecoratedStart(targetView) - startAfterPadding } // The top of the view including its decoration, margin and padding. private fun distanceToTop(layoutManager: LayoutManager, targetView: View): Int = verticalHelper(layoutManager).run { getDecoratedStart(targetView) - startAfterPadding } private fun isEmpty(firstItemPos: Int) = firstItemPos == NO_POSITION private fun LinearLayoutManager.isLastItemCompletelyVisible() = findLastCompletelyVisibleItemPosition() == itemCount - 1 private fun getOrientationHelper(layoutManager: LinearLayoutManager): OrientationHelper = when { layoutManager.canScrollHorizontally() -> horizontalHelper(layoutManager) layoutManager.canScrollVertically() -> verticalHelper(layoutManager) else -> throw newException(layoutManager) } private fun newException(layoutManager: LayoutManager) = UnsupportedOperationException("Unexpected layoutManager: ${layoutManager.javaClass.name}") } }
Microsoft Office 2010 が動かなくなった(KB4461627)
2019-01-05にPCを再起動したところ、Excel を起動すると、起動後スプレッドシート自体が表示されるがしばらく Excel が操作不能という状態が続き、その後 Excel が突然落ちるという、再現可能な現象が発生した。
windows update が問題だったらしく、Excel 2010 (KB4461627)、2019 年 1 月 2日の更新プログラム をアンインストールすることで解決した。