Android 핸들러 튜토리얼 및 예제

Handler는 스레드의 MessageQueue와 관련된 MessageRunnable 객체를 보내고 처리할 수 있는 android.os 패키지에 정의된 스레딩 클래스입니다.

Handler 인스턴스를 생성하여 시작합니다. 그런 다음 해당 인스턴스는 단일 스레드 및 해당 스레드의 메시지 대기열과 연결됩니다. 방금 생성한 핸들러는 생성 중인 스레드의 스레드 또는 메시지 큐에 바인딩됩니다. 따라서 메시지와 실행 파일을 해당 메시지 대기열로 전달하고 메시지 대기열에서 나올 때 실행할 수 있습니다.

핸들러 사용

핸들러에는 두 가지 주요 용도가 있습니다.

  1. 앞으로 실행해야 하는 메시지 및 실행 파일의 예약.
  2. 백그라운드 스레드에서 수행해야 하는 작업을 대기열에 넣습니다.

핸들러와 루퍼

Handler는 인프라 수준에서 Android에서 스레딩을 수행하는 방법에 대한 기본 클래스입니다. 루퍼와 함께 작동합니다. 이들은 함께 활동 수명 주기 메서드 호출을 포함하여 기본 스레드가 수행하는 모든 작업을 뒷받침합니다.

‘루퍼’는 메시지 루프 스레드에 대한 디스패치 작업을 처리합니다. 반면 ‘Handler’는 두 가지 역할을 수행합니다.

  1. 먼저 Looper 대기열에 메시지를 제출하기 위한 인터페이스를 제공합니다. 2. 두 번째로 Looper가 메시지를 발송할 때 해당 메시지를 처리하기 위한 콜백을 구현합니다.

각 핸들러는 단일 루퍼와 확장하여 하나의 스레드와 루퍼 MessageQueue에 바인딩됩니다.

우리는 Handler가 Looper 스레드에 작업을 제출하는 인터페이스를 제공한다고 말했습니다. 그 외에도 Handler는 제출된 메시지를 처리하는 코드도 정의합니다.

예를 들어 다음 코드에서 MyHandler 클래스는 HandlerhandleMessage() 메서드를 재정의합니다.

여기에서 메시지 처리 코드를 작성합니다.

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

‘MyThread’라는 스레드를 생성했다고 가정해 보겠습니다. 그런 다음 기본 생성자 Handler()를 통해 MyThread 스레드 내부에 핸들러를 만들 수 있습니다.

따라서 myHandler는 메인 스레드의 Looper 대신 현재 스레드의 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 스레드는 대기열에 메시지가 추가될 때까지 Looper 클래스의 loop() 메서드 내에서 대기합니다.

그런 다음 다른 스레드가 해당 대기열에 메시지를 추가할 수 있습니다. 그것은 submit() 메소드를 사용하여 그렇게 할 수 있습니다.

이 경우 대기 스레드는 핸들러의 handleMessage() 메서드를 호출하여 대상 MyHandler에 메시지를 전달합니다.

Handler 인스턴스/객체를 사용하면 모든 스레드에서 Handler 클래스로 메시지를 보낼 수 있으며 결과적으로 항상 Looper 스레드로 전달되고 올바른 Handler에 의해 처리됩니다.

핸들러 빠른 예

Handler 빠른 예제, 스니펫 및 방법을 살펴보겠습니다.

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에서 파생됩니다. 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.with(this)
                .load(R.drawable.splash)
                .into(background);

마지막으로 핸들러를 인스턴스화합니다.

        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의 ‘postDelayed()’ 메서드를 사용하여 webview를 새로 고칩니다. webview의 reload() 메소드를 사용하여 webview를 참조합니다. 지연 시간을 밀리초 단위로 전달합니다.

void refreshWebPage(){
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                LightningView webview = getCurrentWebView();
                if (webview != null) {
                    webview.reload();
        }
            }
        }, 200);
    }
4. 핸들러를 사용하여 애니메이션을 로드하는 방법

Android는 애니메이션이 풍부한 프레임워크입니다. Hnadler가 있으면 애니메이션을 사용하는 기능이 더 쉬워졌습니다. 핸들러를 사용하여 애니메이션을 로드하는 방법을 보여줄 수 있는 샘플 스니펫을 살펴보겠습니다. 애니메이션 리소스 ID, 지연 시간 및 컨텍스트 개체에 대한 보기를 전달합니다. 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);
    }

핸들러 포스트()

핸들러에는 post()라는 메서드가 있습니다.

post(Runnable r)

보시다시피 이 메서드는 실행 가능한 개체를 사용합니다. 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. Executor를 사용한 핸들러의 post() 메소드

Executor는 제출된 Runnable 작업을 실행하는 개체입니다.

public DownloadStatusDeliveryImpl(final Handler handler) {
    this.mDownloadStatusPoster = new Executor() {
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}
3. 전체 핸들러 재사용 가능 유틸리티 클래스

다음은 핸들러 사용법을 자세히 조사하는 유틸리티 클래스의 예입니다.

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. 메인 스레드에서 지정된 Runnable을 실행하는 방법

UI 스레드에서 실행할 작업을 전달합니다.

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: 다양한 핸들러 예제

이 예제에서는 android.os.Handler 클래스를 사용하는 동안 접하게 될 다양한 실제 사용 시나리오를 탐색합니다.

1단계: Java 또는 Kotlin 프로젝트 만들기

안드로이드 스튜디오에서 자바 프로젝트를 생성합니다. kotlin을 만들고 코드 변환기를 사용하여 Java에서 kotlin으로 변환할 수도 있습니다.

2단계: 종속성

이 프로젝트에는 종속성이 필요하지 않습니다.

3단계: 권한

이 프로젝트에는 권한이 필요하지 않습니다.

4단계: 레이아웃 디자인

여기에는 기본 활동 레이아웃에 많은 텍스트 보기를 추가하는 작업이 포함됩니다. 버튼도 추가합니다. 수직 선형 레이아웃을 사용하여 구성할 수 있습니다.

활동_메인.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>

5단계: 코드 작성

그런 다음 코드를 작성했습니다. 먼저 ‘AppCompatActivity’를 확장하여 기본 활동을 만듭니다.

Handlers, Runnables 및 Messages의 사용법을 보여주는 메소드가 있는 Main 활동:


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";

먼저 다음 코드를 사용하여 일반 스레드로 작업을 게시할 수 있습니다.

    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 주석을 사용하여 이 창과 텍스트 보기로 작업을 게시하는 방법입니다.

    @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();
    }

누출이 없는 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. 코드 작성자 팔로우

예제 2: ProgressBar가 있는 핸들러 예제

이 튜토리얼에서는 핸들러를 사용하여 백그라운드 스레드에서 사용자 인터페이스 스레드로 업데이트를 게시하는 방법을 보고 싶습니다.

버튼을 클릭하고 백그라운드에서 무거운 작업을 시뮬레이션합니다. 한편 작업이 계속됨에 따라 진행률 표시줄을 업데이트할 수 있습니다.

(ㅏ). 메인 액티비티.자바

이것이 주요 활동입니다. AppCompatActivity에서 파생됩니다. 우리가 추가하는 가져오기 중 하나는 android.os 패키지의 Handler입니다.

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

우리는 세 개의 인스턴스 필드를 유지할 것입니다:

  1. 핸들러 – 스레드의 MessageQueue와 관련된 MessageRunnable 객체를 보내고 처리할 수 있는 android.os 패키지에 정의된 클래스입니다.
  2. ProgressBar – 진행 상황을 표시할 수 있는 위젯입니다.
  3. 버튼 – 작업 버튼.

전체 코드는 다음과 같습니다.

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();
    }
}

(ㄴ). 활동_메인.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: 타이머가 있는 간단한 핸들러 예제

이 예제는 Timer와 함께 Handler를 사용하는 방법을 탐구합니다.

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. [다운로드 코드](https://downgit.github.io/#/home?url=https://github.com/sdwfqin/AndroidSamples/tree/master/app/src/main/java/com/sdwfqin/sample /핸들러)
2. 코드 작성자 팔로우

메모리 안전 핸들러

Handler의 원래 구현은 항상 실행 대기열의 핸들러에 대한 하드 참조를 유지합니다. ‘android.os.Handler’에 게시된 Message 또는 Runnable의 모든 개체는 한동안 하드 참조됩니다. 익명의 Runnable을 생성하고 큰 시간 초과로 ‘postDelayed’를 호출하면 해당 Runnable은 시간 초과가 경과할 때까지 메모리에 유지됩니다. Runnable이 작게 보이더라도 일반적으로 Activity 또는 Fragment만큼 큰 소유자 클래스를 간접적으로 참조합니다.

자세한 내용은 여기를 참조하세요.

솔루션 – 약한 핸들러 사용

약한 핸들러란?

android.os.Handler의 메모리보다 안전한 구현입니다.

WeakHandlerandroid.os.Handler보다 까다롭습니다. WeakReferences를 실행 가능 파일과 메시지에 유지하고 WeakHandler 인스턴스가 더 이상 참조되지 않으면 GC가 이를 수집할 수 있습니다.

WeakHandler

어떻게 사용합니까?

1단계: 설치

다음과 같이 프로젝트 수준의 build.gradle 파일에 Jitpack을 maven url로 등록합니다.

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

그런 다음 앱 수준 build.gradle 파일의 종속성 클로저 아래에 구현 문을 추가합니다.


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

동기화하여 설치합니다.

2단계: 코드 작성

단순히 android.os.Handler의 드롭인 교체로 WeakHandler를 사용할 수 있습니다. ‘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. 자세히 알아보기