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

我是Android的新手。我想知道Looper类做什么,以及如何使用它。我读过安卓循环程序类文档,但我不能完全理解它。 我在很多地方见过它,但无法理解它的目的。谁能帮助我定义Looper的目的,并提供一个简单的例子,如果可能的话?< / p >
210945 次浏览

什么是环形使者?

Looper是一个用于执行队列中的消息(可运行对象)的类。普通线程没有这样的队列,例如简单线程没有任何队列。它执行一次,方法执行完成后,线程将不再运行另一个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();
}
}

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

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

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

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

下面是一个文章,它解释了Looper的生命周期,如何使用它以及LooperHandler中的用法

enter image description here

循环器=线程+消息队列

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线程)中使用处理程序将任务发布到Looper上执行。

handler.post(new Runnable()
{
public void run() {
//This will be executed on thread using Looper.
}
});

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

电影有一个synchronized MessageQueue,用于处理放在队列上的消息。

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

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

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

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

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

  1. 延长Thread

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

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

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

您可以在GUI框架的上下文中更好地理解什么是Looper。《环形使者》有两项任务。

  1. 循环程序将一个正常的线程转换为持续运行的线程,直到Android应用程序运行。

  2. Looper提供了一个队列,待完成的作业将在其中排队。

正如你所知道的,当一个应用程序启动时,系统为应用程序创建了一个执行线程,称为“主线程”,而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()。例如,主线程完成了显示第一个屏幕的初始工作,然后休眠。当获取一个新的要做的工作时,它将被唤醒,这意味着被中断。到目前为止一切顺利,但此时我们需要一个类似队列的数据结构来容纳多个作业。想想当用户连续触摸屏幕时,任务需要较长时间才能完成的情况。因此,我们需要有一个数据结构来保存要以先进先出的方式完成的作业。此外,您可以想象,使用中断实现到达线程时始终运行和处理作业并不容易,并导致复杂且通常不可维护的代码。我们宁愿为此目的创建一个新机制,并且that is what Looper is about.;环形使者级的正式文件说,“线程默认情况下没有与之相关的消息循环”,而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框架中出现的问题的类。但这种需求也可能发生在其他情况下。实际上,它是多线程应用程序的一个非常著名的模式,你可以在"Java并发编程"作者:Doug Lea(特别是第4.1.4章“工人线程”;会很有帮助)。而且,你可以想象这种机制在Android框架中并不是唯一的,但是所有的GUI框架可能都需要一些类似的东西。在Java Swing框架中也可以找到几乎相同的机制。

最简单的循环器定义处理程序:

电影是一个将线程转换为管道螺纹的类,而处理程序为你提供了一种机制,可以将任务从任何其他线程推入这个管道。

一般措词的详细说明:

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

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

例子:

循环程序和处理程序或管道线程的非常完美的例子是下载多个图像或在单个线程中逐个上传到服务器(Http),而不是在后台为每个网络调用启动一个新线程。

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

Android Guts: Intro to Loopers and Handlers

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

电影将普通的Thread转换为消息循环。Looper的关键方法是:

void prepare ()

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

void loop ()

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

void quit()

退出循环器。

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

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

enter image description here

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

你需要将Looper处理程序相关联。

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

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

post (Runnable r)

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

boolean sendMessage (Message msg)

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

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

在某些情况下,你不能在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 .

这个答案与问题无关,但looper的使用以及人们在这里的所有答案中创建处理程序和looper的方式都是明显的坏习惯(尽管有些解释是正确的),我不得不发布这个:

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

全面实施

什么是环形使者?

从文档

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.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();
}
}

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

理解循环线程

一个java线程,一个执行单元,被设计用来在它的run()方法中执行任务&在此之后终止: enter image description here < / p >

但在Android中,有许多用例,我们需要保持线程活跃,并等待用户输入/事件等。UI线程又名Main Thread

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

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

enter image description here

现在需要注意的是,虽然主线程是Java线程,但它一直在监听用户事件,并在屏幕上绘制60帧/秒的帧,并且在每个周期后仍然不会死亡。这是怎么回事?

答案是《环形使者》:循环器是一个类,用于保持线程活动并管理消息队列以执行任务 线程。< / p >

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

循环器的目的是保持一个线程活着,并等待下一个循环 输入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的其他有意义的用例:

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

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

我将尽量简单地解释循环器类的目的。 对于普通的Java线程,当run方法完成执行时,我们说线程已经完成了它的工作,在那之后线程就不再存在了。 如果我们想在程序中使用同一个线程执行更多的任务,而这个线程已经不再存在了,该怎么办? 现在有问题了,对吧?是的,因为我们想要执行更多的任务,但线程不再活跃。这就是环形使者来救我们的地方。 循环者,顾名思义就是循环。Looper只不过是线程中的一个无限循环。因此,它将使线程保持无限长的时间,直到显式地调用quit()方法。在无限活动的线程上调用quit()方法将使线程内的无限循环中的条件为假,因此,无限循环将退出。因此,线程将死亡或不再存在。在我们的线程上调用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

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

首先,我们需要实例化handler(提供的循环程序而不是默认的循环程序)中的变量处理程序,它要求主线程(loop . 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
}