Looper 的目的是啥以及如何使用它?

Posted

技术标签:

【中文标题】Looper 的目的是啥以及如何使用它?【英文标题】:What is the purpose of Looper and how to use it?Looper 的目的是什么以及如何使用它? 【发布时间】:2011-11-27 16:54:52 【问题描述】:

我是 android 新手。我想知道Looper 类的作用以及如何使用它。我已经阅读了 Android Looper class documentation 但我无法完全理解它。 我在很多地方都见过它,但无法理解它的目的。任何人都可以通过定义Looper 的目的来帮助我吗?如果可能的话,还可以举一个简单的例子吗?

【问题讨论】:

我刚刚在 Safari Books Online 上找到了关于 Looper 及其使用的非常彻底和清晰的解释。不幸的是,我怀疑如果只在有限的时间内免费访问。 safaribooksonline.com/library/view/efficient-android-threading/… Android 文章和参考页面要求您拥有并理解上一篇文章,然后才能掌握当前文章。建议你阅读 Api 指南中的 Activity 和 Service 文章,然后阅读 Handler 和 Looper。如果您了解线程是什么(不是 android 线程,而是一般的线程......例如 POSIX),这也会有所帮助。 我发现这篇文章很有用:codetheory.in/… 为线程创建一个无限循环来执行,以便线程可以在每次迭代时检查任务队列,并执行任务。该任务称为消息。这是一种称为事件循环的常见设计模式,只是抽象出来的。如果多个线程共享一个事件循环,则您拥有一个线程池。主线程默认必须有循环,因为它是一个等待ui事件任务的非终止线程。其他非终止类型的程序(如视频游戏)具有事件循环。普通旧线程没有循环,但可以用looper api添加。 【参考方案1】:

什么是 Looper?

Looper 是一个用于在队列中执行 Messages(Runnables) 的类。普通线程没有这样的队列,例如简单线程没有任何队列。它执行一次,方法执行完成后,线程不会再运行另一个Message(Runnable)。

我们可以在哪里使用 Looper 类?

如果有人想要执行多条消息(Runnables),那么他应该使用负责在线程中创建队列的 Looper 类。 例如,在编写从 Internet 下载文件的应用程序时,我们可以使用 Looper 类将要下载的文件放入队列中。

它是如何工作的?

prepare() 方法来准备Looper。然后您可以使用loop() 方法在当前线程中创建一个消息循环,现在您的 Looper 已准备好执行队列中的请求,直到您退出循环。

这是您可以用来准备 Looper 的代码。

class LooperThread extends Thread 
      public Handler mHandler;

      @Override
      public void run() 
          Looper.prepare();

          mHandler = new Handler() 
              @Override
              public void handleMessage(Message msg) 
                  // process incoming messages here
              
          ;

          Looper.loop();
      
  

【讨论】:

一个 AsyncTask 更适合这个目的,因为它封装了所有的线程管理,所以更简单。 应该在run()和handleMessage()方法之前有@Override注解 文档表明你必须调用looper.quit。在上面的代码中, Looper.loop 将无限期阻塞。 如何退出循环。我的意思是在上面的代码示例中在哪里包含 Looper.quit()? 我认为使用HandlerThread 会更好,它是一个方便的带有弯针的线程类。【参考方案2】:

在Service 中处理多个向下或上传项目是一个更好的例子。

HandlerAsnycTask 通常用于在UI(线程)和工作线程之间传播事件/消息或延迟操作。所以它们与 UI 更相关。

Looper 在后台处理线程相关队列中的任务(Runnables、Futures) - 即使没有用户交互或显示 UI(应用程序在调用期间在后台下载文件) )。

【讨论】:

【参考方案3】:

Android Looper 是将MessageQueue 附加到Thread 的包装器,它管理队列处理。它在 Android 文档中看起来非常神秘,很多时候我们可能会遇到Looper 相关的 UI 访问问题。如果我们不了解基础知识,就会变得非常难以处理。

这是一个article,它解释了Looper的生命周期、如何使用它以及LooperHandler中的用法

Looper = 线程 + 消息队列

【讨论】:

这并不能解释为什么要使用这个类,只解释如何使用。【参考方案4】:

Looper 允许在单个线程上按顺序执行任务。处理程序定义了我们需要执行的那些任务。这是我试图在此示例中说明的典型场景:

class SampleLooper extends Thread 
@Override
public void run() 
  try 
    // preparing a looper on current thread     
    // the current thread is being detected implicitly
    Looper.prepare();

    // now, the handler will automatically bind to the
    // Looper that is attached to the current thread
    // You don't need to specify the Looper explicitly
    handler = new Handler();

    // After the following line the thread will start
    // running the message loop and will not normally
    // exit the loop unless a problem happens or you
    // quit() the looper (see below)
    Looper.loop();
   catch (Throwable t) 
    Log.e(TAG, "halted due to an error", t);
   


现在我们可以在其他一些线程(比如 ui 线程)中使用处理程序将任务发布到 Looper 上执行。

handler.post(new Runnable()

public void run() 
//This will be executed on thread using Looper.
    
);

在 UI 线程上,我们有一个隐式 Looper,允许我们处理 ui 线程上的消息。

【讨论】:

它不会锁定任何UI进程,是真的吗? 感谢您提供有关如何将“工作”发布到队列的示例 这并不能解释为什么要使用这个类,只解释如何使用。 SampleLooper 是内部类吗?【参考方案5】:

Looper 有一个 synchronized MessageQueue,用于处理放置在队列中的消息。

它实现了Thread 特定的存储模式。

每个Thread 只有一个Looper。关键方法包括prepare()loop()quit()

prepare() 将当前的Thread 初始化为Looperprepare() 是使用ThreadLocal 类的static 方法,如下所示。

   public static void prepare()
       ...
       sThreadLocal.set
       (new Looper());
   
    prepare() 必须在运行事件循环之前显式调用。 loop() 运行事件循环,等待消息到达特定线程的消息队列。收到下一条消息后,loop() 方法将消息分派到其目标处理程序 quit() 关闭事件循环。它不会终止循环,而是将特殊消息排入队列

Looper 可以通过几个步骤在Thread 中编程

    扩展Thread

    调用Looper.prepare()将线程初始化为Looper

    创建一个或多个Handler(s) 来处理传入的消息

    调用Looper.loop() 处理消息,直到循环被告知quit()

【讨论】:

【参考方案6】:

在 GUI 框架的上下文中,您可以更好地理解 Looper 是什么。 Looper 是用来做两件事的。

1) Looper 转换一个普通线程,当它的 run() 方法返回时终止,变成连续运行的东西,直到 Android 应用程序运行,这是 GUI 中需要的框架(从技术上讲,它仍然会在 run() 方法返回时终止。但让我澄清一下我在下面的意思)。

2) Looper 提供了一个队列,待完成的工作被排入队列,这在 GUI 框架中也是需要的。

您可能知道,当启动应用程序时,系统会为应用程序创建一个执行线程,称为“主线程”,而 Android 应用程序通常在默认情况下完全在单个线程上运行,即“主线程”。但是主线程并不是什么秘密的、特殊的线程。它只是一个普通线程,类似于您使用new Thread() 代码创建的线程,这意味着它会在其 run() 方法返回时终止!想想下面的例子。

public class HelloRunnable implements Runnable 
    public void run() 
        System.out.println("Hello from a thread!");
    

    public static void main(String args[]) 
        (new Thread(new HelloRunnable())).start();
    

现在,让我们将这个简单的原则应用到 Android 应用中。如果 Android 应用程序在普通线程上运行会发生什么?一个名为“main”或“UI”的线程或任何启动您的应用程序的线程,并绘制所有 UI。因此,第一个屏幕显示给用户。所以现在怎么办?主线程终止?不,不应该。它应该等到用户做某事,对吧?但是我们怎样才能实现这种行为呢?好吧,我们可以试试Object.wait()Thread.sleep()。例如,主线程完成其初始工作以显示第一个屏幕,然后进入睡眠状态。当一个新的工作被获取时,它会被唤醒,这意味着被中断。到目前为止一切顺利,但此时我们需要一个类似队列的数据结构来保存多个作业。考虑一个用户连续触摸屏幕的情况,一项任务需要更长的时间才能完成。因此,我们需要一个数据结构来保存要以先进先出方式完成的工作。此外,您可能会想象,使用中断实现 everrunning-and-process-job-when-arrived 线程并不容易,并且会导致复杂且通常无法维护的代码。我们宁愿为此目的创建一个新机制,这就是 Looper 的全部意义。 official document of Looper class 说,“线程默认情况下没有与之关联的消息循环”,而 Looper 是一个“用于为线程运行消息循环”的类。现在你可以理解它的意思了。

为了让事情更清楚,让我们检查一下主线程被转换的代码。这一切都发生在ActivityThread class。在它的 main() 方法中,你可以找到下面的代码,它将一个普通的主线程变成了我们需要的东西。

public final class ActivityThread 
    ...
    public static void main(String[] args) 
        ...
        Looper.prepareMainLooper();
        Looper.loop();
        ...
    

@987654323@ 方法无限循环并出列消息并一次处理一个:

public static void loop() 
    ...
    for (;;) 
        Message msg = queue.next(); // might block
        if (msg == null) 
            // No message indicates that the message queue is quitting.
            return;
        
        ...
        msg.target.dispatchMessage(msg);
        ...
    

所以,基本上 Looper 是一个用于解决 GUI 框架中出现的问题的类。但这种需求也可能发生在其他情况下。实际上它是一个非常著名的多线程应用程序模式,您可以在 Doug Lea 的“Java 中的并发编程”中了解更多信息(特别是第 4.1.4 章“工作线程”会有所帮助)。另外,你可以想象这种机制在 Android 框架中并不是唯一的,但所有的 GUI 框架都可能需要与此有些相似。您可以在 Java Swing 框架中找到几乎相同的机制。

【讨论】:

这是唯一真正解释为什么会使用 Looper 类的答案。我不确定为什么它不是最佳答案,三个评分较高的答案什么都解释不了。 @AK。这就是为什么我添加了这个答案,即使它看起来为时已晚。很高兴我的回答对您有所帮助! :) 在阅读本文之前,我就像“Looper ???”现在“哦,是的,让我们讨论一下”。谢谢你,很好的答案:) 快速提问。您说在主线程中提取所有 UI 元素后,它会进入睡眠状态。但是假设用户与屏幕上的一个按钮进行交互,那个按钮不是点击甚至放入主队列,然后某个对象会将它分派给正确的活动,然后该活动的主线程是清醒的,它将执行该按钮单击的回调中的代码? 哇,解释的这么清楚。我想知道为什么这不是公认的答案。【参考方案7】:

Looper & Handler 的最简单定义:

Looper 是一个将线程转换为 Pipeline Thread 的类,Handler 为您提供了一种机制,可以将任务从任何其他线程。

一般措辞的细节:

所以 PipeLine 线程 是一个可以通过 Handler 接受来自其他线程的更多任务的线程。

Looper 之所以这样命名,是因为它实现了循环——接受下一个任务,执行它,然后接受下一个任务,依此类推。 Handler之所以称为handler,是因为它每次用于处理或接受来自任何其他线程的下一个任务,并传递给Looper(线程或PipeLine线程)。

示例:

Looper and Handler 或 PipeLine Thread 非常完美的例子是在单个线程中下载多个图像或将它们逐个上传到服务器(Http),而不是在后台为每个网络调用启动一个新线程。

在这里阅读更多关于 Looper 和 Handler 以及 Pipeline Thread 的定义:

Android Guts: Intro to Loopers and Handlers

【讨论】:

【参考方案8】:

Thread 的生命周期在run() 方法完成后结束。同一线程无法再次启动。

Looper 将正常的Thread 转换为消息循环。 Looper的关键方法是:

void prepare ()

将当前线程初始化为looper。这使您有机会创建处理程序,然后在实际开始循环之前引用此循环器。调用该方法后一定要调用loop(),调用quit()结束。

void loop ()

在这个线程中运行消息队列。一定要调用 quit() 来结束循环。

void quit()

退出循环。

导致 loop() 方法终止,不再处理消息队列中的任何消息。

Janisha 的 mindorks article 很好地解释了核心概念。

Looper 与线程相关联。如果您在 UI 线程上需要 LooperLooper.getMainLooper() 将返回关联的线程。

您需要将Looper 与Handler 关联。

LooperHandlerHandlerThread是Android解决异步编程问题的方式。

一旦你有了Handler,你就可以调用下面的API。

post (Runnable r)

使 Runnable r 添加到消息队列中。 runnable 将在附加此处理程序的线程上运行。

boolean sendMessage (Message msg)

在当前时间之前的所有未决消息之后,将消息推送到消息队列的末尾。它将在 handleMessage(Message) 中,在附加到此处理程序的线程中接收。

HandlerThread 是一个方便的类,用于启动具有循环器的新线程。然后可以使用 looper 来创建处理程序类

在某些情况下,您无法在 UI 线程上运行 Runnable 任务。 例如网络操作:在套接字上发送消息,打开 URL 并通过阅读 InputStream 获取内容

在这些情况下,HandlerThread 很有用。您可以从HandlerThread 获取Looper 对象,并在HandlerThread 上创建Handler 而不是主线程。

HandlerThread 代码如下:

@Override
public void run() 
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) 
        mLooper = Looper.myLooper();
        notifyAll();
    
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;

示例代码参考下面的帖子:

Android: Toast in a thread

【讨论】:

【参考方案9】:

这个答案与问题无关,但使用 looper 以及人们在此处的所有答案中创建处理程序和 looper 的方式都是不好的做法(尽管有些解释是正确的),我必须发布这个:

HandlerThread thread = new HandlerThread(threadName);
thread.start();
Looper looper = thread.getLooper();
Handler myHandler = new Handler(looper);

对于full implementation

【讨论】:

【参考方案10】:

什么是 Looper?

来自文档

Looper

Looper 用于为thread 运行消息循环的类。默认情况下,线程没有与之关联的消息循环;要创建一个,请在要运行循环的线程中调用prepare(),然后调用loop() 让它处理消息,直到循环停止。

Looper 是一个消息处理循环: Lo​​oper 的一个重要特性是它与创建 Looper 的线程相关联 Lo​​oper 类维护一个MessageQueue,其中包含一个消息列表。 Looper 的一个重要特性是它与创建 Looper 的线程相关联。 Looper 之所以如此命名,是因为它实现了循环——接受下一个任务,执行它,然后接受下一个任务,依此类推。 Handler 被称为处理程序,因为有人无法发明更好的名称 Android Looper 是 Android 用户界面中的一个 Java 类,它与 Handler 类一起处理 UI 事件,例如按钮单击、屏幕重绘和方向切换。

它是如何工作的?

创建循环器

线程在运行后通过调用Looper.prepare() 获得LooperMessageQueueLooper.prepare()标识调用线程,创建Looper和MessageQueue对象并关联线程

示例代码

class MyLooperThread extends Thread 

      public Handler mHandler; 

      public void run()  

          // preparing a looper on current thread  
          Looper.prepare();

          mHandler = new Handler()  
              public void handleMessage(Message msg)  
                 // process incoming messages here
                 // this will run in non-ui/background thread
               
          ; 

          Looper.loop();
       
  

欲了解更多信息,请查看下面的帖子

What is the relationship between Looper, Handler and MessageQueue in Android? Android Guts: Intro to Loopers and Handlers Understanding Android Core: Looper, Handler, and HandlerThread Handler in Android What Is Android Looper? Android: Looper, Handler, HandlerThread. Part I. MessageQueue and Looper in Android

【讨论】:

【参考方案11】:

了解 Looper 线程

Java 线程是一个执行单元,旨在在其 run() 方法中执行任务并在此之后终止:

但在 Android 中有很多用例,我们需要保持线程处于活动状态并等待用户输入/事件,例如。 UI 线程又名Main Thread

Android 中的主线程是一个 Java 线程,它首先由 JVM 在启动应用程序时启动,并一直运行直到用户选择关闭它或遇到未处理的异常。

当一个应用程序启动时,系统会创建一个线程 应用程序的执行,称为“main”。这个话题很 很重要,因为它负责向 适当的用户界面小部件,包括绘图事件。

现在要注意的是,虽然主线程是 Java 线程,但它一直在监听用户事件并在屏幕上绘制 60 fps 的帧,并且在每个循环后它仍然不会死机。怎么会这样?

答案是 Looper 类:Looper 是一个类,用于保持线程处于活动状态并管理消息队列以在其上执行任务 那个线程。

默认情况下,线程没有与之关联的消息循环,但您可以通过在 run 方法中调用 Looper.prepare() 来分配一个,然后调用 Looper.loop()。

Looper 的目的是保持一个 Thread 活着并等待下一个循环 输入Message 对象以执行计算,否则将得到 在第一个执行周期后销毁。

如果你想深入了解 Looper 如何管理 Message 对象队列,那么你可以查看 Looperclass 的源代码:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/os/Looper.java

以下是如何创建Looper Thread 并使用LocalBroadcastActivity 类通信的示例

class LooperThread : Thread() 

    // sendMessage success result on UI
    private fun sendServerResult(result: String) 
        val resultIntent = Intent(ServerService.ACTION)
        resultIntent.putExtra(ServerService.RESULT_CODE, Activity.RESULT_OK)
        resultIntent.putExtra(ServerService.RESULT_VALUE, result)
        LocalBroadcastManager.getInstance(AppController.getAppController()).sendBroadcast(resultIntent)
    

    override fun run() 
        val looperIsNotPreparedInCurrentThread = Looper.myLooper() == null

        // Prepare Looper if not already prepared
        if (looperIsNotPreparedInCurrentThread) 
            Looper.prepare()
        

        // Create a handler to handle messaged from Activity
        handler = Handler(Handler.Callback  message ->
            // Messages sent to Looper thread will be visible here
            Log.e(TAG, "Received Message" + message.data.toString())

            //message from Activity
            val result = message.data.getString(MainActivity.BUNDLE_KEY)

            // Send Result Back to activity
            sendServerResult(result)
            true
        )

        // Keep on looping till new messages arrive
        if (looperIsNotPreparedInCurrentThread) 
            Looper.loop()
        
    

    //Create and send a new  message to looper
    fun sendMessage(messageToSend: String) 
        //Create and post a new message to handler
        handler!!.sendMessage(createMessage(messageToSend))
    


    // Bundle Data in message object
    private fun createMessage(messageToSend: String): Message 
        val message = Message()
        val bundle = Bundle()
        bundle.putString(MainActivity.BUNDLE_KEY, messageToSend)
        message.data = bundle
        return message
    

    companion object 
        var handler: Handler? = null // in Android Handler should be static or leaks might occur
        private val TAG = javaClass.simpleName

    

用法

 class MainActivity : AppCompatActivity() 

    private var looperThread: LooperThread? = null

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // start looper thread
        startLooperThread()

        // Send messages to Looper Thread
        sendMessage.setOnClickListener 

            // send random messages to looper thread
            val messageToSend = "" + Math.random()

            // post message
            looperThread!!.sendMessage(messageToSend)

           
    

    override fun onResume() 
        super.onResume()

        //Register to Server Service callback
        val filterServer = IntentFilter(ServerService.ACTION)
        LocalBroadcastManager.getInstance(this).registerReceiver(serverReceiver, filterServer)

    

    override fun onPause() 
        super.onPause()

        //Stop Server service callbacks
     LocalBroadcastManager.getInstance(this).unregisterReceiver(serverReceiver)
    


    // Define the callback for what to do when data is received
    private val serverReceiver = object : BroadcastReceiver() 
        override fun onReceive(context: Context, intent: Intent) 
            val resultCode = intent.getIntExtra(ServerService.RESULT_CODE, Activity.RESULT_CANCELED)
            if (resultCode == Activity.RESULT_OK) 
                val resultValue = intent.getStringExtra(ServerService.RESULT_VALUE)
                Log.e(MainActivity.TAG, "Server result : $resultValue")

                serverOutput.text =
                        (serverOutput.text.toString()
                                + "\n"
                                + "Received : " + resultValue)

                serverScrollView.post(  serverScrollView.fullScroll(View.FOCUS_DOWN) )
            
        
    

    private fun startLooperThread() 

        // create and start a new LooperThread
        looperThread = LooperThread()
        looperThread!!.name = "Main Looper Thread"
        looperThread!!.start()

    

    companion object 
        val BUNDLE_KEY = "handlerMsgBundle"
        private val TAG = javaClass.simpleName
    

我们可以改用异步任务或意图服务吗?

异步任务旨在在后台执行简短操作,并在 UI 线程上提供进度和结果。异步任务有限制,例如您不能创建超过 128 个异步任务ThreadPoolExecutor 将只允许 最多 5 个异步任务

IntentServices 也被设计为执行较长时间的后台任务,您可以使用LocalBroadcastActivity 通信。但是服务在任务执行后被销毁。如果你想让它运行很长时间而不是像while(true)... 这样的操作。

Looper Thread 的其他有意义的用例:

用于2路套接字通信,服务器继续监听客户端套接字并回写确认

在后台处理位图。将图片url传递给Looper线程,它会应用滤镜效果并将其存储在临时位置,然后广播图片的临时路径。

【讨论】:

【参考方案12】:

我会尽量简单地解释 looper 类的目的。 对于普通的 Java 线程,当 run 方法完成执行时,我们说线程已经完成了它的工作,此后线程不再存在。 如果我们想用不再存在的同一个线程在整个程序中执行更多任务怎么办? 哦,现在有问题吧?是的,因为我们想要执行更多任务,但线程不再活跃。这是 Looper 进来拯救我们的地方。 Looper 顾名思义就是循环。 Looper 只不过是线程内的无限循环。因此,它使线程无限期地保持活动状态,直到我们显式调用 quit() 方法。在无限存活的线程上调用 quit() 方法会使线程内部无限循环中的条件为假,从而退出无限循环。因此,线程将死亡或不再存在。在我们的 Thread 上调用与 looper 连接的 quit() 方法至关重要,否则它们将像 Zombies 一样出现在您的系统中。 因此,例如,如果我们想创建一个后台线程来对其执行多项任务。我们将创建一个简单的 Java 线程,并使用 Looper 类来准备一个 Looper,并将准备好的 Looper 附加到该线程,这样我们的线程就可以尽可能长地存活,因为我们随时可以随时调用 quit() 来终止我们的线程。所以我们的 looper 将使我们的线程保持活动状态,因此我们将能够使用同一个线程执行多个任务,当我们完成时,我们将调用 quit() 来终止线程。 如果我们希望我们的主线程或 UI 线程在某些 UI 元素上显示由后台线程或非 UI 线程计算的结果怎么办? 为此,出现了处理程序的概念; 通过处理程序我们可以进行进程间通信,或者说通过处理程序两个线程可以相互通信。 因此,主线程将有一个关联的处理程序,后台线程将通过该处理程序与主线程通信,以完成在主线程上的某些 UI 元素上显示由它计算的结果的任务。 我知道我在这里只解释理论,但尝试理解这个概念,因为深入理解这个概念非常重要。我将在下面发布一个链接,该链接将带您观看有关 Looper、Handler 和 HandlerThread 的小型视频系列,我强烈建议您观看它,所有这些概念都将通过示例来阐明。

https://www.youtube.com/watch?v=rfLMwbOKLRk&list=PL6nth5sRD25hVezlyqlBO9dafKMc5fAU2&index=1

【讨论】:

【参考方案13】:

我尝试用 Kotlin 举个例子。下面是代码示例。

首先,我们需要从请求主线程 (Looper.getMainLooper()) 的 Handler(提供的 Looper 而不是默认的)中实例化变量处理程序。

getAllCourses() 函数需要返回 LiveData,因此我们使用 handler.postDelayed() 将其添加到消息队列中,并在常量 SERVICE_LATENCY_IN_MILLIS 中指定的 x 毫秒后运行。

请随时为我的解释详细阐述更多措辞,以便更清楚。

class RemoteDataSource private constructor(private val jsonHelper: JsonHelper) 

    private val handler = Handler(Looper.getMainLooper())

    companion object 
        private const val SERVICE_LATENCY_IN_MILLIS: Long = 2000

        @Volatile
        private var instance: RemoteDataSource? = null

        fun getInstance(helper: JsonHelper): RemoteDataSource =
                instance ?: synchronized(this) 
                    RemoteDataSource(helper).apply  instance = this 
                
    

    fun getAllCourses(): LiveData<ApiResponse<List<CourseResponse>>> 
        EspressoIdlingResource.increment()
        val resultCourse = MutableLiveData<ApiResponse<List<CourseResponse>>>()
        handler.postDelayed(
            resultCourse.value = ApiResponse.success(jsonHelper.loadCourses())
            EspressoIdlingResource.decrement()
        , SERVICE_LATENCY_IN_MILLIS)
        return resultCourse
    

【讨论】:

以上是关于Looper 的目的是啥以及如何使用它?的主要内容,如果未能解决你的问题,请参考以下文章

Plist:它是啥以及如何使用它

ObjectMapper 的目标是啥?

ObjectMapper 的目标是啥?

实用层的目的是啥?它如何与 Web 应用程序架构中的其他层联系起来?

Postgres 中的 pg_class 是啥以及如何使用 pgAdmin 4 查看它?

TensorBoard 中的 hp_metric 是啥以及如何摆脱它?