Android 进程内部的消息机制

Android 消息机制

参考了一些其他人的资料,加上自己的理解,总结了一下,给自己做个备忘。

Android的消息机制有进程间通信的消息机制和进程内部线程间通信的消息机制,在这里只分析Hanlder相关的线程间通信的消息机制;可以分成三部分:消息循环、消息发送、消息处理,下面分开来讨论。

 

一、消息循环

在这里将消息循环分为三个阶段:消息循环的建立、消息循环的运行以及消息循环的结束。

1、消息循环的建立

Android中,消息循环可以分为两类:主线程(UI线程)的消息循环和子线程的消息循环。

注:进程创建的第一个线程一般就称为该进程的主线程,在需要创建Activity的进程中,UI线程通常是第一个启动的线程,所以UI线程一般也是该进程的主线程。

 

主线程的消息循环在当前应用的进程创建时建立。一般情况下,由launcher启动的应用通常会运行在新的进程中,大致流程如下:

应用的默认Activity在创建时,ActivityManagerService.java

 

private final void startProcessLocked (ProcessRecord app, String hostingType, String hostingNameStr)

{

       …

                   int pid = Process.start("android.app.ActivityThread",

                    mSimpleProcessManagement ? app.processName : null, uid, uid,

                    gids, debugFlags, null);

       …

}

此处创建了一个新的进程并运行ActivityThread类的main方法:

public static final void main(String[] args)

{

       …

       Looper.prepareMainLooper();//创建主线程的消息循环和消息队列,该方法只允许主线程调用。

       …

       Looper.loop();//进入主线程消息循环

       …

}

   

    public static final void prepareMainLooper() {

        prepare();//创建并保存looper对象(即消息循环)

    setMainLooper(myLooper());//再将looper对象保存在static成员变量mMainLooper中,以便将来通过getMainLooper()方法可以获取到该对象

              …

}

此处通过getMainLooper()方法获取到的是当前进程中主线程(UI线程)的消息循环;通常情况下,不同的应用运行在不同的进程中,所以应用之间不能通过直接向对方消息队列插入消息的方式来通信。

    public static final void prepare() {

        if (sThreadLocal.get() != null) {//每个线程只能有一个消息循环

            throw new RuntimeException("Only one Looper may be created per thread");

        }

        sThreadLocal.set(new Looper());//保存在线程局部变量sThreadLocal中,以确保每个线程看到的都是属于自己的looper

}

 

looper类的构造方法中:

public class Looper {

    private Looper() {

        mQueue = new MessageQueue();//创建消息队列

        mRun = true;

        mThread = Thread.currentThread();

    }

}

      

MessageQueue的构造方法:

    MessageQueue() {

        nativeInit();//通过JNI创建了一个navtiveMessageQueue,并在C++层创建了一个Looper对象。

    }

   

子线程的消息循环在线程运行时创建。通常情况下,我们通过实例化Thread类并重载run()方法来得到一个新的线程,在这种情况下,run()方法执行完成之后线程就结束了,并没有消息循环的概念。要建立一个带消息循环的线程就需要利用Android提供的HandlerThread类。

HandlerThread myThread;

myThread = new HandlerThread("myThread");

myThread.start();

这样就启动了一个带有消息循环的子线程。大致流程如下:

public class HandlerThread extends Thread {

 

           public void run() {

        …

        Looper.prepare();//创建looper,该过程同主线程,但因为线程局部变量的原因,看到的是属于当前线程自己的looper

        synchronized (this) {

            mLooper = Looper.myLooper();

            notifyAll();

        }

        …

        Looper.loop();//进入循环

        …

    }

}

子线程可以通过Looper.myLooper()获取到自己的消息循环;

 

主线程和子线程消息循环大致是相同的,总结一下消息循环的创建过程:

1) Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue

2) JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;

3) C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当 Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。

前面的代码分析,只涉及到第1)点。

 

       2、消息循环的运行

       从前面可以看到,主线程ActivityThreadmain方法和子线程的run方法,最终都调用loop()方法进入消息循环。

 

    public static final void loop() {

        Looper me = myLooper();//从线程局部变量中获取当前线程looper

        MessageQueue queue = me.mQueue;//获取消息队列

        …

        while (true) {

            Message msg = queue.next(); //获取队列的下一条消息

 

            if (msg != null) {

                if (msg.target == null) {

                    // No target is a magic identifier for the quit message.

                    return;

                }

                msg.target.dispatchMessage(msg);//消息分发

                                

                msg.recycle();//释放资源

            }

        }

}

其中,queue.next()除了从队列中获取消息之外,还关系到循环的等待和唤醒。

       public class MessageQueue {

             

       final Message next() {

              …

              nativePollOnce(mPtr, nextPollTimeoutMillis);

              ...

              final long now = SystemClock.uptimeMillis();

        final Message msg = mMessages;

        if (msg != null) {

               final long when = msg.when;

                if (now >= when) {

                   mBlocked = false;

                mMessages = msg.next;

                msg.next = null;

                return msg;

            }

        }

 

           if (pendingIdleHandlerCount < 0) {//idleHandler队列

               pendingIdleHandlerCount = mIdleHandlers.size();

        }

              for (int i = 0; i < pendingIdleHandlerCount; i++) {

              …

                     keep = idler.queueIdle();             

              …   

              }

             

}

nextPollTimeoutMillis = 0;//执行完idleHandler任务之后,需要重置等待时间,以防在执行idleHandler时有新消息产生。

}

 

nativePollOnce()是一个native方法作用是控制当前消息循环的等待和唤醒,nextPollTimeoutMillis参数表示等待的时间,值为-1表示当前队列没有消息,进入等待状态;mPtr参数指向nativeMessageQueue

当队列中没有消息的时候,循环就等待在nativePollOnce()这里,有新消息产生时,循环被唤醒,并从nativePollOnce()处继续运行。

另外,需要注意的是,在查询到消息队列中没有消息且进入等待之前,还要查看是否需要执行idleHandler的任务,idleHandler是一个对立的队列。如果有应用实现了IdleHandlerInterface,则需要在睡眠之前先执行完idleHandler任务。

可以通过如下方法添加idleHandler任务:

 

    private void addIdleHandlerDemo() {

        MessageQueue queue = Looper.myQueue();

        queue.addIdleHandler(new MessageQueue.IdleHandler() {

            public boolean queueIdle() {

                …//需要执行的任务;

                return false;

            }

        });

    }

       可以看出idleHandler任务的响应不会很及时,因为它必须等到消息队列为空的时候才会被调度到。

 

 

3、消息循环的结束

当从消息队列中取得的消息满足如下条件时,消息循环就会结束:

 

if (msg.target == null)

{

        // No target is a magic identifier for the quit message.

        return;

      }

调用HandlerThread对象的quit()方法,或者直接调用Looper对象的quit()方法都可以向消息队列中插入一个target==null的消息,从而终止消息循环。

 

二、消息发送

主线程和子线程消息的发送没有区别,可采用如下几种方式:

 

1handleMessage方式

 

    private final Handler mIntentHandler = new Handler() {

        public void handleMessage(Message msg) {

               switch(msg.what)

                     {

                            …

}

        }

};

 

Message msg = myHandler.obtainMessage(1);

myHandler.sendMessageDelayed(msg, 2000);

 

通过这种方式发送消息的方法有好几个:

sendMessage(Message msg)

sendEmptyMessage(int what)

sendEmptyMessageDelayed(int what, long delayMillis)

sendEmptyMessageAtTime(int what, long uptimeMillis)

sendMessageDelayed(Message msg, long delayMillis)

sendMessageAtTime(Message msg, long uptimeMillis)

最终都是调用了同一个方法:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis)

    {

        boolean sent = false;

        MessageQueue queue = mQueue;

        if (queue != null) {

            msg.target = this;

            sent = queue.enqueueMessage(msg, uptimeMillis);

        }

        else {

            RuntimeException e = new RuntimeException(

                this + " sendMessageAtTime() called with no mQueue");

            Log.w("Looper", e.getMessage(), e);

        }

        return sent;

    }

获取消息队列,并向队列中插入消息,其中时间戳uptimeMillis是消息中定义的时间戳加上当前系统时间得到的一个值。

还有一个方法sendMessageAtFrontOfQueue (Message msg),它没有调用sendMessageAtTime,而是直接调用enqueueMessage方法将消息插入到了队列头部,且时间戳为0,而这个消息也会在loop的下一次迭代中被执行到。该方法极易引起时序问题,慎用!!!

 

final boolean enqueueMessage(Message msg, long when) {

       …

       if (p == null || when == 0 || when < p.when) {

                msg.next = p;

                mMessages = msg;

                needWake = mBlocked; // new head, might need to wake up

            } else {

                Message prev = null;

                while (p != null && p.when <= when) {

                    prev = p;

                    p = p.next;

                }

                msg.next = prev.next;

                prev.next = msg;

                needWake = false; // still waiting on head, no need to wake up

            }

       …

       if (needWake) {

            nativeWake(mPtr);//唤醒处于等待状态的消息循环

     }

}

在上面的方法中可以看出,队列中的消息是按照时间由小到大排列的,如果当前消息被插入到队列头部,则表示之前消息循环可能处于等待状态,需要将之唤醒。

2Runnable方式

 

    final Runnable mTestRunnable= new Runnable() {

        public void run() {

              …//to do

        }

    };

Handler mHandler = new Handler();

mHandler.postDelayed(mTestRunnable, 10000);

通过这种方式发送消息的方法有好几个:

              post(Runnable r)

              postAtTime(Runnable r, long uptimeMillis)

              postAtTime(Runnable r, Object token, long uptimeMillis)

              postDelayed(Runnable r, long delayMillis)

       这种方式发送消息,本质上跟1中的方法是相同的,最终也是调用了sendMessageAtTime方法向队列中插入消息,不同的地方在于事先需要调用如下方法将Runnable封装成一个消息。

           private final Message getPostMessage(Runnable r) {

        Message m = Message.obtain();

        m.callback = r;

        return m;

}

这里同样有一个例外的方法:

    public final boolean postAtFrontOfQueue(Runnable r)

    {

        return sendMessageAtFrontOfQueue(getPostMessage(r));

    }

慎用!!!

 

总结:

从前面两种消息发送的方式可以看出,他们都是通过Handler将消息插入到了一个消息队列中,可以得出一个结论:Handler必须依附于一个具有消息循环/消息队列的线程。

 

Handler mHandler = new Handler();这种方式创建的handler依附于当前线程,如果当前线程没有消息队列,则会报出异常:"Can't create handler inside thread that has not called Looper.prepare()"

可以采用如下的方式让handler依附于一个子线程:

HandlerThread myThread;

myThread = new HandlerThread("myThread");

myThread.start();

Handler myHandler = new Handler(myThread.getLooper())

这样,handler就可以依附于线程myThread

 

三、消息处理

       消息的处理比较简单,在前面提到的消息循环中:

 

              public static void loop() {

              …

                     msg.target.dispatchMessage(msg);

              …

}

Loop从消息队列获取到消息之后,通过dispatchMessage分发消息,而target是一个Handler类型的参数,在Handler类的sendMessageAtTime方法中:

public boolean sendMessageAtTime(Message msg, long uptimeMillis)

{

              msg.target = this;

              …

       }

       可以看出,target指向的是发送消息的Handler对象,所以消息分发的目标对象即是当时发送这个消息的Handler对象。

 

       大致分发流程如下:

    public void dispatchMessage(Message msg) {

        if (msg.callback != null) {

            handleCallback(msg);

        } else {

            if (mCallback != null) {

                if (mCallback.handleMessage(msg)) {

                    return;

                }

            }

            handleMessage(msg);

        }

    }

如果消息的callback参数不为空,则执行callback指向的方法即用第二部分消息发送的第二种方式Runnablerun()方法;否则就执行第一种方式中的handleMessage()方法。

标题基于Python的自主学习系统后端设计与实现AI更换标题第1章引言介绍自主学习系统的研究背景、意义、现状以及本文的研究方法和创新点。1.1研究背景与意义阐述自主学习系统在教育技术领域的重要性和应用价值。1.2国内外研究现状分析国内外在自主学习系统后端技术方面的研究进展。1.3研究方法与创新点概述本文采用Python技术栈的设计方法和系统创新点。第2章相关理论与技术总结自主学习系统后端开发的相关理论和技术基础。2.1自主学习系统理论阐述自主学习系统的定义、特征和理论基础。2.2Python后端技术栈介绍DjangoFlask等Python后端框架及其适用场景。2.3数据库技术讨论关系型和非关系型数据库在系统中的应用方案。第3章系统设计与实现详细介绍自主学习系统后端的设计方案和实现过程。3.1系统架构设计提出基于微服务的系统架构设计方案。3.2核心模块设计详细说明用户管理、学习资源管理、进度跟踪等核心模块设计。3.3关键技术实现阐述个性化推荐算法、学习行为分析等关键技术的实现。第4章系统测试与评估对系统进行功能测试和性能评估。4.1测试环境与方法介绍测试环境配置和采用的测试方法。4.2功能测试结果展示各功能模块的测试结果和问题修复情况。4.3性能评估分析分析系统在高并发等场景下的性能表现。第5章结论与展望总结研究成果并提出未来改进方向。5.1研究结论概括系统设计的主要成果和技术创新。5.2未来展望指出系统局限性并提出后续优化方向。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值