return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
这个方法会调用 enqueueMessage()
,然后传入当前 Handler的 MessageQueue()
mQueue,我们在开篇讲过,mQueue是构造函数中就被创建,它是传入的 Looper的MessageQueue。
// Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 1
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); // 2
}
注释1:把msg.target = this
,就是说这个消息发送给当前Handler的。
注释2:调用 MessageQueue.enqueueMessage()
,传入消息体和延时时间。这个动作就是把消息加入到 MQ中了。
来看一下加入过程:
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
…
synchronized (this) {
…
msg.markInUse();
msg.when = when;
Message p = mMessages; :
boolean needWake;
if (p == null || when == 0 || when < p.when) { //1
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;😉 { // 2
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
注释1:如果当前的Message是非延时任务,或者说比列表最后一个Message的执行时间要快,那么它就插队,插到前面去。
注释2:否则的话,就往列表后边找,找到 末尾或者第一个Message执行时间比当前的要晚的,然后插进去。
这段代码很简单,要么当前任务 插到前面,要么插到后面最早执行的位置。
到这里,一个Message的入队就讲完了。
2.6 关于消息分发 msg.target.dispatchMessage(msg)
回到Looper中,在死循环里,它会调用 msg.target.dispatchMessage(msg)
, 我们知道,msg.target是发送消息的Handler,那么这里调用了 目标Handler的dispatchMessage()
把Message发送出去,来看看这个方法:
// Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); // 1
} else {
if (mCallback != null) { // 2
if (mCallback.handleMessage(msg)) { // 3
return;
}
}
handleMessage(msg); // 4
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
注释1:如果msg.callback
不为空,则直接调用这个Callback的 run()
,这个情况是我们在Message中传入了一个Callback的方法,它在处理时,会直接调用这个Callback方法。
注释2、注释3:如果在创建 Handler(构造函数中)时带入了 一个 Callback,则直接调用这个 Callback,这个构造函数我们在开篇看过。
注释4:如果都不是上述情况,则调用 handleMessage()
。
// Handler.java
/**
- Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
handleMessage()
是一个空方法,英文文档的解释为: 子类必须实现这个方法
这就是为什么我们在自己使用的时候,一定要重写 handleMessage()
,然后对发送过来的消息做处理。
当这个消息被调用时,也说明了 Message被分发成功了。
到这里,handler的源码也讲解的差不多了。
==============================================================================
这里画一个图便于理解:
这里手写一个简单例子,在子线程通过Handler去更新UI线程:
public class MainActivity extends AppCompatActivity {
private TextView textView;
private static final int COUNT = 0x00001;
private Handler handler = new Handler() { // 1
@Override
public void handleMessage(Message msg) {
if (msg.what == COUNT) {
textView.setText(String.valueOf(msg.obj));
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.btn);
new Thread() { // 2
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
Thread.sleep(500);
Message msg = new Message();
msg.what = COUNT;
msg.obj = “计数:” + i;
handler.sendMessage(msg); // 3
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
注释1中:我们在主线程创建了一个Handler,那么其Looper自然就是UI线程的Looper
注释2:开启一个子线程
注释3:在子线程中,持有一个主线程的Handler,然后给这个Handler发送消息。
上面代码非常容易理解,但是可能会出现内存泄漏,所以这里只是做一个子线程更新UI的认知。
========================================================================================
开篇我说过,Handler的出现是子线程不能更新UI,所以Handler起到了这个作用。
这一章是来自于我实习面试的时候,面试官在Handler这点上问我:那主线程怎么更新子线程?或者子线程怎么更新其他的子线程?
我当时由于没有细读Handler的源码,所以我粗略的回答:只要持有对方线程的Handler,就可以更新了。
其实我回答也没错,但是这谈不上及格的答案,面试官也不会喜欢,下面我将用代码来演示一遍:
public class MainActivity extends AppCompatActivity {
private static final String TAG = “MainActivity”;
private MyThread thread;
class MyThread extends Thread {
private Looper looper;
@Override
public void run() {
Looper.prepare(); // 1
Log.d(TAG, “子线程为->” + Thread.currentThread() + “”);
looper = Looper.myLooper(); // 2
Looper.loop(); // 3
}
}
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, “主线程为->” + Thread.currentThread() + “”);
thread = new MyThread();
thread.start();
try {
sleep(2000); // 4
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler = new Handler(thread.looper) { // 5
@Override
public void handleMessage(Message msg) {
Log.d(TAG, “当前线程为->” + Thread.currentThread() + “”);
}
};
mHandler.sendEmptyMessage(0);
}
}
主线程要更新子线程,所以主线程要持有子线程的Handler,所以需要用子线程来构造一个Handlder
注释1:为子线程创建一个Looper
注释2:获取子线程Looper
注释3:打开子线程Looper的轮询
注释4:主线程睡一下,避免 子线程还没有完全起来的时候就在主线程获取子线程的Looper了
注释5:通过子线程的Looper创建属于子线程的Handler,然后在主线程中使用Handler发送消息。
打印如下:
说明消息已经从主线程传递到子线程当中去了。
方法同上。下面写两个线程 MyThread1
和 MyThread2
,然后让2线程给1线程发消息:
public class MainActivity extends AppCompatActivity {
private static final String TAG = “MainActivity”;
private MyThread1 thread1;
private MyThread2 thread2;
private Handler thread1Handler;
class MyThread1 extends Thread {
@Override
public void run() {
Log.d(TAG, “子线程1为->” + Thread.currentThread() + “”);
Looper.prepare();
thread1Handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, “这个消息是从->” + msg.obj + “线程发过来的,在” + Thread.currentThread() + “线程中获取的”);
}
};
Looper.loop();
}
}
class MyThread2 extends Thread {
@Override
public void run() {
Log.d(TAG, “子线程2为->” + Thread.currentThread() + “”);
Message msg = thread1Handler.obtainMessage();
msg.obj = Thread.currentThread();
thread1Handler.sendMessage(msg);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread1 = new MyThread1();
thread2 = new MyThread2();
thread1.start();
try {
Thread.sleep(500); // 让Thread1完全起来
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
打印结果如下:
所以一开始的问题:如果在主线程中更新子线程,在子线程中更新另外的子线程的解答:
(1)要持有目标线程的Handler
(2)因为非UI线程是不会自己开启轮询的,所以要手动在子线程中启动 Looper.prepare()
和 Looper.loop()
another question: HandlerThread
在上面的手动写法中,我们还要写 Thread.sleep
保证子线程启动起来,并且我们还要手动在子线程中手写 Looper.prepare()
、Looper.loop()
,这无疑有一些麻烦,Android提供了一个便于在子线程开启Handler、Looper的类,就是 HandlerThread
==================================================================================
HandlerThread顾名思义,就是使用 Handler的线程。它的源码非常简单,来看看:
先来看看其构造函数:
// HandlerThread.java
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
}
HandlerThread
是继承Thread的,在构造函数中,传入线程名称和优先级。
接下来看看其 run方法:
// HandlerThread.java
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
run方法中,写了 Looper.prepare()
,然后锁住对象去获取Looper,通过 Process.setThreadPriority()
来设置线程优先级。
然后调用 onLooperPrepared()
,这是个空方法,表示我们在自己使用HandlerThread时,可以通过重写这个方法来在Looper准备完成后做一些想做的事情。
如何使用HandlerThread来发送消息呢?HandlerThread实现了下面的方法:
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
通过 getThreadHandler()
我们可以得到当前线程的Handler,并且它会在 getLooper()
中保证Looper是起来的。
所以这也避免了我们手写 wait()、sleep()的麻烦。
这里总结一下HandlerThread:
HandlerThread是实现了Thread的线程,它start后会进行 Looper.prepare()、Looper.loop(),并且在我们想要使用其Handler时,能保证Handler不为空。它适合的场景是:主线程需要更新子线程的数据、子线程更新另一个子线程的数据。被更新的子线程可以使用HandlerThread
。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
8a3f0fd6ac81d625c.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-822EUi7c-1712579035542)]
最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!
[外链图片转存中…(img-NHJXiR19-1712579035543)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算