PR

Kotlinで停止したタイマを再開できない時の解決方法

Androidアプリ

タイマは動かせます、停止もできます、けど再開できません…

こうした悩みを解決します。

本記事を通じてできるようになること

・タイマ使用時にエラーがでないよう設計する
・タイマON/OFFをボタン制御で切り替える
・タイマタスクで直接メインアクティビティのUI要素にアクセスする
・テキストビューの値が数値だけか判別する

サンプルプログラムはこちら

ソースコードも提供しています
サンプルプログラムのソースコードを見る

長くなりました、ではいきましょう。

立プロ

新卒でメーカーに入り、10年間組み込みの現場で設計を行う。
今は個人事業主として自作の組み込み機器開発や、エージェント様に紹介いただき業務委託を行っています。
C,C#,JavaScript, Vue, PHP, VBA, GAS, Kotlinなど、扱う言語が増えゆく日々。

立プロをフォローする

結論:タイマは使う度に新たに作る必要がある

結論から先に言うと、タイマは使い捨てのスレッドです。

タイマは停止、再開という概念のものではなく、停止するときはタイマスレッドを破棄、再開したいときは新たに作り直しと考えます。

そのため、設計にあたってタイマ開始する直前にタイマオブジェクトを生成する必要があります。

こんな感じでタイマを開始させます。

 val timer = Timer() /*これが重要!*/
 timer.scheduleAtFixedRate(1000, 3000) {
     //do something
 }

一度だけタイマを実行するならつまずかないと思います。

開始したタイマを停止して再開したいときに問題が起こるんですね。

慌てずゆっくり読み進めて理解していきましょうね。

タイマ再開時にアプリが停止する問題

テキトーに関数作って引数のbool値でタイマの開始、停止を試みます。

この処理だと1回目はタイマが正常動作しますが、2回目のタイマ動作をしようとした瞬間にアプリが落ちます。

val timer = Timer()

fun timerstart(flag:Boolean){
    if(flag == true) {
        timer.scheduleAtFixedRate(1000, 3000) {
            //do something
        }
    } else {
        timer.cancel()
    }
}

動作を考える

まず事前に定義しているval timer = Timer()でタイマオブジェクトができました。

そしてtrueを引数として、timerstart関数をよんでタイマをスタートします。

上記は1秒のウェイトの後、3秒毎に処理を行うscheduleAtFixedRateメソッドを使っています。

タイマを止めるときはfalseを引数にtimerstart関数をよんでタイマが停止します。

ここまでは設計通りに動くはずです。

タイマ再開時に落ちる原因と対策

タイマは実は既存スレッドとは別のスレッドを作り、その中でタスクを実行しています。

なので、cancel()でタイマを停止するというのは、タイマスレッドを終了(=破棄)しているということになります。

タイマスレッドが終了して無くなっているのにタイマを再開しようとすると、タイマの実体がないのでアプリ側が処理できずに落ちてしまいます。

そのため、冒頭でも書きましたがタイマを開始する前にはタイマオブジェクトを作る必要があるのです。

タイマは再開するというより、タイマを作る→動かす→捨てる→タイマを作る→…を毎回繰り返していることになります。

val timer = Timer()

fun timerstart(flag:Boolean){
    if(flag == true) {
        timer = Timer() //作る
        timer.scheduleAtFixedRate(1000, 3000) { //動かす
            //do something
        }
    } else {
        timer.cancel() //捨てる
    }
}

タイマからUI要素にアクセスすると落ちる問題と対策

タイマは独自のスレッドを作って動くので、アクティビティやフラグメントのUI要素に直接アクセスすることはできません。

この問題を機に、タイマはスレッドだと気づく人もいるのでは。

UIを制御したい場合は、runOnUiThreadメソッドを用いる必要があります。

runOnUiThreadが分からない方はこちら

動作中のタイマの実行間隔は変更できない問題と対策

動作中のタイマの実行間隔に引数を参照させて、動作中に実行間隔を可変させることもできません。

いったんタイマを停止して、再度実行間隔を変えてから再度動かす必要があります。

val timer = Timer()
var temp = 10

fun timerstart(flag:Boolean){
    if(flag == true) {
        timer = Timer() //作る
        timer.scheduleAtFixedRate(1000, temp*1000) { //これはできない
            //do something
        }
    } else {
        timer.cancel() //捨てる
    }
}

サンプルプログラム提供中

タイマを用いてテキストビューの数値を1秒毎にインクリメントするプログラムを提供しています。

手っ取り早くタイマを動かしたいという方は下記からソースコードをダウンロードして使ってみてください。

サンプルプログラムのソースコードを見る

まとめ

タイマは使い捨てのスレッドです

タイマ開始直前にタイマオブジェクトを生成する必要があります(そうでないとアプリが落ちる)

コメント

タイトルとURLをコピーしました