注册

Android Handler消息传递机制

Android中只允许UI线程(也就是主线程)修改Activity里的UI组件。实际开发中,新启动的线程需要周期性地改变界面组件的属性值就需要借助Handler的消息传递机制。

Handler类

Handler类的主要作用:

在新启动的线程中发送消息
在主线程中获取、处理消息
Handler类包含如下方法用于发送、处理消息。

handleMessage(Message msg):处理消息的方法。该方法通常用于被重写。
hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息。
hasMessages(int what,Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
多个重载的 Message obtainMessage():获取消息。
sendEmptyMessage(int what):发送空消息。
sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒之后发送空消
sendMessage(Message msg):立即发送消息。
sendMessageDelayed(Message msg,long delayMillis):指定多少毫秒之后发送消息。
借助于上面这些方法,程序可以方便地利用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 nextActivityWeakReference;

public ImageHandler(WeakReference 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;

@Override
public 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));

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.next);
imageShow = findViewById(R.id.headImg);
new Timer().schedule(new TimerTask() {
@Override
public 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(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}



该构造器使用了private修饰,表明程序员无法通过构造器创建Looper对象。从上面的代码不难看出,程序在初始化Looper时会创建一个与之关联的 MessagQueue,这个MessageOuee就负责管理消息。

Handler:它的作用有两个,即发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程中有一个MessageQueue;否则消息就没有 MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分如下两种情况处理。
在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息了。
程序员自己启动的子线程,必须自己创建一个Looper对象,并启动它。创建 Looper对象调用它的prepare(方法即可。
prepare()方法保证每个线程最多只有一个Looper对象。prepare()方法的源代码如下:

public static void prepare() {
prepare(true);
}

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}



接下来调用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 block
if (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 logger
final 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的步骤如下:

调用Looper的 prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
有了Looper之后,创建 Handler子类的实例,重写 handleMessage(方法,该方法负责处理来自其他线程的消息。
调用Looper的loopO方法启动Looper。

实例:使用新线程实现点击图片弹出图片内容

1.布局文件:


android: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">

android:id="@+id/imageView"
android:layout_width="50dp"
android:layout_height="50dp"
tools:ignore="MissingConstraints"
tools:src="@drawable/ic_launcher_foreground" />

android: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" />





布局文件比较简单,就是使用约束布局,在其中放入一个图片控件和文本控件(不展示)。
JAVA代码:

private ImageThread imageThread;

class ImageHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if(msg.what == 0x123){
String imageText = msg.getData().getString("ImageText");
Toast.makeText(mContext, imageText, Toast.LENGTH_LONG).show();
}
}
}

class ImageThread extends Thread {
private Handler mHandler;

@Override
public void run() {
Looper.prepare();
mHandler = new ImageHandler();
Looper.loop();
}
}



上述代码定义了一个线程的子类和Handler的子类,在Android Studio的比较新的版本不能直接使用Handler类实例对象并重新handleMessage(已废弃,旧版本可以),必须通过Handler子类实例对象
在Activity的onCreate()或者Fragment的onCreateView()方法中加入以下代码:启动新线程,监听图片的点击事件,向新线程中的Handler发送消息。

imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putString("ImageText", imageData.getImageText());
msg.setData(bundle);
imageThread.mHandler.sendMessage(msg);
}
});
imageThread = new ImageThread();
imageThread.start();


————————————————
版权声明:本文为CSDN博主「maisomgan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45828419/article/details/115523133

1 个评论

学习一下

要回复文章请先登录注册