【Android源码 栏目提醒】:以下是网学会员为您推荐的Android源码 -Android应用程序线程消息循环模型分析 - 电子设计,希望本篇文章对您学习有所帮助。
Android应用程序线程消息循环模型分析 我们知道
Android应用程序是通过消息来驱动的即在应用程序的主线程UI线程中有一个消息循环负责处理消息队列中的消息。
我们也知道
Android应用程序是支持多线程的即可以创建子线程来执行一些计算型的任务那么这些子线程能不能像应用程序的主线程一样具有消息循环呢这些子线程又能不能往应用程序的主线程中发送消息呢本文将分析
Android应用程序线程消息处理模型为读者解答这两个问题 在开发
Android应用程序中有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些不需要与应用程序界面交互的计算型的任务。
如果这些子线程具有消息循环那么它们就能够常驻在应用程序中不定期的执行一些计算型任务了当我们需要用这些子线程来执行任务时就往这个子线程的消息队列中发送一个消息然后就可以在子线程的消息循环中执行我们的计算型任务了。
我们在前面一篇文章
Android系统默认Home应用程序Launcher的启动过程源代码分析中介绍Launcher的启动过程时在Step 15LauncherModel.startLoader中Launcher就是通过往一个子线程的消息队列中发送一个消息sWorker.postmLoaderTask然后子线程就会在它的消息循环中处理这个消息的时候执行从PackageManagerService中获取系统中已安装应用程序的信息列表的任务即调用Step 16中的LoaderTask.run函数。
在开发
Android应用程序中有时候我们又需要在应用程序中创建一些子线程来执行一些需要与应用程序界面进交互的计算型任务。
典型的应用场景是当我们要从网上下载文件时为了不使主线程被阻塞我们通常创建一个子线程来负责下载任务同时在下载的过程将下载进度以百分比的形式在应用程序的界面上显示出来这样就既不会阻塞主线程的运行又能获得良好的用户体验。
但是我们知道
Android应用程序的子线程是不可以操作主线程的UI的那么这个负责下载任务的子线程应该如何在应用程序界面上显示下载的进度呢如果我们能够在子线程中往主线程的消息队列中发送消息那么问题就迎刃而解了因为发往主线程消息队列的消息最终是由主线程来处理的在处理这个消息的时候我们就可以在应用程序界面上显示下载进度了。
上面提到的这两种情况
Android系统都为我们提供了完善的解决方案前者可以通过使用HandlerThread类来实现而后者可以使用AsyncTask类来实现本文就详细这两个类是如何实现的。
不过为了更好地理解HandlerThread类和AsyncTask类的实现我们先来看看应用程序的主线程的消息循环模型是如何实现的。
1. 应用程序主线程消息循环模型 在前面一篇文章
Android应用程序进程启动过程的源代码分析一文中我们已经分析应用程序进程主线程的启动过程了这里主要是针对它的消息循环模型作一个总结。
当运行在
Android应用程序框架层中的ActivityManagerService决定要为当前启动的应用程序创建一个主线程的时候它会在ActivityManagerService中的startProcessLocked成员函数调用Process类的静态成员函数start为当前应用程序创建一个主线程 java view plaincopy 1. public final class ActivityManagerService extends ActivityManagerNative 2. implements Watchdog.Monitor BatteryStatsImpl.BatteryCallback 3. 4. ...... 5. 6. private final void startProcessLockedProcessRecord 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.startquotandroid.app.ActivityThreadquot 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文件中 java view plaincopy 1. public final class ActivityThread 2. ...... 3. 4. public static final void mainString args 5. ...... 6. 7. Looper.prepareMainLooper 8. 9. ...... 10. 11. ActivityThread thread new ActivityThread 12. thread.attachfalse 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文件中 java view plaincopy 1. public class Looper 2. ...... 3. 4. // sThreadLocal.get will return null unless youve 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 RuntimeExceptionquotOnly one Looper may be created per threadquot 16. 17. sThreadLocal.setnew Looper 18. 19. 20. ...... 21. 22. public static final void prepareMainLooper 23. prepare 24. setMainLoopermyLooper 25. ...... 26. 27. 28. private synchronized static void setMainLooperLooper 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 LoopersThreadLocal.get 40. 41. 42. ...... 43. Looper类的静态成员函数prepareMainLooper是专门应用程序的主线程调用的应用程序的其它子线程都不应该调用这个函数来在本线程中创建消息循环对象而应该调用prepare函数来在本线程中创建消息循环对象下一节我们介绍一个线程类HandlerThread 时将会看到。
为什么要为应用程序的主线程专门准备一个创建消息循环对象的函数呢这是为了让其它地方能够方便地通过Looper类的getMainLooper函数来获得应用程序主线程中的消息循环对象。
获得应用程序主线程中的消息循环对象又有什么用呢一般就是为了能够向应用程序主线程发送消息了。
在prepareMainLooper函数中首先会调用prepare函数在本线程中创建一个消息循环对象然后将这个消息循环对象放在线程局部变量sThreadLocal中 java view plaincopy 1. sThreadLocal.setnew Looper 接着再将这个消息循环对象通过调用setMainLooper函数来保存在Looper类的静态成员变量mMainLooper中 java view plaincopy 1. mMainLooper looper 这样其它地方才可以调用getMainLooper函数来获得应用程序主线程中的消息循环对象。
消息循环对象创建好之后回到ActivityThread类的main函数中接下来就是要进入消息循环了 java view plaincopy 1. Looper.loop Looper类具体是如何通过loop函数进入消息循环以及处理消息队列中的消息可以参考前面一篇文章
Android应用程序消息处理机制Looper、Handler分析这里就不再分析了我们只要知道ActivityThread类中的main函数执行了这一步之后就为应用程序的主线程准备好消息循环就可以了。
2. 应用程序子线程消息循环模型 在Java框架中如果我们想在当前应用程序中创建一个子线程一般就是通过自己实现一个类这个类继承于Thread类然后重载Thread类的run函数把我们想要在这个子线程执行的任务都放在这个run函数里面实现。
最后实例这个自定义的类并且调用它的start函数这样一个子线程就创建好了并且会调用这个自定义类的run函数。
但是当这个run函数执行完成后子线程也就结束了它没有消息循环的概念。
前面说过有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些计算型任务这时候就可以考虑使用
Android系统提供的HandlerThread类了它具有创建具有消息循环功能的子线程的作用。
HandlerThread类实现在frameworks/base/core/java/
android/os/HandlerThread.java文件中这里我们通过使用情景来有重点的分析它的实现。
在前面一篇文章
Android系统默认Home应用程序Launcher的启动过程源代码分析中我们分析了Launcher的启动过程其中在Step 15LauncherModel.startLoader和Step 16LoaderTask.run中Launcher会通过创建一个HandlerThread类来实现在一个子线程加载系统中已经安装的应用程序的任务 java view plaincopy 1. public class LauncherModel extends BroadcastReceiver 2. ...... 3. 4. private LoaderTask mLoaderTask 5. 6. private static final HandlerThread sWorkerThread new HandlerThreadquotlauncher-loaderquot 7. static 8. sWorkerThread.start 9. 10. private static final Handler sWorker new HandlersWorkerThread.getLooper 11. 12. ...... 13. 14. public void startLoaderContext context boolean isLaunching 15. ...... 16. 17. synchronized mLock 18. ...... 19. 20. // Dont bother to start the thread if we know its not going to do anything 21. if mCallbacks null ampamp mCallbacks.get null 22. ...... 23. 24. mLoaderTask new LoaderTaskcontext isLaunching 25. sWorker.postmLoaderTask 26. 27. 28. 29. 30. ...... 31. 32. private class LoaderTask implements Runnable 33. ...... 34. 35. public void run 36. ...... 37. 38. keep_running: 39. ...... 40. 41. // second step 42. if loadWorkspaceFirst 43. ...... 44. loadAndBindAllApps 45. else 46. ...... 47. 48. 49. ...... 50. 51. 52. ...... 53. 54. 55. ...... 56. 57. 58. ...... 59. 在这个LauncherModel类中首先创建了一个HandlerThread对象 java view plaincopy 1. private static final HandlerThread sWorkerThread new HandlerThreadquotlauncher-loaderquot 接着调用它的start成员函数来启动一个子线程 java view plaincopy 1. static 2. sWorkerThread.start 3. 接着还通过这个HandlerThread对象的getLooper函数来获得这个子线程中的消息循环对象并且使用这个消息循环创建对象来创建一个Handler java view plaincopy 1. private static final Handler sWorker new HandlersWorkerThread.getLooper 有了这个Handler对象sWorker之后我们就可以往这个子线程中发送消息然后在处理这个消息的时候执行加载系统中已经安装的应用程序的任务了在startLoader函数中 java view plaincopy 1. mLoaderTask new LoaderTaskcontext isLaunching 2. sWorker.postmLoaderTask 这里的mLoaderTask是一个LoaderTask对象它实现了Runnable接口因此可以把这个LoaderTask对象作为参数传给sWorker.post函数。
在sWorker.post函数里面会把这个LoaderTask对象封装成一个消息并且放入这个子线程的消息队列中去。
当这个子线程的消息循环处理这个消息的时候就会调用这个LoaderTask对象的run函数因此我们就可以在LoaderTask对象的run函数中通过调用loadAndBindAllApps来执行加载系统中已经安装的应用程序的任务了。
了解了HanderThread类的使用方法之后我们就可以重点地来分析它的实现了 java view plaincopy 1. public class HandlerThread extends Thread 2. ...... 3. private Looper mLooper 4. 5. public HandlerThreadString name 6. supername 7. ...... 8. 9. 10. ...... 11. 12. public void run 13. ...... 14. Looper.prepare 15. synchronized this 16. mLooper Looper.myLooper 17. ...... 18. 19. ...... 20. Looper.loop 21. ...... 22. 23. 24. public Looper getLooper 25. ...... 26. return mLooper 27. 28. 29. ...... 30. 首先我们看到的是HandlerThread类继承了Thread类因此通过它可以在应用程序中创建一个子线程其次我们看到在它的run函数中会进入一个消息循环中因此这个子线程可以常驻在应用程序中直到它接收收到一个退出消息为止。
在run函数中首先是调用Looper类的静态成员函数prepare来准备一个消息循环对象 java view plaincopy 1. Looper.prepare 然后通过Looper类的myLooper成员函数将这个子线程中的消息循环对象保存在HandlerThread类中的成员变量mLooper中 java view plaincopy 1. mLooper Looper.myLooper 这样其它地方就可以方便地通过它的getLooper函数来获得这个消息循环对象了有了这个消息循环对象后就可以往这个子线程的消息队列中发送消息通知这个子线程执行特定的任务了。
最在这个run函数通过Looper类的loop函数进入消息循环中 java view plaincopy 1. Looper.loop 这样一个具有消息循环的应用程序子线程就准备就绪了。
HandlerThread类的实现虽然非常简单当然这得益于Java提供的Thread类和
Android自己本身提供的Looper类但是它的想法却非常周到为应用程序开发人员提供了很大的方便。
3. 需要与UI交互的应用程序子线程消息模型 前面说过我们开发应用程序的时候经常中需要创建一个子线程来在后台执行一个特定的计算任务而在这个任务计算的过程中需要不断地将计算进度或者计算结果展现在应用程序的界面中。
典型的例子是从网上下载文件为了不阻塞应用程序的主线程我们开辟一个子线程来执行下载任务子线程在下载的同时不断地将下载进度在应用程序界面上显示出来这样做出来程序就非常友好。
由于子线程不能直接操作应用程序的UI因此这时候我们就可以通过往应用程序的主线程中发送消息来通知应用程序主线程更新界面上的下载进度。
因为类似的这种情景在实际开发中经常碰到
Android系统为开发人员提供了一个异步任务类AsyncTask来实现上面所说的功能即它会在一个子线程中执行计算任务同时通过主线程的消息循环来获得更新应用程序界面的机会。
为了更好地分析AsyncTask的实现我们先举一个例子来说明它的用法。
在前面一篇文章
Android系统中的广播Broadcast机制简要介绍和学习计划中我们开发了一个应用程序Broadcast其中使用了AsyncTask来在一个线程在后台在执行计数任务计数过程通过广播Broadcast来将中间结果在应用程序界面上显示出来。
在这个例子中使用广播来在应用程序主线程和子线程中传递数据不是最优的方法当时只是为了分析
Android系统的广播机制而有意为之的。
在本节内容中我们稍微这个例子作一个简单的修改就可以通过消息的方式来将计数过程的中间结果在应用程序界面上显示出来。
为了区别
Android系统中的广播Broadcast机制简要介绍和学习计划一文中使用的应用程序Broadcast我们将本节中使用的应用程序命名为Counter。
首先在
Android源代码工程中创建一个
Android应用程序工程名字就为Counter放在packages/experimental目录下。
关于如何获得
Android源代码工程请参考在Ubuntu上下载、编译和安装
Android最新源代码一文关于如何在
Android源代码工程中创建应用程序工程请参考在Ubuntu上为
Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文。
这个应用程序工程定义了一个名为shy.luo.counter的package这个例子的源代码主要就是实现在这个目录下的Counter.java文件中 java view plaincopy 1. package shy.luo.counter 2. 3. import
android.app.Activity 4. import
android.content.ComponentName 5. import
android.content.Context 6. import
android.content.Intent 7. import
android.content.IntentFilter 8. import
android.os.Bundle 9. import
android.os.AsyncTask 10. import
android.util.Log 11. import
android.view.View 12. import
android.view.View.OnClickListener 13. import
android.widget.Button 14. import
android.widget.TextView 15. 16. public class Counter extends Activity implements OnClickListener 17. private final static String LOG_TAG quotshy.luo.counter.Counterquot 18. 19. private Button startButton null 20. private Button stopButton null 21. private TextView counterText null 22. 23. private AsyncTaskltInteger Integer Integergt task null 24. private boolean stop false 25. 26. Override 27. public void onCreateBundle savedInstanceState 28. su.