1 生命周期
service:是一个后台服务,专门用来处理常驻后台的工作的组件。为了决定哪些进程留下,哪些进程被杀死,系统根据在进程中在运行的组件及组件的状态,为每一个进程分配了一个优先级等级,优先级最低的进程首先被杀死。
Android会尽可能高的估量一个进程的级别。比如,一个进程拥有一个可见状态的activity和一个service,这个进程会被认为是可见进程,而不是服务进程。
一个进程的级别可能会由于其它进程依赖于它而升高。一个为其它进程提供服务的进程级别永远高于使用它服务的进程。比如说,如果A进程中的内容提供者为进程B中的客户端提供服务,或进程A中的服务为进程B中的组件所绑定,则A进程高于或者等于进程B的等级。
2 进程的五个常用等级
2.1 前台进程:杀死前台进程需要用户交互,因为前台进程的优先级是最高的
(1)进程持有一个正在与用户交互的Activity。
(2)进程持有一个Service,这个Service处于这几种状态:
①Service与用户正在交互的Activity绑定;
②Service调用了startForeground(),在前台运行;
③Service执行在用户正在交互的Activity的生命周期回调函数中(onCreate(), onStart(), onDestroy())。
(3)进程持有一个BroadcastReceiver,这个BroadcastReceiver正在执行它的 onReceive() 方法。
2.2 可见进程
如果一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。可见的进程也是很重要的,一般不会被销毁,除非是为了保证所有前台进程的运行而不得不杀死可见进程。
当满足如下任一条件时,进程被认为是可见的:
(1)进程持有一个Activity,这个Activity不在前台,但是仍然被用户可见(处于onPause()调用后又没有调用onStop()的状态,比如:前台的activity打开了一个对话框,这样activity就会在其后可见);
(2)进程持有一个Service,这个Service和一个可见的(或者前台的)Activity绑定。
2.3 服务进程
一个进程中运行着一个service,这个service是通过startService()开启的,比如后台播放音乐,后台下载数据等。
2.4 后台进程
通常会有很多个后台进程存在,它们会被保存在一个LRU (least recently used)列表中,这样就可以确保用户最近使用的activity最后被销毁,即最先销毁时间最远的activity。
(1)**进程持有一个用户不可见的Activity(activity的onStop()被调用,当此Activity退出到桌面),就认为进程是一个后台进程。**后台进程不直接影响用户体验,系统会为了优先级高的进程而任意杀死后台进程。
2.5 空进程:Empty process
**一个进程当中已经没有数据在运行了,但是内存当中还为这个应用驻留了一个进程空间。保存这种进程的唯一理由是为了缓存的需要,为了加快下次要启动这个进程中的组件时的启动时间。**系统为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。
(1)一个进程不包含任何活跃的应用组件,则认为是空进程(android设计的,为了第二次启动更快,采取的一个权衡)
3 在Service中新开线程和在Activity直接开线程的区别
因为服务进程的优先级比后台进程的优先级高,所以对于一个需要启动一个长时间操作的activity来说,尤其是对于操作将超出activity的持续时间时,开启一个service比创建一个工作线程的方法更好。比如:要上传一个图片文件,应该开启一个service来进行上传工作,这样在用户离开activity时工作仍在进行。
(1)若直接在Activity中新开一条线程来做耗时操作,当该Activity退出到桌面或其他情况时将成为一个后台进程。
(2)若在Service中新启动线程,将会保证操作至少有服务进程的优先级。
4 保证Service不死常用技巧总结(要根据自己的需要来使用)
4.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>
4.2 startForeground()方法
在onStartCommand里面调用startForeground()方法把Service提升为前台进程级别,然后再onDestroy里面要记得调用stopForeground ()方法。
4.3 onStartCommand方法,返回START_STICKY
在onStartCommand方法中手动返回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(Intent,int,int)后,服务被异常kill掉,系统不会自动重启该服务;
(3)START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入;
(4)START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
4.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")) {
//在这里写重新启动service的相关操作
startUploadService(context);
}
}
}
4.5 监听(系统)广播判断Service状态
通过监听系统、QQ,微信,系统应用,友盟推送等的一些广播,比如:手机重启、界面唤醒、应用状态改变等监听并捕获到,然后判断我们的Service是否还存活,并唤醒自己的App。这是大厂常用方法,例如:阿里收购友盟后,通过友盟的广播唤醒阿里的App,然后把自己启动了。
<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);
}
}
4.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类。
4.7 类似监听锁屏方法
4.7.1 需求
QQ采取在锁屏的时候启动一个1个像素的Activity,当用户解锁以后将这个Activity结束掉(顺便同时把自己的核心服务再开启一次)。
故事:小米撕逼。
背景:当手机锁屏的时候什么都干死了,为了省电。
锁屏界面在上面盖住了。
监听锁屏广播,锁了---启动这个Activity。
监听锁屏的,开启---结束掉这个Activity。
要监听锁屏的广播---动态注册。
ScreenListener.begin(new xxxListener
onScreenOff()
);
被系统无法杀死的进程。
4.7.2 参考源码
4.8 双进程守护+JobScheduler调度
4.8.1 概念
双进程守护—可以防止单个进程杀死,同时可以防止第三方的360清理掉。
一个进程被杀死,另外一个进程又被他启动。相互监听启动。A<—>B,杀进程是一个一个杀的。本质是和杀进程时间赛跑。
JobScheduler源码分析:JobScheduler来执行一些需要满足特定条件但不紧急的后台任务,把任务加到系统调度队列中,当到达任务窗口期的时候就会执行,我们可以在这个任务里面启动我们的进程。这样可以做到将近杀不死的进程。
4.8.2 参考源码
4.8.3 结果截图
4.9 其他
(1)App运营商与手机厂商合作,列入白名单。
(2)利用账号同步机制唤醒我们的进程。
(3)NDK来解决,Native进程来实现双进程守护。