由于内存限制,android系统会在内存不足时回收进程,
一、进程回收顺序
回收优先级:前台进程<可视进程<服务进程<后台进程<内容供应根节点<空进程
oom_adj越大 越可能被回收
系统进程 <0 前台进程0 可见进程1
进程级别参考:https://juejin.im/entry/58acf391ac502e007e9a0a11
1、Foreground process 前台进程
下面几种情况属于前台进程:
(1)Activity正在与用户进程交互(Activity的onResume已经被调用)
(2)与正在和用户交互的Activity绑定的Service
(3)Service运行在前台——Service中调用了startForeground函数
(4)Service正在执行生命周期回调函数(onCreate,onStart,onDestory)
(5)BroadcastReceiver正在执行onReceive方法
2、Visible process 可视进程
下面几种情况属于可视进程:
(1)Activity没有运行在前台,但是用户仍然可见(它的onPause方法被调用),例如:当前台Activity启动了一个Dialog,这样Dialog运行在前台,Activity仍然可见,属于可视进程。
(2)与一个可视的Activity绑定的服务所在的进程
3、Service process 服务进程
运行服务的进程被startService()启动,并且没有进入上面1中(3)、(4)这两种情况。例如,音乐播放、网络下载数据
4、Background process 后台进程
当Activity不可见的时候,它的进程属于后台进程(Activity的onStop方法被调用)
5、Empty process 空进程
没有包含活动应用组件的进程为空进程,也就是进程的应用组件已经运行完毕。
查看某个包的进程
Terminal 或者 cmd 进入 adb shell 模式
ps|grep 包名
第一个参数:u0_a124 当前用户
第二个参数:进程ID
第三个参数: 进程的父进程ID
第四个参数:进程的虚拟内存大小
第五个参数:实际内存大小
最后一个参数:进程名
查看某个进程的进程优先级(oom_adj)
cat /proc/进程id/oom_adj
(注意 cat后要加空格)
permission denied
要root
检查Service是否开启在前台
1 用 cat /proc/进程名/包名
查看开启前后的进程优先级变化 优先级降低了并且为0即为前台进程
2 或者
dumpsys activity services PackageName
查看 services 找到相应的services名
发现 isForeground=true 即为前台进程
一、白色手段
开启前台Service,会在通知栏显示
通过notification方式 如音乐播放
如果希望从前台移除这个服务,只需要调用stopForeground(),一般情况我们只需要在onStartCommand里面调用 startForeground,然后再onDestroy里面调用stopForeground即可。
public class WhiteService extends Service {
private static final String TAG = WhiteService.class.getSimpleName();
private static final int NOTIFICATION_FLAG =0X11;
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 在Android进行通知处理,首先需要重系统哪里获得通知管理器NotificationManager,它是一个系统Service。
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 设置点击通知跳转的Intent
Intent nfIntent = new Intent(this, MainActivity.class);
// 设置 延迟Intent
// 最后一个参数可以为PendingIntent.FLAG_CANCEL_CURRENT 或者 PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, nfIntent, 0);
//构建一个Notification构造器
Notification.Builder builder = new Notification.Builder(this.getApplicationContext());
builder.setContentIntent(pendingIntent) // 设置点击跳转界面
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
R.mipmap.fight_total_money2x)) // 设置下拉列表中的图标(大图标)
.setTicker("您有一个notification")// statusBar上的提示
.setContentTitle("这是标题") // 设置下拉列表里的标题
.setSmallIcon(R.mipmap.fight_total_order2x) // 设置状态栏内的小图标24X24
.setContentText("这是内容") // 设置详细内容
.setContentIntent(pendingIntent) // 设置点击跳转的界面
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
.setDefaults(Notification.DEFAULT_VIBRATE) //默认震动方式
.setPriority(Notification.PRIORITY_HIGH) //优先级高
Notification notification = builder.build(); // 获取构建好的Notification
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
notification.flags |= Notification.FLAG_AUTO_CANCEL; // FLAG_AUTO_CANCEL表明当通知被用户点击时,通知将被清除。
notification.flags |= FLAG_ONGOING_EVENT; //将此通知放到通知栏的"Ongoing"即"正在运行"组中
notification.flags |= FLAG_NO_CLEAR; //表明在点击了通知栏中的"清除通知"后,此通知不清除,常与FLAG_ONGOING_EVENT一起使用
manager.notify(NOTIFICATION_FLAG, notification);
// 启动前台服务
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(NOTIFICATION_FLAG, notification);// 开始前台服务
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
// 停止前台服务--参数:表示是否移除之前的通知
stopForeground(true);
Log.d(TAG, "onDestroy");
}
}
开启服务:
Intent intent = new Intent(MainActivity.this,WhiteService.class);
startService(intent);// (服务与开启者无联系的启动形式 )
结束服务:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopService(intent);
}
});
更新通知:
重新创建notification.builder和notification
notificationManager调用 notify方法
其实就是再创建一个notification 只是标志和之前的那个一样 这样就会更新前面的通知了
二、灰色保活
也是开启前台Service,但是不会在通知栏显示
adb shell 模式下:
dumpsys activity services PackageName
若Service有 isForeground=true 而通知栏却没有显示 则是灰色保活方式
方式:
API < 18,启动前台Service时直接传入空的 new Notification();
API >= 18,在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除
public class GrayService extends Service {
private static final String TAG = GrayService.class.getSimpleName();
private final static int GRAY_SERVICE_ID = 0x12;
public GrayService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
//API 18以下,直接发送Notification并将其置为前台
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
startForeground(GRAY_SERVICE_ID, new Notification());
} else {
//API 18以上,发送Notification并将其置为前台后,启动InnerService
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.fight_total_money2x);
Notification notification = builder.build(); // 获取构建好的Notification
startForeground(GRAY_SERVICE_ID, notification);
startService(new Intent(this, GrayInnerService.class));
}
Log.d(TAG, "GrayServiceOnCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
// 停止前台服务--参数:表示是否移除之前的通知
stopForeground(true);
Log.d(TAG, "onDestroy");
}
}
public class GrayInnerService extends Service {
private static final String TAG = GrayInnerService.class.getSimpleName();
public static final int GRAY_INNER_SERVICE_ID=0x12;
public GrayInnerService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
//发送与GrayService中ID相同的Notification,然后将其取消并取消自己的前台显示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.fight_total_order2x);
Notification notification = builder.build(); // 获取构建好的Notification
startForeground(GRAY_INNER_SERVICE_ID,notification);
// 延迟0.1s 终止掉innerService 这样 通知栏图标会清除
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null) {
manager.cancel(GRAY_INNER_SERVICE_ID);
}else {
Log.e(TAG, "notification is null!");
}
stopSelf();
}
},100);
Log.d(TAG, "GrayInnerServiceOnCreate");
}
}
三、黑色手段
利用不同的app进程使用广播来进行相互唤醒
(1)开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app
最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍视频),CONNECTIVITY_ACTION(网络切换)等三种广播
(2)接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝
(3)app互相唤醒
四、白名单方式
系统给app加入白名单,系统不会杀死白名单中的app
Android8.0以上 电池优化策略 应用在后台经过 idle timeout后 会禁用后台服务 stopService 并禁止启动服务
解决:
1 设置为前台服务
2 用JobIntentService 参考:https://blog.csdn.net/weixin_37577039/article/details/78495357
3 用JobService+JobScheduler
1 可以推迟的非面向用户的任务(如定期数据库数据更新)
2 当充电时才希望执行的工作(如备份数据)
3 需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取内置数据)
4 希望作为一个批次定期运行的许多任务
开启时间能保证实时吗 好像有延迟
能挂多久后台
doze mode(息屏了低电耗模式) 系统不允许运行 JobScheduler
1 创建JobService
class XXXJobService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
mXX = params.extras.getString(XX_KEY)
开启线程执行任务
}
override fun onStopJob(params: JobParameters): Boolean {
Logger.t(TAG).d("===onStop SSDPJob===")
return false
}
override fun onDestroy() {
super.onDestroy()
Logger.t(TAG).d("===onDestroy SSDPJob===")
}
2 通过JobScheduler开启JobService
if (mJobScheduler == null) {
mJobScheduler = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
}
val bundle = PersistableBundle()
bundle.putString(XXKEY,XXValue)
val builder: JobInfo.Builder = JobInfo.Builder(XX_JOB_ID,
ComponentName(applicationContext,XXXJobService::class.java))
builder.setMinimumLatency(latency) // 延迟lantency毫秒后开启任务
builder.setPersisted(false) // 是否设备重启扔执行
builder.setExtras(bundle) // 设置额外参数
mJobScheduler?.schedule(builder.build())
3 停止JobService
mJobScheduler?.cancel(SSDP_JOB_ID)
若在前台:
不包含service/broadcastReceiver,只有一个Activity,那么系统不会重新启动该应用
包含service/broadcastReceiver,只有一个Activity,那么系统会重新启动该应用(包括service/broadcastReceiver) 但不会重启该界面
不包含service/broadcastReceiver,但是当前栈中包含两个Activity, A–>B, 如果B crash,那么系统会重启该应用(application)也会进入A
不包含service/broadcastReceiver,但是当前栈中包含三个Activity, A–>B–>C, 如果C crash,那么系统会重启该应用(application) 进入B,并且A仍然存在,即可以从重启的Back到A
若在后台:
不会重启
但是若重新在前台了 是会和在前台的表现一样
sevice是可以根据START_STICK标志位判断是否crash了重新启动
静态广播
收到了广播才会创建 而且是收到一次创建一次 执行一次构造函数
若crash了 不管应用在前台还是后台 只要应用创建了(application oncreate) 都是会重启
动态广播
是不会自动重启的 除非进入到了你的启动逻辑 因为动态广播启动和关闭是自己设置的