Android Handler
标签: Android Handler JavaScript博客 51CTO博客
2023-05-15 18:24:06 92浏览
多线程的应用在Android开发中是非常常见的,常用方法主要有:
1.继承Thread类(继承 Thread 类和实现 Runnable 接口的区别)
2.实现 Runnable 接口(继承 Thread 类和实现 Runnable 接口的区别)
3.Handler
4.AsyncTask(异步任务)
5.HandlerThread
下面就来看一下关于Handler 的理解和用法:
1.Handler是什么
答:Handler 是 Android 给我们提供来更新 UI 的一套机制,也是一套消息机制;我们可以通过它发送消息,也可以通过它处理消息。
下面是 Handler 官网的介绍(做了下翻译):
处理程序允许您发送和处理消息与线程的 MessageQueue 相关联的可运行对象。每个处理程序实例都与单个线程和该线程的消息队列相关联。当您创建一个新的处理程序时,它被绑定到创建它的线程/消息队列——从那时起,它将向该消息队列传递消息和可运行项,并在它们从消息队列中出来时执行它们。
2.为什么要用 Handler,它有什么用途,不用不可以吗
答:不用是不可以的。Android 在设计时,封装了一套消息的创建、传递和处理机制,如果不遵循这套机制就没有办法更新 UI ,并且会抛出异常。
下面是 Handler 用途官网的介绍(做了下翻译):
处理程序有两种主要用途: (1) 将消息和可运行项作为将来某个时间点执行; (2) 将要在不同线程上执行的操作加入队列。
知识:Handler 两个用途:1.可以定时发送 Message 或 Runnable 对象;2.在一个线程处理一个动作。
3. Handler 怎么用
下面你将会了解:1.如何发送延迟消息,2.如何发送循环消息,3.如何发送带参数的消息以及拦截消息。
先瞅瞅官方的文档对 Handler 如何使用的描述(下面是知识薄浅的我翻译过来的):
调度消息通过post(Runnable)、postAtTime(Runnable, long)、postdelay (Runnable, long)、sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message, long) 和 sendMessageDelayed(Message, long) 方法来完成。post 版本允许您将接收到的可运行对象加入队列,以便消息队列调用它们; sendMessage 版本允许您将包含一组数据的消息对象编入队列,这些数据将由处理程序的 handleMessage(Message) 方法处理(需要实现处理程序的子类)。
发送到处理程序时,您可以允许在消息队列准备就绪时立即处理项,或者指定处理前的延迟时间或处理项的绝对时间。后两种方法允许您实现超时、计时和其他基于时间的行为。
为应用程序创建进程时,其主线程专门用于运行一个消息队列,该队列负责管理顶级应用程序对象(活动、广播接收器等)和它们创建的任何窗口。您可以创建自己的线程,并通过处理程序与主应用程序线程进行通信。这是通过从新的线程调用相同的post或sendMessage方法来完成的。然后,将在处理程序的消息队列中调度给定的 Runnable 或消息,并在适当的时候进行处理。
文档说的很详细,但是很繁琐,其实我们用到的主要有四个方法:post(Runnable)、postdelay (Runnable, long)、sendMessage(Message)、sendMessageDelayed(Message, long) ;
要注意的是:Handler 发消息是分为了两种,一种是 Message 对象(sendMessage(Message)),另一种是 Runnable 对象(post(Runnable));而在代码最底层就会发现, Runnable 最后也是被封装成 Message 对象。
3.1 发送延迟消息(post(Runnable) 简单用法)
下面是创建子线程,延迟更新 UI (这是下面三个例子的源码,没币的私我发):
private TextView textView;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
//创建子线程,利用Handler更新UI
new Thread() {
@Override
public void run() {
try {
//0.5秒后更新UI
Thread.sleep(500);
//用 Handler 更新 TextView
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("更新成功");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();//开始执行
}
分析一下上面的代码:先创建了子线程 Thread 然后在里面延迟了0.5秒后,利用 handler.post(Runnable) 方法更新了 UI ,非常简单。
3.2 发送循环消息(postDelayed(Runnable long) 简单用法)
下面是发送循环消息,每一秒换一张图片(多用于图片定时轮换):
private int images[] = {R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark};
private int index;//索引指定图片当前在哪个位置;
private MyRunnable myRunable = new MyRunnable();
private Handler handler = new Handler();
class MyRunnable implements Runnable {
@Override
public void run() {
index++;
index = index % 3;
//根据索引取出数组中的图片
imageView.setImageResource(images[index]);
//每隔一秒去切换一下图片
handler.postDelayed(myRunable, 1000);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_post_delayed);
//0.5秒后调用runnable切换图片
handler.postDelayed(myRunable, 500);
}
分析一下上面的代码:首先定义了一个装有三种颜色的数组和一个索引值;然后自定义了 MyRunnable 继承了 Runnable ,在 run() 方法中执行了为 ImageView 设置数组中的颜色,利用 handler.postDelayed() 方法设置每过一秒,再次执行该方法;最后在 onCreate() 方法中,调用 handler.postDelayed() 开始执行切换图片。这样便达到了每过一秒去更换一次图片的效果。
3.3 发送带参数的消息以及拦截消息(sendMessage() 简单用法)
下面是 sendMessage() 的简单用法(携带了数据):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_send_message);
//创建子线程,在里面往主线程发送消息,更新UI
new Thread() {
@Override
public void run() {
try {
//0.5秒后发送消息更新UI
Thread.sleep(500);
Message message = new Message();
message.arg1 = 88;
message.arg2 = 66;
//往下面的实体类添加一组数据
Person person = new Person();
person.age=18;
person.name="小王";
message.obj=person;
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
//实体类,有人的姓名年龄等
class Person {
public int age;
public String name;
public String toString() {
return "name=" + name + "age=" + age;
}
}
//接收消息更新UI
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
textView.setText(msg.arg1 + "和" + msg.arg2+msg.obj);
}
};
分析一下上面的代码:首先创建了一个子线程,子线程休息0.5秒后,创建了 message 对象,message 携带了三组数据,前两组分别是整型的 arg1 和 arg2,第三组数据为 object 类型的一组个人信息,最后通过 handler.sendMessage() 方法,将携带数据的消息发送出去。上面代码38行,创建了一个 handler 对象,在里面接受了消息并且设置到 TextView 中。
下面是 handler 如何拦截消息的:
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
Toast.makeText(getApplicationContext(), "消息被拦截", Toast.LENGTH_SHORT).show();
//下面的返回值为false时,不拦截消息;否则拦截消息,不会再走下面的handlerMessage()
return true;
}
}) {
@Override
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "消息没有被拦截", Toast.LENGTH_SHORT).show();
}
};
分析一下上面的代码:在处理消息之前加入了 Handler.Callback() 方法,方法内返回值如果为 false不拦截消息,否则拦截消息(这是上面三个例子的源码,没币的私我发)。
4.为什么设计为只通过 Handler 更新 UI
答:最根本的目的是为了解决多线程并发问题。
比如:一个 Activity 多个线程更新 UI,而且没有加锁机制,那会怎么样?
会出现更新界面混乱;因为没有加锁,引起了多线程并发问题。
那么我们对更新 UI 的操作进行加锁处理,又会怎么样?
会引起性能下降;每一个加锁的方法都会导致性能有一定的下降。
知识:Android 提供了一套更新 UI 的机制,只需要遵循它就可以,这样我们不用关心多线程并发的问题;所有更新 UI 的操作,都在主线程的消息队列中轮询处理。
5.Handler 原理,它与 Looper 、MessageQueue 有什么联系
5.1 Handler 、Looper 、MessageQueue 、Message 描述
5.1.1 Handler
Handler 中封装了消息的发送和处理(消息的发送:sendMessage();消息的处理:handleMessage())。
5.1.2 Looper
1.Looper 是消息盛装的载体,一个线程中只会有一个 Looper 实例;
2.其内部初始化了一个 MessageQueue (消息队列),所有的 Handler 发的消息都走向这个消息队列;
3.在 Looper 中,会调用一个 Looper.loop() 方法,调用这个方法后,进入了死循环,这样便起到了轮询的作用;不断从 MessageQueue 中取出消息,一有消息便处理(消息最后又交给 Handler 自己处理)。
4.要注意的是:主线程的 Looper 对象会自动生成,而子线程 Looper 对象要手动调用方法创建。
5.1.3 MessageQueue 描述
MessageQueue (消息队列)添加处理的消息,存储特点:先进先出;
5.1.4 Message 描述
Message 顾名思义消息;它可以携带少量的消息(也就是我们所说的参数),一般使用 arg1 和 arg2 字段来携带整型数据,使用 obj 字段携带一个 Object 对象。
三者的关系:Handler 会和 Looper 进行关联,也就是在 Handler 内部可以找到 Looper(如何关联要看下面源码),找到了 Looper 也就找到了 MessageQueue 。在 Handler 中发消息,其实就是向 MessgeQueue 发消息。
知识:Handler 负责发送处理消息,Looper负责接收 Handler 发送的消息并把消息回传给 Handler 自己,MessageQueue 是储存消息的容器(下面的源码解释了 Handler 如何发送消息, Looper 如何接收到消息后又如何回传给 Handler 的)。
看完上面的 Handler 、 Looper 与 MessageQueue 的理论知识,肯定对它们之间的关系有一定的理解;接下来看源码,加深理解它们的关系:
5.2 Handler、Looper源码
5.2.1 Looper 源码
Looper 中有两个重要的方法(翻译来为:准备和循环):prepare() 和 loop() ;先看准备方法 prepare() 。
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));
}
从上面代码看出:代码中 prepare() 方法是一个 boolean 类型的,true 表示允许退出,false 则表示 Looper 不允许退出(ThreadLocal这里不再说了,想了解的可以去搜一下),先做一个是否为空的判断这个判断保证了一个线程中只有一个Looper实例;然后在将一个 Looper 的实例放入了 ThreadLocal ,之后调用了 new Looper(quitAllowed) ,下面看 Looper 的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从上面代码看出:在里面创建了一个MessageQueue(消息队列),到此为止就能看出来 Looper 与 MessageQueue 关联起来了,也可以看出 Looper 与 Thread 关联起来了。上面是 prepare() 方法,下面继续看 Looper 的另一个循环方法 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.");
}
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();
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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(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();
}
}
上面代码可以看出:先调用了 myLooper() 方法,看下 myLooper() 方法内有什么:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
上面代码可以看出: myLooper() 内部返回了 sThreadLocal 存储的 Looper 实例(上面的 prepare() 方法内放入了一个 Looper 实例),如果 Looper 实例为空则抛出异常;也就是说 loop() 方法必须在 prepare() 方法之后运行,继续回到 loop() 方法:
第六行的 me.mQueue 获取当前 Looper 对应的消息队列;
剩下的内容就是主要的死循环方法:
第十三行的 for( ; ; ){ ...,说明进入了 loop() 进入了死循环,不停的处理消息队列中的消息;
第十四行的 queue.next() 方法是获取消息;
第十五行判断了如果没有消息则退出循环。
第二十七行的 msg.target.dispatchMessage(msg); 其中 target 就是 Handler ,dispatchMessage() 方法就是处理消息。这行代码的意思就是:用 Message 调用 Handler 来处理消息,最后处理消息的还是 Handler 。
第四十四行将 Message 放入消息池,释放消息占据的资源。
5.2.2 Handler 源码
Handler 源码主要看三个地方:1.Handler 如何与 Looper 关联起来。2.Handler 如何发消息。3.Handler如何接收并处理消息。
Handler 如何与 Looper 关联起来:
public Handler(Callback callback, boolean async) {
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 = callback;
mAsynchronous = async;
}
从上面代码第十行可以看出:通过 Looper.myLooper() 方法取出了当前线程保存的 Looper 实例;
代码第十一行:判断 mLooper 对象是否为空,为空抛出异常信息。 mLooper 对象为空,说明没有初始化 Looper 对象,也就是说没有调用 looper.prepare() 方法;
代码十五行:获取了 Looper 实例中的 MessageQueue (消息队列);
到此为止,Handler 与 Looper 就关联起来了。
Handler 如何发送消息:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
上面代码可以看出,里面只调用了 sendMessageDelayed() 方法,下面看这个方法内有什么:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
上面代码可以看出,里面又调用了 sendMessageAtTime() 方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
上面代码可以看出,首先获取到 MesageQueue(消息队列),并且判断是否为空,为空的话返回 false 发消息失败。否则调用 enqueueMessage() 方法;下面看 enqueueMessage() 方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
上面代码可以看出,首先设置 msg 的 target 对象,并且指向了自己,也就是说把当前的 handler 作为 msg 的 target 属性。发消息最后也是说 Handler 发消息最后保存到了消息队列中。
Handler如何接收并处理消息:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
上面代码可以看出,先判断 Message 的回调是否为空;然后再判断 Handler 的回调是否为空(一般拦截消息会利用 Handler 回调,上面有说如何拦截消息);最后进入方法 handlerMessgae() :
public void handleMessage(Message msg) {
}
上面代码可以看出,handlerMessgae() 这个方法为空的,因为一般我们自己接收到消息后去处理消息。
下面是对 Handler 、Lopper 和 MessgaeQueue 重要方法的总结:
名称 |
重要方法 |
作用 |
Handler |
sendMessage(Message msg) sendmessageDelayed(Message msg, long delayMillis) |
1.发送消息 2.发送 Message 或 Runnable 两种对象 3.将消息发送到消息队列中 |
post(Runnable run) postdelay(Runnable run , long delayMillis) |
||
dispatchMessage(Message msg) |
分发消息,分发给对应的 Handler |
|
handleMessage(Message msg) |
我们自己收到消息做出的处理(多为更新 UI) |
|
MessageQueue |
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) |
将消息放入消息队列(先进先出的原则) |
Message next() |
将消息移出消息队列 |
|
Looper |
prepare() |
创建 Looper对象和 MessageQueue 对象 |
loop() |
轮询从 MessageQueue 取出消息 并发送给 Handler 处理; 无消息时阻塞 |
下面是主线程运行,Handler 与 Looper 关联的实例图:
关联完毕后就可以发消息了,如下图:
到这个地方,对 Handler 应该有一定的认识和了解以及简单的发消息。
6.使用 Handler 会遇到哪些问题
内容还在更新。。。
7.如何实现一个与线程相关的 Handler(手动写主线程给子线程发送消息并在子线程中处理消息)
上面全都是讲的子线程去给主线程发送消息,主线程用 Handler 处理消息执行更新 UI 的操作,那么如何实现在子线程中使用 Handler 呢(主线程给子线程发送消息并在子线程中处理消息)?
下面我来自定义一个子线程,并且在子线程创建 Looper 、Handler ;
再讲一下 Looper 与 Handler 关联的步骤(上面有图):
1.创建子线程;
2.手动调用 Looper.prepare() 方法(在 prepare() 内部创建了 MessgeQueue);
3.创建 Handler;
4.手动调用 Looper.loop() 方法,开始轮询;
要注意的是:只有在主线程下,Android 为我们封装了 Looper 的创建与轮询方法,如果自己写,一定要手动调用这两个方法;
这样我们就自定义 Handler 与 Looper 关联起来了。然后在主线程中发消息就好了,具体看下面代码:
private Button button1;
//1.创建子线程
class Mythread extends Thread {
public Handler handler;
@Override
public void run() {
//2.手动调用 Looper.prepare 方法
Looper.prepare();
//3.创建 handler
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(ZidingyiHandlerActivity.this, "自定义", Toast.LENGTH_SHORT).show();
}
};
//4.手动调用 Looper.loop() 方法,开始轮询
Looper.loop();
//Handler 与 Looper 手动关联完毕
}
}
private Mythread mythread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zidingyi_handler);
//在主线程初始化,并开始执行
mythread = new Mythread();
mythread.start();
button1 = findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//在主线程发送一个空消息
mythread.handler.sendEmptyMessage(1);
}
});
}
8.HandlerThread 是什么
看完上面的 7.如何实现一个与线程相关的 Handler(主线程给子线程发送消息并在子线程中处理消息)是不是感觉很繁琐,要自己去调用 Looper 的两个方法,不能简单一点吗?所有就有了 HandlerThread。
HandlerThread 定义:
顾名思义:handler 处理者,Thread 线程。也就是创建一个线程去处理消息(在子线程处理消息);再看官网的定义:
它用于启动具有 looper 的新线程的便利类。然后,可以使用 looper 来创建处理程序类。注意,仍然必须调用 start()。
根据官网给的定义,可以看出 HandlerThread 中为我们创建了 Looper ,只需调用 start() 方法即可。
HandlerThread 源码:
下面先通过源码了解一下 HandlerThread ,先看一下它的构造方法 :
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
上面代码可以看出: HandlerThread 继承了 Thread,并且在里面定义了一个 Looper 对象,记住这些就够了,接下来看 HandlerThread 里面两个重要方法 run() 方法和 getLooper() 方法,下面先看 run() 方法:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
上面代码可以看出:HandlerThread 的 run() 方法中帮我们调用了 Looper.prepare() 初始化了 Looper 对象,最后有帮我们调用了 Looper.loop() 轮询的方法。这样我们通过 HandlerThread (子线程)使用 Handler 时,不用手动调用 Looper 的两个方法,方便了很多。下面是 getLooper() 方法:
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
从上面代码可以看出: getLooper() 方法中经历了判断是否存活、Looper是否为空,最后返回了一个 Looper 对象。看完源码接下来通过代码了解使用方法。
HandlerThread 使用:
HandlerThread 一般用于主线程往子线程发消息,子线程去做一些耗时操作,下面通过 HandlerThread 来做一个每隔一秒去更新一次 UI 的耗时操作:
private Button button1;
private TextView textView;
private HandlerThread thread;//定义一个HandlerThread
private Handler threadHandler;//子线程的Handler
private Handler Mainhandler = new Handler();//主线程的Handler
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_seard);
button1 = findViewById(R.id.button1);
textView = findViewById(R.id.textView);
//为HandlerThread指定一个名字,并开始执行
thread = new HandlerThread("HandlerThread");
thread.start();
//子线程中的handler
threadHandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
try {
Thread.sleep(1000);
//每隔一秒发送一条消息
threadHandler.sendEmptyMessage(1);
//在主线程中更新UI
Mainhandler.post(new Runnable() {
@Override
public void run() {
int a = (int) (Math.random() * 100); //a是已经生成的随机数
textView.setText("1秒更新一次:" + a);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("thread1", Thread.currentThread() + "==");//打印线程的id
}
};
//发送空的消息
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
threadHandler.sendEmptyMessage(1);
}
});
}
在上面代码 4.5.6 行可以看到,首先定义了 HandlerThread,子线程的 threadHandler和主线程的 MainHandler。
在代码 17.18 行可以看出,为 HandlerThread 指定了一个名字并且开始执行。
在代码 21 行,我们初始化了子线程中的 Handler ,并且指定了 Looper 为 HandlerThread 中的 Looper。(上面有讲:HandlerThread 的 getLooper() 方法返回了一个 Looper 对象)
在代码 25 行:模仿耗时操作,线程休息一秒后,发送一条消息。
在代码 29 行至 35 行:在上面子线程进行耗时操作完成后,我们又回到主线程去更新了 UI 视图。
在代码 47 行:设置点击事件,开始去每隔一秒去更新一次 UI 视图。
9.主线程向子线程发消息(HandlerThread 使用)
了解完上面的 8.HandlerThread 是什么之后,应该对主线程向子线程发消息会有简单了解,因为主线程向子线程发消息要通过 HandlerThread ,而上面的例子就是一个主线程向子线程发消息的例子;下面再通过一个简单的例子去加深,这个一个1秒后主线程向子线程发消息,子线程收到消息后再向主线程发消息,这样不停去轮换。下面看代码:
private Button button1;
private HandlerThread thread;
private Handler threadHandler;//子线程的Handler
//主线程的Handler
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Message message = new Message();
//主线程收到消息子线程发的消息后,向子线程发消息;
threadHandler.sendMessageDelayed(message,1000);
Log.e("=====", "MainHandler" );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zi_zhu);
button1 = findViewById(R.id.button1);
//为HandlerThread指定一个名字,并开始执行
thread = new HandlerThread("HandlerThread");
thread.start();
//子线程中的handler
threadHandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Message message = new Message();
//子线程收到主线程发的消息后,向主线程发消息;
handler.sendMessageDelayed(message,1000);
Log.e("=====", "ThreadHandler" );
}
};
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//开始执行,主线程向子线程发消息
handler.sendEmptyMessage(1);
}
});
}
从上面代码 2 至 5 行可以看出,先创建一个子线程 threadHandler ,然后创建子线程的 Handler,最后创建并初始化主线程的 Handler 。
从代码 7 至 12 行可以看出,主线程收到了子线程发来的消息并且又向子线程发消息。
从代码 22 至 24 行可以看出,我们为HandlerThread指定一个名字,并开始执行。
从代码 26 至 35 行可以看出,子线程收到了主线程发来的消息并且又向主线程发消息。
这样一来一回,就实现了无限的循环。
代码 41 行,开始执行主线程向子线程发送消息。这样开始了无限的循环。
下面是执行结果的部分代码:
到此,主线程向子线程发消息便讲完了。
10.Android 更新 UI 的几种方式
Android 提供给我们更新 UI 的方法一共有四种:
1. runOnUiThreead() 方法
2. view post() 方法
3. handler post() 方法 (上面有详细讲解,不再讲)
4. handler message() 方法(上面有详细讲解,不再讲)
下面来看第一种方法 runOnUiThreead() :
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zi_zhu);
textView = findViewById(R.id.textView);
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("123");
}
});
}
方法很简单,下面看第二种方法 view post() :
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zi_zhu);
textView = findViewById(R.id.textView);
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("1234");
}
});
}
上面的两种方法都很简单,但不常用,所以也不过多去说。比较常用的是第四种,没有很明白的可以再看看上面。
11.非 UI 线程真的不能更新 UI 吗
答案是:非 UI 线程是可以更新 UI ,但不推荐。
先看下面代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zi_zhu);
textView = findViewById(R.id.textView);
new Thread(){
@Override
public void run() {
textView.setText("123");
}
}.start();
}
从上面代码可以看出,代码很简单,创建了一个子线程去更新 TextView 。然后运行会发现,TextView 更新成功,并且没有报任何错误。。再看下面代码,将子线程休眠一秒后更新 UI :
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zi_zhu);
textView = findViewById(R.id.textView);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
textView.setText("123");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
运行后,可以发现程序崩溃并报出异常。这是为什么,就休眠一秒就崩溃了。
都知道 Activity 会执行生命周期方法 onCreate() 和 onResume() ;如果在 onResume() 方法之前更新 UI ,程序不会报错,休眠一秒后,程序走了 onResume() 方法,程序便崩溃。
因为在 onResume() 方法中,会创建 ViewRootImpl (不清楚的话,点这里了解它);
ViewRootlmpl 会执行方法 invalidateChild();
而 invalidateChild() 方法会调用 checkThread() 方法来检查当前线程是否为主线程,不是在主线程会崩溃,并报出异常。
这样大家应该了解了:
非 UI 线程是可以更新 UI,但是只能在 onResume() 方法调用之前更新才会成功,因为ViewRootlmpl 会在 onResume() 方法中创建,而 ViewRootlmpl 会执行检查当前线程是否为主线程。
所以我们直接在 onCreate() 方法中创建子线程并更新 UI 会成功。
讲到这里,关于 Handler 的内容也就讲完了。。。
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论