Looper的用途是什么?如何使用它?


454

我是Android新手。我想知道Looper该类的作用以及如何使用它。我已经阅读了Android Looper类文档,但无法完全理解。我在很多地方都看到过它,但无法理解它的目的。任何人都可以通过定义目标Looper以及通过给出一个简单示例来帮助我吗?


7
我刚刚找到了Looper及其在Safari联机丛书中的用法的非常彻底和清晰的解释。不幸的是,我怀疑是否只能在有限的时间内免费使用。safaribooksonline.com/library/view/efficient-android-threading/...
乔·拉普

1
Android文章和参考页要求您对上一篇文章有​​所了解,然后才能理解当前文章。建议您阅读Api指南中的“活动和服务”文章,然后阅读Handler和Looper。如果您了解什么是线程(不是android线程,而是一般的线程,例如POSIX),它也会有所帮助。
FutureSci '16

1
我发现这篇文章很有用:codetheory.in/…–
Herman

Answers:


396

什么是Looper?

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

在哪里可以使用Looper类?

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

怎么运行的?

prepare()准备弯针的方法。然后,您可以使用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();
      }
  }

17
为此,AsyncTask更好,并且因为它封装了所有线程管理,所以它不那么复杂。
Fernando Gallego

4
在run()和handleMessage()方法之前应具有@Override注释
Andrew Mackenzie

5
该文档表明您必须调用looper.quit。在上面的代码中,Looper.loop将无限期阻塞。
AndroidDev

2
如何退出循环。我的意思是在上面的代码示例中在哪里包括Looper.quit()?
Seenu69

6
我认为最好使用HandlerThread,它是带有循环程序的线程的便捷类。
Nimrod Dayan 2014年

287

您可以更好地了解GUI框架中的Looper。Looper可以做2件事。

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()。例如,主线程完成其初始工作以显示第一个屏幕,然后进入休眠状态。唤醒后,这意味着在获取新工作时被打断。到目前为止,一切都很好,但是目前,我们需要一个类似队列的数据结构来容纳多个作业。考虑一种情况,当用户连续触摸屏幕并且任务需要较长时间才能完成。因此,我们需要一个数据结构来容纳要以先进先出方式完成的工作。同样,您可能会想到,使用中断来实现到达线程时总是运行和处理作业并不容易,并且会导致复杂且通常无法维护的代码。我们宁愿为此目的创建一种新的机制,这就是Looper的全部意义所在Looper类正式文件说,“默认情况下,线程没有与之关联的消息循环”,而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框架中找到几乎相同的机制。


26
这是唯一可以解释为什么会使用Looper类的任何答案。我不确定为什么它不是最佳答案,三个评分较高的答案却无济于事。
安德鲁·科斯特

4
@AK。这就是为什么我添加了这个答案,即使它似乎为时已晚。我很高兴我的回答对您有所帮助!:)
김 준 호

1
@ Hey-men-
whatsup

1
在阅读这篇文章之前,我就像“ Looper ???”。现在,“哦,是的,我们讨论一下”。谢谢男人,很好的回答:)
umerk44'2

快速提问。您说过,在提取所有UI元素后,在主线程中将其置于睡眠状态。但是,假设用户与屏幕上的按钮进行了交互,即使单击按钮也没有将它放入主队列中,然后某个对象会将其分派到正确的活动,然后该活动的主线程处于唤醒状态,它将执行单击该按钮的回调中的代码?
CapturedTree

75

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进程,是真的吗?
gumuruh 2014年

4
感谢您提供有关如何将“工作”发布到队列中的示例
Peter Lillevold

这并不能说明为什么要使用此类,而只能说明如何使用。
安德鲁·科斯特

33

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

这是一篇说明Looper生命周期,如何使用它以及Looperin的用法的文章Handler

在此处输入图片说明

Looper =线程+ MessageQueue


3
这并不能说明为什么要使用此类,而只能说明如何使用。
安德鲁·科斯特

14

Looper和Handler的定义:

Looper是一个将线程转换为管道线程处理程序的类为您提供了一种机制,可以将任务从任何其他线程推入该线程。

细节:

因此,PipeLine线程是可以通过处理程序从其他线程接受更多任务的线程。

活套被命名是因为它实现了循环-需要下一个任务,执行它,然后采取下一个等。该处理程序之所以称为处理程序,是因为它用于每次从任何其他线程处理或接受该下一个任务并传递给Looper(线程或管道线程)。

例:

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

在此处阅读有关Looper和Handler以及Pipeline Thread的定义的更多信息:

Android胆量:Looper和处理程序简介


7

一个活套synchronized MessageQueue这些会用来对放置在该队列过程的消息。

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

Looper每只一个Thread。主要的方法包括prepare()loop()quit()

prepare()将电流初始化ThreadLooperprepare()static使用ThreadLocal如下所示的类的方法。

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

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

  1. 延伸 Thread

  2. 调用Looper.prepare()以将Thread初始化为Looper

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

  4. 调用Looper.loop()以处理消息,直到告知循环为止quit()

5

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

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

void prepare ()

将当前线程初始化为循环程序。这使您有机会创建在实际开始循环之前引用该循环程序的处理程序。确保在调用此方法后调用loop(),并通过调用quit()结束它。

void loop ()

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

void quit()

退出弯针。

使loop()方法终止,而不处理消息队列中的更多消息。

mindorks文章通过Janishar解释了不错的方式的核心概念。

在此处输入图片说明

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

您需要LooperHandler关联。

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

拥有之后Handler,您可以调用以下API。

post (Runnable r)

使Runnable r添加到消息队列。可运行对象将在此处理程序所连接的线程上运行。

boolean sendMessage (Message msg)

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

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

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

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

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:在线程中吐司


5

了解Looper线程

一个Java线程,一个旨在在其run()方法中执行任务并在此之后终止的执行单元: 在此处输入图片说明

但是在Android中,有很多用例需要保持Thread存活并等待用户输入/事件,例如。UI线程又名Main Thread

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

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

在此处输入图片说明

现在需要注意的是,尽管主线程是Java线程,但它一直在监听用户事件并在屏幕上绘制60 fps帧,并且在每个周期之后它都不会消失。怎么了

答案是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 Thread和与Activity类进行通信LocalBroadcast

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还设计了更长的时间做后台任务,您可以用于LocalBroadcast与进行通信Activity。但是服务在任务执行后被破坏。如果您想让它长时间运行,则需要做类似的工作 while(true){...}

Looper线程的其他有意义的用例:

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

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


4

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

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

全面实施


3

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

Handler并且AsnycTask通常用于在UI(线程)和辅助线程之间传播事件/消息或延迟操作。因此它们与UI更相关。

A 在后台处理与线程相关的队列中的Looper任务(Runnables,Futures)-即使没有用户交互或没有显示的UI(应用程序在通话期间在后台下载文件)也是如此。


1

什么是Looper?

从DOCS

Looper

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

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

怎么运行的?

在此处输入图片说明

创建Looper

线程在运行后通过调用获得Looper和。识别调用线程,创建一个Looper和对象并关联该线程MessageQueueLooper.prepare()Looper.prepare()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();
      } 
  }

有关更多信息,请在下面的帖子中查看


太好了,我了解力学。谢谢
Serg Burlaka
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.