メインスレッド以外でUIを変更する方法

Androidアプリにおいて、メイン(UI)スレッド以外のスレッドで、UIを変更しようとすると、実行時に下記の例外が発生する。
これは「UI部品の操作はUIスレッドから行わなければならない」というAndroidの制約のためである。

今回は、メイン(UI)スレッド以外から、UI操作を行う方法を紹介する。

<発生する例外>

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
  at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6566)
  at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:911)
  at android.view.View.requestLayout(View.java:18814)
  at android.view.View.requestLayout(View.java:18814)
  at android.view.View.requestLayout(View.java:18814)
  ...

<例外が発生するソースコード>

MainActivity.java

package biz.accele.samplethreadui;

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

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        final Button   btn1;
        final TextView txt1;

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn1 = (Button)findViewById(R.id.btn1);
        txt1 = (TextView)findViewById(R.id.txt1);

        btn1.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                // 別スレッドを実行
                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        // 別スレッドでUIを変更しようとしている
                        txt1.setText("本日は晴天なり");
                    }
                }).start();
            }
        });
    }
}

activity_main.xml

<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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Go"/>

    <TextView
        android:id="@+id/txt1"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

<例外を回避して、別スレッドからUIを変更する方法>

具体的な方法としては、Handlerを使用して、別スレッドからメイン(UI)スレッドに処理を依頼することになる。

MainActivity.java

package biz.accele.samplethreadui;

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

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        final Button   btn1;
        final TextView txt1;

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn1 = (Button)findViewById(R.id.btn1);
        txt1 = (TextView)findViewById(R.id.txt1);

        // メイン(UI)スレッドでHandlerのインスタンスを生成する
        final Handler handler = new Handler();

        btn1.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                // 別スレッドを実行
                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        // Handlerを使用してメイン(UI)スレッドに処理を依頼する
                        handler.post(new Runnable() {
                            @Override
                            public void run() {

                                txt1.setText("本日は晴天なり");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

activity_main.xml

変更なし

今回の例の様にUI上のテキストを変更するために別スレッドにすることは無いと思うが、時間のかかる処理を別スレッドで実行し、その結果をUI上に反映したいことは、よくあるケースである。
その際は今回紹介した方法を試して頂きたい。

Enjoy Programing!!