2018年Android的保活方案效果统计

33次阅读

共计 16354 个字符,预计需要花费 41 分钟才能阅读完成。

一、常见保活方案
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…
阅读更多
做后台是选择 Java、Go,还是 PHP?
NDK 项目实战—高仿 360 手机助手之卸载监听
AndroidUtils:Android 开发不得不收藏的 Utils
(Android)面试题级答案(精选版)
Google 开发者大会:你不得不知的 Tensorflow 小技巧
相信自己,没有做不到的,只有想不到的
在这里获得的不仅仅是技术!

正文完
 0