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
#
関連
-
警告: 構成 'compile' は廃止され、'implementation' と 'api' に置き換わりました。
-
エミュレータです。PANIC: AVDのシステムパスが壊れています。ANDROID_SDK_ROOTの値を確認してください。
-
アプリケーションがメインスレッドで過剰に作業している可能性があります。
-
MyEclipseの起動時に以下のようなエラーが発生したため、ログファイルを参照してください。
-
AndroidのEditTextにデフォルト値を設定する方法とヒントを設定する方法
-
Androidです。ViewPagerで現在のインターフェイスのFragmentを取得する
-
Android マルチメディア MediaPlayerの使用方法詳細
-
Android Studioの開発環境構築とAndroid Studioエミュレータの作成
-
no target device found 問題が解決した
-
AndroidManifest.xml ファイルが見つからない 解決方法
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Android Studio + Gradle またはコマンドラインを使用した Android apk の署名とパッケージング
-
Solve Android 仮想メソッドの呼び出しに失敗する。NULLオブジェクトの参照で
-
アンドロイド アルメアビ アルメアビ-v7a
-
障害発生 [INSTALL_FAILED_OLDER_SDK] 解決方法
-
RuntimeException: Unable to start activity ComponentInfo{xxx}: java.lang.NullPoi Androidの開発において、アクティビティを開始できません。
-
Android 開発の問題点:ActivityNotFoundException: 明示的なアクティビティクラスを見つけることができません
-
Androidの内部育成に磨きをかける2年間
-
Android 高機能版 (xxv) setTextColor() パラメータ設定方法
-
スピナー実装のダウンメニューとイベントリスニング(グラフィックモード)
-
Androidアプリ】【形状利用概要