进程保活比较坑,android系统从5.0以前,很多程序员的保活大多都是采取的是android的漏洞,但android系统从5.0以后,很多app都可能被杀死,双进程守护已不能满足进程保活了,我查阅很多资料,对此进行试验,发现这些答案已无法满足5.0以后的进程守护问题。但是android5.0系统以后,本身提供了JobService和JobScheduler,我们可以采取JobScheduler来进行保活:
1.做一个双进程守护
本地服务:
public class LocalService extends Service { private MyBinder binder; private MyServiceConnection conn; private PendingIntent pIntent; @Nullable @Override public IBinder onBind(Intent intent) { return binder; } @Override public void onCreate() { super.onCreate(); if(binder == null){ binder = new MyBinder(); } conn = new MyServiceConnection(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public int onStartCommand(Intent intent, int flags, int startId) { /** * 连接远程服务 */ this.bindService(new Intent(this, RemoteService.class), conn, Context.BIND_IMPORTANT); //提高服务优先级,避免过多被杀掉,采用360的方式 return START_STICKY; } class MyBinder extends IMyAidlInterface.Stub{ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } } class MyServiceConnection implements ServiceConnection { //连接成功 @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { //说明远程服务挂了 LocalService.this.startService(new Intent(LocalService.this, RemoteService.class)); //连接远程服务 LocalService.this.bindService(new Intent(LocalService.this, RemoteService.class), conn, Context.BIND_IMPORTANT); } } }
远程进程:本地服务和远程服务的代码是相同的,仅仅只为验证进程守护
public class RemoteService extends Service { private MyBinder binder; private MyServiceConnection conn; private PendingIntent pIntent; @Nullable @Override public IBinder onBind(Intent intent) { return binder; } @Override public void onCreate() { super.onCreate(); if(binder == null){ binder = new MyBinder(); } conn = new MyServiceConnection(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public int onStartCommand(Intent intent, int flags, int startId) { /** * 连接本地服务 * * 参数Context.BIND_IMPORTANT指定绑定标示符,可提高服务的优先级别 * Android 4.0(API 14) 引入了一些新的标示符: * 1.BIND_ADJUST_WITH_ACTIVITY Service的优先级将相对于其绑定的Activity,Activity到前台,则Service优先级相对提升,Activity到后台,则Servcie优先级相对降低。 * 2.BIND_ABOVE_CLIENT和BIND_IMPORTANT 当你的客户端在前台,这个标示符下的Service也变得重要性相当于前台的Activity,优先级迅速提升。若是BIND_ABOVE_CLIENT,则优先级已经超过了Activity,也就是说Activity要比Service先死,当资源不够的时候。 * 3.BIND_NOT_FOREGROUND 你所绑定的Service优先级永远高不过前台Activity。 * 4.BIND_WAIVE_PRIORITY 绑定的服务不可调整自身的优先级。 */ this.bindService(new Intent(this, LocalService.class), conn, Context.BIND_IMPORTANT); //不要返回super.onStartCommand(……),也是为了提高优先级,避免被清理 return START_STICKY; } class MyBinder extends IMyAidlInterface.Stub{ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } } class MyServiceConnection implements ServiceConnection { //连接成功 @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { //复活本地服务 RemoteService.this.startService(new Intent(RemoteService.this, LocalService.class)); //连接本地服务 RemoteService.this.bindService(new Intent(RemoteService.this, LocalService.class), conn, Context.BIND_IMPORTANT); } } }
分别在将这两个服务在清单文件中进行配置, 清单文件配置的属性 android:process=":remote" 是可以为任意组件和应用指定进程
<service android:name="com.zc9000.vpn.VPNService" android:enabled="true" /> <service android:name="com.service.LocalService"/> <service android:name="com.service.RemoteService" android:process=":remote"/>
上面的双进程绑定只能实现单进程被杀死情况下的重启,如果双进程同时被杀死,就无法实现进程的守护。
JobScheduler和JobSerVice配合之使用才能做到进程的重启
JobService采用了ADIL+Handler的方式来传递消息
代码展示:双进程被杀死后被调用onstopJob,所以我们可以将同时杀死的远程服务和本地服务进行重启
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public class JobHandleService extends JobService { private int kJobId = 0; @Override public void onCreate() { super.onCreate(); Log.e("TAG", "jobService create"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e("TAG", "jobService start"); //通过这种方式来启动我们的服务 scheduleJob(getJobInfo()); return START_NOT_STICKY; } @Override public boolean onStartJob(JobParameters params) { Log.e("TAG555", "job start"); boolean isLocalServiceWork = isServiceWork(this, "com.service.LocalService"); boolean isRemoteServiceWork = isServiceWork(this, "com.service.RemoteService"); Log.i("isIMServiceWork",isIMServiceWork+"---"+isVPNServiceWork); if (!isLocalServiceWork || !isRemoteServiceWork){ this.startService(new Intent(this, LocalService.class)); this.startService(new Intent(this, RemoteService.class)); HooliganActivity. startHooligan(); Toast.makeText(this, "process start", Toast.LENGTH_LONG).show(); } return true; } @Override public boolean onStopJob(JobParameters params) { Log.e("TAG", "job stop"); scheduleJob(getJobInfo()); return true; } public void scheduleJob(JobInfo t){ Log.e("TAG", "Scheduling job"); JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); tm.schedule(t); } public JobInfo getJobInfo(){ JobInfo.Builder builder = new JobInfo.Builder(kJobId++, new ComponentName(this, JobHandleService.class)); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); builder.setPersisted(true); builder.setRequiresCharging(false); builder.setRequiresDeviceIdle(false); builder.setPeriodic(10); return builder.build(); } /** * 判断某个服务是否正在运行的方法 * @param mContext * @param serviceName 是包名+服务的类名(例如:net.loonggg.testbackstage.TestService) * @return true代表正在运行,false代表服务没有正在运行 */ public boolean isServiceWork(Context mContext, String serviceName){ boolean isWork = false; ActivityManager myAM = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(100); if (myList.size() <= 0){ return false; } for (int i = 0; i < myList.size(); i++) { String mName = myList.get(i).service.getClassName().toString(); if (mName.equals(serviceName)){ isWork = true; break; } } return isWork; } }
初始化时进程的重启是必要的所以我们要在每次杀死在打开都会走的界面中开启我们的进程
在这里我是在欢迎界面中做的这个开启的服务,在onCreate()中进行配置
JobScheduler是Job的调度类,负责执行,取消任务等逻辑。
@Override protected void onCreate(Bundle arg0) { super.onCreate(arg0); setContentView(R.layout.em_activity_splash); rootLayout = (RelativeLayout) findViewById(R.id.splash_root); versionText = (TextView) findViewById(R.id.tv_version); this.startService(new Intent(this, JobHandleService.class)); this.startService(new Intent(this, LocalService.class)); this.startService(new Intent(this, RemoteService.class)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE); JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobHandleService.class.getName())) .setPeriodic(10) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); jobScheduler.schedule(jobInfo); } } @Override protected void onCreate(Bundle arg0) { super.onCreate(arg0); setContentView(R.layout.em_activity_splash); rootLayout = (RelativeLayout) findViewById(R.id.splash_root); versionText = (TextView) findViewById(R.id.tv_version); this.startService(new Intent(this, JobHandleService.class)); this.startService(new Intent(this, LocalService.class)); this.startService(new Intent(this, RemoteService.class)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE); JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobHandleService.class.getName())) .setPeriodic(10) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); jobScheduler.schedule(jobInfo); } }
在声明问JobService后,我们还需要对其在清单文件中进行配置
android:permission="android.permission.BIND_JOB_SERVICE"的配置是必须的,少了这个配置会报权限问题
<service android:name="com.service.JobHandleService" android:enabled="true" android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE"> </service>
说起权限问题,我们一定要记住在清单文件中配置权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
上面的方法基本已经可以保证了进程的保活,但是进程保活不代表你启动的进程是没有问题的,是的,我在使用过程中发现,进程被启动起来了,可是当别给我发来消息时,我还接受不到我需要的信息,在查看资料的过成中发现,QQ产生的黑科技,采用的是1像素保活,那么我们是不是也可以通过打开一个透明页面进行保活呢?打开一个透明的activity,相当开启了主线程,主线程打开了,相当整个服务也没拉起,我们可以打开一个透明的界面来重新的开启进程,,而界面时透明的,所以开启程序并不会被发现。
代码如下:我们把他叫做一像素保活
public class HooliganActivity extends Activity { private static HooliganActivity instance; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); System.out.println("HooliganActivity onCreate"); // Toast.makeText(this,"HooliganActivity onCreate", Toast.LENGTH_SHORT).show(); instance = this; Window window = getWindow(); window.setGravity(Gravity.LEFT | Gravity.TOP); WindowManager.LayoutParams params = window.getAttributes(); params.x = 0; params.y = 0; params.height = 1; params.width = 1; window.setAttributes(params); onDestroy(); } /** * 开启保活页面 */ public static void startHooligan() { // Toast.makeText(PChatApplication.getInstance(),"startHooligan", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(PChatApplication.getInstance(), HooliganActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // intent.putExtra("finishApp", true); PChatApplication.getInstance().startActivity(intent); } @Override protected void onDestroy() { super.onDestroy(); System.out.println("HooliganActivity onDestroy"); // Toast.makeText(this,"HooliganActivity onDestroy", Toast.LENGTH_SHORT).show(); instance = null; Intent intent= new Intent(Intent.ACTION_MAIN); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //如果是服务里调用,必须加入new task标识 intent.addCategory(Intent.CATEGORY_HOME); startActivity(intent); } /** * 关闭保活页面 */ public static void killHooligan() { if(instance != null) { // Toast.makeText(PChatApplication.getInstance(),"killHooligan", Toast.LENGTH_SHORT).show(); instance.finish(); } } @Override protected void onResume() { super.onResume(); System.out.println("HooliganActivity onResume"); } }
打开了透明界面,也阻挡了我们的点击事件,在思考时,想法时,掉用home键,就可以直接回到主页面,代码在透明的界面中。
许多人有个习惯,就是长按android的home键,然后清除刚才看的应用,
android:excludeFromRecents="true"的属性的作用恰恰就是让你在长按home键的时候在弹出的应用列表中隐藏你的应用,达到隐藏应用程序进行的目的。
android:exported="false"属性的添加是为了防止外部调用,防止特定敏感操作或者钓鱼欺骗,调用时添加权限
android:finishOnTaskLaunch属性为true的activity中按home键返回到[home screen]屏幕后,再点击该应用的图标启动程序时,则系统会调用该activity的[onDestroy]销毁。因为点击应用的图标启动程序时,重新启动了这个任务。
对于一像素是不可避免的:
<activity android:name="com.service.HooliganActivity" android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard" android:excludeFromRecents="true" android:exported="false" android:finishOnTaskLaunch="false" android:launchMode="singleInstance" android:theme="@style/HooliganActivityStyle"/>
样式(style):
<style name="HooliganActivityStyle"> <item name="android:windowBackground">@color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowNoDisplay">false</item> <item name="android:windowDisablePreview">true</item> </style>实现上面的就能保证部分手机的保活,另一部分是不同的手机厂商对源码做了不同的更改,所以对于不同的手机,这段代码是不可用的。