Android ハンドラのチュートリアルと例題

Handlerは、android.osパッケージで定義されたthreadingクラスで、threadMessageQueueに関連付けられたMessageRunnable`オブジェクトを送信したり処理したりすることができます。

まず、Handlerのインスタンスを作成します。そして、そのインスタンスは1つの thread とその thread のメッセージキューに関連付けられます。作成したHandlerは、それを作成したthreadthreadやメッセージ・キューに関連付けられます。そのため、メッセージや runnables をそのメッセージキューに配信したり、メッセージキューから出てきたメッセージを実行したりすることができます。

ハンドラ`の用途

ハンドラ`には主に2つの用途があります。

  1. 将来実行する必要のあるメッセージや runnables をスケジューリングする。
    1. バックグラウンドの thread で実行する必要のあるアクションをエンキューする。

HandlerLooperについて

ハンドラは、アンドロイドでスレッドを行う際に、インフラレベルで基本となるクラスです。このクラスは Looper と連携して動作します。これらのクラスは、メインの thread が行うすべてのこと(Activity ライフサイクルメソッドの呼び出しを含む)を支えます。

ルーパーは、メッセージループである スレッド に作業をディスパッチすることを担当します。一方で、Handlerは2つの役割を果たします。

  1. まず、メッセージを Looper のキューに送信するためのインターフェイスを提供します。
    キューにメッセージを送信するインターフェースを提供する。
    2.第二に、Looperによってメッセージがディスパッチされたときに、それらのメッセージを処理するためのコールバックを実装する。

Handlerは、1つのLooperにバインドされ、ひいては、1つのthreadとそのLooperのMessageQueueにバインドされます。

先ほど、HandlerLooperのスレッドに仕事を依頼するインタフェースを提供すると言いました。それとは別に、Handlerは送信されたメッセージを処理するコードも定義します。

例えば以下のコードでは、MyHandlerクラスがHandlerhandleMessage()メソッドをオーバーライドしています。

ここに、メッセージを処理するコードを書きます。

public class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        // your message handling code here
    }
}

MyThread」という名前の「スレッド」を作成したとしましょう。次に、MyThreadというスレッドの中に、デフォルトのコンストラクタHandlerを使ってハンドラを作成します。
コンストラクタHandler()を使って、MyThreadの中にハンドラを作成します。

したがって、myHandlerはメインのthreadLooperではなく、現在のthreadLooperにアタッチされることになります。

public class MyThread extends Thread{
    private Handler myHandler;
    @Override
    public void run() {
        Looper.prepare();
        myHandler = new MyHandler();
        Looper.loop();
    }
    public Handler getHandler(){
        return myHandler;
    }
}

起動すると、Looper threadLooper クラスの loop() メソッドの中で、キューにメッセージが追加されるのを待ちます。

その後、別の thread がそのキューにメッセージを追加することができます。これには submit() メソッドを使用します。

これが起こると、待っている threadhandlerhandleMessage() メソッドを呼び出して、メッセージをターゲットの MyHandler にディスパッチします。

Handlerインスタンス/オブジェクトは、どのthreadからでもHandlerクラスにメッセージを送ることができ、その結果、メッセージは常にLooperthreadにディスパッチされ、適切なHandler`によって処理されることになります。

Handler の簡単な例

それでは、Handlerの簡単な例、スニペット、HowTosを見てみましょう。

1. 1. Handler を使ってスプラッシュアクティビティを表示する方法

今回の目的は、Handlerを使ってスプラッシュアクティビティを表示することです。

以下のレイアウトが activity_splash としてあるとします。イメージビューといくつかのテキストビューがあります。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout

    android_id="@+id/activity_splash"
    android_layout_width="match_parent"
    android_layout_height="match_parent"
    tools_context="com.pchef.cc.personalchef.Splash">

    <ImageView
        android_layout_width="match_parent"
        android_layout_height="match_parent"
        android_id="@+id/s_img"
        android_alpha="0.7"
        android_src="@drawable/splash"
        android_scaleType="centerCrop"/>

    <LinearLayout
        android_layout_width="match_parent"
        android_layout_height="wrap_content"
        android_orientation="vertical"
        android_layout_centerInParent="true">

        <TextView
            android_layout_width="200dp"
            android_layout_height="wrap_content"
            android_text="Personal Chef"
            android_layout_gravity="center"
            android_gravity="center"
            android_textColor="#fff"
            android_fontFamily="cursive"
            android_textStyle="bold|italic"
            android_layout_marginBottom="50dp"
            android_textSize="60dp"/>

        <TextView
            android_layout_width="match_parent"
            android_layout_height="wrap_content"
            android_gravity="center"
            android_textColor="#fff"
            android_textSize="22dp"
            android_fontFamily="sans-serif-condensed"
            android_id="@+id/tv1"
            android_text="Dont know what to cook ?"
            />

        <TextView
            android_layout_width="match_parent"
            android_layout_height="wrap_content"
            android_id="@+id/tv2"
            android_gravity="center"
            android_layout_marginLeft="20dp"
            android_layout_marginRight="20dp"
            android_textSize="18dp"
            android_textColor="#fff"

            android_fontFamily="sans-serif-condensed"
            android_text="Our Personal Chef Can help you"
            android_layout_marginTop="14dp" />

    </LinearLayout>

</RelativeLayout>

次に、「SpashActivity」を作成します。これはAppCompatActivityから派生したものです。このアクティビティは AppCompatActivity から派生したもので、Handler 自体や、イメージローダーのライブラリである Glide など、いくつかのインポートを行っています。

import android.content.Intent;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.Spinner;

import com.bumptech.glide.Glide;

import java.util.concurrent.TimeUnit;

public class Splash extends AppCompatActivity {...}

背景にはImageViewを使用します。

    ImageView background;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        background = (ImageView) findViewById(R.id.s_img);

Glideを使って画像をイメージビューに読み込みます。

        Glide.with(this)
                .load(R.drawable.splash)
                .into(background);

最後に、handlerのインスタンスを作成します。

        final Handler handler = new Handler();

そして、run()メソッドを実装するRunnableを渡して、postDelayed()メソッドを呼び出します。

run()`メソッドの中で、バックグラウンドの作業を行います。遅延時間をミリ秒単位で渡していることにも注意してください。

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //Do something after delay
                finish();
                startActivity(new Intent(Splash.this, Home.class));
            }
        }, 3000);
    }

}
3. WebViewを更新するためのHandlerの使い方

この簡単なサンプルでは、HandlerpostDelayed()メソッドを使って、ウェブビューを更新したいと思います。ウェブビューを更新するには、そのウェブビューの reload() メソッドを使用します。遅延時間をミリ秒単位で渡します。

void refreshWebPage(){
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                LightningView webview = getCurrentWebView();
                if (webview != null) {
                    webview.reload();
        }
            }
        }, 200);
    }
4. アニメーションを読み込むためのHandlerの使い方

Androidはアニメーションが豊富なフレームワークです。ハナドラがあることで、アニメーションの使用が容易になりました。ここでは、Handlerを使ってアニメーションを読み込む方法を示すサンプルスニペットを見てみましょう。
アニメーションさせるビュー、アニメーションのリソースID、遅延時間、Contextオブジェクトを渡します。ここではHandlerpostDelayedメソッドを使います。ここでは匿名クラスの Runnable と遅延時間を渡します。

public static void animationIn(final View view, final int animation, int delayTime, final Context context) {
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        public void run() {
            Animation inAnimation = AnimationUtils.loadAnimation(
                    context.getApplicationContext(), animation);
            view.setAnimation(inAnimation);
            view.setVisibility(View.VISIBLE);
        }
    }, delayTime);
}

アニメーションを入れるだけではなく、アニメーションを消すこともできます。

 public static void animationOut(final View view, final int animation, int delayTime, final boolean isViewGone, final Context context) {
        view.setVisibility(View.VISIBLE);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            public void run() {
                Animation outAnimation = AnimationUtils.loadAnimation(
                        context.getApplicationContext(), animation);
                view.setAnimation(outAnimation);
                if (isViewGone)
                    view.setVisibility(View.GONE);
                else
                    view.setVisibility(View.INVISIBLE);
            }
        }, delayTime);
    }

Handler post()

Handler には post() というメソッドがあります。

post(Runnable r)

ご覧の通り、このメソッドは runnable オブジェクトを受け取ります。post()メソッドは、Runnable` rをメッセージキューに追加するように**指示します。

このメソッドを使ったいくつかの実例を見てみましょう。

1. キーボードを開くには Handlerpost() メソッドを使用します。

ここでは、Handlerpost()メソッドを使ってキーボードを開く方法を紹介します。

public void openIME(final EditText v) {
    final boolean focus = v.requestFocus();
    if (v.hasFocus()) {
        final Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                InputMethodManager mgr = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
                boolean result = mgr.showSoftInput(v, InputMethodManager.SHOW_FORCED);
                log.debug("openIME " + focus + " " + result);
            }
        });
    }
}
2. ハンドラpost()`メソッドとエクゼキュータの組み合わせ

エクゼキュータは、サブミットされた Runnable のタスクを実行するオブジェクトです。

public DownloadStatusDeliveryImpl(final Handler handler) {
    this.mDownloadStatusPoster = new Executor() {
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}
3. フル Handler 再利用可能なユーティリティクラス

ここでは、Handler の使い方をさらに追求したユーティリティクラスの例を紹介します。

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;

public final class HandlerUtils {

    private HandlerUtils() {
    }

    public static void uiPost(Runnable action) {
        if (action == null) {
            return;
        }
        uiHandler().post(action);
    }

    public static void uiPostDelayed(Runnable action, long delayMillis) {
        if (action == null) {
            return;
        }
        uiHandler().postDelayed(action, delayMillis);
    }

    public static void uiRemoveCallbacks(Runnable action) {
        uiHandler().removeCallbacks(action);
    }

    public static void threadPost(Runnable action) {
        if (action == null) {
            return;
        }
        threadHandler().post(action);
    }

    public static void threadPostDelayed(Runnable action, long delayMillis) {
        if (action == null) {
            return;
        }
        threadHandler().postDelayed(action, delayMillis);
    }

    public static void threadRemoveCallbacks(Runnable action) {
        threadHandler().removeCallbacks(action);
    }

    private static Handler uiHandler() {
        return Holder.handler;
    }

    private interface Holder {
        Handler handler = new Handler(Looper.getMainLooper());
    }

    private static Handler sThreadHandler;

    private static synchronized Handler threadHandler() {
        if (sThreadHandler == null) {
            HandlerThread thread = new HandlerThread("HandlerUtils.sThreadHandler");
            thread.start();
            sThreadHandler = new Handler(thread.getLooper());
        }
        return sThreadHandler;
    }
}

4. メインの thread で指定の Runnable を実行する方法

UI thread で実行するアクションを渡します。

public final class HandlerUtils {
    private static Handler handler = new Handler(Looper.getMainLooper());

    public static void runOnUiThread(Runnable action) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            action.run();
        }
        else {
            handler.post(action);
        }
    }
}

いくつかの例を見てみましょう。

例 1: 様々な Handler の例

この例では、android.os.Handler クラスを使用する際に遭遇するであろう、さまざまな実用的な使用シナリオを探ります。

Step 1: Java または Kotlin プロジェクトの作成

アンドロイドスタジオで Java プロジェクトを作成します。また、kotlin プロジェクトを作成し、コードコンバータを使用して java から kotlin に変換することもできます。

ステップ 2: 依存関係

このプロジェクトに必要な依存関係はありません。

ステップ 3: パーミッション

このプロジェクトに必要なパーミッションはありません。

Step 4: デザインレイアウト

メインのアクティビティレイアウトにテキストビューを多数追加します。また、ボタンも追加します。縦方向のリニアなレイアウトでまとめることができます。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.lomza.examples.handlers.MainActivity">

    <TextView
        android:id="@+id/tv_01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_03"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_04"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_05"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_06"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/button_07"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Post with a view Handler"/>

    <TextView
        android:id="@+id/tv_07"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

Step 5: コードを書く

まず、AppCompatActivityを拡張して、メインのアクティビティを作成します。

メインのアクティビティには、ハンドラー、ランナブル、メッセージの使用方法を示すメソッドがあります。


public class MainActivity extends AppCompatActivity {
    private TextView tv01;
    private TextView tv02;
    private TextView tv03;
    private TextView tv04;
    private TextView tv05;
    private TextView tv06;
    private TextView tv07;
    private Button button07;

    private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
    private Handler currentThreadHandler;
    private Handler decorViewHandler;
    private final ChangeTextHandler customHandler = new ChangeTextHandler(this);
    private final Handler notLeakyHandler = new Handler();
    private final Runnable notLeakyRunnable = new ChangeTextRunnable(this, "Hi from leak-safe Runnable!");

    private static final String TAG = "[Handlers]";
    private static final String BUNDLE_KEY = "greeting";

まず、以下のコードを使って、通常の thread でタスクをポストします。

    private void postTaskWithOrdinaryThread() {
        Runnable notForHandlerTask = new Runnable() {
            @Override
            public void run() {
                // as written here - https://developer.android.com/guide/components/processes-and-threads.html#Threads,
                // do NOT access the Android UI toolkit from outside the UI thread, as sth unexpected may happen
                // for instance, you might get android.view.ViewRootImpl$CalledFromWrongThreadException
                tv01.setText("Hi from Thread(runnable)!");
                // if you call thread.run(), this would be TRUE, as no new Thread would be created
                // read the explanation here - http://stackoverflow.com/a/35264580/655275
                Log.d(TAG, "[postTaskWithOrdinaryThread] Current looper is a main thread (UI) looper: "
                        + (Looper.myLooper() == Looper.getMainLooper()));
            }
        };
        Thread thread = new Thread(notForHandlerTask);
        thread.start();
    }

そして、handlerを使ってタスクをメインのthreadにポストする方法を紹介します。

    @UiThread
    private void postTaskWithHandlerOnMainThread() {
        Runnable mainThreadTask = new Runnable() {
            @Override
            public void run() {
                // since we use Looper.getMainLooper(), we can safely update the UI from here
                tv02.setText("Hi from Handler(Looper.getMainLooper()) post!");
                Log.d(TAG, "[postTaskWithHandlerOnMainThread] Current looper is a main thread (UI) looper: "
                        + (Looper.myLooper() == Looper.getMainLooper()));
            }
        };
        mainThreadHandler.post(mainThreadTask);
    }

以下は、同時実行の threadhandler 付きのタスクをポストする方法です。

    private void postTaskWithHandlerOnCurrentThread() {
        currentThreadHandler = new Handler();
        Runnable currentThreadTask = new Runnable() {
            @Override
            public void run() {
                // since we use current thread (and from onCreate(), it's the UI thread), we can safely update the UI from here
                tv03.setText("Hi from Handler() post!");
                Log.d(TAG, "[postTaskWithHandlerOnCurrentThread] Current looper is a main thread (UI) looper: "
                        + (Looper.myLooper() == Looper.getMainLooper()));
            }
        };
        currentThreadHandler.post(currentThreadTask);

    }

また、バックグラウンドの thread 内でタスクをポストする方法は以下のとおりです。

    private void postTaskInsideBackgroundTask() {
        Thread backgroundThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // pretend to do something "background-y"
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                mainThreadHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        tv04.setText("Hi from a Handler inside of a background Thread!");
                    }
                });
            }
        });

        backgroundThread.start();
    }

以下は、@UiThread アノテーションを使って、このウィンドウとテキストビューを使ってタスクをポストする方法です。

    @UiThread
    private void postTaskWithThisWindowAndTextViewHandlers() {
        // this line will return null from onCreate() (and even if called from onResume()) and cause NPE when trying to post();
        // this is because the handler isn't attached to the view if it's not fully visible
        decorViewHandler = getWindow().getDecorView().getHandler();
        Runnable decorViewTask = new Runnable() {
            @Override
            public void run() {
                // View's post() uses UI handler internally
                tv07.post(new Runnable() {
                    @Override
                    public void run() {
                        tv07.setText("Hi from getWindow().getDecorView().getHandler() > TextView.post()!");
                        Log.d(TAG, "[postTaskWithThisWindowAndTextViewHandlers] Current looper is a main thread (UI) looper: "
                                + (Looper.myLooper() == Looper.getMainLooper()));
                    }
                });
            }
        };
        decorViewHandler.post(decorViewTask);
    }

また、バックグラウンドの threadhandler を使ってタスクをポストする方法は以下の通りです。

    private void postTaskWithHandlerOnBackgroundThread() {
        final Runnable pretendsToBeSomeOtherTask = new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "[postTaskWithHandlerOnBackgroundThread] Is there a looper? " + (Looper.myLooper() != null));
                // you'll get java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                // this is because Handlers need to have a Looper associated with them; when the task was run on the main thread,
                // main thread looper was used, but if we call this from the background thread, there is NO looper to use
                // read more - https://developer.android.com/reference/android/os/Looper.html

                postTaskWithHandlerOnCurrentThread();
            }
        };
        final Thread thread = new Thread(pretendsToBeSomeOtherTask);
        thread.start();
    }

ここでは、漏れのない HandlerRunnable を使ってタスクをポストする方法を紹介します。

    private void postTaskWithNotLeakyHandlerAndRunnable() {
        // in order to eliminate leaks, both Handler and Runnable should be static
        // static inner classes do not hold an implicit reference to the outer class
        // it seems like a lot of useless work, but it's the most accurate and bug-free way
        // read more - http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
        notLeakyHandler.postDelayed(notLeakyRunnable, 500);
    }

以下がフルコードです。

MainActivity.javaを参照してください。

package com.lomza.examples.handlers;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.UiThread;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;

/**
 * Main activity with methods to demonstrates the usage of Handlers, Runnables, and Messages :)
 *
 * @author Antonina Tkachuk
 */
public class MainActivity extends AppCompatActivity {
    private TextView tv01;
    private TextView tv02;
    private TextView tv03;
    private TextView tv04;
    private TextView tv05;
    private TextView tv06;
    private TextView tv07;
    private Button button07;

    private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
    private Handler currentThreadHandler;
    private Handler decorViewHandler;
    private final ChangeTextHandler customHandler = new ChangeTextHandler(this);
    private final Handler notLeakyHandler = new Handler();
    private final Runnable notLeakyRunnable = new ChangeTextRunnable(this, "Hi from leak-safe Runnable!");

    private static final String TAG = "[Handlers]";
    private static final String BUNDLE_KEY = "greeting";

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

        initView();
    }

    @Override
    protected void onStart() {
        super.onStart();

        postTaskWithOrdinaryThread();
        postTaskWithHandlerOnMainThread();
        postTaskWithHandlerOnCurrentThread();
        postTaskInsideBackgroundTask();
        //postTaskWithHandlerOnBackgroundThread();
        postTaskWithNotLeakyHandlerAndRunnable();
        sendMessageToChangeTextHandler();
    }

    @Override
    protected void onStop() {
        super.onStop();
        // when posting Runnables or Messages, always remember to call removeCallbacks() or removeMessages()
        // or removeCallbacksAndMessages() for both.
        // this ensures that all pending tasks don't execute in vain; for instance, the user has left our activity
        // and he doesn't really care if some job is finished or not, so it's our responsibility to cancel it

        // pass null to remove ALL callbacks and messages
        mainThreadHandler.removeCallbacks(null);
        currentThreadHandler.removeCallbacks(null);
        if (decorViewHandler != null)
            decorViewHandler.removeCallbacks(null);
        customHandler.removeCallbacksAndMessages(null);
        notLeakyHandler.removeCallbacks(notLeakyRunnable);
    }

    private void initView() {
        tv01 = (TextView) findViewById(R.id.tv_01);
        tv02 = (TextView) findViewById(R.id.tv_02);
        tv03 = (TextView) findViewById(R.id.tv_03);
        tv04 = (TextView) findViewById(R.id.tv_04);
        tv05 = (TextView) findViewById(R.id.tv_05);
        tv06 = (TextView) findViewById(R.id.tv_06);
        tv07 = (TextView) findViewById(R.id.tv_07);
        button07 = (Button) findViewById(R.id.button_07);
        button07.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                postTaskWithThisWindowAndTextViewHandlers();
            }
        });
    }

    private void postTaskWithOrdinaryThread() {
        Runnable notForHandlerTask = new Runnable() {
            @Override
            public void run() {
                // as written here - https://developer.android.com/guide/components/processes-and-threads.html#Threads,
                // do NOT access the Android UI toolkit from outside the UI thread, as sth unexpected may happen
                // for instance, you might get android.view.ViewRootImpl$CalledFromWrongThreadException
                tv01.setText("Hi from Thread(runnable)!");
                // if you call thread.run(), this would be TRUE, as no new Thread would be created
                // read the explanation here - http://stackoverflow.com/a/35264580/655275
                Log.d(TAG, "[postTaskWithOrdinaryThread] Current looper is a main thread (UI) looper: "
                        + (Looper.myLooper() == Looper.getMainLooper()));
            }
        };
        Thread thread = new Thread(notForHandlerTask);
        thread.start();
    }

    @UiThread
    private void postTaskWithHandlerOnMainThread() {
        Runnable mainThreadTask = new Runnable() {
            @Override
            public void run() {
                // since we use Looper.getMainLooper(), we can safely update the UI from here
                tv02.setText("Hi from Handler(Looper.getMainLooper()) post!");
                Log.d(TAG, "[postTaskWithHandlerOnMainThread] Current looper is a main thread (UI) looper: "
                        + (Looper.myLooper() == Looper.getMainLooper()));
            }
        };
        mainThreadHandler.post(mainThreadTask);
    }

    private void postTaskWithHandlerOnCurrentThread() {
        currentThreadHandler = new Handler();
        Runnable currentThreadTask = new Runnable() {
            @Override
            public void run() {
                // since we use current thread (and from onCreate(), it's the UI thread), we can safely update the UI from here
                tv03.setText("Hi from Handler() post!");
                Log.d(TAG, "[postTaskWithHandlerOnCurrentThread] Current looper is a main thread (UI) looper: "
                        + (Looper.myLooper() == Looper.getMainLooper()));
            }
        };
        currentThreadHandler.post(currentThreadTask);

    }

    private void postTaskInsideBackgroundTask() {
        Thread backgroundThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // pretend to do something "background-y"
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                mainThreadHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        tv04.setText("Hi from a Handler inside of a background Thread!");
                    }
                });
            }
        });

        backgroundThread.start();
    }

    @UiThread
    private void postTaskWithThisWindowAndTextViewHandlers() {
        // this line will return null from onCreate() (and even if called from onResume()) and cause NPE when trying to post();
        // this is because the handler isn't attached to the view if it's not fully visible
        decorViewHandler = getWindow().getDecorView().getHandler();
        Runnable decorViewTask = new Runnable() {
            @Override
            public void run() {
                // View's post() uses UI handler internally
                tv07.post(new Runnable() {
                    @Override
                    public void run() {
                        tv07.setText("Hi from getWindow().getDecorView().getHandler() > TextView.post()!");
                        Log.d(TAG, "[postTaskWithThisWindowAndTextViewHandlers] Current looper is a main thread (UI) looper: "
                                + (Looper.myLooper() == Looper.getMainLooper()));
                    }
                });
            }
        };
        decorViewHandler.post(decorViewTask);
    }

    private void postTaskWithHandlerOnBackgroundThread() {
        final Runnable pretendsToBeSomeOtherTask = new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "[postTaskWithHandlerOnBackgroundThread] Is there a looper? " + (Looper.myLooper() != null));
                // you'll get java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                // this is because Handlers need to have a Looper associated with them; when the task was run on the main thread,
                // main thread looper was used, but if we call this from the background thread, there is NO looper to use
                // read more - https://developer.android.com/reference/android/os/Looper.html

                postTaskWithHandlerOnCurrentThread();
            }
        };
        final Thread thread = new Thread(pretendsToBeSomeOtherTask);
        thread.start();
    }

    private void postTaskWithNotLeakyHandlerAndRunnable() {
        // in order to eliminate leaks, both Handler and Runnable should be static
        // static inner classes do not hold an implicit reference to the outer class
        // it seems like a lot of useless work, but it's the most accurate and bug-free way
        // read more - http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
        notLeakyHandler.postDelayed(notLeakyRunnable, 500);
    }

    private static class ChangeTextRunnable implements Runnable {
        private final WeakReference<MainActivity> activity;
        private final String greetingMessage;

        public ChangeTextRunnable(MainActivity activity, String greetingMessage) {
            this.activity = new WeakReference<>(activity);
            this.greetingMessage = greetingMessage;
        }

        public void run() {
            if (greetingMessage == null) {
                Log.e(TAG, "The message is null ChangeTextRunnable.run()!");
                return;
            }

            MainActivity activity = this.activity.get();
            if (activity == null) {
                Log.e(TAG, "Activity is null ChangeTextRunnable.run()!");
                return;
            }

            activity.tv05.setText(greetingMessage);
        }
    }

    // === OBTAIN AND HANDLE A MESSAGE ===
    private void sendMessageToChangeTextHandler() {
        Message messageToSend = customHandler.obtainMessage();
        Bundle bundle = new Bundle();
        bundle.putString(BUNDLE_KEY, "Hi from custom inner Handler!");
        messageToSend.setData(bundle);
        messageToSend.what = 6;
        customHandler.sendMessage(messageToSend);
    }

    private static class ChangeTextHandler extends Handler {
        private final WeakReference<MainActivity> activity;

        public ChangeTextHandler(MainActivity activity) {
            this.activity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = this.activity.get();
            if (activity == null) {
                Log.e(TAG, "Activity is null ChangeTextHandler.handleMessage()!");
                return;
            }

            final String text = (String) msg.getData().get(BUNDLE_KEY);
            if (!TextUtils.isEmpty(text)) {
                switch (msg.what) {
                    case 6:
                        activity.tv06.setText(text);
                        break;
                    default:
                        activity.tv01.setText(text);
                        break;
                }
            }
        }
    }
    // === END - OBTAIN AND HANDLE A MESSAGE ===
}

実行

最後にプロジェクトを実行します。

参考文献

以下にコードの参考リンクを示します。

番号 リンク
1.|コードのダウンロード

例 2: Handler with ProgressBar の例

このチュートリアルでは、Handlerを使って、バックグラウンドのthreadからユーザーインターフェースのthreadに更新をポストする方法を見てみましょう。

ボタンをクリックして、バックグラウンドで重い作業をしている様子をシミュレートします。その間、作業の進行に合わせてプログレスバーを更新することができます。

(a). MainActivity.java

これはメインのアクティビティです。AppCompatActivityから派生しています。追加したインポートの1つは、android.osパッケージのHandler`です。

...
import android.os.Handler;
...

3つのインスタンスフィールドを管理します。

    1. Handlerandroid.os パッケージで定義されたクラスで、threadMessageQueue に関連付けられた MessageRunnable オブジェクトを送信および処理することができます。
    1. ProgressBar – 進捗状況を表示するためのウィジェットです。
    1. Button – アクションボタンです。

これがフルコードです。

package info.camposha.mrhandler;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;
    private ProgressBar mProgressBar;
    private Button mStartButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        mProgressBar = findViewById(R.id.mProgressBar);
        mStartButton = findViewById(R.id.startBtn);
        mStartButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                performStuff();
            }
        });
    }
    private void performStuff() {
        //Simulate Heavy task in background thread
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 30; i++) {
                    final int currentProgressCount = i;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                //Post updates to the User Interface
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mProgressBar.setProgress(currentProgressCount);
                        }
                    });
                }
            }
        }).start();
    }
}

(b). activity_main.xml

これは、アクティビティのメインレイアウトです。ルートにはLinearLayoutが配置されています。この要素では、子要素を水平または垂直に直線的に配置することができます。また、アプリのヘッダーテキストを表示するTextViewもあります。また、進捗状況を表示するプログレスバーもあります。また、クリックすると「スレッド」を開始し、バックグラウンドの「スレッド」で作業を行うためのボタンも用意されています。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android_layout_width="match_parent"
    android_layout_height="match_parent"

    android_gravity="center"
    android_orientation="vertical"
    tools_context=".MainActivity">

    <TextView
        android_id="@+id/headerLabel"
        android_layout_width="wrap_content"
        android_layout_height="wrap_content"
        android_layout_alignParentTop="true"
        android_layout_centerHorizontal="true"
        android_fontFamily="casual"
        android_text="Handler ProgressBar"
        android_textAllCaps="true"
        android_textSize="24sp"
        android_textStyle="bold" />

    <ProgressBar
        android_id="@+id/mProgressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android_layout_width="match_parent"
        android_layout_height="wrap_content"
        android_layout_margin="10dp"
        android_indeterminate="false"
        android_max="10" />

    <Button
        android_id="@+id/startBtn"
        android_layout_width="wrap_content"
        android_layout_height="wrap_content"
        android_text="Start" />
</LinearLayout>

例 3: タイマーを使ったシンプルな Handler の例

この例では、HandlerTimer と組み合わせて使用する方法を説明します。

ステップ 1: 依存関係

この例ではサードパーティ製の依存関係は必要ありません。

ステップ 2: レイアウト

この例では、レイアウトは必要ありません。

ステップ 3: コードの作成

以下にコードの全文を示します。

HandlerActivity.java (英語)

package com.sdwfqin.sample.handler;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.sdwfqin.sample.R;

import java.lang.ref.WeakReference;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author zhangqin
 */
public class HandlerActivity extends AppCompatActivity {

    private static final String TAG = "HandlerActivity";
    private MyHandler mMyHandler;
    private Timer mTimer;

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

        mMyHandler = new MyHandler(this);

        mMyHandler.sendEmptyMessage(1);

        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                mMyHandler.sendEmptyMessage(2);
            }
        }, 1000, 1000);
        mMyHandler.sendEmptyMessage(3);
    }

    static class MyHandler extends Handler {

        private WeakReference<HandlerActivity> mActivity;

        public MyHandler(HandlerActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = mActivity.get();
            switch (msg.what) {
                case 1:
                    Log.e(TAG, "handlerA:case:1");
                    break;
                case 2:
                    Log.e(TAG, "handlerA:case:2");
                    break;
                case 3:
                    Log.e(TAG, "handlerA:case:3");
                    break;
                default:
                    break;
            }
        }
    }

    @Override
    protected void onDestroy() {
        mTimer.cancel();
        mMyHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

参考

コードを以下からダウンロードしてください。

番号 リンク
1.|コードのダウンロード

メモリセーフな`Handler

オリジナルのHandlerの実装では、実行のキューにHandlerへのハードリファレンスを常に保持します。また、android.os.HandlerにポストされたMessageやRunnable内のオブジェクトは、しばらくの間、ハードリファレンスされます。匿名の Runnable を作成して、大きなタイムアウトで postDelayed を呼び出すと、その Runnable はタイムアウトが過ぎるまでメモリに保持されます。たとえ Runnable が小さく見えても、オーナークラスを間接的に参照しています。オーナークラスは通常 ActivityFragment のような大きなクラスです。

詳しくはこちらをご覧ください。

解決策 – 弱い Handler を使う

weak handler とは?

これは、android.os.Handlerをより安全に実装したものです。

WeakHandlerは、android.os.Handlerよりも厄介で、runnablesやメッセージへのWeakReferencesを保持し、WeakHandler`インスタンスが参照されなくなると、GCがそれらを収集する可能性があります。

WeakHandler

どうやって使うの?

ステップ1: インストール

プロジェクトレベルの build.gradle ファイルで、以下のように Jitpack を maven url として登録します。

repositories {
    maven { url 'https://jitpack.io' }
}

次に、アプリレベルの build.gradle ファイルの dependencies クロージャの下に、implementation statement を追加します。


dependencies {
    implementation 'com.github.badoo:android-weak-handler:1.2'
}

Syncでインストールします。

Step 2: コードを書く

WeakHandler "は、"android.os.Handler "の代わりに使用することができます。使い方は、Handlerと同じです。以下はその例です。

ExampleActivity.javaを参照してください。

import com.badoo.mobile.util.WeakHandler;

public class ExampleActivity extends Activity {

    private WeakHandler handler; // We still need at least one hard reference to WeakHandler

    protected void onCreate(Bundle savedInstanceState) {
        handler = new WeakHandler();
        ...
    }

    private void onClick(View view) {
        handler.postDelayed(new Runnable() {
            view.setVisibility(View.INVISIBLE);
        }, 5000);
    }
}

参考文献

以下のリンクを参照してください。

番号 リンク
1. 続きを読む