程式保活方案

weixin_34119545發表於2017-12-07

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值降低了,程式的等級得到了相應的提高,變得更難以被回收了,這樣可以一定程度上緩解我們的應用被第三方應用或系統管理工具在鎖屏後為省電而被殺死的情況:
鎖屏前後log
鎖屏前後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
首先我們通過繼承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中配置相應的引數:
manifest
 
然後執行服務即可發現,在Service所在程式被殺掉後,我們的Service會自動重啟:
首次執行:
JobService_first
 
使用kill指令殺掉後:
JobService_after_kill
 
該方法依然有它的缺陷:
首先,JobService只適用於Android5.0以上的系統;其次,當程式被force-stop指令殺死後,JobService依舊無法拉活程式。
 

相關文章