服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android编程实现异步消息处理机制的几种方法总结

Android编程实现异步消息处理机制的几种方法总结

2022-07-28 11:26adayabetter Android

这篇文章主要介绍了Android编程实现异步消息处理机制的几种方法,结合实例形式详细总结分析了Android异步消息处理机制的原理、相关实现技巧与操作注意事项,需要的朋友可以参考下

本文实例讲述了Android编程实现异步消息处理机制的几种方法。分享给大家供大家参考,具体如下:

1、概述

Android需要更新ui的话就必须在ui线程上进行操作。否则就会抛异常。

假如有耗时操作,比如:在子线程中下载文件,通知ui线程下载进度,ui线程去更新进度等,这个时候我们就需要用到异步消息处理。

一、什么是Handler

Handler是Android提供用来异步更新UI的一套机制,也是一套消息处理机制,可以用它来发送消息,也可以用它来接收消息。

二、为什么使用Handler

Android在设计之时,就封装了一套消息的创建、传递、处理机制,作为系统原生的异步消息处理机制的实现之一,我们需要遵循这样的处理机制,该机制的另外一种实现是AsyncTask。

三、Handler用法

1、postdelayed()延时发送执行子线程(Demo)
2、sendMessage()回调handleMessage()传递消息
3、sendToTarget()传递消息

四、为什么在Android中只能通过Handler机制在主线程中更新UI?

最根本的是解决多线程并发问题。
假如在同一个Activity中,有多个线程同时更新UI,且没有加锁,那会导致什么问题呢?
UI更新混乱。
假如加锁呢?
会导致性能下降。
使用Handler机制,我们不用去考虑多线程的问题,所有更新UI的操作,都是在 主线程消息队列中轮询去处理的。
Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
—此处有图为证。

Android编程实现异步消息处理机制的几种方法总结

源码解析

1、Looper

对于Looper主要是prepare()loop()两个方法。

A. 首先看prepare()方法

?
1
2
3
4
5
6
public static final void prepare() {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(true));
}

sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例~

B. Looper的构造方法:

?
1
2
3
4
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
  }

在构造方法中,创建了一个MessageQueue(消息队列)。

C. 然后我们看loop()方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
 
    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
 
    for (;;) {
      Message msg = queue.next(); // might block
      if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
      }
 
      // This must be in a local variable, in case a UI event sets the logger
      Printer logging = me.mLogging;
      if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " " +
            msg.callback + ": " + msg.what);
      }
 
      msg.target.dispatchMessage(msg);
 
      if (logging != null) {
        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
      }
 
      // Make sure that during the course of dispatching the
      // identity of the thread wasn't corrupted.
      final long newIdent = Binder.clearCallingIdentity();
      if (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
            + Long.toHexString(ident) + " to 0x"
            + Long.toHexString(newIdent) + " while dispatching to "
            + msg.target.getClass().getName() + " "
            + msg.callback + " what=" + msg.what);
      }
 
      msg.recycle();
    }
}

第2行:

?
1
2
3
public static Looper myLooper() {
return sThreadLocal.get();
}

方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说loop方法必须在prepare方法之后执行。
第6行:拿到该looper实例中的mQueue(消息队列)
13到45行:就进入了我们所说的无限循环。
14行:取出一条消息,如果没有消息则阻塞。
27行:使用调用 msg.target.dispatchMessage(msg);把消息交给msg的target的dispatchMessage方法去处理。Msg的target是什么呢?其实就是handler对象,下面会进行分析。
44行:释放消息占据的资源。

Looper主要作用:

1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。

2、Handler

使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,我们会在声明的时候直接初始化,或者在onCreate中初始化Handler实例。所以我们首先看Handler的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Handler() {
    this(null, false);
}
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
      final Class<? extends Handler> klass = getClass();
      if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
          (klass.getModifiers() & Modifier.STATIC) == 0) {
        Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
          klass.getCanonicalName());
      }
    }
 
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
  }

14行:通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在19行又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。

A.sendMessage方法

辗转反则最后调用了sendMessageAtTime方法。

B. enqueueMessage方法

?
1
2
3
4
5
6
7
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
      msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  }

enqueueMessage中首先为msg.target赋值为this,【如果大家还记得Looper的loop方法会取出每个msg然后交给msg,target.dispatchMessage(msg)去处理消息】,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。

C. dispathMessage方法

?
1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

可以看到,第10行,调用了handleMessage方法,下面我们去看这个方法:

?
1
2
3
4
5
/**
  * Subclasses must implement this to receive messages.
  */
 public void handleMessage(Message msg) {
 }

可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。

3、Handler post

post方法:

?
1
2
3
4
public final boolean post(Runnable r)
{
   return sendMessageDelayed(getPostMessage(r), 0);
}

getPostMessage方法:

?
1
2
3
4
5
private static Message getPostMessage(Runnable r) {
   Message m = Message.obtain();
   m.callback = r;
   return m;
}

可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.
注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
sendMessageDelayed方法和handler.sendMessage方法最终调用的都是:

?
1
2
3
4
5
6
7
8
9
10
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException(
          this + " sendMessageAtTime() called with no mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

可以看到,这里msg的callback和target都有值,那么会执行哪个呢?
看dispatchMessage方法就能看出来。

?
1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
}

第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。
mCallback 的值是如何赋值的,可以查看Handler的构造方法,默认mCallback 的值为Null

到此,这个流程已经解释完毕,总结一下

  • 1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
  • 2、Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
  • 3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。
  • 4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
  • 5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

在Activity中,我们并没有显示的调用Looper.prepare()Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()Looper.loop()方法。

4、扩展

其实Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Thread()
    {
      private Handler handler;
      public void run()
      {
 
        Looper.prepare();
 
        handler = new Handler()
        {
          public void handleMessage(android.os.Message msg)
          {
            Log.e("TAG",Thread.currentThread().getName());
          };
        };
         Looper.loop();
        }

四种更新UI的方法

1、Handler.post();
2、Handler.sendMessage();
3、runOnUIThread()
4、View.post()

查看runOnUIThread()的源代码(Activity中)

Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.
Parameters:
action the action to run on the UI thread
public final void  runOnUiThread(Runnable action) {
       if (Thread.currentThread() != mUiThread) {
           mHandler.post(action);
       } else {
            action.run();
       }
}

补充:

1.异步消息处理机制的另一种实现:AsyncTask:

主要方法:

  • onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI
    Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出ProgressDialog
  • doInBackground(Params… params):

    onPreExecute()方法执行完后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker
    thread来执行这个方法(即在worker thread当中执行),执行完后将执行结果发送给最后一个 onPostExecute
    方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作

  • onProgressUpdate(Progess… values): 这个方法也是在UI

    Thread当中执行的,在异步任务执行的时候,有时需要将执行的进度返回给UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新进度。这个方法在调用之前,我们需要在
    doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将进度时时刻刻传递给
    onProgressUpdate 方法来更新

  • onPostExecute(Result… result): 当异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI

    Thread当中调用的,我们可以将返回的结果显示在UI控件上

希望本文所述对大家Android程序设计有所帮助。

原文链接:https://blog.csdn.net/adayabetter/article/details/81032320

延伸 · 阅读

精彩推荐