LoginSignup
2
2

More than 3 years have passed since last update.

51歳からのプログラミング 備忘 プロセス スレッド Handler -reference

Last updated at Posted at 2019-08-26

<写経> 個人使用用

プロセスとスレッド

https://developer.android.com/guide/components/processes-and-threads?hl=JA
(2019/8/24)

マルチスレッドまとめ的なのはここ

プロセス

  • コンポーネント、プロセス、スレッドの関係
  • マニフェスト設定(コンポーネントとプロセス)
  • プロセスの重要度階層

コンポーネント、プロセス、スレッドの関係

  • アプリケーションで実行するコンポーネントが一つのとき、Androidシステムはシングルスレッド用のLinuxプロセスを開始します。

    • コンポーネント
      アクティビティ/サービス/コンテンツプロバイダ/ブロードキャストレシーバー
      アクティビティ、サービス、ブロードキャストレシーバーはIntent(非同期メッセージ)でアクティベートします。
    • process/Linuxプロセス
      Linuxで実行中のプログラム。
  • デフォルトでは、同じアプリケーションのすべてのコンポーネントは同じプロセスとスレッド(メインスレッド)で実行されます。

  • アプリのコンポーネントが開始したときに、他のプロセスが存在する場合、コンポーネントはそのプロセス内で開始し、同じ実行スレッドを使用します。

  • ただし、アプリケーション内の別のコンポーネントを別のプロセスで実行するように調整でき、あらゆるプロセスに対して別スレッドを作成できます。


マニフェスト設定

特定のコンポーネントが属するプロセスを管理する場合には、マニフェストファイルで指定します。

コンポーネント要素の各タイプのマニフェストエントリ(<activity>/<service>/<receiver>/<provider>)は、コンポーネントを実行するプロセスを指定するandroid:process属性をサポートしてます。

android:processの設定によっては、各コンポーネントが独自のプロセスで実行っせたり、一部のコンポーネントで同じプロセスを共有したり、別のプロセスを使ったりできます。異なるアプリケーションのコンポーネントを同じプロセスで実行させたりもできます(アプリが同じLinux user idを共有してて、同じ証明書で署名されている必要があります)。

<application>要素もandroid:processをサポートしており、全コンポーネントに適用されるデフォルト値を設定します。

Androidシステムは、プロセスをシャットダウンし、アプリケーションコンポーネントを破棄することがあります。破棄するプロセスを決定する際、システムは相対的な需要度によって候補を決めます。後ほど、破棄するプロセスを決定する規則を説明します。


プロセスの重要度階層

Androidシステムは、プロセスで実行しているコンポーネントとコンポーネントの状態に基づいて、各プロセスを「重要度の階層」に位置付けし、重要度の低いプロセスから除去され、システムリソースを回復させようとします。

プロセスの重要度階層 5レベル

  1. フォアグラウンドプロセス
  2. 可視プロセス
  3. サービスプロセス
  4. バックグラウンドプロセス
  5. 空のプロセス
1.フォアグラウンドプロセス
  • ユーザーが操作するActivityのホストになっている(ActivityのonResume()メソッドが呼び出された)
  • ユーザーが操作するアクティビティにバインドされている(Serviceのホストになってる)
  • フォアグランドで実行中のServiceのホストになっている(サービスがstartForeground()を呼び出した)
  • onCreate()、onStart()、onDestroy()のいずれかのライフサイクルコールバックを実行してるServiceのホストになっている
  • onReceive()メソッドを実行しているBroadcastReceiverのホストになっている

メモリやリソースが不足すると、フォアグラウンドプロセスを強制終了する必要が生じます。

2.可視プロセス

フォアグラウンドコンポーネントではないものの、ユーザーに対して通知領域の内容に影響を与える可能性のあるプロセス。以下の場合には可視プロセスとみなされます。

  • フォアグランドにはないけれど、ユーザーに表示されているActivityのホストになっている(onPause()メソッドが呼びだされた)。

  • 可視(またはフォアグラウンドの)アクティビティにバインドされているServiceのホストになってる。

可視プロセスの重要性は高く、フォアグラウンドプロセスの実行を維持する必要がある場合にのみ、強制終了されます。

3.サービスプロセス

startService()メソッドで開始されたサービスを実行するプロセスで、上の2つのカテゴリに分類されないもの。フォアグラウンドプロセスと可視プロセスの全てと合わせて、それらを継続するのにメモリが不足した場合にのみ強制終了の候補となります。

4.バックグラウンドプロセス

ユーザーに表示されてないアクティビティを含むプロセス(onStop()メソッドが呼び出された)。
メモリ回復のためにいつでも強制終了される可能性があります。バックグラウンドサービスはLRU(Least Recently Used:最小使用頻度)リストで管理され、操作の古いものから終了されます。

アクティビティが正確にライフサイクルメソッドを実装し、現在の状態を保持した場合は、強制終了してもアクティビティを再開したときに、視覚的状態を復元します。

5.空のプロセス

アクティブなアプリケーションコンポーネントが一つもないプロセス。このプロセスはプロセスをキャッシュする目的で保持され、素早く次回のコンポーネントを起動させます。このプロセスは頻繁に強制終了します。


複数の重要度のあるコンポーネントでは、一番重要度の高いランクでプロセスを位置付けます。また他のプロセスのために動作しているプロセスは、依存/バインドしてるプロセスよりも下に位置付けれることはありません(下に位置付けられると、依存してるプロセスが破棄された場合に、実行できなくなる)。

サービスはバックグラウンドより重要度が高く、長時間操作するアクティビティ(特にプロセスがアクティビティよりも長く続く場合)では、プロセスはワーカースレッドを作成するよりサービスを使ったほうがいいでしょう。例えば、ウェブに画像をアップロードする場合、アクティビティでは、ユーザーがアクティビティから離れても、バックグラウンドで処理を続行できるよう、サービスを使うということです。ブロードキャストレシーバーでもスレッドに長時間処理させるのではなく、サービスを使いましょう。

スレッド

アプリケーション起動時、システムはアプリケーション実行用のメインスレッドを作成します。メインスレッドは、イベント(描画イベントを含む)を適切なUIウィジットに送信する役割を担うために、非常に重要です。

また、メインスレッドは、アプリがAndroid UI ツールキットのコンポーネント(android.widget/android.viewパッケージのコンポーネント)とやり取りもします。そのため、メインスレッドはUIスレッドとも呼ばれます(メインスレッドがUIスレッドでない特殊なケースもあり)。

コンポーネントのインスタンス毎に別スレッドが作成されることはありません。同じプロセスで実行される全てのコンポーネントは、UIスレッドでインスタンス化され、システムはUIスレッドから送信されたコンポーネントを呼び出します。システムのコールバックに応答するメソッドは、常にプロセスのUIスレッドで実行します。

(例:ユーザーが画面上のボタンをタッチすると、アプリのUIスレッドがタッチイベントをWidgetに送信し、Widgetがタッチ状態を設定して、イベントキューに無効化の要求を送信します。UIスレッドがキューから要求を受信し、Widgetに描画を通知する)

全てがUIスレッドで行われる場合、時間がかかる操作を実行すると、UI全体をブロックする可能性があります。またAndroid UIツールキットはスレッドセーフではないので、ワーカースレッドからUIを操作できません。全ての操作はUIスレッドから行う必要があります。そのため、Androidのシングルスレッドモデルには2つのルールを設けています。

シングルスレッドモデル・ルール

1. UIスレッドをブロックしない
2. UIスレッド以外からAndroid UIツールキットにアクセスしない

  UIスレッド以外からアクセスメソッドが用意されている。
Viewを作成したスレッドからしかViewを操作できない


ワーカースレッド

ワーカースレッドは、UIを持たないスレッドで、受信した処理要求を実行します(backgroundThreadとも呼ばれる)。ワーカースレッドは、処理要求がなくても待機し、処理要求を受信したら実行する、という振る舞いをします。ワーカースレッドは処理内容によって複数存在することがあり、その存在場所をThreadPoolと呼ぶことがあります。

シングルスレッドモデルによって、アプリケーションのUIの応答性のためにもUIスレッドをブロックしてはいけません。即座に実行する必要のない操作は、別のスレッド(ワーカースレッド(バックグラウンドスレッド))で実行します。

<参考:ルール違反のコード>

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

UIスレッドではなく、ワーカースレッドから、ImageViewの変更を試みており、ルール2(Android UIツールキットはUIスレッドのみからアクセス)に違反しています。

UIスレッド以外からのアクセス方法
  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

<参考:View.post(Runnable)による修正コード>
View.post(Runnable)を実装してスレッドセーフにする

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap =
                    loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

ネットワーク操作は別スレッドで実行(loadImageFromNetwork())。ImageViewはUIスレッドから操作(mImageView.post(new Runnable(){}))しています。

View.postを使ってViewをUIスレッドから操作できるようにする方法は、操作が複雑になるとメンテナンスも難しくなります。ワーカースレッドとの複雑なやりとりを処理するために、ワーカースレッドでHandlerを使うと、UIスレッドから配信されたメッセージを処理できます。でも推奨する方法は、AsyncTaskクラスの拡張です。UIを操作するワーカースレッドタスクの実行を簡略化します。

AsyncTaskの利用

AsyncTaskは非同期処理を実行します。スレッドやHandlerを自身で処理する必要はなく、ワーカースレッドの操作をブロックし、結果をUIスレッドに配信します。

AsyncTaskの利用は、AsyncTaskをサブクラス化し、バックグラウンドスレッドのプール内で実行するdoInBackground()コールバックメソッドを実装します。

結果をUIに反映させるために、onPostExecute()を実装します。onPostExecute()は、doInBackground()の結果をUIに配信し、UIスレッドを更新するので、UIを安全にアップデートできます。UIスレッドではexecute()を呼び出し、タスクを実行します。

<参考:前記のコードをAsycnTaskで書き換える>

public void onClick(View v){
   new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String,Void,Bitmap>{
   //   システムはワーカースレッドで処理するために次のメソッドを呼び出し、
   //   AsyncTask.execue()メソッド渡すパラメータを配信します
   protected Bitmap doInBackground(String... urls){
      return loadImageFromNetwork(urls[0]);
   }

   //   システムはUIスレッドで処理するために次のメソッドを呼び出し
   //   doInBackground()メソッドに結果を配信します
   protected void onPostExecute(Bitmap result){
      mImageView.setImageBitmap(result);
   }
}

ワーカースレッドで処理する作業と、UIスレッドで処理する作業を分けたため、UIは安全になり、コードもシンプルになりました。

AsyncTaskの仕組み。

  • ジェネリクスを使ってパラメータのタイプ、進捗値、タスクの最終値を指定できる
  • doInBackground()メソッドがワーカースレッド上で自動的に実行される
  • onPreExecute(),onPostExecute(),onProgressUpdate()は、全てUIスレッドで呼び出される
  • doInBackground()の返値は、onPostExecute()に送られる
  • publishProgress()は、doInBacktround()で呼び出せ、UIスレッドでonProgressUpdate()を実行できる
  • いつでも、どのスレッドからでもタスクをキャンセルできる
    COUSION:ワーカースレッド使用時に発生する可能性のある問題
    画面の向きを変えた場合など、実行時の設定が変更されたことによって、アクティビティが予期せず再起動され、ワーカースレッドが破棄されることがあります。再起動の間、タスクを維持する方法、アクティビティが破棄された時の正しいタスクキャンセルの方法については、以下のサンプルアプリのソースコードを参照>https://code.google.com/archive/p/shelves/
    AsyncTaskについてより詳しくは>https://developer.android.com/reference/android/os/AsyncTask.html?hl=JA

スレッドセーフなメソッド

実装したメソッドが、複数のスレッドから呼び出される場合、メソッドがスレッドセーフになるように作成してください。

主にバインドされたサービスのメソッドなど、リモートで呼び出されるメソッドは、複数のスレッドから呼び出される可能性があります。複数のスレッドから呼び出された場合、リモートで呼び出されるメソッドを実行するスレッドは以下の通りになります。

  • IBinderに実装されたメソッドの呼び出しが、IBinderを実行しているプロセスと同じプロセスで発生した場合、メソッドの実行スレッドは呼び出し側のスレッドです。
  • 別のプロセスから呼び出された場合は、メソッドの実行スレッドは、システムがIBinderと同じプロセスに保持するスレッドプールから選びます。(UIスレッドでは実行されない)。

例えば、サービスのonBind()メソッドが、サービスのプロセスにあるUIスレッドから呼び出されるのに対して、onBind()が返すオブジェクトで実装されたメソッド(RPCメソッドを実装するサブクラスなど)は、スレッドプール内のスレッドから呼び出されます。サービスは複数のクライアントを持てるので、複数のスレッドプールが同じIBinderメソッドを同時に動かせるため、IBinderメソッドはスレッドセーフになるよう実装しなければなりません。

RPCメソッド:メソッドの呼び出しはローカルで、実行はリモート(別プロセス)で行い、結果を呼び出し側に返す。

同様にコンテンツプロバイダは、他のプロセスから送られたデータリクエストを受け取れます。ContentResolverクラスと、ContentProviderクラスによって、プロセス間通信がどのように管理されているのかが見えなくなりますが、それらのリクエストに応答するContentProviderメソッド(query(),insert(),delete(),updata(),getType())は、プロセスのUIスレッドではなく、コンテンツプロバイダのプロセスにあるスレッドプールぁら呼び出されます。これらのメソッドは同時に複数のメソッドから呼ばれる可能性があるので、スレッドセーフになるように実装しなければなりません。

スレッドセーフ

スレッド競合(複数のスレッドが同時の同じクラスやメソッド、変数にアクセスしてデータを破壊する関係)を避けるために、あるスレッドの実行中は、他のスレッドをコントロール(同期(Synchronization)、調停または排他制御)し、複数のスレッドから同時アクセスが生じても、安全なクラスやメソッドにすること。

プロセス間通信(IPC)

Androidでは、リモートプロシージャコール(RPC)を使った、プロセス間通信のメカニズムを備えており、メソッドはアクティビティや他のアプリケーションコンポーネントから呼び出された後、リモート(別プロセス)で実行され、結果を呼び出し側に返します。プロセス間通信の処理はシステムが担っているため、開発者はRPCプログラミングインターフェイスの定義と実装をするだけでOKです。IPCの実行は、アプリがbindService()を使ってサービスにバインドされている必要があります。詳細は>https://developer.android.com/guide/components/services.html?hl=JA

Handle

Handlerを使うと、スレッドのMessageQueueに関連付けられたMessageや実行可能なオブジェクト(Runnableとかも)を送信したり、処理できます。

Handlerのインスタンスは、シングルスレッドと、そのスレッドのメッセージキューに関連づけられてます。

新しいHandlerを生成すると、Handlerは生成したスレッドと、そのメッセージキューに割り当てされます。その後、Handlerはメッセージと実行可能なメッセージキューを配信できるようになり、またキューから取り出してメッセージを実行できるようになります。

主なHandlerの用法

  • メッセージをスケジュールし、スケジュールした時点で実行可能にする
  • メッセージを別のスレッドで実行するようにして、キューに入れる

メッセージをスケジュールする

使うメソッド

Post Version
post(Runnable)/postAtTime(java.lang.Runnable,long)/postDelayed(Runnable,Object,long)

Send Message Version
sendEmptyMessege(int)/sendMessage(Message)/sendMessageAtTime(Message,long)/sendmessageDelayed(Message,long)

Postバージョンは、実行可能なオブジェクトを受け取ると、オブジェクトをキュー入れる働きをします。

SendMassegeバージョンはメッセージオブジェクトをキューに入れます。メッセージオブジェクトは、HandlerのHandleMessage(Message)メソッドで使われる予定のデータの集まりを保持してます。(HandleMessage(Massage)は、Handlerのサブクラスを実装します)

Handlerをpostしたりsendすると、メッセージキューの準備ができ次第、処理できるようになります。処理の先延ばしを指定したり、実行時間を指定したりもできます。

後ほど、タイムアウト処理や、ticks(タイマー的な)など、タイミング動作の実装について説明します。

アプリケーションが実行されたとき、メインスレッドはメッセージキュー実行のための専用スレッドになります。メッセージキューは、アクティビティ、ブロードキャスト、レシーバーなどのトップレベルのアプリケーションオブジェクトや、それらが作るウィンドウを管理します。

Handlerを使えば別のスレッドを作成して、メインアプリケーションと通信できるようになります。そのためには前もってpostやsendMessageメソッドを使います。

RunnableやメッセージはHandler'sメッセージキューでスケジュールされ、適切な時に実行されます。


Handlerまとめ的な

<マルチスレッドとスレッド間通信>
■ Threadクラスの拡張 | Runnableインターフェイスの継承
  UIスレッドとの通信
  view#post(Runnable)
  View#postDelayed(Runnable.long)
  runOnUiThread(Runnable)
■ AsyncTaskクラス
  処理と通信
  execute(),doInBackground(),postExecute()などで
■ Handlerクラス
  マルチスレッド間の通信手段
  別途作ってあるスレッド間の通信を担います
  Message
  Runnable

Handlerまとめ概要

class android.os.Handler

HandlerはUIスレッドやワーカースレッド間の通信を実現します!

Handlerインスタンスを作成したスレッド内に、MessageQueue(メッセージやRunnableを積み上げて保管)と、MessageQueueからメッセージやRunnableを取り出すLooperを生成するね。Handlerはスレッド間通信を実現するので、他のスレッドから参照できるようデザインされてるらしい。

Handlerのインスタンスは生成したスレッドとメッセージキューに関連付けられてて、メッセージなどがキューに追加されるとtrueを返します。指定時間経過前にLooperが終了するとRunnableは放棄されてFalseになるので、Falseが出たらメッセージキューが終了してる可能性がありますとのこと!

Handlerの通信

  • Messageを使う方法
  • Runnableを使う方法

Handlerは、すでに作成されているスレッド間の情報交信手段を提供します。UIスレッドだけでなくワーカースレッド間の通信を実現します。

Messageを使うと、LooperがhandleMessageを処理し、Runnableを使うと、UI側でRunnableを実行するのですね。

HandlerとMessageの事例1
Message
public class MainActivity extends AppCompatActivity {

    TextView textView;

    // UIスレッド(MainActivity)でHandlerインスタンスを生成します
    // これでMessageQueueもLooperもUIスレッドの物だね
    // インスタンス handler は別スレッドから参照可能なので
    // 別スレッドで handler を参照して、messageを載せれば
    // UIスレッド(MainActivity)でLooperが処理してくれるんだぜ!
    // カッコいいじゃん!!
    // では、handler さんに登場願います!
    Handler handler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView1);

        MyLovelyMessage();
    }

    private void MyLovelyMessage() {
        // UIスレッド(MainActivity)で生成した handler にアクセスし
        // handler に Messageを載せて sendMessage!
        // これでUIスレッドのLooperが上手くやってくれるはず!
        Message message = handler.obtainMessage(1);
        handler.sendMessage(message);
    }

    class MyHandler extends Handler{
        @Override
        public void handleMessage(Message message){
            // handle が UIスレッドで何をするかを定義する
            // Looperによって自動的に実行されるよ!
            String msg = String.valueOf(message.what);
            textView.setText(msg);
        }
    }
}

上のコードを内部関数的にシンプルに

Message
public class MainActivity extends AppCompatActivity {

    TextView textView;

    Handler handler = new Handler(){
      @Override
      public void handleMessage(Message message){
          String msg = String.valueOf(message.what);
          textView.setText(msg);
      }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView1);

        MyLovelyRunnable();
    }

    private void MyLovelyRunnable() {
        Message message = handler.obtainMessage(1);
        handler.sendMessage(message);
    }
}
HandlerとRunnableを使った事例
public class MainActivity extends AppCompatActivity {

    TextView textView;

    // HandlerをUIスレッド(MainAvtivity.class)で生成します
    // これで、MessageQueue も Looper も、このスレッド(UIスレッド)に関連付けれます
    //
    // Handlerインスタンスは、他のスレッドからも参照できるので
    // 他のスレッドで、インスタンス handler を使って
    // Runnableをこのスレッド(UIスレッド)に渡せば
    // このスレッド(UIスレッド側)でRunnableを実行できるようになります
    // 素敵ですね!
    // ではHandlerのインスタンスを生成しましょう!!
    Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView1);

        MyLovelyHandler();

    }

    private void MyLovelyHandler(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                // UIスレッド(MainActivity)で生成したHandlerのインスタンスは
                // 他のスレッドからでも参照可能です
                // インスタンスhandlerにRunnableを載せて
                // UIスレッド(MainActivity)に渡して処理してもらいましょう!
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("I Love Handler");
                    }
                });
            }
        }).start();
    }
}



HandlerとService:ファイルを複数に分割した事例

Minターゲット:api28以上にしてあります。

作成ファイル

  • MainActivity.java
    Handlerインスタンスを生成
    ForegroundServiceを実行
  • MyService.java
    Messageを作成してsendMessage
  • MyHandler sendMessageされたメッセージを処理

<注意>
ServiceをForegroundで呼び出すと、5秒以内にService側でstartForeground(int,notification)しないとクラッシュするのですが、コードが長くなるので今回は記載せず、クラッシュするようになってます、すみません。

MainActivity.java
public class MainActivity extends AppCompatActivity {
    public static TextView textView;

    // Handlerオブジェクト生成して、MessageQueueとLooperをUIスレッドに!
    // これでHandleオブジェクトに送られたメッセージをMessageQueueに格納し
    // Looperが勝手に取り出して処理するよ!(処理の内容は、MyHandler.classに定義しとくね!)
    static MyHandler myHandler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.textView);

        // Intentオブジェクトを生成してServiceを呼び出すよ!
        Intent intent = new Intent(this,MyService.class);
        startForegroundService(intent);

    }
}
MyService.java
public class MyService extends Service {
    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent,int flags,int startid){
        // MainActivityのHandleオブジェクトにメッセージを載せてsendMessage!!
        // 送り先はUIスレッドのMessageQueue!
        // あとはLooperがMyHandler.classを実行して処理するんだね!!
        Message message = MainActivity.myHandler.obtainMessage(1);
        MainActivity.myHandler.sendMessage(message);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
MyHandler.java
// UIスレッドのMessageQueueに格納されたメッセージを
// UIスレッドのLooperが、以下のhandleMessage()を実行処理するよ!

public class MyHandler extends Handler {
    @Override
    public void handleMessage(Message message){
        String msg = String.valueOf(message.what);
        MainActivity.textView.setText(msg);
    }
}
Handler Runnable Service でのタイマー事例のサンプルコード

参考にさせていただきました。ありがとうございます。こちら
でもどうして、`myHandler.postDelayed(runnnable,milli)が2回呼ばれてるのか、ちょっとわからなかった。

<概要>
notification処理はしてないので、すぐにクラッシュします。
アプリ起動で自動的にForegroundServiceへ
ForegroundServiceで、handler.postDeployed(runnnable,mill)でタイマー処理
スタートボタンでカウント再開
ストップボタンでカウント停止

MainActivity.java
public class MainActivity extends AppCompatActivity {
    public static TextView textView;
    static Handler myHandler = new Handler();
    boolean signal =false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnStart= findViewById(R.id.btnStart);
        Button btnStop = findViewById(R.id.btnStop);
        textView = findViewById(R.id.textView);

        final Intent intent = new Intent(this,MyService.class);
        startForegroundService(intent);

        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(signal == false){
                    MyService.runnable.run();
                    signal = true;
                }
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myHandler.removeCallbacks(MyService.runnable);
                signal = false;
            }
        });
    }
}

MyService.java
public class MyService extends Service {
    static Runnable runnable;
    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startid) {

        runnable = new Runnable() {
            int i=0;
            @Override
            public void run() {
                i++;
                String st = String.valueOf(i);
                MainActivity.textView.setText(st);
                MainActivity.myHandler.postDelayed(this,1000);
            }
        };

//        MainActivity.myHandler.postDelayed(runnable,1000);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2