Android应用程序线程消息循环模型分析(1)

简介:

      我们知道,Android应用程序是通过消息来驱动的,即在应用程序的主线程(UI线程)中有一个消息循环,负责处理消息队列中的消息。我们也知道,Android应用程序是支持多线程的,即可以创建子线程来执行一些计算型的任务,那么,这些子线程能不能像应用程序的主线程一样具有消息循环呢?这些子线程又能不能往应用程序的主线程中发送消息呢?本文将分析Android应用程序线程消息处理模型,为读者解答这两个问题

        在开发Android应用程序中,有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些不需要与应用程序界面交互的计算型的任务。如果这些子线程具有消息循环,那么它们就能够常驻在应用程序中不定期的执行一些计算型任务了:当我们需要用这些子线程来执行任务时,就往这个子线程的消息队列中发送一个消息,然后就可以在子线程的消息循环中执行我们的计算型任务了。我们在前面一篇文章Android系统默认Home应用程序(Launcher)的启动过程源代码分析中,介绍Launcher的启动过程时,在Step 15(LauncherModel.startLoader)中,Launcher就是通过往一个子线程的消息队列中发送一个消息(sWorker.post(mLoaderTask)),然后子线程就会在它的消息循环中处理这个消息的时候执行从PackageManagerService中获取系统中已安装应用程序的信息列表的任务,即调用Step 16中的LoaderTask.run函数。

        在开发Android应用程序中,有时候我们又需要在应用程序中创建一些子线程来执行一些需要与应用程序界面进交互的计算型任务。典型的应用场景是当我们要从网上下载文件时,为了不使主线程被阻塞,我们通常创建一个子线程来负责下载任务,同时,在下载的过程,将下载进度以百分比的形式在应用程序的界面上显示出来,这样就既不会阻塞主线程的运行,又能获得良好的用户体验。但是,我们知道,Android应用程序的子线程是不可以操作主线程的UI的,那么,这个负责下载任务的子线程应该如何在应用程序界面上显示下载的进度呢?如果我们能够在子线程中往主线程的消息队列中发送消息,那么问题就迎刃而解了,因为发往主线程消息队列的消息最终是由主线程来处理的,在处理这个消息的时候,我们就可以在应用程序界面上显示下载进度了。

        上面提到的这两种情况,Android系统都为我们提供了完善的解决方案,前者可以通过使用HandlerThread类来实现,而后者可以使用AsyncTask类来实现,本文就详细这两个类是如何实现的。不过,为了更好地理解HandlerThread类和AsyncTask类的实现,我们先来看看应用程序的主线程的消息循环模型是如何实现的。

        1. 应用程序主线程消息循环模型

        在前面一篇文章Android应用程序进程启动过程的源代码分析一文中,我们已经分析应用程序进程(主线程)的启动过程了,这里主要是针对它的消息循环模型作一个总结。当运行在Android应用程序框架层中的ActivityManagerService决定要为当前启动的应用程序创建一个主线程的时候,它会在ActivityManagerService中的startProcessLocked成员函数调用Process类的静态成员函数start为当前应用程序创建一个主线程:

  1. public final class ActivityManagerService extends ActivityManagerNative      
  2.         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {      
  3.       
  4.     ......      
  5.       
  6.     private final void startProcessLocked(ProcessRecord app,      
  7.                 String hostingType, String hostingNameStr) {      
  8.       
  9.         ......      
  10.       
  11.         try {      
  12.             int uid = app.info.uid;      
  13.             int[] gids = null;      
  14.             try {      
  15.                 gids = mContext.getPackageManager().getPackageGids(      
  16.                     app.info.packageName);      
  17.             } catch (PackageManager.NameNotFoundException e) {      
  18.                 ......      
  19.             }      
  20.                   
  21.             ......      
  22.       
  23.             int debugFlags = 0;      
  24.                   
  25.             ......      
  26.                   
  27.             int pid = Process.start("android.app.ActivityThread",      
  28.                 mSimpleProcessManagement ? app.processName : null, uid, uid,      
  29.                 gids, debugFlags, null);      
  30.                   
  31.             ......      
  32.       
  33.         } catch (RuntimeException e) {      
  34.                   
  35.             ......      
  36.       
  37.         }      
  38.     }      
  39.       
  40.     ......      
  41.       
  42. }      

        这里我们主要关注Process.start函数的第一个参数“android.app.ActivityThread”,它表示要在当前新建的线程中加载android.app.ActivityThread类,并且调用这个类的静态成员函数main作为应用程序的入口点。ActivityThread类定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

  1. public final class ActivityThread {    
  2.     ......    
  3.     
  4.     public static final void main(String[] args) {    
  5.         ......  
  6.     
  7.         Looper.prepareMainLooper();    
  8.            
  9.         ......    
  10.     
  11.         ActivityThread thread = new ActivityThread();    
  12.         thread.attach(false);    
  13.     
  14.         ......   
  15.         Looper.loop();    
  16.     
  17.         ......   
  18.     
  19.         thread.detach();    
  20.         ......    
  21.     }    
  22.     
  23.     ......    
  24. }    

        在这个main函数里面,除了创建一个ActivityThread实例外,就是在进行消息循环了。

 

        在进行消息循环之前,首先会通过Looper类的静态成员函数prepareMainLooper为当前线程准备一个消息循环对象。Looper类定义在frameworks/base/core/java/android/os/Looper.java文件中:

  1. public class Looper {  
  2.     ......  
  3.   
  4.     // sThreadLocal.get() will return null unless you've called prepare().  
  5.     private static final ThreadLocal sThreadLocal = new ThreadLocal();  
  6.   
  7.     ......  
  8.   
  9.     private static Looper mMainLooper = null;  
  10.   
  11.     ......  
  12.   
  13.     public static final void prepare() {  
  14.         if (sThreadLocal.get() != null) {  
  15.             throw new RuntimeException("Only one Looper may be created per thread");  
  16.         }  
  17.         sThreadLocal.set(new Looper());  
  18.     }  
  19.   
  20.     ......  
  21.   
  22.     public static final void prepareMainLooper() {  
  23.         prepare();  
  24.         setMainLooper(myLooper());  
  25.         ......  
  26.     }  
  27.   
  28.     private synchronized static void setMainLooper(Looper looper) {  
  29.         mMainLooper = looper;  
  30.     }  
  31.   
  32.     public synchronized static final Looper getMainLooper() {  
  33.         return mMainLooper;  
  34.     }  
  35.   
  36.     ......  
  37.   
  38.     public static final Looper myLooper() {  
  39.         return (Looper)sThreadLocal.get();  
  40.     }  
  41.   
  42.     ......  
  43. }  

 

        Looper类的静态成员函数prepareMainLooper是专门应用程序的主线程调用的,应用程序的其它子线程都不应该调用这个函数来在本线程中创建消息循环对象,而应该调用prepare函数来在本线程中创建消息循环对象,下一节我们介绍一个线程类HandlerThread 时将会看到。

        为什么要为应用程序的主线程专门准备一个创建消息循环对象的函数呢?这是为了让其它地方能够方便地通过Looper类的getMainLooper函数来获得应用程序主线程中的消息循环对象。获得应用程序主线程中的消息循环对象又有什么用呢?一般就是为了能够向应用程序主线程发送消息了。

        在prepareMainLooper函数中,首先会调用prepare函数在本线程中创建一个消息循环对象,然后将这个消息循环对象放在线程局部变量sThreadLocal中:

  1. sThreadLocal.set(new Looper());  

        接着再将这个消息循环对象通过调用setMainLooper函数来保存在Looper类的静态成员变量mMainLooper中:

  1. mMainLooper = looper;  

       这样,其它地方才可以调用getMainLooper函数来获得应用程序主线程中的消息循环对象。

 

       消息循环对象创建好之后,回到ActivityThread类的main函数中,接下来,就是要进入消息循环了:

  1. Looper.loop();   

        Looper类具体是如何通过loop函数进入消息循环以及处理消息队列中的消息,可以参考前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析,这里就不再分析了,我们只要知道ActivityThread类中的main函数执行了这一步之后,就为应用程序的主线程准备好消息循环就可以了。





本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966875,如需转载请自行联系原作者
目录
相关文章
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
843 2
|
存储 Android开发 数据安全/隐私保护
如何在Android设备上撤销Flutter应用程序的所有权限?
如何在Android设备上撤销Flutter应用程序的所有权限?
1267 64
|
缓存 Android开发 开发者
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
3200 62
|
开发工具 Android开发 开发者
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
1341 61
|
安全 Java 调度
Netty源码—3.Reactor线程模型二
本文主要介绍了NioEventLoop的执行总体框架、Reactor线程执行一次事件轮询、Reactor线程处理产生IO事件的Channel、Reactor线程处理任务队列之添加任务、Reactor线程处理任务队列之执行任务、NioEventLoop总结。
|
安全 Java
Netty源码—2.Reactor线程模型一
本文主要介绍了关于NioEventLoop的问题整理、理解Reactor线程模型主要分三部分、NioEventLoop的创建和NioEventLoop的启动。
|
缓存 NoSQL 中间件
Redis的线程模型
Redis采用单线程模型确保操作的原子性,每次只执行一个操作,避免并发冲突。它通过MULTI/EXEC事务机制、Lua脚本和复合指令(如MSET、GETSET等)保证多个操作要么全成功,要么全失败,确保数据一致性。Redis事务在EXEC前失败则不执行任何操作,EXEC后失败不影响其他操作。Pipeline虽高效但不具备原子性,适合非热点时段的数据调整。Redis 7引入Function功能,支持函数复用,简化复杂业务逻辑。总结来说,Redis的单线程模型简单高效,适用于高并发场景,但仍需合理选择指令执行方式以发挥其性能优势。
506 6
|
消息中间件 存储 NoSQL
剖析 Redis List 消息队列的三种消费线程模型
Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。 生产环境,很多公司都将 Redis 列表应用于轻量级消息队列 。这篇文章,我们聊聊如何使用 List 命令实现消息队列的功能以及剖析消费者线程模型 。
剖析 Redis List 消息队列的三种消费线程模型
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
1346 5

热门文章

最新文章