以前、ボタンを点滅させたいと質問したところ、タイマーを使うと良いと回答していただき、次のように
onCreate に以下のようにコードを書きました。
Timer().schedule(500, 500) {
timer ++
val x = timer%2 when(x){ 1-> button2.visibility = View.INVISIBLE 0-> button2.visibility = View.VISIBLE } }
変数timerの初期値は0で、アプリ起動と同時に数字を足していく。
定数xに0と1が交互に入る
1のときはbuttom2が非表示
0のときはbuttom2が表示
と考えてコードを書きましたがうまくいきません。
INVISIBLEはできるのですが、VISIBLEのときにアプリが落ちてしまいます。
なぜでしょうか?ご教授お願いいたします。
x
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
ベストアンサー
INVISIBLEはできるのですが、VISIBLEのときにアプリが落ちてしまいます。
なぜでしょうか?ご教授お願いいたします。
なぜアプリが落ちるのか?の直接の答えとしては、「実行してはいけないタイミングでButton#visibility
を書き換えているためにException
が発生し、それをハンドリングしていないから」です。Exceptionをcatch するようにすれば、どんな種類の例外(エラー)が発生しているか分かります。質問者さんのコードとおおむね同じ例で示すと以下のようになります。
Kotlin
1val button2: Button = findViewById(R.id.button2) 2Timer().schedule(500, 500) { 3 try { 4 // 簡単に、ボタンの表示状態でトグル 5 if (button2.visibility == View.VISIBLE) { 6 button2.visibility = View.INVISIBLE 7 } else { 8 button2.visibility = View.VISIBLE 9 } 10 } catch (ex: Exception) { 11 Log.w("TestApp", "EXCEPTION: " + ex.message) 12 } 13}
これは、以下のようなメッセージがlogcatに出力されます。
2019-10-05 11:30:18.049 10517-10555/com.example.myapp1 W/TestApp: EXCEPTION: Only the original thread that created a view hierarchy can touch its views.
では何がいけないのか?という疑問の答えとしては、既に他回答でいただいたように、ボタンが張り付いている画面の処理を担当しているUIスレッドの(少し語弊がある表現ですが)持ち物であるのみが、描画関連処理を正しく完遂できるボタンに、別のスレッドであるタイマー用のスレッドからButton#visibility
を書き換えている、と言う行為です。INVISIBLE
にしてもOKだったのは、ラッキーだった程度のことで、これも本来はやってはいけません。Button
やTextView
などのUIの部品は、基本的にUIスレッドから触る必要があります。
そんな場合は、UIスレッド内でHandler
クラスオブジェクトを生成し、post
メソッドを用いてUIスレッド内で処理を行うようにします。(ここでは述べませんが、他の方法もあります)
Kotlin
1// UIスレッドでHandlerを生成(大事!) 2val handler = Handler() 3Timer().schedule(500, 500) { 4 handler.run { 5 post { 6 if (button2.visibility == View.VISIBLE) { 7 button2.visibility = View.INVISIBLE 8 } else { 9 button2.visibility = View.VISIBLE 10 } 11 } 12 } 13}
隠れている問題
Timer
を使用してタイマースレッドを起動すると、そのスレッドはUIスレッドとは別に生存し続けます。例えばAndroidの「ホーム」や「戻る」ボタンを押して、アプリの画面が終了したように見えてもタイマースレッドは動作し続けています。(Android Studioのデバッガーで確認してみてください)画面の終了などの適切なタイミングに合わせ、停止させる必要があります。ただ、本質問とは別のトピックだと思いましたので、詳細は省かせてもらいます。
余談
「他スレッドからUIを更新する」と言うプログラミング上の話題/問題は、Androidに限らず、またteratailに限らずかなり「あるある」です。Androidプログラミングでの対処の仕方は昔からほぼ定型でたくさん存在しますが、サンプルコードとして圧倒的にJavaのコードが多く、そのJavaのコードからKotlinに転化するのは、特にAndroidプログラミングの初心者さんからすると壁かなぁ、と個人的に思います。(Kotlin初心者の私自身も、結構悩みます)
投稿2019/10/05 03:17
編集2019/10/05 03:29総合スコア9256
0
コルーチンを用いれば、UIスレッド云々を気にせずにループを回せるんじゃないかという解として。
app/build.gradle
(dependenciesの中に1行追加)
gradle
1dependencies { 2 3 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1" 4 5}
MainActivity.kt
kotlin
1class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { 2 3 override fun onCreate(savedInstanceState: Bundle?) { 4 super.onCreate(savedInstanceState) 5 setContentView(R.layout.activity_main) 6 7 launch { 8 while(true) { 9 delay(500) 10 if (button2.visibility == View.VISIBLE) { 11 button2.visibility = View.INVISIBLE 12 } else { 13 button2.visibility = View.VISIBLE 14 } 15 } 16 } 17 } 18 19 override fun onDestroy() { 20 super.onDestroy() 21 cancel() 22 } 23} 24
こんな形でも目的の動作にはなると思います。
投稿2019/10/05 16:07
総合スコア6768
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
kotlinのことはよくわからないですが、一般的なフォームアプリケーションでは、UIコンポーネントに関わる操作はUIスレッド上で実行しなければならない、ということがあります
もしかして、そのタイマハンドラは別スレッドになってるんじゃないでしょうか
投稿2019/10/04 22:25
総合スコア88040
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。