Android性能之多线程篇

来源:互联网 发布:小榕软件 编辑:程序博客网 时间:2024/06/03 16:36

    Android 性能除了与UI,内存等有直接关系外,还与线程使用有密切的关系。

     一、UI线程和工作线程

         Android规定UI更新等操作必须在UI线程(也成主线程)中,建议其他的耗时操作放到work thread(工作线程)中去,避免阻塞主线程,产生卡顿或者ANR等问题。

        问题来了,Android应用中,到底哪个线程是主线程(UI线程)呢?Android应用的入口是在ActivityThread类的main方法,但是ActivityThread本身不是一个线程,承载ActivityThread类运行的线程为主线程,也就是UI线程,其他线程都是工作线程。下图的代码操作都执行在主线程中,包括系统事件(屏幕发生旋转),输入事件(用户点击,滑动),程序回调服务,UI绘制,闹钟事件,Activity生命周期回调方法等,我们在上述事件或者方法中插入的线程也执行主程序。

                     

          如果在主线程中执行的任务过于繁重,就可能导致接收到刷新UI信号(VSync)的时候因为资源被占用而无法完成这次刷新操作,这明显就发生了掉帧现象,用户明显感知到卡顿,这块原理详见:http://blog.csdn.net/wei_lei/article/details/70460132

         为了避免出现主线程一直做繁重的工作,我们需要借助多线程技术来将繁重的工作放到工作线程中,主线程只专注系统回调,UI刷新工作,下图为多线程模型下的UI渲染机制。

                                    

二、Android中多线程实现的几种方式

       2.1 AsyncTask

               AsyncTask,是Android系统提供的一个实现多线程的手段,它为UI线程与工作线程之间进行快速的切换提供了一种简单便捷的机制。适用于需要立刻启动,但是异步执行生命        周期短暂的使用场景。

              AsyncTask具体的线程切换原理如下图所示:

                     

            AsyncTask的onPreExcute()方法运行在主线程中,主要做一些准备工作,而doInBackground()方法已经切换到工作线程了,这个时候可以处理一些耗时操作,比如网络请求等,当而onPostExecute方法把工作线程切换到了主线程,内部实现是runOnUiThread方法,这个方法内部实现其实通过Handler发送了message方法。

          默认情况下,AsyncTask内部队列是串行执行的,一旦一个队列中某个任务卡住了,后续所有的任务都被block住,可以使用executeOnExecutor()方法来强制指定AsyncTask使用线程池并发调度任务。

         使用AsyncTask虽然和方便切换工作线程和UI线程,但是很容易导致内存泄露,把AsyncTask写成Activity的内部类形式,很容易因为其生命周期不稳定导致Activity发生泄露。

        2.2 Thread和Handler

         这是Android中标准机制,用于Work Thread中发送消息到主线程中,让主线程去做更新操作,如下图所示:

                      

      关于Handler,Looper,MessageQueue和Message这些关系,本篇不详细展开。

       2.3 线程池

         线程池(ThreadPoolExecutor)主要用作分拆很多任务,并发进行的场景。比如,我们设计系统时可以针对不同的任务设置一个单独的守护线程来专门处理这项任务。例如使用IO Thread来专门处理系统的IO操作。地图里面组件初始化等操作用的是单独的线程池。线程池工作模型如下图所示:

                     

             使用线程池需要注意的是并发梳理的控制。因为CPU每次只能同时执行固定数量的线程数,一旦同时并发的线程数量超过了CPU能够同时执行的阈值,CPU需要花费精力来判断哪些线程的优先级比较高,需要在不同的线程之间进行调度切换,这样反而会导致性能下降,而且每次新开一个线程,会消耗至少64K+内存。

             2.4 IntentService

               默认Service运行在主线程中,而IntentService是内部封装了HandlerThread,在onHandlerIntent里面回调处理放到IntentService的任务,因此IntentService保留了Service的特性,不受主页生命周期影响,也兼具了异步线程的特性。

              注意点,IntentService内置的是HandlerThread作为异步线程,所以每一个交给IntentService的任务都将以队列的方式串行执行,如果队列中有任务执行时间过长,那么会导致后续的任务会被延迟处理。

三、使用多线程就一定能提高性能?

          上文中提到了Android的主线程和工作线程,我们也明白了Android中主线程负责UI的更新与渲染。现在有一个问题,我们使用了多线程就一定能提高性能吗?答案是不一    定!  为什么这么说呢? 因为线程的运行是通过CPU进行调度的,在任意一个时刻(注意这个词。时刻,非一段时间,单位可以精确到ms级别,比如第1ms 的时候是一个A线程在运行,但第2ms有可能是B线程在运行),只有一个线程在运行,CPU会对各个线程的运行进行调度,主要负责某个时间片上切换到指定的线程运行。但是,如果当前运行的线程过多,CPU就会根据各个线程的优先级进行调度,需要在大量的线程中进行切换,这就会造成严重的性能下降。

        因此,地图9.x版本开始,对启动速度优化的时候,也考虑了这个问题,地图的解决方案是梳理地图开启启动的线程,然后按照场景来划分,哪些线程是必须开机运行,哪些可以延迟启动,比如页面栈框架,底图模块等核心功能必须在启动的时候首先加载,而其他的功能可以延迟加载,这样就能减少开机运行的线程数量,避免给主线程带来太大的压力。

       真实优化过程中,还需要结合具体的业务场景来考虑,做到灵活应用!

0 0