1. ホーム
  2. Android

Androidにおけるメッセージの仕組みの分析 - 解決策。ビュー階層を作成した元のスレッドだけが、そのビューに触れることができる。

2022-02-13 04:28:08
Androidのメッセージングメカニズムを分析する前に、コードの一部分を見てみましょう。 
public class MainActivity extends Activity implements View.OnClickListener {
	
	private TextView stateText;
	private Button btn;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        stateText = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
        
        btn.setOnClickListener(this);
    }

	@Override
	public void onClick(View v) {
		new WorkThread().start();
	}
	
	// Work Threads
	private class WorkThread extends Thread {
		@Override
		public void run() {
			//...... Process the more time-consuming operations
			
			//change state after processing is complete
			stateText.setText("completed");
		}
	}
}

このコードは正常に見えるかもしれませんが、実行すると致命的な例外が報告されることがわかります。

ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8
ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.


どうなっているのでしょうか?Androidのビューコンポーネントはスレッドセーフではなく、ビューを更新する場合は、子スレッドではなくメインスレッドで行う必要があるからです。

せっかくなので、子スレッドでメインスレッドに通知し、メインスレッドで更新を行うようにしましょう。では、メインスレッドへの通知はどのように行うのでしょうか。Handlerオブジェクトを使う必要があります。

上のコードを少し修正してみましょう。

public class MainActivity extends Activity implements View.OnClickListener {
	
	private static final int COMPLETED = 0;
	
	private TextView stateText;
	private Button btn;
	
	private Handler handler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			if (msg.what == COMPLETED) {
				stateText.setText("completed");
			}
		}
	};
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        stateText = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
        
        btn.setOnClickListener(this);
    }

	@Override
	public void onClick(View v) {
		new WorkThread().start();
	}
	
	// Work Threads
	private class WorkThread extends Thread {
		@Override
		public void run() {
			//...... Process the more time-consuming operations
			
			// send a message to handler when processing is complete
			Message msg = new Message();
			msg.what = COMPLETED;
			handler.sendMessage(msg);
		}
	}
}


このように、複雑なタスク処理は子スレッドに任せ、子スレッドはハンドラオブジェクトを介してメインスレッドにビューが更新されたことを通知するという、メッセージング機構が重要な役割を果たす処理によって、スレッドセーフの問題を解決することができるのです。

次に、Androidのメッセージングメカニズムを分解して説明します。

Androidはグローバルなメッセージループ機構を持つメッセージ駆動型プログラムであり、GoogleはWindowsのメッセージループ機構を参考にしてAndroidにメッセージループ機構を実装しました。AndroidはLooperとHandlerによってメッセージループ機構を実装しています。Androidのメッセージループはスレッド固有であり、各スレッドは独自のメッセージキューとメッセージループを持つことができます。

AndroidのLooperは、スレッドのメッセージキューとメッセージループを管理する役割を担っています。現在のスレッドのLooperオブジェクトはLooper.myLooper()で、現在のプロセスのメインスレッドのLooperオブジェクトはLooper.getMainLooper()で得られます。

前述したように、Androidのメッセージキューとメッセージループはスレッドに特化しています。スレッドはメッセージキューとメッセージループを持つことができ、特定のスレッドからのメッセージはこのスレッドにのみ配信され、スレッドやプロセスをまたいで通信することはできません。スレッドにメッセージキューとメッセージループを持たせたい場合は、スレッド内でLooper.prepare()を呼んでメッセージキューを作成し、Looper.loop()を呼んでメッセージループに入る必要があります。作成したワーカスレッドはこちらです。

	class WorkThread extends Thread {
	      public Handler mHandler;

	      public void run() {
	          Looper.prepare();

	          mHandler = new Handler() {
	              public void handleMessage(Message msg) {
	                  // Handle the received message
	              }
	          };

	          Looper.loop();
	      }
	  }


このようにして、作成したワーカスレッドにはメッセージ処理の仕組みが備わっています。

では、先ほどの例でLooper.prepare()とLooper.loop()の呼び出しがないのはなぜでしょうか。それは、Activityがメインスレッドで動作するUIスレッドであり、AndroidシステムがActivityの起動時にメッセージキューとメッセージループを作成するからです。

メッセージキュー(MessageQueue)とメッセージループ(Looper)が最も言及されていますが、それぞれのメッセージ処理場所にHandlerの存在が見られますが、これは何をするものでしょうか?Handlerの役割は、特定のLooperが管理するメッセージキューにメッセージを追加し、そのメッセージキューのメッセージを分配して処理することです。Handlerを構築する際、Looperオブジェクトを指定することができますが、指定しない場合は、現在のスレッドのLooperオブジェクトを使用して生成されます。以下は、Handlerの2つのコンストラクタ・メソッドです。

/**
     * Default constructor associates this handler with the queue for the
     * current thread.
     *If there isn't one, this handler won't be able to receive messages.
     * If there isn't one, this handler won't be able to receive messages.
     */
    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }

/**mQueue = mLooper.mQueue; mCallback = null; } }
     mQueue = mLooper.mQueue; mCallback = null; } /* * Use the provided queue instead of the default one.
     */mQueue = mLooper.
    public Handler(Looper looper) {
        mLooper = looper. mQueue = looper;
        mQueue = looper. mQueue;
        mQueue = looper.mQueue; mCallback = null;
    }



以下は、メッセージング・メカニズムのいくつかの重要なメンバー間の関係を示す図です。

Activityの中に複数のワーカスレッドを作り、それらのスレッドがActivityのメインスレッドのメッセージキューにメッセージを入れると、そのメッセージはメインスレッドで処理されることになります。メインスレッドは一般にビューコンポーネントの更新操作を担当するため、この方法はスレッドセーフでないビューコンポーネントでうまく機能します。

では、子スレッドはどのようにしてメッセージをメインスレッドのメッセージキューに入れるのでしょうか?メインスレッドのLooperでHandlerオブジェクトを作りさえすれば、HandlerのsendMessageメソッドが呼ばれたときに、システムはメインスレッドのメッセージキューにメッセージを入れ、handleMessageメソッドが呼ばれたときにメインスレッドのメッセージキューのメッセージを処理することになるのです。

メインスレッドのHandlerオブジェクトにアクセスするサブスレッドについて、「複数のサブスレッドがメインスレッドのHandlerオブジェクトにアクセスする場合、メッセージの送信や処理にデータの不整合は発生しないか?答えは、Handlerオブジェクトが管理するLooperオブジェクトは、メッセージキューへのメッセージ追加もメッセージキューからのメッセージ読み出しも同期的に保護されたスレッドセーフなものなので、データの不整合は発生しないので問題ないです。

Androidのメッセージ処理の仕組みを深く理解することは、アプリケーション開発において重要であり、スレッド同期についてより深く理解することができますので、この記事が友人の役に立てば幸いです。


のソースから記事を転載しています。 http://blog.csdn.net/liuhe688/article/details/6407225 #