Looper的目的是什么,如何使用它?

530

我是Android的新手,想了解Looper类是做什么的,以及如何使用它。我已经阅读了Android Looper类文档,但我仍然无法完全理解它。

我在很多地方都看到了它,但不理解它的用途。是否有人能够通过定义Looper的目的,并在可能的情况下给出一个简单的示例来帮助我?


12
我刚刚在Safari Books Online 上找到了一份非常透彻且清晰的关于 Looper 及其在Android中的使用解释。不幸的是,我怀疑这个资源的免费访问时间有限。链接如下:https://www.safaribooksonline.com/library/view/efficient-android-threading/9781449364120/ch04.html - Joe Lapp
1
Android的文章和参考页面要求您在理解当前文章之前必须先了解之前的文章。我建议您阅读Api指南中的Activity和Service文章,然后再阅读Handler和Looper。如果您了解线程是什么(不是Android线程,而是一般的线程...例如POSIX),那也会有所帮助。 - FutureSci
1
我发现这篇文章很有用:http://codetheory.in/android-handlers-runnables-loopers-messagequeue-handlerthread/ - Hermandroid
1
创建一个无限循环,以便线程可以通过它执行并检查任务队列的每个迭代,并执行任务。该任务称为消息。这是一种常见的设计模式,称为事件循环,只是抽象化了。如果多个线程共享事件循环,则具有线程池。默认情况下,主线程必须拥有循环,因为它是一个非终止线程,等待UI事件任务。其他非终止类型的程序(如视频游戏)也有事件循环。普通的旧线程没有循环,但可以使用looper api添加。 - the_prole
@JoeLapp 感谢提供链接,我正在尝试理解代码,我相信 class TextHandlerTask 中的 while(Thread.currentThread().isInterrupted()){ 这一行应该被反转为 while(!Thread.currentThread().isInterrupted()){} 才能使其循环,这意味着它将重新尝试读取直到线程被中断,我的理解正确吗? - Delark
1
@JoeLapp,现在是2023年,你的O'reilly第4章线程通信链接仍然有效!太棒了。 - WebViewer
14个回答

437

Looper是什么?

Looper是一个类,用于在队列中执行消息(Runnables)。普通线程没有这样的队列,例如普通线程没有任何队列。它只会执行一次,并且在方法执行完成后,该线程将不会再运行其他消息(Runnable)。

Loops可以在哪里使用?

如果有人想要执行多个消息(Runnables),那么应该使用Looper类来在线程中创建一个队列。 例如,在编写从互联网下载文件的应用程序时,我们可以使用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();
      }
  }

20
AsyncTask 更适合这个目的并且更简单,因为它封装了所有线程管理。 - Fernando Gallego
4
应该在 run() 和 handleMessage() 方法之前添加 @Override 注解。 - Andrew Mackenzie
5
文档指示你必须调用 looper.quit。在你上面的代码中,Looper.loop 将无限期地阻塞。 - Johann
4
如何退出循环。我的意思是在上面的代码示例中在哪里包含Looper.quit()? - Seenu69
7
我认为最好使用HandlerThread这个方便创建带有Looper的线程的类。 - Nimrod Dayan
显示剩余11条评论

406

在GUI框架的上下文中,你可以更好地理解Looper是什么。Looper的作用有两个:

  1. 将普通线程转换为一直运行直到Android应用程序结束。

  2. 提供一个队列,将需要执行的任务加入其中。

当一个应用程序启动时,系统会为该应用程序创建一个执行线程,称为“main”线程。默认情况下,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()。例如,主线程完成了显示第一个屏幕的初始工作,并休眠。当获取新的任务时,它会被唤醒,也就是被中断了。到目前为止都还好,但现在我们需要一个类似队列的数据结构来按先进先出的方式保存多个作业。想想当用户连续触摸屏幕时,任务需要更长时间才能完成的情况。因此,我们需要一个数据结构来保存要以先进先出的方式完成的作业。此外,你可能会想象,使用中断来实现永久运行和处理作业的线程并不容易,并且会导致复杂且难以维护的代码。我们最好为这种目的创建一个新机制,这就是 Looper 的全部内容。Loops 类的官方文档说,“线程默认情况下不具有与其关联的消息循环”,而 Looper 是一个“用于运行线程的消息循环”的类。现在你可以理解这意味着什么了。

为了让事情更清楚,让我们来看一下将主线程转换的代码。这些都发生在 ActivityThread 类中。在它的 main() 方法中,你可以找到下面的代码,它将普通线程转换为我们所需的东西。

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

Looper.loop() 方法会无限循环并出队一个消息,一次处理一个:

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框架中找到完全相同的机制。


47
这是唯一一个实际解释Looper类何时使用的答案。我不确定为什么它不是最佳答案,因为评分更高的三个答案都没有解释清楚。 - Andrew Koster
7
@AK。这就是为什么我即使看起来晚了,也加了这个回答。很高兴我的回答对你有帮助! :) - 김준호
1
在阅读这篇文章之前,我还不知道“Looper”是什么,但现在我很想讨论它了。谢谢你,回答得非常好 :) - umerk44
快速问题。您说在主线程中绘制出所有UI元素后,它会被置于休眠状态。但是假设用户与屏幕上的按钮交互,那么该按钮点击事件不是被放入主队列中,然后某个对象将其分派到正确的活动中,然后该活动的主线程会唤醒并执行该按钮点击的回调代码吗? - user5228393
1
哇,讲得非常清楚。我想知道为什么这不是被接受的答案。 - Avijeet
1
太棒了。再好不过了。 - Rishabh Dutt Sharma

82

Looper允许在单个线程上按顺序执行任务。而handler定义了需要执行的任务。这是我试图在这个例子中说明的典型情况:

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线程)中使用处理程序(handler)将任务发布到Looper上执行。
handler.post(new Runnable()
{
public void run() {
//This will be executed on thread using Looper.
    }
});

在UI线程中,我们有一个隐式的Looper,可以让我们处理UI线程上的消息。


它不会锁定任何用户界面过程,这是真的吗? - gumuruh
4
感谢您提供如何将“作业”提交到队列中的示例。请问您需要翻译其他内容吗? - Peter Lillevold
2
这并没有解释为什么要使用这个类,只是如何使用。 - Andrew Koster
SampleLooper是内部类吗? - Sever

41

Android中的Looper是一个包装器,用于将MessageQueue附加到Thread并管理队列处理。在Android文档中,它看起来非常神秘,而且我们经常会遇到与Looper相关的UI访问问题。如果我们不了解基础知识,处理起来就会变得非常困难。

这里有一篇article,解释了Looper的生命周期,如何使用它以及在Handler中使用Looper的用法。

enter image description here

Looper = 线程 + 消息队列


7
此处仅介绍如何使用该类,未说明为何要使用该类。 - Andrew Koster
@AndrewKoster 在我看来,它通过“将MessageQueue附加到Thread”来解决“为什么”的问题。 - WebViewer
@AndrewKoster 在我看来,它通过“将消息队列附加到线程”来解决了“为什么”的问题。 - WebViewer
@WebViewer 김준호的答案提供了更完整的解释。将MessageQueue附加到线程上并不是一个有用的目标,因此它并不能让我们更接近“为什么”的答案。김준호的答案涉及了使用情况,而使用情况才是使用该类的实际原因。 - Andrew Koster
@WebViewer 김준호的回答有一个更完整的解释。将MessageQueue附加到线程上并不是一个有用的最终目标,所以它并不能让我们更接近回答“为什么”。김준호的回答涉及了使用案例,而使用案例才是使用该类的实际原因。 - Andrew Koster
1
@AndrewKoster 感谢您的解释。您知道在Android应用程序中Looper实例的数量是否有上限吗? - WebViewer

18

Looper 和 Handler 的最简定义:

Looper 是把一个线程变成 管道线程 的类,Handler 提供了一种机制,可以从任何其他线程向这个管道推送任务。

通俗易懂的详细解释:

因此管道线程是一条线程,它可以通过 Handler 从其他线程接受更多的任务。

Looper 之所以被称为 Looper,是因为它实现了一个循环 - 获取下一个任务,执行它,然后获取下一个任务,如此循环进行。Handler 被称为处理程序,因为它用于每次从任何其他线程接受和传递下一个任务给 Looper(线程或管道线程)。

示例:

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

了解有关 Looper 和 Handler 以及管道线程定义的更多信息,请点击以下链接:

Android Guts: Intro to Loopers and Handlers


14

理解Looper线程

Java Thread是执行任务并在其run()方法完成后终止的执行单元: enter image description here

但在Android中,有许多用例需要保持线程活动并等待用户输入/事件,例如UI线程,也称为主线程

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

当应用程序启动时,系统为应用程序创建一个执行线程,称为“main”。这个线程非常重要,因为它负责将事件分派到适当的用户界面小部件,包括绘制事件。

enter image description here

现在需要注意的是,尽管主线程是Java线程,但它仍然会持续监听用户事件并在屏幕上绘制60fps的帧,并且在每个周期结束后不会死亡。为什么会这样呢?
答案是Looper类:Looper是一个用于保持线程活动并管理消息队列以在该线程上执行任务的类。
默认情况下,线程不具有与之关联的消息循环,但您可以通过在run方法中调用Looper.prepare()来指定一个消息循环,然后调用Looper.loop()。
Looper的目的是保持线程活动并等待下一个输入Message对象的循环,否则它将在第一次执行周期后被销毁。
如果您想深入了解Looper如何管理Message对象队列,那么可以查看Looperclass的源代码。

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

以下是如何创建一个“Looper线程”,并使用“LocalBroadcast”与“Activity”类通信的示例:
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
    }
}

我们可以使用Async任务或Intent服务吗?

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

  • IntentServices也旨在执行稍长时间的后台任务,您可以使用LocalBroadcastActivity通信。但是服务在任务执行后被销毁。如果要让它长时间运行,则需要进行诸如while(true){...}之类的检查。

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

  • 用于双向套接字通信,其中服务器继续侦听客户端套接字并写回确认

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


7

在完成run()方法后,Java 线程的生命周期就结束了。同一个线程不能再次启动。

Looper可以将普通的Thread转换为消息循环。 Looper的关键方法包括:

void prepare ()

将当前线程初始化为一个循环器。这样您就有机会在实际启动循环之前创建引用此循环器的处理程序。在调用此方法后一定要调用loop(),并通过调用quit()来结束它。

void loop ()

在此线程中运行消息队列。确保调用quit()来结束循环。
void quit()

退出循环。

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

Janishar撰写的mindorks文章以简明易懂的方式解释了核心概念。

enter image description here

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

您需要将LooperHandler关联。

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

一旦您拥有Handler,就可以调用以下API。

post (Runnable r)

将Runnable r添加到消息队列中。该Runnable将在此处理程序所附加的线程上运行。
boolean sendMessage (Message msg)

将消息推送到当前时间之前的所有挂起消息之后的消息队列末尾。它将在此处理程序所附加的线程中的handleMessage(Message)中接收。 HandlerThread是一个方便的类,用于启动具有looper的新线程。然后可以使用looper来创建处理程序类。
在某些情况下,您无法在UI线程上运行可运行任务。例如:网络操作:在套接字上发送消息,打开URL并通过读取InputStream获取内容。
在这些情况下,HandlerThread非常有用。您可以从HandlerThread获取Looper对象,并在HandlerThread而不是主线程上创建一个HandlerHandlerThread代码将如下所示:
@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


7

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

它实现了线程特定存储模式。

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

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

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

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

  1. 扩展 Thread

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

  3. 创建一个或多个 Handler 来处理传入的消息

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

5

这个答案与问题无关,但是在所有的答案中使用looper和创建handler和looper的方式都是很糟糕的做法(尽管有些解释是正确的),我必须发表以下观点:

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

and for a full implementation


4

什么是Looper?

来自文档

Looper

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

  • Looper是一个消息处理循环:
  • Looper的一个重要特点是它与创建Looper的线程相关联。
  • Looper类维护一个包含消息列表的MessageQueue。Looper的一个重要特点是它与创建Looper的线程相关联。
  • Looper之所以被命名为Looper,是因为它实现了循环 - 获取下一个任务,执行它,然后获取下一个任务,以此类推。Handler被称为处理程序,是因为没有人能想出更好的名称。
  • Android Looper是Android用户界面中的一个Java类,与Handler类一起处理UI事件,例如按钮单击、屏幕重绘和方向切换。

它是如何工作的?

enter image description here

创建Looper

线程在运行后通过调用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();
      } 
  }

更多信息请查看以下帖子


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接