Android ハンドラのチュートリアルと例題
Handlerは、
android.osパッケージで定義された
threadingクラスで、
threadの
MessageQueueに関連付けられた
Messageや
Runnable`オブジェクトを送信したり処理したりすることができます。
まず、Handler
のインスタンスを作成します。そして、そのインスタンスは1つの thread
とその thread
のメッセージキューに関連付けられます。作成したHandler
は、それを作成したthread
のthread
やメッセージ・キューに関連付けられます。そのため、メッセージや runnables
をそのメッセージキューに配信したり、メッセージキューから出てきたメッセージを実行したりすることができます。
ハンドラ`の用途
ハンドラ`には主に2つの用途があります。
- 将来実行する必要のあるメッセージや
runnables
をスケジューリングする。 -
- バックグラウンドの
thread
で実行する必要のあるアクションをエンキューする。
- バックグラウンドの
Handler
とLooper
について
ハンドラは、アンドロイドでスレッド
を行う際に、インフラレベルで基本となるクラスです。このクラスは Looper
と連携して動作します。これらのクラスは、メインの thread
が行うすべてのこと(Activity
ライフサイクルメソッドの呼び出しを含む)を支えます。
ルーパーは、メッセージループである スレッド
に作業をディスパッチすることを担当します。一方で、Handler
は2つの役割を果たします。
- まず、メッセージを
Looper
のキューに送信するためのインターフェイスを提供します。
キューにメッセージを送信するインターフェースを提供する。
2.第二に、Looper
によってメッセージがディスパッチされたときに、それらのメッセージを処理するためのコールバックを実装する。
各Handler
は、1つのLooper
にバインドされ、ひいては、1つのthread
とそのLooper
のMessageQueueにバインドされます。
先ほど、Handler
はLooper
のスレッドに仕事を依頼するインタフェースを提供すると言いました。それとは別に、Handler
は送信されたメッセージを処理するコードも定義します。
例えば以下のコードでは、MyHandler
クラスがHandler
のhandleMessage()
メソッドをオーバーライドしています。
ここに、メッセージを処理するコードを書きます。
public class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// your message handling code here
}
}
MyThread」という名前の「スレッド」を作成したとしましょう。次に、MyThread
というスレッド
の中に、デフォルトのコンストラクタ
Handler
を使ってハンドラ
を作成します。
コンストラクタHandler()
を使って、MyThread
の中にハンドラ
を作成します。
したがって、myHandler
はメインのthread
のLooper
ではなく、現在のthread
のLooper
にアタッチされることになります。
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
thread
は Looper
クラスの loop()
メソッドの中で、キューにメッセージが追加されるのを待ちます。
その後、別の thread
がそのキューにメッセージを追加することができます。これには submit()
メソッドを使用します。
これが起こると、待っている thread
は handler
の handleMessage()
メソッドを呼び出して、メッセージをターゲットの MyHandler
にディスパッチします。
Handlerインスタンス/オブジェクトは、どの
threadからでも
Handlerクラスにメッセージを送ることができ、その結果、メッセージは常に
Looperの
threadにディスパッチされ、適切な
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
の使い方
この簡単なサンプルでは、Handler
のpostDelayed()
メソッドを使って、ウェブビューを更新したいと思います。ウェブビューを更新するには、そのウェブビューの 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オブジェクトを渡します。ここではHandler
のpostDelayed
メソッドを使います。ここでは匿名クラスの 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. キーボードを開くには Handler
の post()
メソッドを使用します。
ここでは、Handler
のpost()
メソッドを使ってキーボードを開く方法を紹介します。
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);
}
以下は、同時実行の thread
に handler
付きのタスクをポストする方法です。
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();
}
以下は、code>@UiThread</code アノテーションを使って、このウィンドウとテキストビューを使ってタスクをポストする方法です。
@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);
}
また、バックグラウンドの thread
で handler
を使ってタスクをポストする方法は以下の通りです。
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();
}
ここでは、漏れのない Handler
と Runnable
を使ってタスクをポストする方法を紹介します。
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つのインスタンスフィールドを管理します。
-
Handler
-android.os
パッケージで定義されたクラスで、thread
のMessageQueue
に関連付けられたMessage
とRunnable
オブジェクトを送信および処理することができます。
-
ProgressBar
- 進捗状況を表示するためのウィジェットです。
-
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
の例
この例では、Handler
を Timer
と組み合わせて使用する方法を説明します。
ステップ 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
が小さく見えても、オーナークラスを間接的に参照しています。オーナークラスは通常 Activity
や Fragment
のような大きなクラスです。
詳しくはこちらをご覧ください。
解決策 - 弱い Handler
を使う
weak handler
とは?
これは、
android.os.Handler
をより安全に実装したものです。
WeakHandlerは、
android.os.Handlerよりも厄介で、
runnablesやメッセージへの
WeakReferencesを保持し、
WeakHandler`インスタンスが参照されなくなると、GCがそれらを収集する可能性があります。
どうやって使うの?
ステップ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. | 続きを読む |