2018年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
- 2、降低oom_adj的值:常驻通知栏(可通过启动另外一个服务关闭Notification,不对oom_adj值有影响)、使用”1像素“的Activity覆盖在getWindow()的view上、循环播放无声音频(黑科技,7.0下杀不掉)
- 3、监听锁屏广播:使Activity始终保持前台
- 4、使用自定义锁屏界面:覆盖了系统锁屏界面。
- 5、通过android:process属性来为Service创建一个进程
- 6、跳转到系统白名单界面让用户自己添加app进入白名单
三、复活
- 1、JobScheduler:原理类似定时器,5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权)
- 2、推送互相唤醒复活:极光、友盟、以及各大厂商的推送
- 3、同派系APP广播互相唤醒:比如今日头条系、阿里系
方案实现效果统计
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分钟
结论:高版本情况下可以使用弹出通知栏、双进程、无声音乐提高后台服务的保活概率
实现具体过程
一、双进程实现方案
使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection
的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。
1、新建一个AIDL文件
KeepAliveConnection interface KeepAliveConnection { }
2、新建一个服务类StepService,onBind()方法返回new KeepAliveConnection.Stub()对象,并在ServiceConnection的绑定回调中对守护进程服务类GuardService的启动和绑定。
/** * 主进程 双进程通讯 * * @author LiGuangMin * @time Created by 2018/8/17 11:26 */ public class StepService extends Service { private final static String TAG = StepService.class.getSimpleName(); private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Logger.d(TAG, "StepService:建立链接"); boolean isServiceRunning = ServiceAliveUtils.isServiceAlice(); if (!isServiceRunning) { Intent i = new Intent(StepService.this, DownloadService.class); startService(i); } } @Override public void onServiceDisconnected(ComponentName componentName) { // 断开链接 startService(new Intent(StepService.this, GuardService.class)); // 重新绑定 bindService(new Intent(StepService.this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT); } }; @Nullable @Override public IBinder onBind(Intent intent) { return new KeepAliveConnection.Stub() { }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startForeground(1, new Notification()); // 绑定建立链接 bindService(new Intent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT); return START_STICKY; } }
3、对守护进程GuardService进行和2一样的处理
/** * 守护进程 双进程通讯 * * @author LiGuangMin * @time Created by 2018/8/17 11:27 */ public class GuardService extends Service { private final static String TAG = GuardService.class.getSimpleName(); private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Logger.d(TAG, "GuardService:建立链接"); boolean isServiceRunning = ServiceAliveUtils.isServiceAlice(); if (!isServiceRunning) { Intent i = new Intent(GuardService.this, DownloadService.class); startService(i); } } @Override public void onServiceDisconnected(ComponentName componentName) { // 断开链接 startService(new Intent(GuardService.this, StepService.class)); // 重新绑定 bindService(new Intent(GuardService.this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT); } }; @Nullable @Override public IBinder onBind(Intent intent) { return new KeepAliveConnection.Stub() { }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startForeground(1, new Notification()); // 绑定建立链接 bindService(new Intent(this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT); return START_STICKY; } }
4、在Activity中在启动需要保活的DownloadService服务后然后启动保活的双进程
public class MainActivity extends AppCompatActivity { private TextView mShowTimeTv; private DownloadService.DownloadBinder mDownloadBinder; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mDownloadBinder = (DownloadService.DownloadBinder) service; mDownloadBinder.setOnTimeChangeListener(new DownloadService.OnTimeChangeListener() { @Override public void showTime(final String time) { runOnUiThread(new Runnable() { @Override public void run() { mShowTimeTv.setText(time); } }); } }); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this, DownloadService.class); startService(intent); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); //双守护线程,优先级不一样 startAllServices(); } @Override public void onContentChanged() { super.onContentChanged(); mShowTimeTv = findViewById(R.id.tv_show_time); } @Override protected void onDestroy() { super.onDestroy(); unbindService(mServiceConnection); } /** * 开启所有守护Service */ private void startAllServices() { startService(new Intent(this, StepService.class)); startService(new Intent(this, GuardService.class)); } }
二、监听到锁屏广播后使用“1”像素Activity提升优先级
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(@Nullable Bundle 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 protected void onDestroy() { if (!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) { Intent intentAlive = new Intent(this, DownloadService.class); startService(intentAlive); } super.onDestroy(); } }
2、对广播进行监听,封装为一个ScreenReceiverUtil类,进行锁屏解锁的广播动态注册监听
public class ScreenReceiverUtil { private Context mContext; private SreenBroadcastReceiver mScreenReceiver; private SreenStateListener mStateReceiverListener; public ScreenReceiverUtil(Context mContext) { this.mContext = mContext; } public void setScreenReceiverListener(SreenStateListener mStateReceiverListener) { this.mStateReceiverListener = mStateReceiverListener; // 动态启动广播接收器 this.mScreenReceiver = new SreenBroadcastReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_USER_PRESENT); mContext.registerReceiver(mScreenReceiver, filter); } public void stopScreenReceiverListener() { mContext.unregisterReceiver(mScreenReceiver); } /** * 监听sreen状态对外回调接口 */ public interface SreenStateListener { void onSreenOn(); void onSreenOff(); void onUserPresent(); } public class SreenBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (mStateReceiverListener == null) { return; } if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏 mStateReceiverListener.onSreenOn(); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏 mStateReceiverListener.onSreenOff(); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁 mStateReceiverListener.onUserPresent(); } } } }
3、对1像素Activity进行防止内存泄露的处理,新建一个ScreenManager类
public class ScreenManager { private static final String TAG = ScreenManager.class.getSimpleName(); private static ScreenManager sInstance; private Context mContext; private WeakReference<Activity> mActivity; private ScreenManager(Context mContext) { this.mContext = mContext; } public static ScreenManager getInstance(Context context) { if (sInstance == null) { sInstance = new ScreenManager(context); } return sInstance; } /** 获得SinglePixelActivity的引用 * @param activity */ public void setSingleActivity(Activity activity) { mActivity = new WeakReference<>(activity); } /** * 启动SinglePixelActivity */ public void startActivity() { Intent intent = new Intent(mContext, SinglePixelActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } /** * 结束SinglePixelActivity */ public void finishActivity() { if (mActivity != null) { Activity activity = mActivity.get(); if (activity != null) { activity.finish(); } } } }
4、对1像素的Style进行特殊处理,在style文件中新建一个SingleActivityStyle
<style name="SingleActivityStyle" parent="android:Theme.Holo.Light.NoActionBar"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowFrame">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> <item name="android:windowContentOverlay">@null</item> <item name="android:backgroundDimEnabled">false</item> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowDisablePreview">true</item> <item name="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 { public static final int NOTICE_ID = 100; private static final String TAG = DownloadService.class.getSimpleName(); private DownloadBinder mDownloadBinder; private NotificationCompat.Builder mBuilderProgress; private NotificationManager mNotificationManager; private ScreenReceiverUtil mScreenListener; private ScreenManager mScreenManager; private Timer mRunTimer; private int mTimeSec; private int mTimeMin; private int mTimeHour; private ScreenReceiverUtil.SreenStateListener mScreenListenerer = new ScreenReceiverUtil.SreenStateListener() { @Override public void onSreenOn() { mScreenManager.finishActivity(); Logger.d(TAG, "关闭了1像素Activity"); } @Override public void onSreenOff() { mScreenManager.startActivity(); Logger.d(TAG, "打开了1像素Activity"); } @Override public void onUserPresent() { } }; private OnTimeChangeListener mOnTimeChangeListener; @Override public void onCreate() { super.onCreate(); // 注册锁屏广播监听器 mScreenListener = new ScreenReceiverUtil(this); mScreenManager = ScreenManager.getInstance(this); mScreenListener.setScreenReceiverListener(mScreenListenerer); mDownloadBinder = new DownloadBinder(); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Logger.d(TAG, "onStartCommand"); startRunTimer(); return START_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return mDownloadBinder; } @Override public boolean onUnbind(Intent intent) { Logger.d(TAG, "onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); if (mManager == null) { return; } mManager.cancel(NOTICE_ID); stopRunTimer(); // mScreenListener.stopScreenReceiverListener(); } private void startRunTimer() { TimerTask mTask = new TimerTask() { @Override public void run() { 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 = new Timer(); // 每隔1s更新一下时间 mRunTimer.schedule(mTask, 1000, 1000); } private void stopRunTimer() { if (mRunTimer != null) { mRunTimer.cancel(); mRunTimer = null; } mTimeSec = 0; mTimeMin = 0; mTimeHour = 0; Logger.d(TAG, "时间为:" + mTimeHour + " : " + mTimeMin + " : " + mTimeSec); } public interface OnTimeChangeListener { void showTime(String time); } public class DownloadBinder extends Binder { public void setOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) { mOnTimeChangeListener = onTimeChangeListener; } } }
3、在后台播放音乐
1、准备一段无声的音频,新建一个播放音乐的Service类,将播放模式改为无限循环播放。在其onDestroy方法中对自己重新启动。
public class PlayerMusicService extends Service { private final static String TAG = PlayerMusicService.class.getSimpleName(); private MediaPlayer mMediaPlayer; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Logger.d(TAG, TAG + "---->onCreate,启动服务"); mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent); mMediaPlayer.setLooping(true); } @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run() { startPlayMusic(); } }).start(); return START_STICKY; } private void startPlayMusic() { if (mMediaPlayer != null) { Logger.d(TAG, "启动后台播放音乐"); mMediaPlayer.start(); } } private void stopPlayMusic() { if (mMediaPlayer != null) { Logger.d(TAG, "关闭后台播放音乐"); mMediaPlayer.stop(); } } @Override public void onDestroy() { super.onDestroy(); stopPlayMusic(); Logger.d(TAG, TAG + "---->onCreate,停止服务"); // 重启自己 Intent intent = new Intent(getApplicationContext(), PlayerMusicService.class); startService(intent); } }
2、 在保活的DownloadServie服务类的onCreate方法中对PlayerMusicService进行启动
Intent intent = new Intent(this, PlayerMusicService.class); startService(intent);
3、在Manifest文件中进行注册
<service android:name=".service.PlayerMusicService" android:enabled="true" android:exported="true" android:process=":music_service" />
4、使用JobScheduler唤醒Service
1、新建一个继承自JobService的ScheduleService类,在其onStartJob回调中对DownloadService进行存活的判断来重启。
public class ScheduleService extends JobService { private static final String TAG = ScheduleService.class.getSimpleName(); @Override public boolean onStartJob(JobParameters params) { boolean isServiceRunning = ServiceAliveUtils.isServiceAlice(); if (!isServiceRunning) { Intent i = new Intent(this, DownloadService.class); startService(i); Logger.d(TAG, "ScheduleService启动了DownloadService"); } jobFinished(params, false); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
2、 在DownloadService服务类中进行JobScheduler的注册和使用
/** * 使用JobScheduler进行保活 */ private void useJobServiceForKeepAlive() { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); if (jobScheduler == null) { return; } jobScheduler.cancelAll(); JobInfo.Builder builder = new JobInfo.Builder(1024, new ComponentName(getPackageName(), ScheduleService.class.getName())); //周期设置为了2s builder.setPeriodic(1000 * 2); builder.setPersisted(true); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); int schedule = 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" />
关于推送类拉活
根据华为官方文档集成HUAWEI Push
- 1、华为畅玩5X(6.0):APP全部进程被杀死时可以被拉起。
- 2、华为nove 3e(8.0):APP全部进程被杀死时无法被拉起,能收到推送。
- 3、华为荣耀10(8.1):同2
结论:理论情况下,华为推送应该可以拉起华为机器才对,感觉是我没花钱的原因
补充:ServiceAliveUtils 类如下
public class ServiceAliveUtils { public static boolean isServiceAlice() { boolean isServiceRunning = false; ActivityManager manager = (ActivityManager) MyApplication.getMyApplication().getSystemService(Context.ACTIVITY_SERVICE); if (manager == null) { return true; } for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if ("demo.lgm.com.keepalivedemo.service.DownloadService".equals(service.service.getClassName())) { isServiceRunning = true; } } return isServiceRunning; } }
作者:minminaya
链接:https://www.jianshu.com/p/b53...
阅读更多
AndroidUtils:Android开发不得不收藏的Utils
Google开发者大会:你不得不知的Tensorflow小技巧
相信自己,没有做不到的,只有想不到的
在这里获得的不仅仅是技术!