Android Handler消息传递机制
Android中只允許UI線程(也就是主線程)修改Activity里的UI組件。實際開發中,新啟動的線程需要周期性地改變界面組件的屬性值就需要借助Handler的消息傳遞機制。
Handler類
Handler類的主要作用:
- 在新啟動的線程中發送消息
- 在主線程中獲取、處理消息
Handler類包含如下方法用于發送、處理消息。
借助于上面這些方法,程序可以方便地利用Handler來進行消息傳遞。
關于Handler的源碼解讀,可參考別人寫的《Android 多線程之 Handler 源碼分析》
實例:自動輪播圖片
本實例通過一個新線程來周期性的修改ImageView所顯示的圖片(因為不允許其他線程訪問Activity的界面組件,故在程序中發送消息通知系統更新ImageView組件,故不需要實例Looper),布局文件非常簡單,故直接給程序代碼:
package com.example.testapp1.activity;import android.os.Bundle; import android.os.Handler; import android.os.Message;import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity;import com.example.testapp1.R; import com.example.testapp1.control.RoundImageView;import java.lang.ref.WeakReference; import java.util.Timer; import java.util.TimerTask;public class NextActivity extends AppCompatActivity {private RoundImageView imageShow;static class ImageHandler extends Handler {private WeakReference<NextActivity> nextActivityWeakReference;public ImageHandler(WeakReference<NextActivity> nextActivityWeakReference) {this.nextActivityWeakReference = nextActivityWeakReference;}private int[] imageIds = new int[]{R.drawable.a383f7735d8cd09fb81ff979b2f3d599, R.drawable.b6ab4abe4db592b27ea678345b0c3416, R.mipmap.head1, R.drawable.b6ab4abe4db592b27ea678345b0c3416};private int currentImageId = 0;@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (msg.what == 0x1233) {nextActivityWeakReference.get().imageShow.setImageResource(imageIds[currentImageId++ % imageIds.length]);}}}ImageHandler imageHandler = new ImageHandler(new WeakReference<>(this));@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.next);imageShow = findViewById(R.id.headImg);new Timer().schedule(new TimerTask() {@Overridepublic void run() {imageHandler.sendEmptyMessage(0x1233);}}, 0, 2000);} }上述代碼中,TimeTask對象的本質就是啟動一條新線程。
Handler、Loop、MessageQueue的工作原理
- Message: Handler接收和處理的消息對象。
- Looper:每個線程只能擁有一個Looper。它的loop方法負責讀取 MessageQueue中的消息,讀到信息之后就把消息交給發送該消息的Handler進行處理。
- MessageQueue:消息隊列,它采用先進先出的方式來管理Message。程序創建Looper對象時,會在它的構造器中創建MessageQueue對象。Looper的構造器源代碼如下:
該構造器使用了private修飾,表明程序員無法通過構造器創建Looper對象。從上面的代碼不難看出,程序在初始化Looper時會創建一個與之關聯的 MessagQueue,這個MessageOuee就負責管理消息。
- Handler:它的作用有兩個,即發送消息和處理消息,程序使用Handler發送消息,由Handler發送的消息必須被送到指定的MessageQueue。也就是說,如果希望Handler正常工作,必須在當前線程中有一個MessageQueue;否則消息就沒有 MessageQueue進行保存了。不過MessageQueue是由Looper負責管理的,也就是說,如果希望Handler正常工作,必須在當前線程中有一個Looper對象。為了保證當前線程中有Looper對象,可以分如下兩種情況處理。
prepare()方法保證每個線程最多只有一個Looper對象。prepare()方法的源代碼如下:
接下來調用Looper的靜態loop()方法來啟動它。loop()方法使用一個死循環不斷取出MessageQueue中的消息,并將取出的消息分給該消息對應的Handler進行處理。下面是Looper類的loop()方法的源代碼:
/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}// Make sure the observer won't change while processing a transaction.final Observer observer = sObserver;final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (slowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");slowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.slowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}}歸納起來,Looper、MessageQueue、Handler各自的作用如下:
- Looper:每個線程只有一個Looper,它負責管理MessageQueue,會不斷地從MessageQueag中取出消息,并將消息分給對應的Handler處理。
- MessageQueue:由Looper負責管理。它采用先進先出的方式來管理Message。
- Handler:它能把消息發送給 Looper管理的MessageQueue,并負責處理 Looper分給它的消息。
在線程中使用Handler的步驟如下:
實例:使用新線程實現點擊圖片彈出圖片內容
1.布局文件:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/constraintlayout2"xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_gravity="center"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/imageView"android:layout_width="50dp"android:layout_height="50dp"tools:ignore="MissingConstraints"tools:src="@drawable/ic_launcher_foreground" /><TextViewandroid:id="@+id/textView3"android:layout_width="100dp"android:layout_height="50dp"android:gravity="center"android:visibility="gone"app:layout_constraintStart_toEndOf="@+id/imageView"app:layout_constraintTop_toTopOf="@+id/constraintlayout2"tools:text="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"tools:visibility="visible" /></androidx.constraintlayout.widget.ConstraintLayout>布局文件比較簡單,就是使用約束布局,在其中放入一個圖片控件和文本控件(不展示)。
JAVA代碼:
上述代碼定義了一個線程的子類和Handler的子類,在Android Studio的比較新的版本不能直接使用Handler類實例對象并重新handleMessage(已廢棄,舊版本可以),必須通過Handler子類實例對象
在Activity的onCreate()或者Fragment的onCreateView()方法中加入以下代碼:啟動新線程,監聽圖片的點擊事件,向新線程中的Handler發送消息。
總結
以上是生活随笔為你收集整理的Android Handler消息传递机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Activity的启动模
- 下一篇: Android 三方库okhttp、gs