全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

栏目: Android · 发布时间: 7年前

内容简介:本文原作者“minminaya”,作者网站:minminaya.cn,为了提升文章品质,即时通讯网对内容作了幅修订和改动,感谢原作者。对于IM应用和消息推送服务的开发者来说,在Android机型上的后台保活是个相当头疼的问题。

本文原作者“minminaya”,作者网站:minminaya.cn,为了提升文章品质,即时通讯网对内容作了幅修订和改动,感谢原作者。

1、引言

对于IM应用和消息推送服务的开发者来说,在Android机型上的后台保活是个相当头疼的问题。

老板一句: “为什么微信、QQ能收到消息,而你写的APP却不行?”,直接让人崩溃,话说老板你这APP要是整成微信、APP那么牛,直接进手机厂商白名单,还要 程序员 在这瞎忙活?

好了,抱怨归抱怨,活还得干,不然靠谁养活广大苦逼的程序员?

回到正题,Android程序员都知道,随着Android系统的不断完善和升级,Andriod应用的后台保活是一次比一次难(详见《 Android P正式版即将到来:后台应用保活、消息推送的真正噩梦 》),但日子还得过,只能一次次绞尽脑汁想出各种黑科技。但不幸的是,因为Andriod系统的不断升级,各种黑科技也只能适应某些版本的Android系统,无法一劳永逸解决问题。

全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

▲ Android各版本都是用“甜品”命名的

正因为Android系统版本的差异,也导致了各种保活黑科技的运行效果大相径庭,所以本文正好借此机会,盘点一下当前主流(截止2019年前)的保活黑科技在市面上各版本Android手机上的运行效果,希望能给大家提供一些客观的参考。

学习交流:

- 即时通讯/推送技术开发交流4群: 101279154 [推荐]

- 移动端IM开发入门文章:《 新手入门一篇就够:从零开发移动端IM

(本文同步发布于: http://www.52im.net/thread-2176-1-1.html

2、先总结一下,Android端APP为何要搞保活黑科技?

* 本节内容摘录自即时通讯网整理的《 Android P正式版即将到来:后台应用保活、消息推送的真正噩梦 》一文

其实Android端APP搞保活的目的倒不是为了干什么见不得人的坏事(但不排除动机不纯的开发者),主要是像IM即时通讯应用和资讯类应用等需要搞后台消息推送、运动类应用需要在后台实时监测用户的运动数据等,因为现在越来越多的手机厂商为了省电策略考虑,基本上如果你的应用没有被加入白名单,一旦处于后台就会被系统限制甚至干掉,但使用APP的用户才不听你这些解释——反正“我”就要你的APP能如期正常运行,开发者也是不得已而为之。

以消息推送为例,当APP处于后台或关闭时,消息推送对于某些应用来说非常有用,比如:

1)IM即时通讯聊天应用:聊天消息通知、音视频聊天呼叫等,典型代表有:微信、QQ、易信、米聊、钉钉、Whatsup、Line;

2)新闻资讯应用:最新资讯通知等,典型代表有:网易新闻客户端、腾讯新闻客户端;

3)SNS社交应用:转发/关注/赞等通知,典型代表有:微博、知乎;

4)邮箱客户端:新邮件通知等,典型代表有:QQ邮箱客户端、Foxmail客户端、网易邮箱大师;

5)金融支付应用:收款通知、转账通知等,典型代表有:支付宝、各大银行的手机银行等;

.... ....

在上述的各种应用中,尤其对于用户接触最多、最平常的IM聊天应用或新闻资讯来说,保活和消息推送简直事关APP的“生死”,消息推送这种能力已经被越来越多的APP作为基础能力之一,因为移动互联网时代下,用户的“全时在线”能力非常诱人和强大,能随时随地即时地将各种重要信息推送给用户,无疑是非常有意义的。

题外话: 实际上,对于后台消息推送能力,Android原版系统早就内置了系统级推送服务(跟iOS上的 APNs服务 是一个东西),它就是GCM服务(现在升级为FCM了),但众所周之的原因,谷哥的服务在国内都是用不了的(你懂的)—— 无奈啊!

(有关GCM的介绍详见:《 移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信) 》、《 为何微信、QQ这样的IM工具不使用GCM服务推送消息? 》、《 求教android消息推送:GCM、XMPP、MQTT三种方案的优劣 》)。

全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

▲ 如果Android能有iOS的APNs这么强势的方案存在,那该是多美的事 ...

3、相关文章

应用保活终极总结(一):Android6.0以下的双进程守护保活实践

应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)

应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)

Android进程保活详解:一篇文章解决你的所有疑问

Android端消息推送总结:实现原理、心跳保活、遇到的问题等

深入的聊聊Android消息推送这件小事

微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)

Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

4、常见的Android端保活黑科技方案盘点

主要黑科技方案有:

1)监听广播:监听全局的静态广播,比如时间更新的广播、开机广播、解锁屏、网络状态、解锁加锁亮屏暗屏(3.1版本),高版本需要应用开机后运行一次才能监听这些系统广播,目前此方案失效。可以更换思路,做APP启动后的保活(监听广播启动保活的前台服务);
2)定时器、JobScheduler:假如应用被系统杀死,那么定时器则失效,此方案失效。JobService在5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权);
3)双进程(NDK方式Fork子进程)、双Service守护:高版本已失效,5.0起系统回收策略改成进程组。双Service方案也改成了应用被杀,任何后台Service无法正常状态运行;
4)提高Service优先级:只能一定程度上缓解Service被立马回收。

针对上述方案,具体的实现思路,通常是这样的:

1)进程拉活:AIDL方式单进程、双进程方式保活Service(最极端的例子就是推送厂商的互相唤醒复活:极光、友盟、以及各大厂商的推送,同派系APP广播互相唤醒:比如今日头条系、阿里系);
2)降低oom_adj的值:常驻通知栏(可通过启动另外一个服务关闭Notification,不对oom_adj值有影响)、使用”1像素“的Activity覆盖在getWindow()的view上(据传某不可言说的IM大厂用过这个方案,虽然他们从未正面承认过)、循环播放无声音频(黑科技,7.0下杀不掉);
3)监听锁屏广播:使Activity始终保持前台;
4)使用自定义锁屏界面:覆盖了系统锁屏界面;
5)创建子进程:通过android:process属性来为Service创建一个进程;
6)白名单:跳转到系统白名单界面让用户自己添加app进入白名单。

5、汇总一下,主要的保活黑科技方案的具体代码实现

5.1 黑科技代码实现1:双进程拉活方案的代码实现

使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。

关于本方案的具体实现,即时通讯网的以下文章有更详细的介绍,您也可以仔细研读:

应用保活终极总结(一):Android6.0以下的双进程守护保活实践

应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)

应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)

本方案的具体代码实现,主要由以下4步构成。

1)新建一个AIDL文件:

KeepAliveConnection
interfaceKeepAliveConnection  {
}

2)新建一个服务类StepService,onBind()方法返回new KeepAliveConnection.Stub()对象,并在ServiceConnection的绑定回调中对守护进程服务类GuardService的启动和绑定:

/**
* 主进程 双进程通讯
*
* @author LiGuangMin
* @time Created by 2018/8/17 11:26
*/
public class StepService extends Service {
   privatefinalstaticString TAG = StepService.class.getSimpleName();
   privateServiceConnection mServiceConnection = newServiceConnection() {
       @Override
       publicvoidonServiceConnected(ComponentName componentName, IBinder iBinder) {
           Logger.d(TAG, "StepService:建立链接");
           booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();
           if(!isServiceRunning) {
               Intent i = newIntent(StepService.this, DownloadService.class);
               startService(i);
           }
       }
 
       @Override
       publicvoidonServiceDisconnected(ComponentName componentName) {
           // 断开链接
           startService(newIntent(StepService.this, GuardService.class));
           // 重新绑定
           bindService(newIntent(StepService.this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
       }
   };
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
       returnnewKeepAliveConnection.Stub() {
       };
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       startForeground(1, newNotification());
       // 绑定建立链接
       bindService(newIntent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
       returnSTART_STICKY;
   }
}

3)对守护进程GuardService进行和2一样的处理:

/**
* 守护进程 双进程通讯
*
* @author LiGuangMin
* @time Created by 2018/8/17 11:27
*/
publicclassGuardService extendsService {
   privatefinalstaticString TAG = GuardService.class.getSimpleName();
   privateServiceConnection mServiceConnection = newServiceConnection() {
       @Override
       publicvoidonServiceConnected(ComponentName componentName, IBinder iBinder) {
           Logger.d(TAG, "GuardService:建立链接");
           booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();
           if(!isServiceRunning) {
               Intent i = newIntent(GuardService.this, DownloadService.class);
               startService(i);
           }
       }
 
       @Override
       publicvoidonServiceDisconnected(ComponentName componentName) {
           // 断开链接
           startService(newIntent(GuardService.this, StepService.class));
           // 重新绑定
           bindService(newIntent(GuardService.this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);
       }
   };
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
       returnnewKeepAliveConnection.Stub() {
       };
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       startForeground(1, newNotification());
       // 绑定建立链接
       bindService(newIntent(this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);
       returnSTART_STICKY;
   }
}

4)在Activity中在启动需要保活的DownloadService服务后然后启动保活的双进程:

public class MainActivity extends AppCompatActivity {
   privateTextView mShowTimeTv;
   privateDownloadService.DownloadBinder mDownloadBinder;
   privateServiceConnection mServiceConnection = newServiceConnection() {
       @Override
       publicvoidonServiceConnected(ComponentName name, IBinder service) {
           mDownloadBinder = (DownloadService.DownloadBinder) service;
           mDownloadBinder.setOnTimeChangeListener(newDownloadService.OnTimeChangeListener() {
               @Override
               publicvoidshowTime(finalString time) {
                   runOnUiThread(newRunnable() {
                       @Override
                       publicvoidrun() {
                           mShowTimeTv.setText(time);
                       }
                   });
               }
           });
       }
 
       @Override
       publicvoidonServiceDisconnected(ComponentName name) {
       }
   };
 
   @Override
   protectedvoidonCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
 
       Intent intent = newIntent(this, DownloadService.class);
       startService(intent);
       bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
       //双守护线程,优先级不一样
       startAllServices();
   }
 
   @Override
   publicvoidonContentChanged() {
       super.onContentChanged();
       mShowTimeTv = findViewById(R.id.tv_show_time);
   }
 
   @Override
   protectedvoidonDestroy() {
       super.onDestroy();
       unbindService(mServiceConnection);
   }
 
   /**
    * 开启所有守护Service
    */
   privatevoidstartAllServices() {
       startService(newIntent(this, StepService.class));
       startService(newIntent(this, GuardService.class));
   }
}

5.2 黑科技代码实现2:监听到锁屏广播后使用“1”像素Activity提升优先级

“1”像素保活这么流氓的手段,传说是某IM大厂用过的方案 ...

本方法的具体代码实现主要由以下6步组成。

1)该Activity的View只要设置为1像素然后设置在Window对象上即可。在Activity的onDestroy周期中进行保活服务的存活判断从而唤醒服务。”1像素”Activity如下:

public class SinglePixelActivity extends AppCompatActivity {
   private static final String TAG = SinglePixelActivity.class.getSimpleName();
 
   @Override
   protected void onCreate(@NullableBundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       Window mWindow = getWindow();
       mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
       WindowManager.LayoutParams attrParams = mWindow.getAttributes();
       attrParams.x = 0;
       attrParams.y = 0;
       attrParams.height = 1;
       attrParams.width = 1;
       mWindow.setAttributes(attrParams);
       ScreenManager.getInstance(this).setSingleActivity(this);
   }
 
   @Override
   protectedvoidonDestroy() {
       if(!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) {
           Intent intentAlive = newIntent(this, DownloadService.class);
           startService(intentAlive);
       }
       super.onDestroy();
   }
}

2)对广播进行监听,封装为一个ScreenReceiverUtil类,进行锁屏解锁的广播动态注册监听:

public class ScreenReceiverUtil {
   privateContext mContext;
   privateSreenBroadcastReceiver mScreenReceiver;
   privateSreenStateListener mStateReceiverListener;
 
   publicScreenReceiverUtil(Context mContext) {
       this.mContext = mContext;
   }
 
   publicvoidsetScreenReceiverListener(SreenStateListener mStateReceiverListener) {
       this.mStateReceiverListener = mStateReceiverListener;
       // 动态启动广播接收器
       this.mScreenReceiver = newSreenBroadcastReceiver();
       IntentFilter filter = newIntentFilter();
       filter.addAction(Intent.ACTION_SCREEN_ON);
       filter.addAction(Intent.ACTION_SCREEN_OFF);
       filter.addAction(Intent.ACTION_USER_PRESENT);
       mContext.registerReceiver(mScreenReceiver, filter);
   }
 
   publicvoidstopScreenReceiverListener() {
       mContext.unregisterReceiver(mScreenReceiver);
   }
 
   /**
    * 监听sreen状态对外回调接口
    */
   publicinterfaceSreenStateListener {
       voidonSreenOn();
 
       voidonSreenOff();
 
       voidonUserPresent();
   }
 
   publicclassSreenBroadcastReceiver extendsBroadcastReceiver {
       @Override
       publicvoidonReceive(Context context, Intent intent) {
           String action = intent.getAction();
           if(mStateReceiverListener == null) {
               return;
           }
           if(Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
               mStateReceiverListener.onSreenOn();
           } elseif(Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
               mStateReceiverListener.onSreenOff();
           } elseif(Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁
               mStateReceiverListener.onUserPresent();
           }
       }
   }
}

3)对1像素Activity进行防止内存泄露的处理,新建一个ScreenManager类:

public class ScreenManager {
   privatestaticfinalString TAG = ScreenManager.class.getSimpleName();
   privatestaticScreenManager sInstance;
   privateContext mContext;
   privateWeakReference<Activity> mActivity;
 
   privateScreenManager(Context mContext) {
       this.mContext = mContext;
   }
 
   publicstaticScreenManager getInstance(Context context) {
       if(sInstance == null) {
           sInstance = newScreenManager(context);
       }
       returnsInstance;
   }
 
   /** 获得SinglePixelActivity的引用
    * @param activity
    */
   publicvoidsetSingleActivity(Activity activity) {
       mActivity = newWeakReference<>(activity);
   }
 
   /**
    * 启动SinglePixelActivity
    */
   publicvoidstartActivity() {
       Intent intent = newIntent(mContext, SinglePixelActivity.class);
       intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       mContext.startActivity(intent);
   }
 
   /**
    * 结束SinglePixelActivity
    */
   publicvoidfinishActivity() {
       if(mActivity != null) {
           Activity activity = mActivity.get();
           if(activity != null) {
               activity.finish();
           }
       }
   }
}

4)对1像素的Style进行特殊处理,在style文件中新建一个SingleActivityStyle:

<stylename="SingleActivityStyle"parent="android:Theme.Holo.Light.NoActionBar">
       <itemname="android:windowBackground">@android:color/transparent</item>
       <itemname="android:windowFrame">@null</item>
       <itemname="android:windowNoTitle">true</item>
       <itemname="android:windowIsFloating">true</item>
       <itemname="android:windowContentOverlay">@null</item>
       <itemname="android:backgroundDimEnabled">false</item>
       <itemname="android:windowAnimationStyle">@null</item>
       <itemname="android:windowDisablePreview">true</item>
       <itemname="android:windowNoDisplay">false</item>

5)让SinglePixelActivity使用singleInstance启动模式,在manifest文件中:

<activity
           android:name=".activity.SinglePixelActivity"
           android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
           android:excludeFromRecents="true"
           android:finishOnTaskLaunch="false"
           android:launchMode="singleInstance"
           android:theme="@style/SingleActivityStyle"/>

6)在保活服务类DownloadService中对监听的广播进行注册和对SinglePixelActivity进行控制:

public class DownloadService extends Service {
   publicstaticfinalintNOTICE_ID = 100;
   privatestaticfinalString TAG = DownloadService.class.getSimpleName();
   privateDownloadBinder mDownloadBinder;
   privateNotificationCompat.Builder mBuilderProgress;
   privateNotificationManager mNotificationManager;
 
   privateScreenReceiverUtil mScreenListener;
   privateScreenManager mScreenManager;
   privateTimer mRunTimer;
 
   privateintmTimeSec;
   privateintmTimeMin;
   privateintmTimeHour;
 
   privateScreenReceiverUtil.SreenStateListener mScreenListenerer = newScreenReceiverUtil.SreenStateListener() {
       @Override
       publicvoidonSreenOn() {
           mScreenManager.finishActivity();
           Logger.d(TAG, "关闭了1像素Activity");
       }
 
       @Override
       publicvoidonSreenOff() {
           mScreenManager.startActivity();
           Logger.d(TAG, "打开了1像素Activity");
       }
 
       @Override
       publicvoidonUserPresent() {
       }
   };
   privateOnTimeChangeListener mOnTimeChangeListener;
 
   @Override
   publicvoidonCreate() {
       super.onCreate();
 
//        注册锁屏广播监听器
       mScreenListener = newScreenReceiverUtil(this);
       mScreenManager = ScreenManager.getInstance(this);
       mScreenListener.setScreenReceiverListener(mScreenListenerer);
 
       mDownloadBinder = newDownloadBinder();
       mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       Logger.d(TAG, "onStartCommand");
       startRunTimer();
       returnSTART_STICKY;
   }
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
 
       returnmDownloadBinder;
   }
 
   @Override
   publicbooleanonUnbind(Intent intent) {
       Logger.d(TAG, "onUnbind");
       returnsuper.onUnbind(intent);
   }
 
   @Override
   publicvoidonDestroy() {
       super.onDestroy();
       NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
       if(mManager == null) {
           return;
       }
       mManager.cancel(NOTICE_ID);
       stopRunTimer();
//        mScreenListener.stopScreenReceiverListener();
   }
 
   privatevoidstartRunTimer() {
       TimerTask mTask = newTimerTask() {
           @Override
           publicvoidrun() {
               mTimeSec++;
               if(mTimeSec == 60) {
                   mTimeSec = 0;
                   mTimeMin++;
               }
               if(mTimeMin == 60) {
                   mTimeMin = 0;
                   mTimeHour++;
               }
               if(mTimeHour == 24) {
                   mTimeSec = 0;
                   mTimeMin = 0;
                   mTimeHour = 0;
               }
               String time = "时间为:"+ mTimeHour + " : "+ mTimeMin + " : "+ mTimeSec;
               if(mOnTimeChangeListener != null) {
                   mOnTimeChangeListener.showTime(time);
               }
               Logger.d(TAG, time);
           }
       };
       mRunTimer = newTimer();
       // 每隔1s更新一下时间
       mRunTimer.schedule(mTask, 1000, 1000);
   }
 
   privatevoidstopRunTimer() {
       if(mRunTimer != null) {
           mRunTimer.cancel();
           mRunTimer = null;
       }
       mTimeSec = 0;
       mTimeMin = 0;
       mTimeHour = 0;
       Logger.d(TAG, "时间为:"+ mTimeHour + " : "+ mTimeMin + " : "+ mTimeSec);
   }
 
   publicinterfaceOnTimeChangeListener {
       voidshowTime(String time);
   }
 
   publicclassDownloadBinder extendsBinder {
       publicvoidsetOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {
           mOnTimeChangeListener = onTimeChangeListener;
       }
   }
}

6.3 黑科技代码实现3:在后台播放音乐

后台播放音乐这种保活方法,亲身经历过:

记得当时用的是某运动记步APP,它为了保活就是这么干的。之所以被我发现,是因为在我的Android手机上,每次打开这个APP居然总能莫名其妙听到若有若无的环境噪音样的声音,尤其安静的场所下更明显。我个人估计这个APP里用的保活音频文件,很可能就是程序员在简陋的条件下随手自已录制的,虽然也是不得以为之,但做法确实是有点粗糙。

好了,回到正题,本方案的具体代码实现主要是以下3步。

1)准备一段无声的音频,新建一个播放音乐的Service类,将播放模式改为无限循环播放。在其onDestroy方法中对自己重新启动:

public class PlayerMusicService extends Service {
   privatefinalstaticString TAG = PlayerMusicService.class.getSimpleName();
   privateMediaPlayer mMediaPlayer;
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
       returnnull;
   }
 
   @Override
   publicvoidonCreate() {
       super.onCreate();
       Logger.d(TAG, TAG + "---->onCreate,启动服务");
       mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
       mMediaPlayer.setLooping(true);
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       newThread(newRunnable() {
           @Override
           publicvoidrun() {
               startPlayMusic();
           }
       }).start();
       returnSTART_STICKY;
   }
 
   privatevoidstartPlayMusic() {
       if(mMediaPlayer != null) {
           Logger.d(TAG, "启动后台播放音乐");
           mMediaPlayer.start();
       }
   }
 
   privatevoidstopPlayMusic() {
       if(mMediaPlayer != null) {
           Logger.d(TAG, "关闭后台播放音乐");
           mMediaPlayer.stop();
       }
   }
 
   @Override
   publicvoidonDestroy() {
       super.onDestroy();
       stopPlayMusic();
       Logger.d(TAG, TAG + "---->onCreate,停止服务");
       // 重启自己
       Intent intent = newIntent(getApplicationContext(), PlayerMusicService.class);
       startService(intent);
   }
}

2)在保活的DownloadServie服务类的onCreate方法中对PlayerMusicService进行启动:

Intent intent = newIntent(this, PlayerMusicService.class);
startService(intent);

3)在Manifest文件中进行注册:

<service
           android:name=".service.PlayerMusicService"
           android:enabled="true"
           android:exported="true"
           android:process=":music_service"/>

6.4 黑科技代码实现4:使用JobScheduler唤醒Service

本方案代码实现由以下3步组成。

1)新建一个继承自JobService的ScheduleService类,在其onStartJob回调中对DownloadService进行存活的判断来重启:

public class ScheduleService extends JobService {
   privatestaticfinalString TAG = ScheduleService.class.getSimpleName();
 
   @Override
   publicbooleanonStartJob(JobParameters params) {
 
       booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();
       if(!isServiceRunning) {
           Intent i = newIntent(this, DownloadService.class);
           startService(i);
           Logger.d(TAG, "ScheduleService启动了DownloadService");
       }
       jobFinished(params, false);
       returnfalse;
   }
 
   @Override
   publicbooleanonStopJob(JobParameters params) {
       returnfalse;
   }
}

2)在DownloadService服务类中进行JobScheduler的注册和使用:

/**
    * 使用JobScheduler进行保活
    */
   private void useJobServiceForKeepAlive() {
       JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
       if(jobScheduler == null) {
           return;
       }
       jobScheduler.cancelAll();
       JobInfo.Builder builder =
           newJobInfo.Builder(1024, newComponentName(getPackageName(), ScheduleService.class.getName()));
       //周期设置为了2s
       builder.setPeriodic(1000* 2);
       builder.setPersisted(true);
       builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
       intschedule = jobScheduler.schedule(builder.build());
       if(schedule <= 0) {
           Logger.w(TAG, "schedule error!");
       }
   }

3)在manifest文件中进行权限设置:

<service
           android:name=".service.ScheduleService"
           android:enabled="true"
           android:exported="true"
    android:permission="android.permission.BIND_JOB_SERVICE"/>

7、总结一下,以上方案在当前主流手机上的运行效果

【1】双进程守护方案(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:原生任务栏滑动清理app,Service会被杀掉,然后被拉起,接着一直存活;
2)金立F100(5.1):一键清理直接杀掉整个app,包括双守护进程。不手动清理情况下,经测试能锁屏存活至少40分钟;
3)华为畅享5x(6.0):一键清理直接杀掉整个app,包括双守护进程。不手动清理下,锁屏只存活10s。结论:双进程守护方案失效;
4)美图m8s(7.1.1):一键清理直接杀掉整个app,包括双守护进程。不清理情况下,锁屏会有被杀过程(9分钟左右被杀),之后重新复活,之后不断被干掉然后又重新复活。结论:双守护进程可在后台不断拉起Service;
5)原生7.0:任务栏清除APP后,Service存活。使用此方案后Service照样存活;
6)LG V30+(7.1.2):不加双进程守护的时候,一键清理无法杀掉服务。加了此方案之后也不能杀掉服务,锁屏存活(测试观察大于50分钟);
7)小米8(8.1):一键清理直接干掉app并且包括双守护进程。不清理情况下,不加守护进程方案与加守护进程方案Service会一直存活,12分钟左右closed。结论:此方案没有起作用。

▲ 结论: 除了华为此方案无效以及未更改底层的厂商不起作用外(START_STICKY字段就可以保持Service不被杀)。此方案可以与其他方案混合使用。

【2】监听锁屏广播打开1像素Activity(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用);
2)华为畅享5x(6.0):锁屏只存活4s。结论:方案失效;
3)美图m8s(7.1.1):同原生5.0;
4)原生7.0:同美图m8s;
5)LG V30+(7.1.2):锁屏后情况跟不加情况一致,服务一致保持运行,结论:此方案不起作用;
6)小米8(8.1):关屏过2s之后app全部被干掉。结论:此方案没有起作用。

▲ 结论: 此方案无效果。

【3】故意在后台播放无声的音乐(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用);
2)华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务,锁屏8分钟后依然存活。结论:此方案适用;
3)美图m8s(7.1.1):同5.0;
4)原生7.0:任务管理器中关闭APP后服务被干掉,大概过3s会重新复活(同仅START_STICKY字段模式)。结论:看不出此方案有没有其作用;
5)LG V30+(7.1.2):使用此方案前后效果一致。结论:此方案不起作用;
6)小米8(8.1):一键清理可以杀掉服务。锁屏后保活超过20分钟。

▲ 结论: 成功对华为手机保活。小米8下也成功突破20分钟。

【4】使用JobScheduler唤醒Service(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。结论:此方案起作用;
2)华为畅享5x(6.0):一键清理直接杀掉APP,过12s左右会自动重启服务,JobScheduler起作用;
3)美图m8s(7.1.1):一键清理直接杀掉APP,无法自动重启;
4)原生7.0:同美图m8s(7.1.1);
5)小米8(8.1):同美图m8s(7.1.1)。

▲ 结论: 只对5.0,5.1、6.0起作用。

【5】混合使用的效果,并且在通知栏弹出通知:

1)原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。锁屏超过11分钟存活;
2)华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务。结论:方案适用;
3)美图m8s(7.1.1):一键清理APP会被杀掉。正常情况下锁屏后服务依然存活;
4)原生7.0:任务管理器中关闭APP后服务被干掉,过2s会重新复活;
5)小米8(8.1):一键清理可以杀掉服务,锁屏下后台保活时间超过38分钟;
6)荣耀10(8.0):一键清理杀掉服务,锁屏下后台保活时间超过23分钟。

▲ 结论: 高版本情况下可以使用弹出通知栏、双进程、无声音乐提高后台服务的保活概率。

8、补充:ServiceAliveUtils 类代码如下

public class ServiceAliveUtils {
   publicstaticbooleanisServiceAlice() {
       booleanisServiceRunning = false;
       ActivityManager manager =
           (ActivityManager) MyApplication.getMyApplication().getSystemService(Context.ACTIVITY_SERVICE);
       if(manager == null) {
           returntrue;
       }
       for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
           if("demo.lgm.com.keepalivedemo.service.DownloadService".equals(service.service.getClassName())) {
               isServiceRunning = true;
           }
       }
       returnisServiceRunning;
   }
}

9、写在最后

Android P(即Android 9)已于2018年8月7日的正式发布,此版本的Android省电策略等限制,对于APP的后台保活来说将更为困难。预计2019年Android P将会成为Android设备的主流系统,到那时才是真正噩梦的开始。

关于Android P在保活方面的问题,请详细阅读《 Android P正式版即将到来:后台应用保活、消息推送的真正噩梦 》。

附录:更多有关IM/推送的心跳保活处理的文章

应用保活终极总结(一):Android6.0以下的双进程守护保活实践

应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)

应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)

Android进程保活详解:一篇文章解决你的所有疑问

Android端消息推送总结:实现原理、心跳保活、遇到的问题等

深入的聊聊Android消息推送这件小事

为何基于TCP协议的移动端IM仍然需要心跳保活机制?

微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)

微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)

移动端IM实践:实现Android版微信的智能心跳机制

移动端IM实践:WhatsApp、Line、微信的心跳策略分析

Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

>> 更多同类文章 ……

(本文同步发布于: http://www.52im.net/thread-2176-1-1.html


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art of UNIX Programming

The Art of UNIX Programming

Eric S. Raymond / Addison-Wesley / 2003-10-3 / USD 54.99

Writing better software: 30 years of UNIX development wisdom In this book, five years in the making, the author encapsulates three decades of unwritten, hard-won software engineering wisdom. Raymond b......一起来看看 《The Art of UNIX Programming》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具