03.app保活解決方案

黑夜路口發表於2018-04-25

最近針對我們專案中app經常收不到推送的問題作了一些處理,增加app的保活管理。我們知道當安卓程式退到後臺之後,很容易被系統殺死,這個時候推送訊息一般都是收不到的。我也觀察了一些比較主流的app,像同花順,釘釘,甚至是支付寶我都很少在後臺收到過訊息,尤其是支付寶計步功能老是不準,很有可能就是這種問題導致的

當然沒有百分之百可以實現保活的解決方案,即便是從ndk層面去進行,但至少我們要盡一些努力,我採用的是雙程式守護+1畫素Activity的實現方式

雙程式守護:

開啟兩個service,分別在兩個程式中執行,啟動一個service時,通過aidl的方式將它和另一個service繫結在一起,當其中一個service被殺掉的時候,另一個service中重新將它啟動

service1啟動繫結service2

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //提高程式優先順序
        startForeground(GuardServiceId2,new Notification());
        //繫結建立連結
        bindService(new Intent(getApplicationContext(),GuardService2.class),connection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

service1和service2的連結斷開時,說明service2被殺死,這時重新繫結

private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            ArLog.i("TAG", "connect to guardservice2");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            // 斷開連結 ,重新啟動,重新繫結
            startService(new Intent(getApplicationContext(), GuardService2.class));
            bindService(new Intent(getApplicationContext(),GuardService2.class),connection, Context.BIND_IMPORTANT);
            ArLog.i("TAG", "disconnect from guardservice2");
        }
    };

service2繫結service1的邏輯同上

這樣一來,基本上在一定程度上可以保證app常駐記憶體了,保險起見,我們再加一層保護,建立兩個JobService
分別位於上邊兩個程式中(安卓5.0以上適用),開啟一個輪巡任務不斷的檢查service存活情況,如果不在了,啟動它,注意JobService需要許可權

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 開啟一個輪尋
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId,new ComponentName(this,JobWakeUpService1.class));
        //設定每兩秒鐘一次
        jobBuilder.setPeriodic(2000);
        jobScheduler.schedule(jobBuilder.build());
        ArLog.i("TAG", "start JobWakeUpService1");
        return START_STICKY;
    }

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        // 開啟定時任務,定時輪尋 , 看MessageService有沒有被殺死
        // 如果殺死了啟動  輪尋onStartJob

        // 判斷服務有沒有在執行
        boolean messageServiceAlive = serviceAlive(GuardService1.class.getName());
        if(!messageServiceAlive){
            ArLog.i("TAG", "GuardService1 is killed ,restart it");
            startService(new Intent(this,GuardService1.class));
        }
        boolean JobWakeUpService2Alive = serviceAlive(JobWakeUpService2.class.getName());
        if(!JobWakeUpService2Alive){
            ArLog.i("TAG", "JobWakeUpService2 is killed ,restart it");
            startService(new Intent(this,JobWakeUpService2.class));
        }
        return false;
    }

1畫素Activity:

監聽系統鎖屏訊息,在螢幕鎖定的時候開啟一個Activity,這個Activity只有一個畫素大小,當螢幕開啟的時候再關閉這個Activity,達到app一直位於前臺程式的目的,提高程式的優先順序,降低系統殺死app的概率,在這個Activity啟動時設定app只有一個畫素大小,既可以減少佔用空間,也可以防止開鎖屏期間被使用者發現這個奇怪的頁面

        Window window = getWindow();
        window.setGravity(Gravity.LEFT & Gravity.TOP);
        WindowManager.LayoutParams attributes = window.getAttributes();
        attributes.x = 0;
        attributes.y = 0;
        attributes.width = 1;
        attributes.height = 1;
        window.setAttributes(attributes);

在AndroidManifest中配置

<activity android:name=".general.guard.DaemonActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
            android:excludeFromRecents="true"
            android:exported="false"
            android:finishOnTaskLaunch="false"
            android:launchMode="singleInstance"
            android:theme="@style/DaemonActivityStyle"/>

完整程式碼:

GuardService1

public class GuardService1 extends Service {
    private final int GuardServiceId2 = 2;
    @Override
    public void onCreate() {
        super.onCreate();
        ArLog.i("TAG", "GuardService1 wait for signal");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //提高程式優先順序
        startForeground(GuardServiceId2,new Notification());
        //繫結建立連結
        bindService(new Intent(getApplicationContext(),GuardService2.class),connection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }
    @Override
    public IBinder onBind(Intent intent) {
        return new GuardAIDL.Stub(){};
    }
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            ArLog.i("TAG", "connect to guardservice2");
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            // 斷開連結 ,重新啟動,重新繫結
            startService(new Intent(getApplicationContext(), GuardService2.class));
            bindService(new Intent(getApplicationContext(),GuardService2.class),connection, Context.BIND_IMPORTANT);
            ArLog.i("TAG", "disconnect from guardservice2");
        }
    };
    @Override
    public void onDestroy() {
        ArLog.i("TAG", "guardservice2 is onDestroy");
        unbindService(connection);
        super.onDestroy();
    }
}

GuardService2

public class GuardService2 extends Service {
    private final int GuardServiceId1 = 1;
    @Override
    public void onCreate() {
        super.onCreate();
        ArLog.i("TAG", "GuardService2 wait for signal");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //提高程式優先順序
        startForeground(GuardServiceId1,new Notification());
        //繫結建立連結
        bindService(new Intent(getApplicationContext(),GuardService1.class),connection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }
    @Override
    public IBinder onBind(Intent intent) {
        return new GuardAIDL.Stub(){};
    }
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            ArLog.i("TAG", "connect to guardservice1");
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            // 斷開連結 ,重新啟動,重新繫結
            startService(new Intent(getApplicationContext(), GuardService1.class));
            bindService(new Intent(getApplicationContext(),GuardService1.class),connection, Context.BIND_IMPORTANT);
            ArLog.i("TAG", "disconnect from guardservice1");
        }
    };
    @Override
    public void onDestroy() {
        ArLog.i("TAG", "guardservice1 is onDestroy");
        unbindService(connection);
        super.onDestroy();
    }
}

JobWakeUpService1

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobWakeUpService1 extends JobService {
    private int jobId = 12;
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 開啟一個輪尋
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId,new ComponentName(this,JobWakeUpService1.class));
        //設定每兩秒鐘一次
        jobBuilder.setPeriodic(2000);
        jobScheduler.schedule(jobBuilder.build());
        ArLog.i("TAG", "start JobWakeUpService1");
        return START_STICKY;
    }
    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        // 開啟定時任務,定時輪尋 , 看MessageService有沒有被殺死
        // 如果殺死了啟動  輪尋onStartJob
        // 判斷服務有沒有在執行
        boolean messageServiceAlive = serviceAlive(GuardService1.class.getName());
        if(!messageServiceAlive){
            ArLog.i("TAG", "GuardService1 is killed ,restart it");
            startService(new Intent(this,GuardService1.class));
        }
        boolean JobWakeUpService2Alive = serviceAlive(JobWakeUpService2.class.getName());
        if(!JobWakeUpService2Alive){
            ArLog.i("TAG", "JobWakeUpService2 is killed ,restart it");
            startService(new Intent(this,JobWakeUpService2.class));
        }
        return false;
    }
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        return false;
    }
    /**
     * 判斷某個服務是否正在執行的方法
     * @param serviceName
     *            是包名+服務的類名(例如:net.loonggg.testbackstage.TestService)
     * @return true代表正在執行,false代表服務沒有正在執行
     */
    private boolean serviceAlive(String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager)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;
    }
}

JobWakeUpService2

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobWakeUpService2 extends JobService {
    private int jobId = 13;
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 開啟一個輪尋
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId,new ComponentName(this,JobWakeUpService2.class));
        //設定每兩秒鐘一次
        jobBuilder.setPeriodic(2000);
        jobScheduler.schedule(jobBuilder.build());
        ArLog.i("TAG", "start JobWakeUpService2");
        return START_STICKY;
    }
    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        // 開啟定時任務,定時輪尋 , 看MessageService有沒有被殺死
        // 如果殺死了啟動  輪尋onStartJob
        // 判斷服務有沒有在執行
        boolean messageServiceAlive = serviceAlive(GuardService2.class.getName());
        if(!messageServiceAlive){
            ArLog.i("TAG", "GuardService2 is killed ,restart it");
            startService(new Intent(this,GuardService1.class));
        }
        boolean JobWakeUpService1Alive = serviceAlive(JobWakeUpService1.class.getName());
        if(!JobWakeUpService1Alive){
            ArLog.i("TAG", "JobWakeUpService1 is killed ,restart it");
            startService(new Intent(this,JobWakeUpService1.class));
        }
        return false;
    }
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        return false;
    }
   /**
     * 判斷某個服務是否正在執行的方法
     * @param serviceName
     *            是包名+服務的類名(例如:net.loonggg.testbackstage.TestService)
     * @return true代表正在執行,false代表服務沒有正在執行
     */
    private boolean serviceAlive(String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager)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;
    }
}

DaemonActivity

public class DaemonActivity extends AppCompatActivity {
    private static final java.lang.String TAG = "DaemonActivity";
    private static DaemonActivity context;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.context = this;
        Window window = getWindow();
        window.setGravity(Gravity.LEFT & Gravity.TOP);
        WindowManager.LayoutParams attributes = window.getAttributes();
        attributes.x = 0;
        attributes.y = 0;
        attributes.width = 1;
        attributes.height = 1;
        window.setAttributes(attributes);
    }
    /**
     * 監聽手機的鎖屏和開屏廣播,鎖屏後呼叫start開啟這個activity
     * 呼叫此方法時,activity未啟動,不能使用this.context,只能用application的
     */
    public static void startDaemon(){
        Intent intent = new Intent(SightPlusApplication.getContext(),DaemonActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        SightPlusApplication.getContext().startActivity(intent);
        ArLog.i(TAG,"startDaemon");
    }
    /**
     * 監聽手機的鎖屏和開屏廣播,開屏後呼叫stop銷燬這個activity
     * 呼叫此方法時,可以使用activity的context
     */
    public static void stopDaemon(){
        if (context != null){
            ArLog.i(TAG,"stopDaemon");
            context.finish();
        }
    }
}

AndroidManifest配置

<!--雙程式守護-->
        <service
            android:name="cn.easyar.sightplus.general.guard.GuardService1"
            android:enabled="true"
            android:exported="true"
            android:process=":Service1" />
        <service
            android:name="cn.easyar.sightplus.general.guard.GuardService2"
            android:enabled="true"
            android:exported="true"
            android:process=":Service2" />
        <service
            android:name="cn.easyar.sightplus.general.guard.JobWakeUpService1"
            android:enabled="true"
            android:exported="true"
            android:process=":Service1"
            android:permission="android.permission.BIND_JOB_SERVICE" />

        <service
            android:name="cn.easyar.sightplus.general.guard.JobWakeUpService2"
            android:enabled="true"
            android:exported="true"
            android:process=":Service2"
            android:permission="android.permission.BIND_JOB_SERVICE" />
        <!--1畫素Activity-->
        <activity android:name=".general.guard.DaemonActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
            android:excludeFromRecents="true"
            android:exported="false"
            android:finishOnTaskLaunch="false"
            android:launchMode="singleInstance"
            android:theme="@style/DaemonActivityStyle"/>

增加一個管理類GuardAppManager一鍵啟動和關閉

public class GuardAppManager {
    private GuardAppManager(){}
    private static volatile GuardAppManager mInstance;
    private BootCompleteReceiver mReceiver;

    public static GuardAppManager getInstance(){
        if (mInstance == null){
            synchronized (GuardAppManager.class){
                if (mInstance == null){
                    return new GuardAppManager();
                }
            }
        }
        return mInstance;
    }
    public class BootCompleteReceiver extends BroadcastReceiver {
        @Override public void onReceive(Context context, Intent intent) {
            if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                ArLog.i("TAG","ACTION_SCREEN_OFF");
                DaemonActivity.startDaemon();
            }
            else if(intent.getAction().equals(Intent.ACTION_SCREEN_ON)){
                ArLog.i("TAG","ACTION_SCREEN_ON");
                DaemonActivity.stopDaemon();
            }
        }
    }
    /**
     * 開啟守護
     * @param context
     */
    public void start(Context context){
        context.startService(new Intent(context, GuardService1.class));
        context.startService(new Intent(context, GuardService2.class));
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            //必須大於5.0
            context.startService(new Intent(context,JobWakeUpService2.class));
            context.startService(new Intent(context,JobWakeUpService1.class));
        }
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mReceiver = new BootCompleteReceiver();
        context.registerReceiver(mReceiver,filter);
    }
    /**
     * 關閉守護
     * @param context
     */
    public void stop(Context context){
        if (mReceiver != null){
            context.unregisterReceiver(mReceiver);
        }
    }
}

開啟守護

GuardAppManager.getInstance().start(this);

關閉守護

GuardAppManager.getInstance().stop(this);


相關文章