进程保活方案
1、开启一个像素的Activity
据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播,我试过了,的确好使,如下。
①OnePixelActivity
public class OnePixelActivity extends Activity { private static final String TAG = "MyLog"; public static OnePixelActivity instance = null; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_one_pixel); Window window = getWindow(); // 放在左上角 window.setGravity(Gravity.START | Gravity.TOP); WindowManager.LayoutParams layoutParams = window.getAttributes(); // 宽高为1px layoutParams.width = 1; layoutParams.height = 1; // 起始坐标 layoutParams.x = 0; layoutParams.y = 0; window.setAttributes(layoutParams); instance = this; Log.d(TAG, "activity onCreate"); } @Override protected void onDestroy() { instance = null; Log.d(TAG, "activity onDestroy"); super.onDestroy(); } }
②编写广播接收器监听锁屏和解锁action:
public class ScreenBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "MyLog"; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { case Intent.ACTION_SCREEN_ON: { // Log.d(TAG, "screen_on"); // 关闭一像素Activity if (OnePixelActivity.instance != null) { OnePixelActivity.instance.finish(); } break; } case Intent.ACTION_SCREEN_OFF: { Log.d(TAG, "screen_off"); // 开启一像素Activity Intent activityIntent = new Intent(context, OnePixelActivity.class); activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(activityIntent); break; } default: break; } } }
值得注意的是Intent.ACTION_SCREEN_ON与Intent.ACTION_SCREEN_OFF只有通过Context.registerReceiver方法注册的广播接收器才能监听到,官方解释如下:
SCREEN_ON屏幕亮起同上,在此就不给出展示了。
③下面给出Service的例子,我们在启动服务时使用registerReceiver注册监听器,然后在注销服务时注销监听器:
public class WorkService extends Service { private static final String TAG = "MyLog"; private ScreenBroadcastReceiver receiver; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "service onCreate"); receiver = new ScreenBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(receiver, intentFilter); } @Override public void onDestroy() { Log.d(TAG, "service onDestroy"); unregisterReceiver(receiver); super.onDestroy(); } }
④主Activity启动服务后关闭自身,模拟没有Activity的情况:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this, WorkService.class); startService(intent); finish(); } }
通过adb shell可以看到,在锁屏前应用所处的进程oom_adj值是较高的,锁屏后由于启动了Activity,oom_adj值降低了,进程的等级得到了相应的提高,变得更难以被回收了,这样可以一定程度上缓解我们的应用被第三方应用或系统管理工具在锁屏后为省电而被杀死的情况:
2.利用Notification提升权限
这种方法也适用于Service在后台提供服务的场景。由于没有Activity的缘故,我们Service所在进程的oom_adj值通常是较高的,进程等级较低,容易被系统回收内存时清理掉。这时我们可以通过startForeground方法,把我们的服务提升为前台服务,提高进程的等级。但提升为前台服务必须绑定一个相应的Notification,这是我们不愿意看到的。
原理:Android 的前台service机制。但该机制的缺陷是通知栏保留了图标。
对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。
这方案实际利用了Android前台service的漏洞。微信在评估了国内不少app已经使用后,才进行了部署。其实目标是让大家站同一起跑线上,哪天google 把漏洞堵了,效果也是一样的。
public class KeepLiveService extends Service { public static final int NOTIFICATION_ID=0x11; public KeepLiveService() { } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); //API 18以下,直接发送Notification并将其置为前台 if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) { startForeground(NOTIFICATION_ID, new Notification()); } else { //API 18以上,发送Notification并将其置为前台后,启动InnerService Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build()); startService(new Intent(this, InnerService.class)); } } public static class InnerService extends Service{ @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); //发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示 Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build()); new Handler().postDelayed(new Runnable() { @Override public void run() { stopForeground(true); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.cancel(NOTIFICATION_ID); stopSelf(); } },100); } } }
3.利用JobScheduler机制拉活
JobService和JobScheduler是Android5.0(API 21)引入的新API,我们可以通过该机制来拉活我们的Service所在进程。
首先我们通过继承JobService类来实现自己的Service,记得重写onStartJob和onStopJob方法。然后我们在onCreate方法里面通过JobScheduler来调度我们的Service,值得注意的是需要把参数设置为Persisted:
public class MyJobService extends JobService { private static final String TAG = "MyLog"; @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate"); JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(this, MyJobService.class)); // 设置执行延迟 builder.setOverrideDeadline(0); // 设置持续运行 builder.setPersisted(true); JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(builder.build()); } @Override public boolean onStartJob(JobParameters params) { Log.d(TAG, "onStartJob"); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
使用JobService和把Service设置为Persisted都需要我们在Manifest中配置相应的参数:
然后运行服务即可发现,在Service所在进程被杀掉后,我们的Service会自动重启:
首次运行:
使用kill指令杀掉后:
该方法依然有它的缺陷:
首先,JobService只适用于Android5.0以上的系统;其次,当进程被force-stop指令杀死后,JobService依旧无法拉活进程。