如何查看某个进程的oom_adj数值呢?
oom_adj 存储在proc/PID/oom_adj文件中,其中PID是进程的id,直接 adb shell进入手机根目录查看这个文件即可。
演示一下:以我自己的项目为例,app中有两个进程,一个是主进程,另一个是运行service的进程取名为:remote。首先用android studio查看每个进程的PID:
【1】adb shell
【2】ps
【3】cat /proc/进程id/oom_adj
举例:cat /proc/20084/oom_adj
优化思路:
该方案适用场景: 本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间内会杀死后台进程.适用于android所有版本。1像素Activity的特点: 需要设置该activity的style设置透明,在手机锁屏时start;在屏幕解锁时finish,主要作用就是在App退到后台之后且锁屏的时候启动一个看不见的Activity,造成一个该App没有回退到后台的假象,降低被杀的几率,伪代码如下:
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
//设定一像素的activity
//设定一像素的activity
Window window = getWindow();
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.x = 0;
params.y = 0;
params.y = 0;
params.height = 1;
params.height = 1;
params.width = 1;
params.width = 1;
window.setAttributes(params);
window.setAttributes(params);
}
public void onReceive(final Context context, Intent intent) {
public void onReceive(final Context context, Intent intent) {
Log.i("ScreenStateReceiver", "---屏幕锁屏监听---");
Log.i("ScreenStateReceiver", "---屏幕锁屏监听---");
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
//屏幕锁定,开启OnePixelActivity
//屏幕锁定,开启OnePixelActivity
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
else if (action.equals(Intent.ACTION_SCREEN_ON)) {
//屏幕解锁,finish OnePixelActivity
//屏幕解锁,finish OnePixelActivity
}
}
说起来很简单,但是还是有几个知识点需要注意下。
关于这一点有一个细节需要处理,为了放置用户快速 进行锁屏和截屏的切换操作,而导致OnePixelActivity频繁开关。需要用一个Handler发送一个延迟消息处理最佳:
private Handler mHandler;
private Handler mHandler;
private boolean isScreenOn = true;
private boolean isScreenOn = true;
private PendingIntent pendingIntent;
private PendingIntent pendingIntent;
private List
private ListscreenStateListeners = null; screenStateListeners = null;
@Override
@Override
public void onReceive(final Context context, Intent intent) {
public void onReceive(final Context context, Intent intent) {
String action = intent.getAction();
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
//标记屏幕为锁屏状态
//标记屏幕为锁屏状态
isScreenOn = false;
isScreenOn = false;
//开启一像素的Activity
//开启一像素的Activity
startOnePixelActivity(context);
startOnePixelActivity(context);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
else if (action.equals(Intent.ACTION_SCREEN_ON)) {
//标记屏幕为解屏状态
//标记屏幕为解屏状态
isScreenOn = true;
isScreenOn = true;
if(pendingIntent!=null){
if(pendingIntent!=null){
pendingIntent.cancel();
pendingIntent.cancel();
}
}
}
//开启一像素的Activity
//开启一像素的Activity
private void startOnePixelActivity(final Context context){
private void startOnePixelActivity(final Context context){
if(mHandler==null){
if(mHandler==null){
mHandler = new Handler(Looper.myLooper());
mHandler = new Handler(Looper.myLooper());
}
mHandler.postDelayed(new Runnable() {
mHandler.postDelayed(new Runnable() {
@Override
@Override
public void run() {
public void run() {
//如果屏幕此时已经打开,则不执行
//如果屏幕此时已经打开,则不执行
if(isScreenOn){
if(isScreenOn){
return;
return;
}
if(pendingIntent!=null){
if(pendingIntent!=null){
pendingIntent.cancel();
pendingIntent.cancel();
}
Intent startOnePixelActivity = new Intent(context, OnePixelActivity.class);
Intent startOnePixelActivity = new Intent(context, OnePixelActivity.class);
startOnePixelActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startOnePixelActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//启动一像素包活activity
//启动一像素包活activity
pendingIntent = PendingIntent.getActivity(context, 0, startOnePixelActivity, 0);
pendingIntent = PendingIntent.getActivity(context, 0, startOnePixelActivity, 0);
try {
try {
pendingIntent.send();
pendingIntent.send();
} catch (Exception e) {
catch (Exception e) {
e.printStackTrace();
e.printStackTrace();
}
notifyScreenOff();
notifyScreenOff();
}
},1000);
1000);
}
android:theme="@style/onePixelActivity"
android:theme="@style/onePixelActivity"
android:launchMode="singleInstance"
android:launchMode="singleInstance"
android:excludeFromRecents="true"/>
android:excludeFromRecents="true"/>
为什么需要将此Activity的启动模式设置为singleInstance呢?原因是因为如果设置成其他模式,如果按照如下步骤操作的话会出现不友好的状况:
1、启动App(比如进入MainActivity),按home键让app返回后台
2、锁定屏幕,此时注册好的监听广播会启动OnePixelActivity
3、解锁屏幕,此时广播接受到此广播后会finish 掉OnePixelActivity.
因为OnePixelActivity是非singleInstance,所以此时本来已经进入后台的MainActivity的页面会自动打开。给用户造成干扰:我明明已经按下home键了,怎么此页面又自动打开了?而换成OnePixelActivity的启动模式改成singleInstance的话就可以很好的避免此问题。
另外需要注意的是,当屏幕解锁的时候,OnePixelActivity的onResume得到执行,所以在该Activity的onResume方法执行finish效果最好:
//OnePixelActivity的onResume
protected void onResume() {
protected void onResume() {
super.onResume();
super.onResume();
if (DeviceUtils.isScreenOn(this)) {//如果屏幕已经打开
if (DeviceUtils.isScreenOn(this)) {//如果屏幕已经打开
finish();
finish();
}
}
处理逻辑跟OneActivity一样,在App回到后台且锁屏的时候在Service里播放一个无声的mp3文件,当解锁的时候暂停改mp3的播放。
if(screenOn){//屏幕解锁
(screenOn){//屏幕解锁
puseMp3();
puseMp3();
}else{//锁屏
else{//锁屏
playMp3();
playMp3();
所以此是可以在程序启动的时候启用这个service,在在service里面注册锁屏广播,需要注意的一些地方(此Service姑且称之为KeepAliveService):
//播放无声音乐的Service
public class KeepAliveService extends Service {
class KeepAliveService extends Service {
private boolean isScreenON = true;//控制暂停
private boolean isScreenON = true;//控制暂停
private MediaPlayer mediaPlayer;
private MediaPlayer mediaPlayer;
//锁屏广播监听
//锁屏广播监听
private ScreenStateReceiver screenStateReceiver;
private ScreenStateReceiver screenStateReceiver;
@Override
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
public int onStartCommand(Intent intent, int flags, int startId) {
//注册锁屏广播
//注册锁屏广播
registerScreenStateReceiver();
registerScreenStateReceiver();
//初始化播放器
//初始化播放器
initMediaPlayer();
initMediaPlayer();
return START_STICKY;
return START_STICKY;
}
}
需要注意的是在播放器播放的时候,需要判断当前情况是否是解屏状态,而且在播放完成监听里面,继续调用start方法循环播放mp3,部分代码如下(本篇博文只贴部分关键代码,全部代码点此查看):
2、将HideNotifactionService调用startForeground将其设置为前台Service!
3、但是该Service调用startForeground方法的时候,第一个参数传的ID跟KeepAliveService调用时传同样的ID。
这样的话就确保有HideNotifactionService和KeepAliveService两个发送具有同样ID的Notification!
4、调用HideNotifactionService的stopSelf方法关闭自己,随着这个Service的关闭,通知栏的那条相同id的通知也就没了;但是同样确保了KeepAliveService的进程优先级得到了提高!
HideNotifactionService代码如下:
可以创建另一个Service,名为HideNotifactionService,该Service在KeepAliveService里面启动之
//KeepAliveService的onStartCommand方法
public int onStartCommand(Intent intent, int flags, int startId) {
public int onStartCommand(Intent intent, int flags, int startId) {
//省略注册锁屏广播以及初始化播放器的代码
//省略注册锁屏广播以及初始化播放器的代码
//将自己设置为前台Service
//将自己设置为前台Service
startForeground(this);
startForeground(this);
//启动HideNotificationService
//启动HideNotificationService
startService(new Intent(this, HideNotificationService.class));
startService(new Intent(this, HideNotificationService.class));
return START_STICKY;
return START_STICKY;
}
android9.0系统下,讨论如何延长APP退到后台的保活/复活时间
一、7.0及以上不存在真正意义的保活。
二、盘点目前在9.0上,可能有效的“白色手段”保活手段(这里不讨论黑色和灰色手段)。
1.仿TIM引导用户打开“后台自启动”和加入“手机白名单”。
测试开始后台自启动,测试通知栏多久还能收到通知
华为M10 9.0
时长3到5小时没被杀死
VIVO x23 9.0(数字以分钟计算)
1 Y收到
2 Y
3 Y
6 Y
8 通知栏已无通知(app被系统杀死)
10 N收不到
抛出问题:使用H5界面引导用户打开后台自启动,用户如果会同意吗?
2.开启前台服务,会生成多余的通知,被产品否定
3.优化应用内存,正在进行中...
4.仿网易云音乐,复写锁屏界面。
5.集成华为/小米/oppo/vivo/魅族手机系统级推送。
6.集成“信鸽/个推/极光/友盟等”第三方推送,以期达到关联启动。
三、盘点已经失效或者不适合的保活黑科技,这里的“不适合”是因为我们做政府项目,后台是布在内网上的。
1.双进程守护方案,华为6.0就失效
2.监听锁屏/亮屏/解锁广播,打开1像素Activity,华为6.0就失效,因广播被取消了
3.故意在后台播放无声的音乐,华为M10手机9.0失效
4.使用JobScheduler唤醒Service,7.0以上失效
5.集成华为/小米/oppo/vivo/魅族等push,因为项目本地化部署,不适合
6.推送互相唤醒复活:极光、友盟、以及各大厂商的推送,因为项目本地化部署,不适合
7.同派系APP广播互相唤醒:比如今日头条系、阿里系,因为项目本地化部署,不适合
8.使用自定义锁屏界面:覆盖了系统锁屏界面。网易云音乐就是如此,但是会生成一个常驻通知栏的通知
9.把APP设置为系统应用,不适合,因为需要权限等
10.native进程(已报废)
11.利用账号同步机制拉活,失效了
12. 提高Service优先级,比如onStartCommand返回START_STICKY,没什么效果