Android面试题汇总-Handler

1、Handler的实现原理

在Android开发中,Handler是一个非常重要的组件,它允许你发送和处理MessageRunnable对象与一个线程的MessageQueue

  1. Looper:每个线程可以有一个Looper,它负责循环遍历线程的MessageQueue
  2. MessageQueue:这是一个等待处理的消息队列,MessageRunnable对象可以被插入到这个队列中。
  3. Handler:当你创建一个Handler实例时,它会与创建它的线程的Looper绑定。你可以使用Handler来发送消息或者在该线程上执行代码块。

当你向Handler发送一个Message或者Runnable时,它会被放入与Handler关联的LooperMessageQueue中。然后,Looper会处理队列中的每个消息,将它们一个接一个地分发给Handler,这样Handler就可以在其绑定的线程上处理它们。

这是一个典型的使用场景:

// 在主线程中创建Handler
Handler handler = new Handler(Looper.getMainLooper());// 发送一个Runnable到MessageQueue
handler.post(new Runnable() {@Overridepublic void run() {// 这段代码会在主线程中执行}
});

在这个例子中,Runnable会被添加到主线程的MessageQueue中,并且当轮到它执行时,它会在主线线程中运行。这使得Handler成为在不同线程之间进行通信和工作调度的一个非常有用的工具。

2、子线程中能不能直接new一个Handler?为什么主线程可以?

在Android中,Handler是一个非常有用的工具,用于在不同线程之间进行通信和工作调度。

  1. 主线程中创建Handler
    • 在主线程中,你可以直接使用new Handler()来创建一个Handler实例。这是因为主线程默认已经有一个与之关联的Looper,而Handler需要与Looper配对使用。
    • 主线程的Looper负责循环遍历主线程的MessageQueue,以便处理消息和运行Runnable对象。
    • 因此,主线程中的Handler可以直接与主线程的Looper关联,从而在主线程上执行操作。
  2. 子线程中创建Handler
    • 在子线程中,你不能直接使用new Handler()来创建Handler实例。这是因为子线程默认没有与之关联的Looper
    • 如果你在子线程中直接创建Handler,它将无法接收消息,因为没有可用的Looper
    • 为了在子线程中使用Handler,你需要先创建一个带有Looper的线程,然后将Handler与该线程的Looper关联。
  3. 在子线程中创建Handler的解决方案
    • 你可以使用HandlerThread来创建一个带有Looper的子线程,然后在该子线程中创建Handler
    • HandlerThread是一个包含了Looper的线程,它允许你在子线程中使用Handler,而不需要手动管理Looper的创建和循环。
    • 这样,你就可以在子线程中直接使用new Handler()来创建Handler实例。

总之,主线程中可以直接创建Handler,因为主线程默认有一个与之关联的Looper。而在子线程中,你需要使用HandlerThread或手动创建带有Looper的线程,然后将Handler与该线程的Looper关联,以便在子线程中使用Handler

3、Handler导致的内存泄露原因及其解决方案

在Android开发中,Handler是一个强大的工具,用于在不同线程之间进行通信和工作调度。然而,如果不小心使用,它也可能导致内存泄露。

内存泄露的定义

内存泄露是指应用程序未能释放不再需要的内存,或者无意中保留了对对象的引用,从而阻止垃圾回收器回收内存。结果是,应用程序的内存使用量随着时间的推移不断增加,最终导致性能下降甚至应用程序崩溃。

Handler导致内存泄露的常见原因

以下是导致内存泄露的常见情况,以及每种情况的代码示例:

  1. 保持对Activity和Fragment的引用
    • 当对Activity或Fragment的引用被保持的时间超过必要时,它们容易导致内存泄露。
    • 例如,如果后台任务持有对已关闭的Activity的引用,该Activity的资源可能无法正确释放。
  2. 上下文(Context)的错误使用
    • 错误使用Android的Context对象可能导致微妙的内存泄露。
    • 例如,将Activity的Context引用存储在长时间存活的对象中,可能会阻止该Activity被垃圾回收。
  3. AsyncTask和线程的错误处理
    • 线程和AsyncTask在Android应用程序中用于后台处理,但如果处理不当,它们也可能引入内存泄露。
    • 例如,如果AsyncTask持有对Activity的引用,而Activity已经被销毁,那么Activity的资源可能无法释放。

解决方案

为了避免Handler导致的内存泄露,可以采取以下措施:

  • 使用静态内部类

    • 将Handler定义为静态内部类,而不是匿名内部类
    • 静态内部类不会隐式持有对外部类的引用,因此Activity或Fragment不会被泄露。
    • 示例代码如下:
    public class MyActivity extends Activity {private static class MyHandler extends Handler {private final WeakReference<MyActivity> mActivity;MyHandler(MyActivity activity) {mActivity = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {MyActivity activity = mActivity.get();if (activity != null) {// 处理消息}}}private final MyHandler mHandler = new MyHandler(this);
    }
    
  • 使用弱引用(WeakReference)

    • 如果需要在Handler中引用外部类,使用弱引用来避免直接持有对外部类的引用。
    • 弱引用不会阻止外部类被垃圾回收。
    • 示例代码中的WeakReference<MyActivity>就是这样的用法。
4、一个线程可以有几个Handler?几个Looper?几个MessageQueue对象?

在Android中,线程与LooperMessageQueue的关系是一对一的,但一个线程可以有多个Handler。下面是这些组件之间关系的详细说明:

  • Looper:每个线程最多只能有一个LooperLooper负责管理线程的MessageQueue,并且循环处理消息队列中的消息。
  • MessageQueue:与Looper一样,每个线程也只能有一个MessageQueue。这是因为MessageQueue是由Looper创建和管理的,用于存储和分发消息。
  • Handler:一个线程可以创建多个Handler实例。所有的Handler都会与创建它们的线程的Looper绑定,因此它们实际上共享同一个MessageQueue

这意味着,尽管你可以在一个线程中创建多个Handler来处理消息或执行代码块,但所有这些Handler都会将消息发送到同一个MessageQueue中,并由同一个Looper进行处理。

这里是一个简单的代码示例,展示了如何在一个线程中使用多个Handler

// 创建一个新线程,并在其中设置Looper和MessageQueue
Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();// 在同一个线程中创建多个HandlerHandler handler1 = new Handler();Handler handler2 = new Handler();// ... 可以继续创建更多的Handler// 使用Handler发送消息或执行Runnablehandler1.post(new Runnable() {@Overridepublic void run() {// 执行一些操作}});handler2.post(new Runnable() {@Overridepublic void run() {// 执行另一些操作}});// 开始循环处理消息Looper.loop();}
});
thread.start();

在这个例子中,handler1handler2都与同一个线程的LooperMessageQueue绑定。它们可以独立地发送消息和执行任务,但所有的消息都会被同一个Looper处理。

总结一下,一个线程可以有多个Handler,但只能有一个Looper和一个MessageQueue

5、Message对象创建的方式有哪些?区别是什么?

在Android中,Message对象可以通过几种不同的方式创建,每种方式都有其特定的用途和区别。以下是创建Message对象的几种常见方法:

  1. 直接实例化
    • 你可以直接使用new Message()来创建一个新的Message对象。
    • 这种方式简单直接,但不是最高效的方法,因为它每次都会创建一个新的对象。
  2. Message.obtain()
    • Message.obtain()是一种更优的创建Message对象的方法。
    • 它从消息池中获取一个可用的Message对象,如果消息池为空,则创建一个新的Message对象。
    • 这种方式比直接实例化更高效,因为它重用了消息池中的对象,减少了内存分配和垃圾回收的开销。
  3. Handler.obtainMessage()
    • Handler.obtainMessage()方法会创建一个Message对象,并自动将其与Handler关联。
    • 这种方法不仅重用了消息池中的对象,还自动设置了Messagetarget字段,指向创建它的Handler
  4. Message.obtain(Handler h)
    • 类似于Handler.obtainMessage()Message.obtain(Handler h)也会创建一个Message对象,并将其target字段设置为传入的Handler
    • 这种方法允许你在创建Message时指定一个特定的Handler作为目标。
  5. Message.obtain(Handler h, int what)
    • 这个方法除了设置Messagetarget字段外,还可以设置what字段,这个字段通常用于描述消息的类型或用途。
  6. Message.obtain(Handler h, int what, Object obj)
    • 这个方法在设置targetwhat字段的同时,还允许你附加一个obj字段,这个字段可以包含任何类型的对象。
  7. Message.obtain(Handler h, Runnable callback)
    • 如果你想要在Message被处理时执行一个Runnable,可以使用这个方法。
    • MessageHandler处理时,Runnable会被执行。

每种创建Message对象的方法都有其适用场景。通常,使用Message.obtain()Handler.obtainMessage()是最佳实践,因为它们通过重用消息池中的对象来提高性能。直接实例化Message对象通常不推荐,除非你有特定的理由需要这样做。

6、Handler 有哪些发送消息的方法

在 Android 中,Handler 是一种用于实现异步消息传递机制的重要工具。它允许你在不同线程之间发送和处理消息,特别是在工作线程中更新 UI。下面是 Handler 发送消息的几种常见方法:

  1. sendMessage(Message):通过此方法发送消息到消息队列。你可以在消息中携带参数,例如整数标识符、字符串、对象等。这是最常用的方式。

    // 创建消息对象
    Message msg = Message.obtain();
    msg.what = 1; // 消息标识
    msg.obj = "Hello, World!"; // 消息内容// 发送消息到消息队列
    handler.sendMessage(msg);
    
  2. sendEmptyMessage(int):发送一个空的消息,只包含整数标识符。

    handler.sendEmptyMessage(2); // 发送空消息,标识为2
    
  3. sendMessageAtTime(Message, long):在未来的某个时间点发送消息。你可以指定消息的执行时间。

    long futureTimeMillis = SystemClock.uptimeMillis() + 1000; // 1秒后
    handler.sendMessageAtTime(msg, futureTimeMillis);
    
  4. sendMessageDelayed(Message, long):延时一定时间后发送消息。

    long delayMillis = 2000; // 延时2秒
    handler.sendMessageDelayed(msg, delayMillis);
    

这些方法允许你根据不同的需求选择合适的方式来发送消息,以实现线程之间的通信和任务调度。

如果你在 Android 开发中遇到了内存泄漏或其他问题,记得检查 Handler 的使用,特别是在工作线程中使用 Handler 时要注意线程安全和资源释放。

7、Handler的post与sendMessage的区别和应用场景

在 Android 开发中,Handlerpost 方法和 sendMessage 方法都用于在不同线程之间进行通信,尤其是在后台线程与主线程(UI线程)之间。但它们在使用和应用场景上有所不同。

post(Runnable) 方法:

  • post 方法允许你发送一个 Runnable 对象到 Handler 所关联的消息队列。
  • Runnable 对象到达队列前面时,它的 run 方法会被调用。
  • 这个方法通常用于当你只需要执行一些代码而不需要发送任何消息时
  • post 方法适合用于简单的场景,实现起来比较方便。
handler.post(new Runnable() {@Overridepublic void run() {// 这里的代码会在 Handler 所在的线程中执行}
});

sendMessage(Message) 方法:

  • sendMessage 方法允许你发送一个 Message 对象到 Handler 所关联的消息队列。
  • Message 对象包含了一个描述信息的 what 字段,以及可选的数据对象。
  • Message 到达队列前面时,HandlerhandleMessage 方法会被调用,并且这个 Message 对象会作为参数传递给它。
  • 这个方法适用于需要传递更多信息或者需要根据消息类型进行不同处理的场景。
Message msg = Message.obtain();
msg.what = 1; // 消息标识符
// ... 设置其他数据
handler.sendMessage(msg);

总结来说,post 方法更适合于执行简单的任务,而 sendMessage 方法则适用于需要传递复杂数据或者根据消息类型进行分别处理的情况。

在实际开发中,选择使用 post 还是 sendMessage 取决于你的具体需求。如果你只是想在主线程执行一段代码,post 方法会更简单。如果你需要进行更复杂的通信,比如发送带有数据的消息,并在 Handler 中根据消息类型做出不同的响应,那么 sendMessage 方法会更合适。

8、Handler postDelayed后消息队列有什么变化,假设先 postDelayed10s, 再 postDelayed1s, 怎么处理这2条消息

在 Android 中,Handler 是一种用于实现异步消息传递机制的重要工具。它允许你在不同线程之间发送和处理消息,特别是在工作线程中更新 UI。让我们来探讨一下 Handler 中的 postDelayed 方法和 sendMessageDelayed 方法,以及它们在消息队列中的处理方式。

  1. postDelayed(Runnable, long) 方法:

    • postDelayed 方法允许你发送一个 Runnable 对象到 Handler 所关联的消息队列。
    • Runnable 对象到达队列前面时,它的 run 方法会被调用。
    • 这个方法通常用于当你只需要执行一些代码而不需要发送任何消息时。
    • postDelayed 方法适合用于简单的场景,实现起来比较方便。
    new Handler().postDelayed(new Runnable() {@Overridepublic void run() {// 延时到了的操作}
    }, 10000); // 10秒延时
    
  2. sendMessageDelayed(Message, long) 方法:

    • sendMessageDelayed 方法允许你发送一个 Message 对象到 Handler 所关联的消息队列。
    • Message 对象包含了一个描述信息的 what 字段,以及可选的数据对象。
    • Message 到达队列前面时,HandlerhandleMessage 方法会被调用,并且这个 Message 对象会作为参数传递给它。
    • 这个方法适用于需要传递更多信息或者需要根据消息类型进行不同处理的场景。
    Message msg1 = Message.obtain();
    msg1.what = 1; // 消息标识
    // ... 设置其他数据Message msg2 = Message.obtain();
    msg2.what = 2; // 另一个消息标识
    // ... 设置其他数据handler.sendMessageDelayed(msg1, 10000); // 10秒延时
    handler.sendMessageDelayed(msg2, 1000); // 1秒延时
    

现在,让我们看看这两条消息在消息队列中的处理方式:

  • 首先,msg1(10秒延时)会被插入到队列的合适位置,因为它的执行时间较长。
  • 然后,msg2(1秒延时)会被插入到队列中,但它的执行时间较短,因此会排在 msg1 后面。

总结:

  • postDelayedsendMessageDelayed 都可以实现延时操作,但前者更适合简单的任务,后者适用于需要传递更多信息或根据消息类型进行不同处理的场景。
  • 在消息队列中,延时消息会根据执行时间进行重新排序,确保按照预期的延时顺序执行。
9、MessageQueue是什么数据结构

在 Android 中,MessageQueue 是一个用于处理消息的数据结构,它是 Android 消息处理机制的核心。让我们深入了解一下 MessageQueue 的设计和工作原理。

  1. 数据结构
    • MessageQueue 内部维护了一个单向链表,用于存储待处理的消息。
    • 每个消息都是一个 Message 对象,包含了描述信息的 what 字段以及可选的数据对象。
    • 链表中的消息按照时间先后顺序存储,即时间较早的消息靠近队头。
  2. 操作
    • 当你调用 post()sendMessage() 方法时,消息会被加入到队列中。
    • Looper 会从消息队列中取出消息,并交给对应的 Handler 去处理。
    • MessageQueue 的操作包括入队和出队,确保消息按照预期的顺序执行。
  3. 延时发送
    • 当消息需要延时发送时,Android 使用了 epoll 机制。
    • sendMessageDelayed 方法会将消息的执行时间加上延迟时间,然后将消息入队。
    • epoll 机制负责唤醒等待的 Looper,以便及时处理消息。

总之,MessageQueue 是一个链表结构,用于存储消息,并保证消息的顺序执行。它是 Android 中实现异步消息传递的关键组件。

10、Handler怎么做到的一个线程对应一个Looper,如何保证只有一个 MessageQueue,ThreadLocal在Handler机制中的作用

在 Android 的 Handler 机制中,确保一个线程只有一个 Looper 和一个 MessageQueue 是通过 ThreadLocal 实现的。ThreadLocal 是一个线程局部变量存储机制,它为每个线程提供了一个独立的变量副本。这是如何工作的:

  1. 一个线程对应一个 Looper
    • 当你在一个线程中调用 Looper.prepare() 方法时,Looper 类会检查当前线程的 ThreadLocal 是否已经有 Looper 对象。
    • 如果没有,它会创建一个新的 Looper 对象,并将其保存在当前线程的 ThreadLocal 中。这样,每个线程只能有一个 Looper 对象。
  2. MessageQueue 的创建
    • Looper 对象在创建时会同时创建一个 MessageQueue
    • 这个 MessageQueue 是与 Looper 相关联的,因此每个 Looper 都有一个唯一的 MessageQueue
  3. ThreadLocalHandler 机制中的作用
    • ThreadLocal 提供了一种将数据与特定线程关联的方式。
    • Handler 实例化时,它会使用 ThreadLocal 来获取与当前线程关联的 Looper 对象。
    • 这样,Handler 就可以将消息发送到正确的 MessageQueue,并确保消息由正确的 Looper 处理。

简而言之,ThreadLocal 在 Android 的 Handler 机制中起到了关键作用,它确保了每个线程都有自己的 LooperMessageQueue,并且这些组件是互不干扰的。这种设计允许 Handler 在正确的线程中处理消息,同时避免了多线程之间的数据冲突。

11、HandlerThread是什么?好处 、原理 、使用场景

HandlerThread 是 Android 中的一个类,它是 Thread 的一个子类,用于在单独的线程中执行任务,同时拥有一个 Looper,允许它处理消息队列。这里是关于 HandlerThread 的一些详细信息:

什么是 HandlerThread: HandlerThread 继承自 Thread 类,它内部包含一个 Looper 对象,可以创建 Handler 类来处理消息。这使得 HandlerThread 可以在其线程中执行耗时操作,而不会影响到主线程(UI线程)的响应性。

好处:

  • 线程管理简化: HandlerThread 简化了线程的创建和消息处理,不需要复杂的线程加 Handler 组合。
  • 提高应用响应性: 允许在后台线程中处理任务,从而避免阻塞主线程。
  • 方便的消息传递: 可以轻松地在工作线程和主线程之间传递消息。

工作原理: HandlerThread 通过内部的 Looper 对象来循环处理消息队列中的消息。当你创建一个 HandlerThread 并调用 start() 方法后,它会在内部创建一个 Looper,然后你可以使用这个 Looper 来创建 Handler 实例。这个 Handler 可以接收消息并在 HandlerThread 的线程中处理它们。

使用场景:

  • 耗时任务处理: 当你有多个需要串行执行的耗时任务时,如文件读写、网络请求等。
  • 异步通信: 当你需要在后台线程和主线程之间进行消息传递时。
  • 周期性任务: 当你需要执行周期性的任务,但又不想影响主线程时。

希望这些信息能帮助你更好地理解和使用 HandlerThread。如果你有更具体的问题或需要示例代码,请告诉我!

12、IdleHandler及其使用场景

IdleHandler 是 Android 中的一个接口,它属于 MessageQueue 类。它的主要作用是在消息队列空闲时执行一些任务。

什么是 IdleHandler: IdleHandler 是一个回调接口,当消息队列(MessageQueue)没有消息要处理,或者下一个消息还没到处理时间时,就会触发 IdleHandler。如果 IdleHandlerqueueIdle() 方法返回 true,则表示它是持久的,会在队列每次空闲时被调用;如果返回 false,则表示它是一次性的,调用后会从队列中移除。

使用场景:

  • 资源回收: 在应用不忙碌时,可以用 IdleHandler 来删除不必要的对象,释放内存等。
  • 延迟处理任务: 可以用来延迟执行一些不紧急的任务,比如延迟加载图片或处理数据。
  • 定时任务: 用于实现一些需要定时执行的任务,例如定时同步数据或发送请求。

如何使用:

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {// 在这里执行空闲时的处理逻辑return false; // 返回 false 表示执行后移除该回调}
});

IdleHandler 提供了一种灵活的方式来优化应用的性能和资源管理。

13、消息屏障,同步屏障机制

Handler 的同步屏障机制允许开发者设置一个屏障,使得屏障之后的消息在处理前必须等待屏障被移除。这种机制在某些场景下非常有用,比如当需要等待一系列操作完成后才继续处理后续消息时。

同步屏障的工作原理

Handler 的同步屏障机制主要依赖于两个方法:sendMessageAtFrontOfQueue(Message msg)removeCallbacksAndMessages(Object token)。这两个方法一起工作,实现了同步屏障的效果。

  • sendMessageAtFrontOfQueue(Message msg): 这个方法将一个消息插入到消息队列的最前面,确保它在下一个消息循环中首先被处理。
  • removeCallbacksAndMessages(Object token): 这个方法用于移除指定标记(token)的消息和回调。在同步屏障的情况下,我们可以使用这个方法来移除屏障,从而允许后续消息继续处理。

总之,同步屏障为 Handler 消息机制增加了一种简单的优先级机制,使得异步消息的优先级高于同步消息。

14、子线程能不能更新UI

在 Android 中,子线程是不能直接更新 UI的,因为 UI 操作必须在主线程(也称为 UI 线程)中执行。如果在子线程中直接更新 UI,会导致应用程序崩溃或出现不可预料的问题。

然而,我们可以使用一些机制来在子线程中安全地更新 UI。以下是几种常见的方法:

  1. Handler 机制

    • 在主线程中创建一个 Handler 对象,并重写 handleMessage() 方法来处理消息。

    • 子线程中发送消息给 Handler,然后在 handleMessage() 中更新 UI。

    • 示例代码:

      Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == 1) {// 在这里更新 UItextView.setText("子线程更新 UI");}}
      };// 在子线程中发送消息
      new Thread(new Runnable() {@Overridepublic void run() {Message message = handler.obtainMessage();message.what = 1;handler.sendMessage(message);}
      }).start();
      
  2. runOnUiThread() 方法

    • 在子线程中使用 runOnUiThread() 方法来更新 UI。

    • 示例代码:

      new Thread(new Runnable() {@Overridepublic void run() {// 耗时操作// ...// 更新 UIrunOnUiThread(new Runnable() {@Overridepublic void run() {textView.setText("子线程更新 UI");}});}
      }).start();
      
  3. View.post() 方法

    • 使用 View.post(Runnable r) 方法来将 UI 更新操作放入 UI 线程的消息队列中。

    • 示例代码:

      imageView.post(new Runnable() {@Overridepublic void run() {// 在这里更新 UIimageView.setImageBitmap(bitmap);}
      });
      

总之,虽然子线程不能直接更新 UI,但通过上述方法,我们可以安全地在子线程中执行耗时操作,并在主线程中更新 UI。

15、为什么Android系统不建议子线程访问UI

这是因为在 Android 中,UI 控件并不是线程安全的。如果多个线程同时访问 UI 控件,可能导致 UI 处于不可预期的状态。那么为什么系统不对 UI 控件的访问加上锁机制呢?原因有两点:

  1. 复杂逻辑:加上锁机制会让 UI 访问的逻辑变得复杂。处理多线程访问的同步问题需要考虑锁的粒度、避免死锁等,增加了代码的复杂性。
  2. 效率降低:锁机制会阻塞某些线程的执行,从而降低了 UI 访问的效率。考虑到这一点,Android 采用了更简单且高效的方法:单线程模型。开发者只需通过 Handler 来切换 UI 访问的执行线程,即可处理 UI 操作,而不必担心复杂的同步问题。

因此,为了确保线程安全且高效地处理 UI 操作,Android 引入了单线程模型,使主线程负责处理 UI 事件,其他线程负责执行耗时操作,从而避免界面卡顿和崩溃等问题。

16、Android中为什么主线程不会因为Looper.loop()里的死循环卡死

在 Android 中,主线程不会因为 Looper.loop() 里的死循环而卡死,这涉及到一些关键原因:

  1. 线程调度:现代操作系统(包括 Android)使用复杂的线程调度机制来确保线程不会无限期地占用 CPU。当主线程在处理一个事件或消息时,如果它花费了太多的时间(即超过了预定的时间片),操作系统会中断它并将 CPU 资源分配给其他线程。这样,主线程就不会因为 Looper.loop() 里的死循环而卡死。
  2. 消息循环机制Looper.loop() 构成了主线程的消息循环机制,它能够高效地分配和处理消息和事件。主线程通过 Looper 在空闲时等待新任务的到达,并在接收到任务时快速地进行处理。这种机制使得主线程能够高效地处理各种事件和消息,确保应用的流畅运行。

综上所述,尽管 Looper.loop() 是一个死循环,但由于事件分发、消息处理、线程调度和非阻塞操作等机制,主线程并不会因此卡死。这使得主线程能够高效地处理各种事件和消息,确保应用的流畅运行。

17、Handler消息机制中,一个Looper是如何区分多个Handler的?当 Activity有多个Handler的时候,怎么样区分当前消息由哪个Handler处理?处理message的时候怎么知道是去哪个callback处理的?

在 Android 的消息机制中,LooperHandler 扮演着关键角色。

  1. 一个线程可以有几个 Looper?几个 Handler?
    • 一个线程可以创建多个 Handler,但只能创建一个 Looper。这意味着多个 Handler 可以与同一个 Looper 相关联。
    • 每个线程只有一个 Looper,负责管理该线程的消息队列。因此,多个 Handler 共用同一个 Looper 和消息队列。
  2. 怎么区分当前消息由哪个 Handler 处理?
    • 当你创建一个 Handler 时,它会自动与当前线程的 Looper 关联起来。因此,每个 Handler 都与同一个 Looper 相关联。
    • 每个 Message 对象都有一个 target 属性,它指向发送该消息的 Handler。因此,通过 msg.target,我们可以区分不同的 Handler
    • Looper 从消息队列中取出消息时,它会调用 msg.target.dispatchMessage(msg),将消息分发到指定的 Handler 进行处理。

总结:

  • 一个线程可以有多个 Handler,但只能有一个 Looper 和一个消息队列。
  • Handler 通过 sendMessage 发送消息时,会将自己存储在消息中的 target 属性,然后 Looper 从消息队列中取出消息并将其发送到指定的 Handler
18、Looper.quit/quitSafely的区别

在 Android 的 Looper 类中,quit()quitSafely() 方法都用于终止消息循环,但它们在处理消息队列时有所不同:

  • quit() 方法:当调用 quit() 时,它会立即终止 Looper,并且不会再处理消息队列中的任何消息。这意味着,如果消息队列中还有未处理的消息,它们将不会被派发和处理,可能导致一些操作未完成就被强制终止。因此,使用 quit() 可能会导致不安全的情况,因为可能有些消息还没来得及处理就被丢弃了。
  • quitSafely() 方法:与 quit() 不同,quitSafely() 会等待消息队列中的所有消息和任务都被处理完毕后才终止 Looper。这包括将消息队列中所有的非延迟消息(即已经到达处理时间的消息)派发出去,让 Handler 去处理。但是,它会清空所有的延迟消息(即那些还没到处理时间的消息)。这样做可以确保所有的操作都能安全地完成,然后再安全地终止消息循环。

总的来说,quitSafely() 方法比 quit() 方法更安全,因为它确保了所有的即时消息都被处理完毕,而不会遗漏任何已经到达的消息。这对于需要完成关键操作的应用程序来说尤其重要,以避免因为消息循环的突然终止而导致的问题。

19、通过Handler如何实现线程的切换

在 Android 中,Handler 是实现线程切换的关键机制之一。

  1. HandlerLooperMessageQueueMessage 的关系

    • Message是一个包含描述和可以发送到 Handler的任意数据对象的消息。它有几个关键变量:

      • target:负责发送并分发该消息的 Handler
      • callback:一个可运行的回调。
      • next:消息池中的下一个消息。
    • MessageQueue 是维护当前线程的 Looper 分发的消息列表。它实际上只是消息队列的管理者,持有由 Message 组成的单链表结构。

    • Looper 是一个消息循环器,用于在某个线程中不断地处理消息队列中的消息。它持有并维护了消息队列。

    • 当我们在子线程中发送消息时,Handler 将这些消息放入消息队列中,然后通知 Looper 开始处理消息。当 Looper 处理到这些消息时,Handler 将这些消息交给主线程的 MessageQueue 进行处理,从而实现将子线程中的任务切换到主线程中执行的效果。

  2. 具体流程

    • 在子线程中创建一个 Handler 对象时,它会默认绑定到当前线程的 Looper 对象。
    • 当我们在子线程中发送消息时,Handler 将这些消息放入消息队列中,然后通知 Looper 开始处理消息。
    • Looper 处理到这些消息时,Handler 将这些消息交给主线程的 MessageQueue 进行处理,从而实现将子线程中的任务切换到主线程中执行的效果。
    • 因为 Looper.loop()HandlerThread 线程中执行,所以 Handler 的处理过程也在 HandlerThread 线程中执行。

总之,Handler 机制通过消息队列和 Looper 实现了线程之间的切换。当我们在子线程中使用 Handler 发送消息时,它会自动将任务切换到主线程中执行,确保了 UI 操作在主线程上进行,同时避免了线程安全问题。

20、Handler 如何与 Looper 关联的

HandlerLooper 是 Android 中消息机制的关键组件,它们协同工作以实现线程间通信和异步任务处理。

  1. Looper(循环器):

    • Looper 负责封装消息循环,使其所绑定的线程能够循环执行消息。

    • 默认情况下,线程是没有与之关联的 Looper的。要创建一个线程作为循环器,需要遵循以下步骤:

      1. 在线程运行之初调用 Looper.prepare()
  2. 然后调用 Looper.loop() 方法,使线程开始循环处理消息。
    3. 当不再需要时,可以调用 Looper.quit() 结束这个线程的循环器(或线程被终止)。

  3. Handler(处理器):

    • Handler 是用于发送和处理消息的关键组件。
    • 每个 Handler 对象都与创建时所处线程相关联的 Looper
    • Handler 可以将消息(Message)和 Runnable 对象发送到消息队列中。
    • 通过以下方法,我们可以将消息加入到消息队列中:
      • post(Runnable)
      • postAtTime(Runnable, long)
      • postDelayed(Runnable, long)
      • sendEmptyMessage(int)
      • sendMessage(Message)
      • sendMessageAtTime(Message, long)
      • sendMessageDelayed(Message, long)
  4. 消息队列(MessageQueue)

    • Looper 主动调用,用于管理消息队列。
    • Message 经过 Handler 的入队操作会加入到 Looper 所拥有的 MessageQueue 中。

总结:

  • Handler 通过消息队列与 Looper 关联,使得线程能够处理消息。
  • 当你创建一个 Handler 实例时,它默认与当前线程的 Looper 关联。
  • 如果有多个 Handler 实例,它们将共享同一个 Looper
21、Looper 如何与 Thread 关联的

LooperThread 之间是通过 ThreadLocal 关联的。

  1. Looper(循环器):

    • Looper 负责封装消息循环,使其所绑定的线程能够循环执行消息。

    • 默认情况下,线程是没有与之关联的 Looper的。要创建一个线程作为循环器,需要遵循以下步骤:

      1. 在线程运行之初调用 Looper.prepare()
      2. 然后调用 Looper.loop() 方法,使线程开始循环处理消息。
      3. 当不再需要时,可以调用 Looper.quit() 结束这个线程的循环器(或线程被终止)。
  2. ThreadLocal

    • ThreadLocal 提供了线程本地变量,这些变量是每个线程私有的副本,线程之间互不干扰。
    • Looper 的实现中,使用了 ThreadLocal 来维护当前线程的 Looper 对象。
    • 当线程启动时,可以通过 Looper.prepare() 方法创建一个 Looper 对象,并通过 Looper.loop() 方法开启轮询循环。
    • 在轮询循环中,Looper 会不断地从消息队列中取出消息,并将消息分发给相应的处理器进行处理。

总结:

  • Looper 通过 ThreadLocalThread 关联,使得线程能够处理消息。
  • 当你创建一个 Handler 实例时,它默认与当前线程的 Looper 关联。
  • 如果有多个 Handler 实例,它们将共享同一个 Looper
22、Looper.loop()源码

Looper 类是 Android 消息处理机制的基本组件之一,它与 HandlerMessageQueue 协同工作。

  1. Looper

    • Looper 负责封装消息循环,使其所绑定的线程能够循环执行消息。

    • 默认情况下,线程是没有与之关联的 Looper的。要创建一个线程作为循环器,需要遵循以下步骤:

      1. 在线程运行之初调用 Looper.prepare()
      2. 然后调用 Looper.loop() 方法,使线程开始循环处理消息。
      3. 当不再需要时,可以调用 Looper.quit() 结束这个线程的循环器(或线程被终止)。
  2. Handler

    • Handler 是用于发送和处理消息的关键组件。
    • 每个 Handler 对象都与创建时所处线程相关联的 Looper
    • Handler 可以将消息(Message)和 Runnable 对象发送到消息队列中。
    • 通过以下方法,我们可以将消息加入到消息队列中:
      • post(Runnable)
      • postAtTime(Runnable, long)
      • postDelayed(Runnable, long)
      • sendEmptyMessage(int)
      • sendMessage(Message)
      • sendMessageAtTime(Message, long)
      • sendMessageDelayed(Message, long)
  3. 消息队列(MessageQueue)

    • Looper 主动调用,用于管理消息队列。
    • Message 经过 Handler 的入队操作会加入到 Looper 所拥有的 MessageQueue 中。

Looper.loop() 方法本身的源代码:

public static void loop() {// 获取当前线程的 Looperfinal Looper me = myLooper();if (me == null) {// 如果没有 Looper 存在(即未调用 Looper.prepare()),抛出异常throw new RuntimeException("No Looper; Looper.prepare() wasn't called.");}// 开始消息循环for (;;) {Message msg = me.mQueue.next(); // 从队列中获取下一条消息if (msg == null) {// 如果没有消息,退出循环return;}// 将消息分发给相应的 Handler 进行处理msg.target.dispatchMessage(msg);// 回收消息msg.recycle();}
}

总结:

  • LooperHandler 协同工作,确保线程安全并处理消息。
  • 理解这一机制对 Android 开发者至关重要。
  • 记住 UI 更新必须在主线程(UI 线程)上进行,这正是 Looper 机制的用途。
23、MessageQueue的enqueueMessage()方法如何进行线程同步的

MessageQueueenqueueMessage() 方法是 Android 消息机制中的关键部分,用于将消息插入到消息队列中。

  1. 消息队列(MessageQueue)
    • MessageQueueMessage(消息)的管理者,负责保存消息的集合,并执行消息入队、出队等操作。
    • MessageQueue 通过同步机制确保多线程环境下的安全操作。
  2. enqueueMessage() 方法
    • enqueueMessage() 将消息插入到消息队列中。
    • 首先,它会检查消息的执行时间 when 和当前队列中的消息。
    • 如果新消息的执行时间更早或队列为空,它会直接将新消息插入到队列的前面。
    • 否则,它会遍历队列,找到适当的位置将新消息插入。
  3. 同步机制
    • enqueueMessage() 方法使用了 synchronized (this) 同步块。
    • 这意味着在多线程环境中,只有一个线程可以执行 enqueueMessage() 方法。
    • 这样可以防止多个线程同时对同一个队列进行操作,确保线程安全。

总结:

  • MessageQueueenqueueMessage() 方法通过同步机制确保线程安全。
  • 它根据消息的执行时间将消息插入到队列中,保证消息的有序性。
24、MessageQueue的next()方法内部原理

MessageQueue.next() 方法是 Android 中消息循环机制的关键部分之一。

  1. 消息循环概述:

    • 在 Android 中,每个线程都有一个消息循环(Looper),负责处理消息队列中的消息。
    • 消息循环的主要任务是从消息队列中取出消息并将其分发给相应的处理器(通常是 Handler)。
    • MessageQueue 是消息循环的核心组件,负责存储待处理的消息。
  2. MessageQueue.next() 方法:

    • MessageQueue.next() 方法用于从消息队列中获取下一条消息。
    • 如果消息队列为空,该方法会阻塞,直到有新的消息添加进来。
    • 下面是 MessageQueue.next() 方法的主要步骤:
    Message next() {final long ptr = mPtr;if (ptr == 0) {return null;}int nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// 获取消息链表头部元素Message msg = mMessages;if (msg != null && msg.target == null) {// 找到第一个异步消息while (msg != null && !msg.isAsynchronous()) {msg = msg.next;}}if (msg != null) {if (now < msg.when) {// 计算剩余时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 处理消息msg.markInUse();return msg;}} else {// 没有可执行的消息,阻塞等待nextPollTimeoutMillis = -1;}}}
    }
    
    • 此方法的关键点:
      • 如果消息队列为空,会阻塞等待新消息的到来。
      • 如果有消息,会检查消息的执行时间,如果已到达执行时间,就处理该消息。
      • 异步消息会被优先处理,同步消息会等待异步消息执行完毕。
  3. 消息的生命周期:

    • 消息从入队到执行的完整生命周期包括:
      1. 消息入队:通过 HandlersendMessage() 等方法将消息添加到消息队列。
      2. 消息取出:MessageQueue.next() 方法从队列中取出消息。
      3. 消息处理:消息被分发给相应的处理器(Handler)执行。
      4. 消息回收:处理完毕后,消息被回收并放回消息对象池。

总之,MessageQueue.next() 方法是 Android 消息循环机制中的关键环节,负责从消息队列中获取消息并将其分发给处理器。

25、子线程中是否可以用MainLooper去创建Handler,Looper和Handler 是否一定处于一个线程

当涉及到 Android 中的 LooperHandler 时,理解它们之间的关系以及如何在不同线程中使用它们是很重要的。

  1. Looper 和 Handler 的关系:

    • Looper 是 Android 中的一个类,用于创建消息循环(message loop)。
    • Handler 是用于处理消息的组件,它与特定线程的 Looper 关联。
    • 每个线程都可以有一个唯一的 Looper,而每个 Looper 可以关联多个 Handler
    • 当你创建一个 Handler 时,它会隐式地与创建它的线程的 Looper 关联。
  2. 在子线程中使用 MainLooper 创建 Handler:

    • 主线程的 Looper 被称为 MainLooper,它用于处理 UI 相关的消息。
    • 如果你在子线程中想要与主线程通信,可以使用 MainLooper 创建一个与主线程关联的 Handler
    • 以下是在子线程中使用 MainLooper 创建 Handler 的示例:
    // 在子线程中创建 Handler 关联到主线程的 Looper
    Handler mainThreadHandler = new Handler(Looper.getMainLooper());// 在子线程中发送消息到主线程
    mainThreadHandler.post(new Runnable() {@Overridepublic void run() {// 在这里更新 UI 或执行其他与主线程相关的操作}
    });
    
    • 这样,你就可以在子线程中使用 mainThreadHandler 来与主线程通信。
  3. 是否一定处于同一个线程:

    • LooperHandler 不一定要在同一个线程中
    • 你可以在一个线程中创建 Handler,然后将其与另一个线程的 Looper 关联。
    • 但是,需要注意的是,如果你在一个没有调用 Looper.prepare() 的线程中创建 Handler,会导致错误。因为 Handler 需要在已准备好的 Looper 环境中使用。

总之,你可以在子线程中使用 MainLooper 创建与主线程关联的 Handler,并且 LooperHandler 不一定要在同一个线程中。

26、ANR和Handler的联系

ANR(Application Not Responding) 是 Android 应用程序中的一种错误,通常发生在应用的主线程被阻塞的情况下。当主线程无法及时响应用户输入事件或绘制 UI 时,系统会触发 ANR 错误。这对用户来说非常糟糕,因为应用无法处理用户的操作,导致用户体验不佳。

让我们深入了解 ANR 和 Handler 之间的关系:

  1. ANR 类型:
    • Android 中的 ANR 可以分为以下几种类型:
      • 输入分发超时(Input dispatch timeout):如果应用在 5 秒内未响应输入事件(例如按键或屏幕触摸),就会触发此类型的 ANR。
      • 执行服务超时(Executing service):如果应用声明的服务在几秒内无法完成 Service.onCreate()Service.onStartCommand()Service.onBind() 的执行,就会触发此类型的 ANR。
      • 未调用 startForeground() 的前台服务(Service.startForeground() not called):如果应用使用 Context.startForegroundService() 启动一个前台服务,但该服务在 5 秒内未调用 startForeground(),就会触发此类型的 ANR。
      • 广播接收器超时(Broadcast of intent):如果广播接收器在一定时间内未执行完毕,就会触发此类型的 ANR。如果应用有前台活动,超时时间为 5 秒。
      • JobScheduler 交互超时(JobScheduler interactions):如果 JobService 在几秒内未从 JobService.onStartJob()JobService.onStopJob() 返回,或者如果用户启动的作业开始执行,而应用在 JobService.onStartJob() 调用后的几秒内未调用 JobService.setNotification(),就会触发此类型的 ANR。
  2. Handler 和 ANR:
    • Handler 是 Android 中用于处理消息的组件,通常与主线程的 Looper 关联。
    • 如果在主线程中使用 Handler 执行耗时操作,可能会导致主线程阻塞,从而触发 ANR。
    • 为避免 ANR,应遵循以下最佳实践:
      • 不要在主线程上执行阻塞或长时间运行的操作。
      • 使用 StrictMode 检测意外的主线程活动。
      • 减少主线程与其他线程之间的锁竞争。
      • 尽量不要在主线程上执行非 UI 相关的工作,例如处理广播或运行服务。
  3. 诊断和修复 ANR:
    • 使用 Android Vitals 可以监控和改善应用的 ANR 率。
    • 如果你的应用遇到 ANR,可以使用 Android Vitals 中的指导来诊断和解决问题。

总之,Handler 在主线程中执行的操作可能导致 ANR,因此在使用 Handler 时要格外小心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/23343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python语言课件:深入探索与实战应用

Python语言课件&#xff1a;深入探索与实战应用 Python&#xff0c;作为一种简洁、易读且功能强大的编程语言&#xff0c;已经广泛应用于数据分析、人工智能、网络爬虫等多个领域。本次课件将从四个方面、五个方面、六个方面和七个方面对Python语言进行深入剖析&#xff0c;帮…

成立不到一年,EDA“黑马”再获亿元级融资,国产自主黄金期加速到来

本土EDA厂商派兹互连 再获亿元级融资 电巢获悉&#xff0c;成都派兹互连电子技术有限公司(以下简称“派兹互连”)于近日完成超亿元产业方追加投资&#xff0c;本轮融资将用于研发投入、产品迭代及技术创新等方面。 同时我们了解到&#xff0c;派兹互连已与多家领先EDA/CAE产品…

干货分享:有哪些好用的绩效管理工具?

绩效管理在诸多企业中占据着举足轻重的地位&#xff0c;但同时也是一个令人头痛的问题。特别是在年终的绩效考评环节&#xff0c;它往往变得流于形式&#xff0c;成了一项例行公事。尽管每个人都被要求参与这一流程&#xff0c;但很少有人真正关心考核结果是否公正合理&#xf…

模拟实现priority_queue

文章目录 priority_queue简介priority_queue的实现Myless和Mygreaterpushpop常规接口 全部代码测试代码 总结 priority_queue简介 priority_queue是优先级队列。 什么是优先级队列&#xff1f; 优先级队列&#xff08;Priority Queue&#xff09;是一种数据结构&#xff0c;用于…

579页 | 工业数字孪生建模与应用(免费下载)

【1】关注本公众号&#xff0c;转发当前文章到微信朋友圈 【2】私信发送 工业数字孪生建模与应用 【3】获取本方案PDF下载链接&#xff0c;直接下载即可。 如需下载本方案PPT/WORD原格式&#xff0c;请加入微信扫描以下方案驿站知识星球&#xff0c;获取上万份PPT/WORD解决方…

节点间通路

题目链接 节点间通路 题目描述 注意点 图是有向图节点编号大于等于 0 小于 n图中可能存在自环和平行边 解答思路 初始想到的是使用广度优先遍历&#xff0c;从start开始&#xff0c;存储每个点所能到达的其他节点集合&#xff0c;直到到达target或者不能到达新的节点为止&…

“新高考”下分班怎么分?

来自安徽的张女士告诉我&#xff1a;上一年孩子升入了高中&#xff0c;但没想到才高一&#xff0c;孩子就面临了一个困难的挑选&#xff1a;312”分班&#xff01; 什么是312”分班呢&#xff1f;许多人或许不明白&#xff0c;便是要求学生在高一入学时&#xff0c;针对于3门必…

服务器数据恢复—raid5阵列磁盘坏道离线导致数据丢失的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌x3850 X5服务器&#xff0c;服务器上有一组由5块硬盘组建的raid5阵列&#xff08;包含一块热备盘&#xff09;&#xff0c;安装linux操作系统&#xff0c;运行oracle数据库。 服务器故障&#xff1a; 服务器上raid5阵列中两块硬盘由于未…

Vue进阶之Vue无代码可视化项目(四)

Vue无代码可视化项目 左侧栏第一步LeftPanel.vueLayoutView.vuebase.css第二步LayoutView.vueLeftPanel.vue编排引擎smooth-dnd安装创建文件SmoothDndContainer.tsutils.tsSmoothDndDraggable.tsLeftPanel.vue左侧栏 第一步 创建LeftPanel LeftPanel.vue <script setup…

空间数据采集与组织、转换与处理;统计数据、GPS数据、矢量数据、栅格数据、遥感云平台数据、点云数据、多维数据获取及处理

你还在为找不到合适的数据而苦恼吗&#xff1f;你还在面对大量数据束手无策&#xff0c;不知如何处理吗&#xff1f;对于从事生产和科研的人员来说&#xff0c;空间数据的采集与管理是地理信息系统&#xff08;GIS&#xff09;和空间分析领域的关键环节。通过准确高效地采集和管…

层出不穷的大模型产品如何选

目录 1.概述 2.使用体验分享 2.1.功能情况 2.2.内容生成质量 2.3.隐私安全性 2.4.小结 3.独特优势和倾向选择 4.未来发展方向 4.1.技术创新 4.2.可持续可扩展性 4.3.用户体验 4.4.应用场景 4.5.政府赋能 4.6.小结 1.概述 目前市面上的大模型AIGC产品有很多&#…

代码随想录——二叉搜索树中的插入操作(Leetcode701)

题目链接 递归 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

mysql8.0主从复制

主服务器&#xff1a;192.168.1.133&#xff1b;从服务器&#xff1a;192.168.1.136&#xff08;实际应用时&#xff0c;改成自己的服务器IP&#xff09; 2台服务器的操作系统&#xff1a;centos7.9&#xff0c;mySQL版本&#xff1a;8.0.24&#xff1b; #主服务器 192.168.1…

林锐C语言--高质量C/C++编程(第五六章)

林锐C语言–高质量C/C编程&#xff08;第五六章&#xff09; 林锐C语言--高质量C/C编程&#xff08;第五六章&#xff09; 林锐C语言--高质量C/C编程&#xff08;第五六章&#xff09;第五章 常量5.1 为什么需要常量5.2 const与#define的比较5.3 常量定义规则5.4 类中的常量 第…

如何理解与学习数学分析——第二部分——数学分析中的基本概念——第7章——连续性

第2 部分&#xff1a;数学分析中的基本概念 (Concepts in Analysis) 7. 连续性(Continuity) 本章首先讨论连续性的直观概念&#xff0c;并介绍与早期数学中常见的函数不同的函数。解释了连续性的定义&#xff0c;并演示了如何使用它来证明函数在一点上连续&#xff0c;以及证…

支付宝推出AI毛发自测工具,上传照片即可自测脱发等级

根据国家卫健委此前公布的数据&#xff0c;我国超过2.5亿人有脱发困扰&#xff0c;平均每6人中就有1人脱发&#xff0c;且近些年来&#xff0c;脱发群体呈年轻化趋势。为了帮助应对“秃”如其来的脱发问题&#xff0c;今日&#xff0c;支付宝发布“AI毛发自测”工具&#xff0c…

Mixly UDP局域网收发数据

一、开发环境 软件&#xff1a;Mixly 2.0在线版 硬件&#xff1a;ESP32-C3&#xff08;立创实战派&#xff09; 固件&#xff1a;ESP32C3 Generic(UART) 测试工具&#xff1a;NetAssist V5.0.1 二、实现功能 ESP32作为wifi sta连接到路由器&#xff0c;连接成功之后将路由器…

代码随想录 day 30

回溯总结&#xff1a; 相当于暴力for循环&#xff0c;其目的用递归控制for循环嵌套的数量。当剪枝时&#xff0c;就可以使得嵌套数量减少。把回溯问题抽象一颗树比较好懂。并且使得代码更简洁。 对于组合问题&#xff0c;什么时候需要startIndex呢&#xff1f; 在一个集合求组合…

计算机网络五层模型,看不懂请你去吃宵夜

大家好&#xff0c;我是徒手敲代码。 今天用最通俗易懂的话&#xff0c;来回答计算机网络五层模型&#xff0c;分别负责什么。 计算机网络的五层模型自底向上分别为物理层、数据链路层、网络层、传输层和应用层&#xff0c;每一层都承担着特定的职责&#xff0c;共同确保数据…

mysql8.0中的mysql.ibd

mysql8.0版本中多了一个mysql.ibd的文件。5.7版本则没有这个文件。 MySQL5.7: .frm文件 存放表结构信息 .opt文件&#xff0c;记录了每个库的一些基本 信息&#xff0c;包括库的字符集等信息 .TRN&#xff0c;.TRG文件用于存放触发器的信 息内容。 在MySQL 8.0之前&#xff0…