首页 >> 大全

1.Handler的工作流程

2023-08-14 大全 24 作者:考证青年

详解

文章目录 2.流程中的重要的几个方法 2..post()与.() 2.的源码2..() 2.消息机制与时间排序 3.的相关知识 3.2内存泄漏 4.

本来按照《开发艺术探索》的进度我应该现在该看线程和线程池了,但是突然觉得那块还有好多东西没有看,要是一并写到 线程和线程池那块有点头重脚轻,所以还是单独写一篇博客,详细解数一下 1.的工作流程

的主要作用是将一个任务从它自己的线程切换到某个指定中的线程去执行

我们用它的主要场景就是,在子线程中无法访问UI,我们只能通过但不限于用来将它从子线程切换到UI线程来执行

至于为什么不能在子线程中访问UI,《艺术开发探索》给出过解释,大概就是说,

无法保证非主线程的安全性,多线程的并发操作可能导致UI控件处于不可预期的状态,

解决这种问题本可以用锁,但是使用了锁之后,会导致2个问题

锁的这种机制会让UI访问的逻辑变复杂会降低UI访问效率,因为加上锁之后保证了多线程的原子性。当有一个线程在访问它的时候,其他线程无法访问它,大大降低了它的运行效率

所以我们选择在主线程又称为UI线程进行UI的访问

我们能用UI线程来访问UI,那就说明了UI线程则具备上面的三种性质

1.保证线程的安全

2.访问的逻辑简单

3.不会降低UI访问的效率

我们来了解一下为什么主线程具有上面的性质

1.1主线程具有如上性质的原因

我们清楚其实是由View和组成的,View就不必说了,而。

我们在介绍对象创建完成的时候,它会将添加到,会创建相应的与它关联

足以见得的重要了,

在主线程中,当需要更新 UI 的时候, 会确保更新操作在主线程中执行,通过线程检查来保证线程安全性。当其他线程尝试更新 UI(例如直接修改 UI 元素属性)时, 会在执行操作之前检查当前线程是否为主线程,如果不是主线程,就会抛出 异常,阻止非主线程更新 UI。

这样就保证了UI线程的安全性与访问的逻辑的简单

至于不会降低UI访问的效率很简单就是因为没加锁不会让某些线程处于停滞状态

前面说的有点多了,那么的工作流程到底是什么呢?

我画了一张流程图

1.2流程图

根据这张图我们来了解几个重要的方法

2.流程中的重要的几个方法 2.中的属性

可以看下这张图,里面有obj,what,,arg1,arg2,

可以用来携带任意类型的数据。它通常用于传递消息中需要携带的额外数据。你可以将任何对象赋值给 obj 属性,并在消息处理时获取和使用这些数据。

what

int

这是一个整型数值,用于标识消息的类型或目的

这是一个 对象,用于指定接收回复消息的目标

arg1

int

它们可以用来携带与消息相关的整型数据。

arg2

int

它们可以用来携带与消息相关的整型数据。

int

这是一个整型数值,表示发送该消息的应用程序的用户标识符

.() 是一个静态方法,用于获取可重用的 对象。它可以避免频繁地创建新的 对象,从而提高性能和效率。

我们这里面主要说3个

2.2.1what

一般我们在一个线程中写

Message  message = new Message();
message.what = 1;
message.obj = "2";
mHandler.sendMessage(message);

然后在主线程中

 Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case 1:mTextView.setText(""+message.obj);}return true;}});

这段代码很简单的说明了中的obj与what的功能

obj主要用来携带值,what是一个标志,在()中我们通过what这个标志进行处理

2.2.

这个标志在IPC通讯的中我们用过,

在另一个进程中

public class MyService extends Service {
private static class MessengerHandler extends Handler{@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what){case 1:Log.d("TAG",msg.getData().getString("data"));Messenger client = msg.replyTo;Message replyMessage = new Message();replyMessage.what = 2;Bundle bundle = new Bundle();bundle.putString("TAG","reply"+"我大后台收到你的来信了");replyMessage.setData(bundle);try {client.send(replyMessage);} catch (RemoteException e) {e.printStackTrace();}break;default:break;}}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {return mMessenger.getBinder();
}
}

我们通过

Messenger client = msg.replyTo;

获得了传递过来的,然后接着获取给我们传递的消息,同时我们又用刚才获得的给发送消息

在中

public class MainActivity extends AppCompatActivity {// 服务端Messengerprivate Messenger mServerMessenger;// 服务端连接状态private boolean mIsBound = false;// 绑定服务端private Button message_0;private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mServerMessenger = new Messenger(service);Message message = new Message();message.what = 1;Bundle bundle = new Bundle();bundle.putString("data","你好啊");message.setData(bundle);message.replyTo = mGetReplyMessenger;try {mServerMessenger.send(message);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);message_0 = findViewById(R.id.message_0);// 绑定服务端if(!mIsBound) {message_0.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mIsBound = true;Intent intent = new Intent(MainActivity.this, MyService.class);bindService(intent, mConnection, Context.BIND_AUTO_CREATE);}});}}@Overrideprotected void onDestroy() {super.onDestroy();// 解绑服务端unbindService(mConnection);}private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());private static class MessengerHandle extends Handler{@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what){case 2:Log.d("TAG",msg.getData().getString("TAG").toString());}}}
}

我们主要看最后面的这个在中被初始化

message.replyTo = mGetReplyMessenger;

然后获得那个进程给我们传递过来的值

2.2. 2..post()与.()

.post() 和 .() 都是 类提供的方法,用于向消息队列发送消息并在指定的时间后处理消息。它们的主要区别在于消息的发送方式和处理机制。

.post():该方法用于将一个 对象提交到消息队列中,以便在主线程中执行。它不需要创建 对象,而是直接将 对象封装成消息并发送到消息队列。当消息处理时, 会将 对象的 run() 方法执行在主线程中。

handler.post(new Runnable() {@Overridepublic void run() {// 在主线程中执行的操作}
});

流程工作坊__流程工作总结

2..():该方法用于发送一个 对象到消息队列中,在指定的时间后处理消息。它需要创建一个 对象,并使用 .() 将消息发送到消息队列中。当消息处理时, 会回调 .() 方法来处理消息。

// 在主线程中创建一个 Handler 对象
Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {// 在主线程中处理消息switch (msg.what) {case MESSAGE_ID:// 处理特定的消息Object obj = msg.obj;// 执行相应的操作break;// 处理其他消息// ...}}
};// 创建一个 Message 对象,并发送到消息队列中
Message message = handler.obtainMessage();
message.what = MESSAGE_ID;
message.obj = someObject;
handler.sendMessage(message);

总的来说,.post() 适用于在主线程中执行简单的代码块或任务,而 .() 更适用于发送包含更多信息的消息,并需要在消息处理中进行更复杂的操作。

其他并没有其他什么区别

我们之前看过**()**的源码

现在看看post的源码

2.2.1post的源码

public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);
}

我们会发现post内部调用了(),其中传递的参数分别是与延时时间

我们再点击()的源码看看

2.2.1.()源码

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

它先进行了一个判断,判断延时时间是否小于0,小于0则给它赋值为0,然后返回()

2.2.1.()源码

public boolean sendMessageAtTime(@NonNull 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);
}

中先判断为不为空,为空的话返回一个异常

否则的话进行(),这个方法应该很眼熟,在中这个是用来添加消息的

2.2.1.3post的流程总结

post()传递的是一个,然后进入方法,它会让你把进行化与一起传进去

然后进入了方法,它会让你把与.() + 一笔给传进去,后面的这个是什么呢?给出的解释是:

然后传递(),把,msg与三个参数一起传进去,进行的插入

我们再进来看看()是怎么把msg插入到里面的

2.的源码

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}synchronized (this) {if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;
}

 if (p == null || when == 0 || when < p.when)

这个if判断中

如果消息队列为空(即没有已存在的消息),

如果当前消息的触发时间为 0(即立即触发),

如果当前消息的触发时间早于消息队列中已有消息的触发时间

那么就将当前消息的 next 属性指向原先的队头 p,即将当前消息插入到原先的队头之前。

将队列的头部指针 更新为当前消息,使其成为新的队头。

根据当前线程的阻塞状态来设置 变量。如果当前线程被阻塞(即等待消息队列),则需要唤醒线程,以便立即处理新插入的消息。

然后在else中

 for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;

如果在==true且当前的插入为异步操作,则取消唤醒

如果没有插入的东西或者当前消息的触发时间早于消息队列中已有消息的触发时间则退出for循环

否则的话就一直进入for循环进行插入操作

2..()

我们点击()的源码

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {return sendMessageDelayed(getPostMessage(r), delayMillis);
}/** @hide */
public final boolean postDelayed(Runnable r, int what, long delayMillis) {return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
}

我们会发现其实post()和()内部是一样的,都调用的(),所以两个唯一的不同就是,post里面传递的为0,而()传递的不一定为0

2.4.1注意: 1.的延迟消息

的延迟消息是确定的吗? ,后续修改系统时间会影响延迟消息吗?

这个回答我们可以看看

()中的源码

 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

中注意:

SystemClock.uptimeMillis() + delayMillis

.() + 是用来计算相对时间的表达式。它的目的是计算出延迟触发的时间点。

.() 是一个用于获取系统启动时间的方法,它返回自系统启动以来经过的毫秒数。它不受系统时间的修改影响。

是延迟的时间间隔,以毫秒为单位。

通过将当前的系统启动时间(.())与延迟的时间间隔()相加,可以得到延迟触发的时间点。

如果我(2000)的话,就有可能延迟超过了。因为的时间为.() +

但是消息延迟不单单因为这个

在系统中,处理消息和执行任务的机制是基于消息循环( Loop)和系统调度。延迟消息的触发时间取决于消息队列中的其他消息、正在执行的任务、系统负载等因素。

因此,尽管你指定了2000毫秒的延迟,但实际触发时间可能会稍有偏差,可能略早或略晚于2000毫秒。

另外,需要注意的是,系统事件(例如屏幕休眠、设备进入深度睡眠模式等)也可能会影响到延迟消息的触发时间,因为在这些事件发生时,消息循环可能会被暂停或受到影响。

后续修改系统时间并不会影响延迟消息,因为我延迟消息本来就和系统时间没有关系,

.() 是一个用于获取系统启动时间的方法,它返回自系统启动以来经过的毫秒数。它不受系统时间的修改影响。

简单总结一下上面的话:的延迟消息不是确定的, 可能时间超过或小于

因为2点:

处理消息和执行任务的机制是基于消息循环( Loop)和系统调度。延迟消息的触发时间取决于消息队列中的其他消息、正在执行的任务、系统负载等因素。系统事件(例如屏幕休眠、设备进入深度睡眠模式等)也可能会影响到延迟消息的触发时间,因为在这些事件发生时,消息循环可能会被暂停或受到影响。

与后续修改系统时间无关。

2.线程与与

一般会问一个线程中允许创建多个嘛?允许创建多个嘛?

在一个线程中,你可以创建多个 对象,并且每个 对象都需要关联一个 对象。所以说,一个线程可以创建多个 ,但每个 都需要有一个关联的 。

3.一个线程中多个怎么判断用哪一个进行接收

我们继续搬上之前那段经典代码

Message  message = new Message();
message.what = 1;
message.obj = "2";
mHandler.sendMessage(message);

 Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case 1:mTextView.setText(""+message.obj);}return true;}});

答案很明显了吧,我们就是通过的what属性来确定后面再里面怎么进行处理

4.如何消耗数据?时间怎么判断? 1.如何消耗数据

我们重新回顾一下的流程

通过/post/这几种方法将/对象传到()然后会调用()将对象加入里面,然后进行初始化方法会调用的set方法然后调用loop进行查找,查找到后调用.进行消息的处理

其中**.()**的这个流程的操作就是如何消耗数据

先判断.为不为null,如果不为null的话,直接()

如果为null的话判断为不为null,这个为一个全局变量,如果它不为null的话则再判断它的.为不为true,如果为true的话就结束

如果为null或者.不为true的话则调用**()**方法

这就是的处理

你有没有发现那个()特别眼熟

 Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case 1:mTextView.setText(""+message.obj);}return true;}});

在.()中重写的就是()

所以我们遇到的大部分情况就是.==null或者.!=true

所以我们需要重写

方法

我们来看看.,,.分别表示什么

1..

.是类中的一个字段,它允许你在发送消息时指定一个对象作为回调函数。当消息被处理时,如果.不为null,将直接执行该回调函数,而不会经过的处理逻辑。

1.

我们可以点击的源码发现它指向

final Callback mCallback;

1..

我们点击()的源码

public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/boolean handleMessage(@NonNull Message msg);
}

时间怎么判断

handler.sendMessageDelayed(message,2000);

表示将在系统启动后约2ms之后进行该操作

2.消息机制与时间排序

给出的解释是:

的消息机制和时间排序基于消息队列()和消息循环()。

消息队列是用来存储和管理待处理的消息的数据结构,它按照消息的触发时间进行排序。当使用发送消息时,消息会被添加到消息队列中,并按照触发时间的顺序插入到合适的位置。

消息循环是一个无限循环,它从消息队列中取出消息并将其交给对应的进行处理。在每次循环迭代中,消息循环会检查消息队列中是否有消息待处理。如果有消息,则根据消息的触发时间和优先级依次处理消息,直到消息队列为空。

通过消息队列和消息循环的配合,能够按照正确的顺序处理消息。消息的触发时间决定了消息在队列中的位置,而消息循环负责按照队列顺序逐个取出消息进行处理。

这种基于消息队列和消息循环的机制可以保证消息的顺序和准确性。较早触发的消息会先被处理,而较晚触发的消息会在之后的时刻被处理,确保了消息处理的有序性。同时,通过消息队列的排序,可以优先处理优先级较高的消息。

3.的相关知识

先说一下的作用,我们在进行

Looper.prepare();

的时候点击源码进去会发现里面进行了的set方法,

而它的作用在我理解就是在多个线程中虽然调用的是同一个,但是它们的值不一样,根本原因是因为内部有一个

我们看看内部的set方法

3.的set方法

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}

我们会发现的set方法会先获得当前的线程,然后获得,判断map为不为空,如果它不为空,就直接把当前的线程和value值传给map,如果map为null的话则创建map

这里面我们就可以明白为什么每个的不一样了

因为

 Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);

我们会发现它是先获得当前的线程,然后再用获得的线程来创建

创建的话很简单

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

就new一个

我们看看内部

3.1.

 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}/*** Construct a new map including all Inheritable ThreadLocals* from given parent map. Called only by createInheritedMap.** @param parentMap the map associated with parent thread.*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}

其实别看这么多的代码,我的理解就是就是用一个Entry数组存储,并结合了哈希表的概念

为什么这么说呢,因为它是一个二维数组,其中第一个为索引值,第二个为存储对象。

存储的是什么呢?Entry 的 key 就是线程的本地化对象 ,而 value 则存放了当前线程所操作的变量副本。

其中最容易被问到的还有就是内存泄漏

我们先了解一下什么是内存泄漏

3.2内存泄漏

在中,内存泄漏指的是应用程序在运行过程中,由于不正确的内存管理导致一些对象无法被垃圾回收器正确释放,从而造成内存资源的浪费和持续占用。这些未释放的对象会继续占用内存空间,导致应用程序的内存占用逐渐增加,最终可能导致内存溢出或导致应用程序运行缓慢、卡顿甚至崩溃。

在中内存泄露的根本原因在于 的生命周期与当前线程 的生命周期相同,且 使用完没有进行手动删除导致的

所以我们如果进行**()方法后不进行.()**方法就会导致内存泄漏

4. 4.1为什么一个线程只能创建一个

我们刚才其实将的时候讲过了

进行初始化的时候会调用的set方法,set方法在内部会获得当前的线程,并根据当前的线程创建,每个线程都有自己的 ,而 中只能保存一个 实例。

4.2为什么陷入死循环的时候不会ANR,主线程是阻塞的嘛?

我们回顾一下的流程,当它被之后,调用 的 loop() 方法,它在执行过程中会不断从消息队列中获取消息,并将消息分发给对应的 进行处理。

的loop是个无限循环的方法,但是不会阻塞主线程,更不会ANR

我们先了解一下什么情况会导致ANR

4.2.1什么情况下会导致ANR

我们一般都知道如果一个界面如果长时间没有反应则是因为它陷入了ANR

但是比较官方的说法是:

在 中,主线程负责处理 UI 相关的操作,包括用户输入、界面更新等。为了保证主线程的响应性, 系统对主线程的响应时间有一定的限制,通常为 5 秒钟。如果主线程在这个时间内没有响应,就会被认为发生了 ANR,并弹出 ANR 对话框

但是我们在进行的loop方法的时候,它会判断当前的中是否有新消息,如果没有新消息,loop() 方法会进入等待状态,不会占用主线程的执行时间片。只有当有新的消息到达时,loop() 方法才会被唤醒并继续执行。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了