Toastの再利用
Androidで、サービス等のUIを持たないコンポーネントから画面にテキストを表示するToastというAPIがある。
割と昔からあるものなので挙動も同じだろうと思っていたら、某社の端末(未発売)でトラブルがあった。
その端末のバグっぽい印象だったので割と邪道な対応で済ませたが、それについてはあまり詳しく書けない。
ついでに手持ちの他の端末でも簡単なテストを行ってみた。
- トースト を表示して4秒経過したらトーストをキャンセルして、トーストへの参照をnullで上書きする。
- トースト を前回表示してから4秒未満で再度トーストを表示した時に、以下のいずれかの処理を行う。
- キャンセルした後に再利用(setTextして再度show)
- キャンセルせずに再利用(setTextして再度show)
- キャンセルした後に新しくmakeTextしてshow
- キャンセルせずに新しくmakeTextしてshow
結果
キャンセルして再利用
-
- 4.4.2~9.0 でうまくいかない。文章は更新されるが、最初のトーストが表示されてから4秒で消えてしまう。
キャンセルせずに再利用
-
- 4.4.2の端末ではOK。最後に文章を更新してshowしてから4秒でトーストが消える。
- 7.1.1の端末ではNG。文章は更新されるが、最初のトーストが表示されてから4秒で消えてしまう。
- 8.0.0の端末ではNG。文章は更新される(+トーストの表示がちらつく)が、最初のトーストが表示されてから4秒で消えてしまう。
- 9.0.0の端末ではNG。文章は更新される(+トーストの表示がちらつく)が、連打するとすぐにトーストが消えてしまう。
キャンセルした後に新しくmakeTextしてshow
-
- 特に問題は見られなかった
キャンセルせずに新しくmakeTextしてshow
-
- 端末によってはトーストがキューに溜まり遅延が発生する
コード例
package jp.juggler.toastsample import android.os.Bundle import android.os.Handler import android.support.v7.app.AppCompatActivity import android.util.Log import android.view.Gravity import android.view.View import android.widget.Toast class MainActivity : AppCompatActivity() { companion object { private const val LOGTAG = "ToastSample" } lateinit var handler : Handler lateinit var btnCancel : View private var nCount = 0 private var lastToast : Toast? = null override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) handler = Handler() btnCancel = findViewById<View>(R.id.btnCancel) btnCancel.setOnClickListener { toastRemover.run() } btnCancel.isEnabled = false findViewById<View>(R.id.btnShow1).setOnClickListener { showToast(1) } findViewById<View>(R.id.btnShow2).setOnClickListener { showToast(2) } findViewById<View>(R.id.btnShow3).setOnClickListener { showToast(3) } findViewById<View>(R.id.btnShow4).setOnClickListener { showToast(4) } } private val toastRemover : Runnable = object : Runnable { override fun run() { handler.removeCallbacks(this) lastToast?.cancel() lastToast = null btnCancel.isEnabled = false } } private fun showToast(type : Int) { try { val gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL val text = "Toast ${++ nCount}" val duration = Toast.LENGTH_LONG var toast = lastToast if(toast != null) { when(type) { 1 -> { // キャンセルして再利用 toast.cancel() toast.setText(text) toast.duration = duration } 2 -> { // キャンセルせずに再利用 toast.setText(text) toast.duration = duration } 3 -> { // キャンセルして新しく作成 toast.cancel() toast = Toast.makeText(this, text, duration) lastToast = toast } 4 -> { // キャンセルせず新しく作成 toast = Toast.makeText(this, text, duration) lastToast = toast } } toast?.setGravity(gravity, 0, 0) toast?.show() } else { toast = Toast.makeText(this, text, duration) lastToast = toast toast.setGravity(gravity, 0, 0) toast.show() } handler.removeCallbacks(toastRemover) handler.postDelayed(toastRemover, 4000L) btnCancel.isEnabled = true } catch(ex : Throwable) { Log.e(LOGTAG, "showToast failed.", ex) } } }
ToastCompat
API level 25 以上で BadTokenException が出る不具合は ToastCompat https://github.com/drakeet/ToastCompat を使うと改善する。