active
。在Android的世界里,一个Activity处于前台之时,如果能采集用户的input
事件,就可以判定为active
,如果中途弹出一个Dialog
,Dialog
变成新的active
实体,直接面对用户的操作。被部分遮挡的activity
尽管依然可见,但状态却变为inactive
。不能正确的区分visible
和active
是很多初级程序员会犯的错误。Service
,如果一个进程包含Service
(称为Service Process),那么在“重要性”上就会被系统区别对待,其优先级自然会高于不包含Service
的进程(称为Background Process),最后还剩一类空进程(Empty Process)。Empty Process初看有些费解,一个Process如果什么都不做,还有什么存在的必要。其实Empty Process并不Empty,还存在不少的内存占用。Memory
被分为Clean Memory
和Dirty Memory
,Clean Memory
是App启动被加载到内存之后原始占用的那一部分内存,一般包括初始的stack, heap, text, data
等segment,Dirty Memory
是由于用户操作所改变的那部分内存,也就是App的状态值。系统在出现Low Memory Warning的时候会首先清掉Dirty Memory,对于用户来说,操作的进度就全部丢失了,即使再次点击App图标,也是一切从头开始。但由于Clean Memory
没有被清除,避免了从磁盘重新读取app数据的io损耗,启动会变快。这也是为什么很多人会感觉手机重启后,app打开的速度都比较慢。Clean Memory
,这部分Memory对于提升App的启动速度大有帮助。显而易见Empty Process的优先级是最低的。
进程的优先级从高到低依次分为五类,越往下,在内存紧张的时候越有可能被系统杀掉。简而言之,越是容易被用户感知到的进程,其优先级必定更高。
线程调度(Thread Scheduling)
Android系统基于精简过后的linux内核,其线程的调度受时间片轮转和优先级控制等诸多因素影响。不少初学者会认为某个线程分配到的time slice多少是按照其优先级与其它线程优先级对比所决定的,这并不完全正确。
Linux系统的调度器在分配time slice
的时候,采用的CFS(completely fair scheduler)
策略。这种策略不但会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice
数量,如果高优先级的线程已经执行了很长时间,但低优先级的线程一直在等待,后续系统会保证低优先级的线程也能获取更多的CPU时间。显然使用这种调度策略的话,优先级高的线程并不一定能在争取time slice
上有绝对的优势,所以Android系统在线程调度上使用了cgroups
的概念,cgroups
能更好的凸显某些线程的重要性,使得优先级更高的线程明确的获取到更多的time slice
。
Android将线程分为多个group
,其中两类group
尤其重要。一类是default group
,UI线程属于这一类。另一类是background group
,工作线程应该归属到这一类。background group
当中所有的线程加起来总共也只能分配到5~10%的time slice
,剩下的全部分配给default group
,这样设计显然能保证UI线程绘制UI的流畅性。
有不少人吐槽Android系统之所以不如iOS流畅,是因为UI线程的优先级和普通工作线程一致导致的。这其实是个误会,Android的设计者实际上提供了background group
的概念来降低工作线程的CPU资源消耗,只不过与iOS不同的是,Android开发者需要显式的将工作线程归于background group
。
new Thread(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); }}).start();所以在我们决定新启一个线程执行任务的时候,首先要问自己这个任务在完成时间上是否重要到要和UI线程争夺CPU资源。如果不是,降低线程优先级将其归于
background group
,如果是,则需要进一步的profile看这个线程是否造成UI线程的卡顿。thread
的优先级也可以改变其所属的control groups
,从而影响CPU time slice
的分配。但进程的属性变化也会影响到线程的调度,当一个App进入后台的时候,该App所属的整个进程都将进入background group
,以确保处于foreground
,用户可见的新进程能获取到尽可能多的CPU资源。用adb可以查看不同进程的当前调度策略。$ adb shell ps -P当你的App重新被用户切换到前台的时候,进程当中所属的线程又会回归的原来的
group
。在这些用户频繁切换的过程当中,thread
的优先级并不会发生变化,但系统在time slice
的分配上却在不停的调整。switch context
也会带来额外的开销。如果随意开启新线程,随着业务的膨胀,很容易在App运行的某个时间点发现几十个线程同时在运行。后果是原本想解决UI流畅性,却反而导致了偶现的不可控的卡顿。new Thread()
new Thread(new Runnable() {@Overridepublic void run() { }}).start();这种方式仅仅是起动了一个新的线程,没有任务的概念,不能做状态的管理。start之后,run当中的代码就一定会执行到底,无法中途取消。
Default
,归于default cgroup
,会平等的和UI线程争夺CPU资源。这一点尤其需要注意,在对UI性能要求高的场景下要记得Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);虽说处于
background group
的线程总共只能争取到5~10%的CPU资源,但这对绝大部分的后台任务处理都绰绰有余了,1ms和10ms对用户来说,都是快到无法感知,所以我们一般都偏向于在background group
当中执行工作线程任务。public class MyAsyncTask extends AsyncTask {@Override protected Object doInBackground(Object[] params) {return null; }@Override protected void onPreExecute() { super.onPreExecute(); }@Override protected void onPostExecute(Object o) { super.onPostExecute(o); }}和使用
Thread()
不同的是,多了几处API回调来严格规范工作线程与UI线程之间的交互。我们大部分的业务场景几乎都符合这种规范,比如去磁盘读取图片,缩放处理需要在工作线程执行,最后绘制到ImageView
控件需要切换到UI线程。Thread()
方式更为灵活。值得注意的是AsyncTask的cancel()
方法并不会终止任务的执行,开发者需要自己去检查cancel
的状态值来决定是否中止任务。background
,对UI线程的执行影响极小。Thread()
和AsyncTask
都会显得力不从心。HandlerThread却能胜任这些需求甚至更多。Handler
,Thread
,Looper
,MessageQueue
几个概念相结合。Handler是线程对外的接口,所有新的message
或者runnable
都通过handler post
到工作线程。Looper
在MessageQueue
取到新的任务就切换到工作线程去执行。不同的post
方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue
概念,可以进行多任务队列管理。MessageQueue
。这一点和Thread(),AsyncTask
都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。HandlerThread
较之Thread(),AsyncTask
需要写更多的代码,但在实用性,灵活度,安全性上都有更好的表现。Thread(),AsyncTask
适合处理单个任务的场景,HandlerThread适合串行处理多任务的场景。当需要并行的处理多任务之时,ThreadPoolExecutor是更好的选择。public static Executor THREAD_POOL_EXECUTOR= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);线程池可以避免线程的频繁创建和销毁,显然性能更好,但线程池并发的特性往往也是疑难杂症的源头,是代码降级和失控的开始。多线程并行导致的bug往往是偶现的,不方便调试,一旦出现就会耗掉大量的开发精力。
IntentService
又是另一种开工作线程的方式,从名字就可以看出这个工作线程会带有service
的属性。和AsyncTask
不同,没有和UI线程的交互,也不像HandlerThread的工作线程会一直存活。IntentService背后其实也有一个HandlerThread来串行的处理Message Queue
,从IntentService的onCreate
方法可以看出:@Overridepublic void onCreate() {// TODO: It would be nice to have an option to hold a partial wakelock// during processing, and to have a static startService(Context, Intent)// method that would launch the service & hand off a wakelock. super.onCreate();HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");thread.start(); mServiceLooper = thread.getLooper();mServiceHandler = new ServiceHandler(mServiceLooper);}只不过在所有的
Message
处理完毕之后,工作线程会自动结束。所以可以把IntentService
看做是Service
和HandlerThread
的结合体,适合需要在工作线程处理UI无关任务的场景。