您的位置:首页 > 移动开发 > Android开发

Android线程的正确使用姿势

2017-04-06 21:53 246 查看

进程优先级(Process Priority)

进程的优先级从高到低依次分为五类,越往下,在内存紧张的时候越有可能被系统杀掉。简而言之,越是容易被用户感知到的进程,其优先级必定更高。



进程的五个常用等级:

一.前台进程(Foreground process)

  前台进程是用户当前做的事所必须的进程,如果满足下面各种情况中的一种,一个进程被认为是在前台:

进程持有一个正在与用户交互的Activity。

进程持有一个Service,这个Service处于这几种状态:①Service与用户正在交互的Activity绑定。②Service是在前台运行的,即它调用了 startForeground()。③Service正在执行它的生命周期回调函数(onCreate(), onStart(), or onDestroy())。

进程持有一个BroadcastReceiver,这个BroadcastReceiver正在执行它的 onReceive() 方法。

杀死前台进程需要用户交互,因为前台进程的优先级是最高的。

二.可见进程(Visible process)

  如果一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可见的:

进程持有一个Activity,这个Activity不在前台,但是仍然被用户可见(处于onPause()调用后又没有调用onStop()的状态,比如,前台的activity打开了一个对话框,这样activity就会在其后可见)。

进程持有一个Service,这个Service和一个可见的(或者前台的)Activity绑定。

可见的进程也被认为是很重要的,一般不会被销毁,除非是为了保证所有前台进程的运行而不得不杀死可见进程的时候。

三.服务进程 (Service process)

  如果一个进程中运行着一个service,这个service是通过 startService() 开启的,并且不属于上面两种较高优先级的情况,这个进程就是一个服务进程。

尽管服务进程没有和用户可以看到的东西绑定,但是它们一般在做的事情是用户关心的,比如后台播放音乐,后台下载数据等。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可见进程的运行需要。

四.后台进程 (Background process)

  如果进程不属于上面三种情况,但是进程持有一个用户不可见的activity(activity的onStop()被调用,但是onDestroy()没有调用的状态),就认为进程是一个后台进程。

后台进程不直接影响用户体验,系统会为了前台进程、可见进程、服务进程而任意杀死后台进程。

通常会有很多个后台进程存在,它们会被保存在一个LRU (least recently used)列表中,这样就可以确保用户最近使用的activity最后被销毁,即最先销毁时间最远的activity。

五.空进程

如果一个进程不包含任何活跃的应用组件,则认为是空进程。

例如:一个进程当中已经没有数据在运行了,但是内存当中还为这个应用驻留了一个进程空间。

保存这种进程的唯一理由是为了缓存的需要,为了加快下次要启动这个进程中的组件时的启动时间。系统为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。

Service中新开线程和直接新开线程的区别

(1)若直接在Activity中新开一条线程来做耗时操作,当该Activity退出到桌面或其他情况时将成为一个后台进程。

(2)若在Service中新启动线程,则此时Android会依据进程中当前活跃组件重要程度,将其判断为服务进程,优先级比(1)高。

因为服务进程的优先级比后台进程的优先级高,所以对于一个需要启动一个长时间操作的activity来说,开启一个service比创建一个工作线程的方法更好,尤其是对于操作将很可能超出activity的持续时间时。

比如要上传一个图片文件,应该开启一个service来进行上传工作,这样在用户离开activity时工作仍在进行。使用service将会保证操作至少有服务进程的优先级。

尽可能保证Service不死常用技巧总结

1、在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时实用于广播。

<service
android:name="com.dbjtech.acbxt.waiqin.UploadService"
android:enabled="true" >
<intent-filter android:priority="1000" >
<action android:name="com.dbjtech.myservice" />
</intent-filter>
</service>


2、在onStartCommand里面调用 startForeground()方法把Service提升为前台进程级别,然后再onDestroy里面要记得调用stopForeground ()方法。

3、onStartCommand方法,返回START_STICKY。

手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了….

public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}


补充说明:onStartCommand()方法,返回的是一个int整形。

这个整形可以有以下四个取值:

1):START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。

2):START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务

3):START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。

4):START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

4、在onDestroy方法里发广播重启service。

service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service。(第三方应用或是在setting里-应用-强制停止时,APP进程就直接被干掉了,onDestroy方法都进不来,所以无法保证会执行)

<receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="com.dbjtech.waiqin.destroy" />//这个就是自定义的action
</intent-filter>
</receiver>


在onDestroy时:

@Override
public void onDestroy() {
stopForeground(true);
Intent intent = new Intent("com.dbjtech.waiqin.destroy");
sendBroadcast(intent);
super.onDestroy();
}


在BootReceiver里:

public class BootReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.dbjtech.waiqin.destroy")) {
//TODO
//在这里写重新启动service的相关操作
startUploadService(context);
}
}
}


5、监听系统广播判断Service状态。

通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活。

<receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<action android:name="com.dbjtech.waiqin.destroy" />
</intent-filter>
</receiver>


BroadcastReceiver中:

@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
System.out.println("手机开机了....");
startUploadService(context);
}
if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
startUploadService(context);
}
}


6、Application加上Persistent属性。

看Android的文档知道,当进程长期不活动,或系统需要资源时,会自动清理门户,杀死一些Service,和不可见的Activity等所在的进程。但是如果某个进程不想被杀死(如数据缓存进程,或状态监控进程,或远程服务进程),应该怎么做,才能使进程不被杀死。

add android:persistent=”true” into the section in your AndroidManifest.xml

加上以上属性相当于将该进程设置为常驻内存进程。

切记,这个不可滥用,一般只适用于放在/system/app下的app,系统中用这个的service,app一多,整个系统可能就会崩溃。

比如系统phone中配置了android:persistent=”true”属性,并且Phone.apk是安装在/system/app/目录下的,所以在开机时会自动启动PhoneApp类。

用什么姿势开线程?

new Thread()

这是Android系统里开线程最简单的方式,也只能应用于最简单的场景,简单的好处却伴随不少的隐患。

new Thread(new Runnable() {
@Override
public void run() {

}
}).start();


这种方式仅仅是起动了一个新的线程,没有任务的概念,不能做状态的管理。start之后,run当中的代码就一定会执行到底,无法中途取消。

Runnable作为匿名内部类还持有了外部类的引用,在线程退出之前,该引用会一直存在,阻碍外部类对象被GC回收,在一段时间内造成内存泄漏。

没有线程切换的接口,要传递处理结果到UI线程的话,需要写额外的线程切换代码。

如果从UI线程启动,则该线程优先级默认为Default,归于default cgroup,会平等的和UI线程争夺CPU资源。这一点尤其需要注意,在对UI性能要求高的场景下要记得

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);


虽说处于background group的线程总共只能争取到5~10%的CPU资源,但这对绝大部分的后台任务处理都绰绰有余了,1ms和10ms对用户来说,都是快到无法感知,所以我们一般都偏向于在background group当中执行工作线程任务。

AsyncTask

一个典型的AsyncTask实现如下:

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线程。

AsyncTask的几处回调都给了我们机会去中断任务,在任务状态的管理上较之Thread()方式更为灵活。值得注意的是AsyncTask的cancel()方法并不会终止任务的执行,开发者需要自己去检查cancel的状态值来决定是否中止任务。

AsyncTask也有隐式的持有外部类对象引用的问题,需要特别注意防止出现意外的内存泄漏。

AsyncTask由于在不同的系统版本上串行与并行的执行行为不一致,被不少开发者所诟病,这确实是硬伤,绝大部分的多线程场景都需要明确任务是串行还是并行。

线程优先级为background,对UI线程的执行影响极小。

HandlerThread

在需要对多任务做更精细控制,线程切换更频繁的场景之下,Thread()和AsyncTask都会显得力不从心。HandlerThread却能胜任这些需求甚至更多。

HandlerThread将Handler,Thread,Looper,MessageQueue几个概念相结合。Handler是线程对外的接口,所有新的message或者runnable都通过handler post到工作线程。Looper在MessageQueue取到新的任务就切换到工作线程去执行。不同的post方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。

HandlerThread背后只有一个线程,所以任务是串行执行的。串行相对于并行来说更安全,各任务之间不会存在多线程安全问题。

HandlerThread所产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue。这一点和Thread(),AsyncTask都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。

HandlerThread较之Thread(),AsyncTask需要写更多的代码,但在实用性,灵活度,安全性上都有更好的表现。

ThreadPoolExecutor

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往往是偶现的,不方便调试,一旦出现就会耗掉大量的开发精力。

ThreadPool较之HandlerThread在处理多任务上有更高的灵活性,但也带来了更大的复杂度和不确定性。

IntentService

不得不说Android在API设计上粒度很细,同一样工作可以通过各种不同的类来完成。IntentService又是另一种开工作线程的方式,从名字就可以看出这个工作线程会带有service的属性。和AsyncTask不同,没有和UI线程的交互,也不像HandlerThread的工作线程会一直存活。IntentService背后其实也有一个HandlerThread来串行的处理Message Queue,从IntentService的onCreate方法可以看出:

@Override
public 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无关任务的场景。

结束语

Android开线程的方式虽然五花八门,但归根到底最后还是映射到linux下的pthread,业务的设计还是脱不了和线程相关的基础概念范畴:线程的执行顺序,调度策略,生命周期,串行还是并行,同步还是异步等等。摸清楚各类API下线程的行为特点,在设计具体业务的线程模型的时候自然轻车熟路了,线程模型的设计要有整个app视角的广度,切忌各业务模块各玩各的。

使用Service还是Thread

Service是Android的四大组件之一,被用来执行长时间的后台任务,同样,线程也可以实现在后台执行任务,它们的区别在哪呢?何时使用Service何时使用Thread呢?今天我也来说说我的理解和总结。

首先,需要了解Service的几个特点。

(1) 默认情况下,Service其实是运行在主线程中的,如果需要执行复杂耗时的操作,必须在Service中再创建一个Thread来执行任务。

(2) Service的优先级高于后台挂起的Activity,当然,也高于Activity所创建的Thread,因此,系统可能在内存不足的时候优先杀死后台的Activity或者Thread,而不会轻易杀死Service组件,即使被迫杀死Service,也会在资源可用时重启被杀死的Service

其实,Service和Thread根本就不是一个级别的东西,Service是系统的四大组件之一,Thread只是一个用来执行后台任务的工具类,它可以在Activity中被创建,也可以在Service中被创建。因此,我们其实不应该讨论该使用Service还是Thread,而是应该讨论在什么地方创建Thread。

典型的应用中,它可以在以下三个位置被创建,不同的位置,其生命周期不一样,所以,我们应该根据该Thread的目标生命周期来决定是在Service中创建Thread还是在Activity中创建它。

(1) 在Activity中被创建

这种情况下,一般在onCreate时创建,在onDestroy()中销毁,否则,Activity销毁后,Thread是会依然在后台运行着。

这种情况下,Thread的生命周期即为整个Activity的生命周期。所以,在Activity中创建的Thread只适合完成一些依赖Activity本身有关的任务,比如定时更新一下Activity的控件状态等。

核心特点:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也没有存活的意义了。

(2)在Application中被创建

这种情况下,一般自定义Application类,重载onCreate方法,并在其中创建Thread,当然,也会在onTerminate()方法中销毁Thread,否则,如果Thread没有退出的话,即使整个Application退出了,线程依然会在后台运行着。

这种情况下,Thread的生命周期即为整个Application的生命周期。所以,在Application中创建的Thread,可以执行一些整个应用级别的任务,比如定时检查一下网络连接状态等等。

核心特点:该Thread的终极目标是为这个APP的各个Activity服务的,包括完成某个Activity交代的任务,主动通知某个Activity一些消息和事件等,APP退出之后该Thread也没有存活的意义了。

以上这两种情况下,Thread的生命周期都不应该超出整个应用程序的生命周期,也就是,整个APP退出之后,Thread都应该完全退出,这样才不会出现内存泄漏或者僵尸线程。那么,如果你希望整个APP都退出之后依然能运行该Thread,那么就应该把Thread放到Service中去创建和启动了。

(3)在Service中被创建

这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service的onCreate()中创建,在onDestroy()中销毁。

所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长连接。

核心特点:该Thread可以为APP提供一些“服务”或者“状态查询”,但该Thread并不需要主动通知APP任何事件,甚至不需要知道APP是谁。

总之,我们不是要考虑该用Thread或者该用Service,而是应该为Thread选择合适的生命周期

Android性能优化典范之多线程篇

为主线程减轻负的多线程方案有哪些呢?这些方案分别适合在什么场景下使用?Android 系统为我们提供了若干组工具类来帮助解决这个问题。

AsyncTask: 为 UI 线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。

HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。

ThreadPool: 把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。

IntentService: 适合于执行由 UI 触发的后台 Service 任务,并可以把后台任务执行的情况通过一定的机制反馈给 UI。

了解这些系统提供的多线程工具类分别适合在什么场景下,可以帮助我们选择合适的解决方案,避免出现不可预期的麻烦。虽然使用多线程可以提高程序的并发量,但是我们需要特别注意因为引入多线程而可能伴随而来的内存问题。举个例子,在 Activity 内部定义的一个 AsyncTask,它属于一个内部类,该类本身和外面的 Activity 是有引用关系的,如果 Activity 要销毁的时候,AsyncTask 还仍然在运行,这会导致 Activity 没有办法完全释放,从而引发内存泄漏。所以说,多线程是提升程序性能的有效手段之一,但是使用多线程却需要十分谨慎小心,如果不了解背后的执行机制以及使用的注意事项,很可能引起严重的问题。

当程序被启动,系统会帮忙创建进程以及相应的主线程,而这个主线程其实就是一个 HandlerThread。这个主线程会需要处理系统事件,输入事件,系统回调的任务,UI绘制等等任务,为了避免主线程任务过重,我们就会需要不断的开启新的工作线程来处理那些子任务。

Memory & Threading

增加并发的线程数会导致内存消耗的增加,平衡好这两者的关系是非常重要的。我们知道,多线程并发访问同一块内存区域有可能带来很多问题,例如读写的权限争夺问题,ABA 问题等等。为了解决这些问题,我们会需要引入锁的概念。

在 Android 系统中也无法避免因为多线程的引入而导致出现诸如上文提到的种种问题。Android UI 对象的创建,更新,销毁等等操作都默认是执行在主线程,但是如果我们在非主线程对UI对象进行操作,程序将可能出现异常甚至是崩溃。



另外,在非 UI 线程中直接持有 UI 对象的引用也很可能出现问题。例如Work线程中持有某个 UI 对象的引用,在 Work 线程执行完毕之前,UI 对象在主线程中被从 ViewHierarchy 中移除了,这个时候 UI 对象的任何属性都已经不再可用了,另外对这个 UI 对象的更新操作也都没有任何意义了,因为它已经从 ViewHierarchy 中被移除,不再绘制到画面上了。



不仅如此,View 对象本身对所属的 Activity 是有引用关系的,如果工作线程持续保有 View 的引用,这就可能导致 Activity 无法完全释放。除了直接显式的引用关系可能导致内存泄露之外,我们还需要特别留意隐式的引用关系也可能导致泄露。例如通常我们会看到在 Activity 里面定义的一个 AsyncTask,这种类型的 AsyncTask 与外部的 Activity 是存在隐式引用关系的,只要 Task 没有结束,引用关系就会一直存在,这很容易导致 Activity 的泄漏。更糟糕的情况是,它不仅仅发生了内存泄漏,还可能导致程序异常或者崩溃。



为了解决上面的问题,我们需要谨记的原则就是:不要在任何非 UI 线程里面去持有 UI 对象的引用。系统为了确保所有的 UI 对象都只会被 UI 线程所进行创建,更新,销毁的操作,特地设计了对应的工作机制(当 Activity 被销毁的时候,由该 Activity 所触发的非 UI 线程都将无法对UI对象进行操作,否者就会抛出程序执行异常的错误)来防止 UI 对象被错误的使用。

使用 AsyncTask 需要注意的问题有哪些呢?

请关注以下几点:

首先,默认情况下,所有的 AsyncTask 任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。假设你按照顺序启动20个 AsyncTask,一旦其中的某个 AsyncTask 执行时间过长,队列中的其他剩余 AsyncTask 都处于阻塞状态,必须等到该任务执行完毕之后才能够有机会执行下一个任务。情况如下图所示:



为了解决上面提到的线性队列等待的问题,我们可以使用 AsyncTask.executeOnExecutor()强制指定 AsyncTask 使用线程池并发调度任务。



其次,如何才能够真正的取消一个 AsyncTask 的执行呢?我们知道 AsyncTaks 有提供 cancel()的方法,但是这个方法实际上做了什么事情呢?线程本身并不具备中止正在执行的代码的能力,为了能够让一个线程更早的被销毁,我们需要在 doInBackground()的代码中不断的添加程序是否被中止的判断逻辑,如下图所示:



一旦任务被成功中止,AsyncTask 就不会继续调用 onPostExecute(),而是通过调用 onCancelled()的回调方法反馈任务执行取消的结果。我们可以根据任务回调到哪个方法(是 onPostExecute 还是 onCancelled)来决定是对 UI 进行正常的更新还是把对应的任务所占用的内存进行销毁等。

最后,使用 AsyncTask 很容易导致内存泄漏,一旦把 AsyncTask 写成 Activity 的内部类的形式就很容易因为 AsyncTask 生命周期的不确定而导致 Activity 发生泄漏



综上所述,AsyncTask 虽然提供了一种简单便捷的异步机制,但是我们还是很有必要特别关注到他的缺点,避免出现因为使用错误而导致的严重系统性能问题。

HandlerThread

HandlerThread 比较合适处理那些在工作线程执行,需要花费时间偏长的任务。我们只需要把任务发送给 HandlerThread,然后就只需要等待任务执行结束的时候通知返回到主线程就好了。

另外很重要的一点是,一旦我们使用了 HandlerThread,需要特别注意给 HandlerThread 设置不同的线程优先级,CPU 会根据设置的不同线程优先级对所有的线程进行调度优化。

Swimming in Threadpools

线程池适合用在把任务进行分解,并发进行执行的场景。通常来说,系统里面会针对不同的任务设置一个单独的守护线程用来专门处理这项任务。例如使用 Networking Thread 用来专门处理网络请求的操作,使用 IO Thread 用来专门处理系统的 I\O 操作。针对那些场景,这样设计是没有问题的,因为对应的任务单次执行的时间并不长而且可以是顺序执行的。但是这种专属的单线程并不能满足所有的情况,例如我们需要一次性 decode 40张图片,每个线程需要执行 4ms 的时间,如果我们使用专属单线程的方案,所有图片执行完毕会需要花费 160ms(40*4),但是如果我们创建10个线程,每个线程执行4个任务,那么我们就只需要16ms就能够把所有的图片处理完毕。

使用线程池需要特别注意同时并发线程数量的控制,理论上来说,我们可以设置任意你想要的并发数量,但是这样做非常的不好。因为 CPU 只能同时执行固定数量的线程数,一旦同时并发的线程数量超过 CPU 能够同时执行的阈值,CPU 就需要花费精力来判断到底哪些线程的优先级比较高,需要在不同的线程之间进行调度切换。

一旦同时并发的线程数量达到一定的量级,这个时候 CPU 在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降。另外需要关注的一点是,每开一个新的线程,都会耗费至少 64K+ 的内存。为了能够方便的对线程数量进行控制,ThreadPoolExecutor 为我们提供了初始化的并发线程数量,以及最大的并发数量进行设置。



另外需要关注的一个问题是:Runtime.getRuntime().availableProcesser()方法并不可靠,他返回的值并不是真实的 CPU 核心数,因为 CPU 会在某些情况下选择对部分核心进行睡眠处理,在这种情况下,返回的数量就只能是激活的 CPU 核心数。

IntentService

默认的 Service 是执行在主线程的,可是通常情况下,这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的 AsyncTask 与 HandlerThread,我们还可以选择使用 IntentService 来实现异步操作。IntentService 继承自普通 Service 同时又在内部创建了一个 HandlerThread,在 onHandlerIntent()的回调里面处理扔到 IntentService 的任务。所以 IntentService 就不仅仅具备了异步线程的特性,还同时保留了 Service 不受主页面生命周期影响的特点。

如此一来,我们可以在 IntentService 里面通过设置闹钟间隔性的触发异步任务,例如刷新数据,更新缓存的图片或者是分析用户操作行为等等,当然处理这些任务需要小心谨慎。

使用 IntentService 需要特别留意以下几点:

首先,因为 IntentService 内置的是 HandlerThread 作为异步线程,所以每一个交给 IntentService 的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

其次,通常使用到 IntentService 的时候,我们会结合使用 BroadcastReceiver 把工作线程的任务执行结果返回给主 UI 线程。使用广播容易引起性能问题,我们可以使用 LocalBroadcastManager 来发送只在程序内部传递的广播,从而提升广播的性能。

我们也可以使用 runOnUiThread() 快速回调到主 UI 线程。

最后,包含正在运行的 IntentService 的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。

Threading and Loaders

当启动工作线程的 Activity 被销毁的时候,我们应该做点什么呢?为了方便的控制工作线程的启动与结束,Android 为我们引入了 Loader 来解决这个问题。我们知道 Activity 有可能因为用户的主动切换而频繁的被创建与销毁,也有可能是因为类似屏幕发生旋转等被动原因而销毁再重建。在 Activity 不停的创建与销毁的过程当中,很有可能因为工作线程持有 Activity 的 View 而导致内存泄漏(因为工作线程很可能持有 View 的强引用,另外工作线程的生命周期还无法保证和 Activity 的生命周期一致,这样就容易发生内存泄漏了)。除了可能引起内存泄漏之外,在 Activity 被销毁之后,工作线程还继续更新视图是没有意义的,因为此时视图已经不在界面上显示了

Loader 的出现就是为了确保工作线程能够和 Activity 的生命周期保持一致,同时避免出现前面提到的问题。

LoaderManager 会对查询的操作进行缓存,只要对应 Cursor 上的数据源没有发生变化,在配置信息发生改变的时候(例如屏幕的旋转),Loader 可以直接把缓存的数据回调到 onLoadFinished(),从而避免重新查询数据。另外系统会在 Loader 不再需要使用到的时候(例如使用 Back 按钮退出当前页面)回调 onLoaderReset()方法,我们可以在这里做数据的清除等等操作。

在 Activity 或者 Fragment 中使用 Loader 可以方便的实现异步加载的框架,Loader 有诸多优点。但是实现 Loader 的这套代码还是稍微有点点复杂,Android 官方为我们提供了使用 Loader 的示例代码进行参考学习。

The Importance of Thread Priority

理论上来说,我们的程序可以创建出非常多的子线程一起并发执行的,可是基于 CPU 时间片轮转调度的机制,不可能所有的线程都可以同时被调度执行,CPU 需要根据线程的优先级赋予不同的时间片。

Android 系统会根据当前运行的可见的程序和不可见的后台程序对线程进行归类,划分为 forground 的那部分线程会大致占用掉 CPU 的90%左右的时间片,background 的那部分线程就总共只能分享到5%-10%左右的时间片。之所以设计成这样是因为 forground 的程序本身的优先级就更高,理应得到更多的执行时间。

默认情况下,新创建的线程的优先级默认和创建它的母线程保持一致。如果主 UI 线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占 CPU 资源,需要把这些线程的优先级进行降低处理,这样才能给帮组 CPU 识别主次,提高主线程所能得到的系统资源。

参考

Android 线程的正确使用姿势

Android开发实践:使用Service还是 Thread

Android_Service多线程断点下载

生还是死?Android 进程优先级详解

【Bugly干货】Android性能优化典范之多线程篇

Android基础总结——进程优先级及提高优先级的方法(Service尽量不死之法)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android