Android 2020年最新保活方案 保活90% 已适配8.0 ,9.0, 10.0(酷狗音乐)

https://www.jianshu.com/p/cfc2a200e46d
 
因为公司app要保活。像酷狗音乐,一直在后台播放音乐。所以研究了下
 
 
系统如何杀死进程?
内存阀值,不同进程优先级导致的不一样的阀值。
空进程:加快启动速度
 
ADJ
 
 
  • 红色部分是容易被回收的进程,属于android进程
  • 绿色部分是较难被回收的进程,属于android进程
  • 其他部分则不是android进程,也不会被系统回收,一般是ROM自带的app和服务才能拥有

如何查看某个进程的oom_adj数值呢?
oom_adj 存储在proc/PID/oom_adj文件中,其中PID是进程的id,直接 adb shell进入手机根目录查看这个文件即可。

演示一下:以我自己的项目为例,app中有两个进程,一个是主进程,另一个是运行service的进程取名为:remote。首先用android studio查看每个进程的PID:


查看命令
https://blog.csdn.net/u013179982/article/details/85697373
 
 
如何在studio中,手动杀死某个进程?
logcat里面的红色按钮。
 
降低自己应用在退回后台时候的oom_adj值
 
oom_adj
表格:
https://www.cnblogs.com/tiger-wang-ms/p/6491429.html
 
查看adj的值:

【1】adb shell

【2】ps

【3】cat /proc/进程id/oom_adj

举例:cat /proc/20084/oom_adj

 
OOM_adj:在不同的前台和后台不一样
 
目前看
1.在前台是0,后台是3
oom_adj值:前台和后台不一样
===================================================
 

优化思路:

  1. 提高进程的优先级,降低自己应用在退回后台时候的oom_adj值
  2. 在进程被kill之后能够唤醒
  3. 优化应用内存,正在进行中...
保活:1像素 ,后台播放音乐,前台进程
拉活:
 
 
保活分析:
1.通知栏(重点分析)
2.第三方推送(no)
不打包push模块。//include ':push'
3.TransferActivity(no)
4.友盟
 
分析方案:
1.先把yodo统一的文件,代码对比(ok)
2.一个一个注释掉。(ok)
3.看日志,是杀死的快?还是因为重新拉活了?
 
 
通过代码:发现yodo的锁屏
 
2.通知栏(ok)
3.自启动权限
 
通知栏
ServiceWakeKeepTicketProcess(ok)
ServiceWakeMainProcess(ok)
KeepAliveBlankVoice(ok)
KeepAliveTool

 
AlarmService
CMainJobService
CSportJobService
RemoteKeepAlive
LocationMonitorService
 
RepeatWorker
Android 使用MarsDaemon进程常驻====DaemonConfigurations
 
广播AlarmStepReceiver
电量广播:
 
 
YODO保活办法:
1.AlarmManager ===[无用] =定时唤醒(它比Service和定时器更加节省资源)
AlarmReceiver
2.AlarmService==[无用](CMonitorSportService,CMonitorMainService<2个都不是service,用于绑定service>)=====(CSportJobService,CMainJobService,和前面对应)======(JobScheduler拉活)
binderService()不一定是Activity
3.BatterReceiver(电量广播拉活)
4.GuardReceiver第三方的包活[无用]
同上SportGuardService
5.Worker--------WorkManager
6.StartSystemReceiver  开机广播[无用]
7.Rejoice 里面通知栏:keepAliveTime
 
锁屏:
startRunFromLockScreen()
Rejoice的方法moveToHome()
被回收:通过上报,数据库中跑步状态的保存
PhoneStateListener:
 
通知栏:
需要不停的刷新通知栏的ui,让系统以为你是前台进程,这样就不会杀掉你了
 
白色保活方案:
1.后台自启动
2.和加入“手机白名单”
 
 
思路:

1.像素Activity

降低oom_adj的值。
当在后台的时候,用1像素
当在前台的时候,恢复
结果:在后台可以达到0
缺点:如果不是锁屏,不能达到这种效果
 

该方案适用场景: 本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间内会杀死后台进程.适用于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
        }
    }
 
 

说起来很简单,但是还是有几个知识点需要注意下。

1、需要注册一个监听屏幕锁屏和截屏的BroadcastReceiver。

关于这一点有一个细节需要处理,为了放置用户快速 进行锁屏和截屏的切换操作,而导致OnePixelActivity频繁开关。需要用一个Handler发送一个延迟消息处理最佳:

 
 
 
 
 
 
 
   private Handler mHandler;private Handler mHandler;
    private boolean isScreenOn = true;private boolean isScreenOn = true;
    private PendingIntent  pendingIntent;private PendingIntent  pendingIntent;
    private List screenStateListeners = null;private List 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);
    }
 
 

2、需要将该activity的 android:excludeFromRecents设置为true3、需要将该Activity设置为singleInstance启动模式

 
 
 
 
 
 
 
   
          
            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();
        }
    }
 
 

2.播放无声音乐

播放无声音乐:原理:

处理逻辑跟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,部分代码如下(本篇博文只贴部分关键代码,全部代码点此查看):

后台播放声音:KeepAliveBlankVoice
 
缺点:
3.前台进程:
缺点:有通知栏
 
8.0以前可以不用通知栏的解决方案:
 

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;
    }
 
 

前面3种是保活方案
 
拉活方案:

一。JobServcie 拉活

原理: 它将后台任务调度直接交给系统服务(JobSchedulerSevice)管理,并且可以设置许多约束条件,如周期调度,延迟调度,网络连接,电源插入,还有AndroidL引入的空闲模式。在条件符合的情况下, 系统服务BindService的方式把应用内Manifest中配置的JobService启动起来,并通过进程间通信Binder方式调用JobService的onStartJob、onStopJob等方法来进行Job的管理。即便在执行任务之前应用程序进程被杀,也不会导致任务中断,Jobservice不会因应用退出而退出  当然JobScheduler不只是用于保活这个黑科技,可以利用这种机制做很多后台定时任务。
二。 账号同步机制拉活
缺点:时间不确定
三。workManager 拉活
四。BatterReceiver(广播拉活)
缺点:失效
五。
 
 
 
 
https://www.jianshu.com/p/dd01580743e7(1像素)
AAR:
https://github.com/Ray512512/KeepAlive
 
 
8.0(双守护)
https://juejin.im/entry/6844903764269219847
保活:
https://www.jianshu.com/p/8f258aedcdcb(多种方案)
https://blog.csdn.net/chunqiuwei/article/details/95649955(锁屏)
得到:
https://blog.csdn.net/hello_json/article/details/72730740?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
 
 
 

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,没什么效果

 
 

你可能感兴趣的:(面试,android,源码分析,android,java,objective-c)