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

时间:2011-09-29 13:04:00

标签: android multithreading android-looper

我是Android新手。我想知道Looper类的作用以及如何使用它。我已阅读Android Looper class documentation,但我无法完全理解它。 我在很多地方都看过它但却无法理解它的目的。任何人都可以通过定义Looper的目的来帮助我,并在可能的情况下给出一个简单的例子吗?

13 个答案:

答案 0 :(得分:383)

什么是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();
      }
  }

答案 1 :(得分:251)

您可以更好地了解Looper在GUI框架中的作用。 Looper可以做两件事。

1)Looper 转换正常线程,当run()方法返回时会终止,直到Android应用程序运行为止,这在GUI中是必需的框架(从技术上讲,当run()方法返回时它仍然终止。但是让我澄清一下我的意思)。

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

正如您所知,当应用程序启动时,系统会为应用程序创建一个执行线程,称为“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所关注的 official document of Looper class表示,"默认情况下,线程没有与它们关联的消息循环",而Looper是一个类"用于为线程"运行消息循环。现在你可以理解它的含义了。

为了使事情更清楚,让我们检查主线程转换的代码。这一切都发生在ActivityThread class。在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框架中出现的问题的类。但是这种需求也可能在其他情况下发生。实际上它是一个非常着名的多线程应用程序模式,您可以在" Java中的并发编程"中了解更多信息。作者:Doug Lea(尤其是第4.1.4章和第34章;工人主题"会有所帮助)。此外,您可以想象这种机制在Android框架中并不是唯一的,但是所有GUI框架可能都需要与此类似。您可以在Java Swing框架中找到几乎相同的机制。

答案 2 :(得分:73)

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

答案 3 :(得分:31)

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

以下Looper解释了Looper生命周期,如何使用它以及Handler中{{1}}的使用

article

Looper =主题+ MessageQueue

答案 4 :(得分:13)

Looper& amp;的定义处理程序:

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

<强>详细信息:

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

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

示例:

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

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

Android Guts: Intro to Loopers and Handlers

答案 5 :(得分:7)

Loopersynchronized MessageQueue,用于处理排队的消息。

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

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

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

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

    1. 扩展Thread

    2. 致电Looper.prepare()将线程初始化为Looper

    3. 创建一个或多个Handler(s)来处理收到的消息

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

答案 6 :(得分:5)

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

Looper将普通Thread转换为消息循环。 Looper的主要方法是:

void prepare ()
  

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

void loop ()
  

在此主题中运行消息队列。一定要调用quit()来结束循环。

void quit()
  

退出弯针。

     

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

Janishar的mindorks article以很好的方式解释了核心概念。

enter image description here

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

您需要LooperHandler相关联。

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

获得Handler后,您可以调用以下API。

post (Runnable r)
  

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

boolean sendMessage (Message msg)
  

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

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

在某些情况下,您无法在UI线程上运行Runnable任务。 例如网络操作:通过阅读InputStream

在套接字上发送消息,打开URL并获取内容

在这些情况下,HandlerThread很有用。您可以从Looper获取HandlerThread对象,并在Handler上创建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: Toast in a thread

答案 7 :(得分:4)

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

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

full implementation

答案 8 :(得分:3)

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

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

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

答案 9 :(得分:1)

了解弯针线程

java线程执行单元,旨在在其run()方法中执行任务,并在此之后终止: enter image description here

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

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

  

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

enter image description here

现在需要注意的是,尽管主线程是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的设计还可以使后台任务的执行时间更长一些,您可以使用LocalBroadcastActivity进行通信。但是服务在任务执行后被破坏。如果您想让它长时间运行,而不是像while(true){...}这样的麻烦事。

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

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

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

答案 10 :(得分:1)

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

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

答案 11 :(得分:0)

  

什么是Looper?

从DOCS

Looper

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

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

它如何工作?

enter image description here

  

创建Looper

线程在运行后通过调用Looper获得Looper.prepare()MessageQueueLooper.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();
      } 
  }

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

答案 12 :(得分:0)

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

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

函数 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
    }