506

我是安卓新手。我想知道这个Looper类做什么以及如何使用它。我已经阅读了 Android Looper 类文档,但我无法完全理解它。我在很多地方都见过它,但无法理解它的目的。Looper任何人都可以通过定义目的并在可能的情况下给出一个简单的例子来帮助我吗?

4

13 回答 13

421

什么是活套?

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();
      }
  }
于 2011-09-29T13:17:25.683 回答
371

在 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()orThread.sleep(). 例如,主线程完成其初始工作以显示第一个屏幕,然后进入睡眠状态。当一个新的工作被获取时,它会被唤醒,这意味着被中断。到目前为止一切顺利,但此时我们需要一个类似队列的数据结构来保存多个作业。考虑一个用户连续触摸屏幕的情况,一项任务需要更长的时间才能完成。因此,我们需要一个数据结构来保存要以先进先出方式完成的工作。此外,您可能会想象,使用中断实现 ever-running-and-process-job-when-arrived 线程并不容易,并且会导致复杂且通常无法维护的代码。我们宁愿为此目的创建一个新机制,这就是 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 框架中找到几乎相同的机制。

于 2015-12-30T04:09:03.050 回答
81

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 线程上的消息。

于 2013-10-01T11:22:08.200 回答
37

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

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

在此处输入图像描述

Looper = 线程 + 消息队列

于 2013-09-18T08:34:20.750 回答
17

Looper & Handler 的最简单定义:

Looper是一个将线程转换为管道线程的类,而Handler为您提供了一种将任务从任何其他线程推送到此管道的机制。

一般措辞的详细信息:

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

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

例子:

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

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

Android Guts:Loopers 和 Handlers 简介

于 2016-08-16T11:12:14.420 回答
11

了解 Looper 线程

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

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

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

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

在此处输入图像描述

现在要注意的是,虽然主线程是 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使用ActivityLocalBroadcast

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 Thread 的其他有意义的用例:

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

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

于 2019-03-14T12:45:36.257 回答
7

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

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

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

prepare()将当前初始化Thread为 a Looperprepare()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()
于 2014-05-26T10:40:43.610 回答
5

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

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

void prepare ()

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

void loop ()

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

void quit()

退出弯针。

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

Janishar 的这篇mindorks 文章以很好的方式解释了核心概念。

在此处输入图像描述

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

您需要LooperHandler关联。

Looper, Handler, 和HandlerThread是 A​​ndroid 解决异步编程问题的方式。

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

post (Runnable r)

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

boolean sendMessage (Message msg)

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

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

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

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

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

于 2017-08-31T08:15:23.790 回答
5

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

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

全面实施

于 2017-11-16T11:53:41.647 回答
3

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

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

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

于 2013-03-20T16:49:52.890 回答
2

什么是活套?

来自文档

Looper

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

  • ALooper是一个消息处理循环:
  • Looper 的一个重要特征是它与创建 Looper 的线程相关联
  • Looper 类维护一个MessageQueue,其中包含一个列表消息。Looper 的一个重要特性是它与创建 Looper 的线程相关联。
  • 之所以Looper如此命名,是因为它实现了循环——接受下一个任务,执行它,然后接受下一个任务,依此类推。之所以Handler称为处理程序,是因为有人无法发明更好的名称
  • AndroidLooper是 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();
      } 
  }

有关更多信息,请查看以下帖子

于 2018-11-27T06:48:42.440 回答
0

我尝试在 Kotlin 中举一个例子。下面是代码示例。

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

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
    }
于 2021-06-07T03:30:01.377 回答
0

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

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

于 2021-01-01T16:36:11.577 回答