Android客戶端提升應用存活率方案

以前公司需求作一款相似滴滴打車派單的app,其中須要app退到後臺後能一直上傳定位的座標,當有任務派發時,能夠進行語音提醒和dialog彈窗提醒。根據以上的需求,這個需求應用長期在後臺定位,必須保證應用是存活的,以前作過一款滑雪測速的項目,也須要後臺不停的採集gps座標。關於應用保活我嘗試過過一下幾種方案:git

  • 1.使用aidl雙進程守護
  • 2.使用系統的alarmmanager機制進行喚醒保活
  • 3.提高應用的優先級
  • 4.模仿百度導航進行保活

這裏的幾種方案我測試得出的結論是第四種方案存活時間最長,但不能保證100%的存活率,我使用的是提高service優先級和模仿百度導航來實現的,百度導航若是長期置於後臺關屏運行也會被系統幹掉


我先說下百度導航的方案和思路,我反覆查看了百度導航app在後臺運行的效果,發現百度導航在後臺播放音頻文件,來讓系統得知它在運行,提高了應用的優先級,不讓系統kill掉它。在不播放路線的時候,它在後臺播放無聲的音頻文件。在得知它的保活方式後,我也開始仿照它的作法來進行實現,發現這樣的方式確實可讓應用存活的時間更長,可是這樣會比較費電,系統會進行提示高耗電。web

首先進入應用我會判斷當前狀態是否爲可接收任務狀態,若是可接收則開啓service,在後臺進行定位,每兩秒上傳一次定位座標給後臺。直接上代碼:緩存

管理是否開啓定位服務bash

public class PollingUtils {

    //開啓輪詢服務  
    public static void startPollingService(Context context, int seconds, Class<?> cls, String action) {
        //獲取AlarmManager系統服務  
        AlarmManager manager = (AlarmManager) context
                .getSystemService(Context.ALARM_SERVICE);

        //包裝須要執行Service的Intent  
        Intent intent = new Intent(context, cls);
        intent.setAction(action);
        PendingIntent pendingIntent = PendingIntent.getService(context, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);

        //觸發服務的起始時間  
        long triggerAtTime = SystemClock.elapsedRealtime();

        //使用AlarmManger的setRepeating方法設置按期執行的時間間隔(seconds秒)和須要執行的Service  
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime,
                seconds * 1000, pendingIntent);
    }

    //中止輪詢服務  
    public static void stopPollingService(Context context, Class<?> cls, String action) {
        AlarmManager manager = (AlarmManager) context
                .getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, cls);
        intent.setAction(action);
        PendingIntent pendingIntent = PendingIntent.getService(context, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        //取消正在執行的服務  
        manager.cancel(pendingIntent);
        context.stopService(intent);
    }
} 
複製代碼

定位servicewebsocket

public class CourierLocationService extends BaseService implements AMapLocationListener {

    //    // 定位相關
//    private LocationClient mLocClient;
//    //
    private int msgId = -1;
    private PowerManager.WakeLock wakeLock = null;
    private AMapLocation mLocation = null;
    private MediaPlayer mediaPlayer = null;
    //
//    @Override
//    public void onCreate() {
//        super.onCreate();
//        // 定位初始化
////        mLocClient = new LocationClient(this);
////        mLocClient.registerLocationListener(this);
////        LocationClientOption option = new LocationClientOption();
////        option.setIsNeedAddress(true);// 設置之後,請求結果 BDLocation#getCity 就不爲null了
////        option.setOpenGps(true);// 打開gps
////        option.setCoorType("bd09ll"); // 設置座標類型
////        option.setScanSpan(10000);// 定位頻率
////        mLocClient.setLocOption(option);
////        mLocClient.start();
//        initBaiDu();
//        //
//        isStarted = true;
//    }
//
//    @Override
//    public int onStartCommand(Intent intent, int flags, int startId) {
//        if (intent != null) {
//            msgId = intent.getIntExtra("msgId", -1);
//        }
//        // 刷新定位
//        if (mLocClient != null && mLocClient.isStarted()) {
//            mLocClient.requestLocation();
//        }
//        return super.onStartCommand(intent, flags, startId);
//    }
//
//    /**
//     * 初始化百度地圖
//     */
//    private void initBaiDu() {
//        // 定位初始化
//        mLocClient = new LocationClient(this);
//        mLocClient.registerLocationListener(locationListener);
//        LocationClientOption option = new LocationClientOption();
//        option.setIsNeedAddress(true);// 設置之後,請求結果 BDLocation#getCity 就不爲null了
////                option.setOpenGps(true);// 打開gps
//        option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);
//        option.setCoorType("bd09ll"); // 設置座標類型
//        option.setScanSpan(20000);// 定位頻率
//        //可選,定位SDK內部是一個service,並放到了獨立進程。
//        //設置是否在stop的時候殺死這個進程,默認(建議)不殺死,即setIgnoreKillProcess(true)
//        option.setIgnoreKillProcess(true);
//        mLocClient.setLocOption(option);
//        mLocClient.start();
//    }
//
//    /**
//     * 定位SDK監聽函數
//     */
//    LocationListener locationListener = new LocationListener() {
//        @Override
//        public void onReceiveLocation(BDLocation location) {
//            int errorCode = location.getLocType();
//            Log.d("33333", "錯誤碼:" + errorCode);
//            if (location == null || Str.isEmpty(location.getCity())) {
//                // 刷新定位
//                if (mLocClient != null) {
//                    SDKInitializer.initialize(BeeApplication.getContext());
//                    mLocClient.unRegisterLocationListener(locationListener);
//                    mLocClient.stop();
//                    mLocClient = null;
//                    initBaiDu();
//                } else {
//                    initBaiDu();
//                }
//            } else {
//                Message msg = Message.obtain();
//                if (msgId == -1) {
//                    msg.what = MsgID.location_baidu;
//                } else {
//                    msg.what = msgId;
//                    if (msgId != MsgID.courier_location_upload_data) {
//                        msgId = -1;// reset
//                    }
//                }
//                msg.obj = location;
//                HandlerMgr.sendMessage(msg, 0);
//            }
//            //
//            // int userId = ((BeeApplication) getApplication()).getUser().getId();
//            // AppHttp.getInstance().beat(userId, location.getLatitude(), location.getLongitude());
//        }
//    };
//
//    @Override
//    public void onDestroy() {
//        super.onDestroy();
//        msgId = -1;// reset
//        mLocClient.unRegisterLocationListener(locationListener);
//        // 退出時銷燬定位
//        mLocClient.stop();
//        //
//        isStarted = false;
//    }

    //聲明AMapLocationClient類對象
    private AMapLocationClient mLocationClient = null;


    @Override
    public void onCreate() {
        super.onCreate();
        Notify.getInstance().startForeground(this);
        initGaoDe();
    }

    private void initGaoDe() {
        //初始化定位
        mLocationClient = new AMapLocationClient(getApplicationContext());
        //設置定位回調監聽
        mLocationClient.setLocationListener(this);
        AMapLocationClientOption option = new AMapLocationClientOption();
        /**
         * 設置定位場景,目前支持三種場景(簽到、出行、運動,默認無場景)
         */
        option.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Sport);
        //設置定位模式爲AMapLocationMode.Device_Sensors,僅設備模式。
//        option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Device_Sensors);
        //獲取一次定位結果:
        //該方法默認爲false。
        option.setOnceLocation(false);

        //獲取最近3s內精度最高的一次定位結果:
        //設置setOnceLocationLatest(boolean b)接口爲true,啓動定位時SDK會返回最近3s內精度最高的一次定位結果。若是設置其爲truesetOnceLocation(boolean b)接口也會被設置爲true,反之不會,默認爲false。
//        option.setOnceLocationLatest(true);
        //設置定位間隔,單位毫秒,默認爲2000ms,最低1000ms。
        option.setInterval(10000);
        //設置是否返回地址信息(默認返回地址信息)
        option.setNeedAddress(true);
        //設置是否容許模擬位置,默認爲true,容許模擬位置
        option.setMockEnable(true);
//        option.setGpsFirst(true);
        //單位是毫秒,默認30000毫秒,建議超時時間不要低於8000毫秒。
//        option.setHttpTimeOut(20000);
        //關閉緩存機制
//        option.setLocationCacheEnable(false);
        if (null != mLocationClient) {
            mLocationClient.setLocationOption(option);
            //設置場景模式後最好調用一次stop,再調用start以保證場景模式生效
            mLocationClient.stopLocation();
            mLocationClient.startLocation();
        }
    }

    //
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            msgId = intent.getIntExtra("msgId", -1);
        }
        flags = START_STICKY;
        acquireWakeLock();
        // 刷新定位
        if (mLocationClient != null && mLocationClient.isStarted()) {
            mLocationClient.startLocation();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onLocationChanged(AMapLocation location) {
        int errorCode = location.getErrorCode();
//        DealTaskService.writerLogToFile("定位的錯誤碼:" + errorCode + ",定位的 MSGID:" + msgId);
        Log.d("33333", "錯誤碼:" + errorCode);
        if (location == null || Str.isEmpty(location.getCity()) || errorCode != 0) {
            mLocationClient.stopLocation();
            mLocationClient.startLocation();
        } else {
            mLocation = location;
        }
        if (mLocation != null) {
            Message msg = Message.obtain();
            if (msgId == -1) {
                msg.what = MsgID.location_baidu;
            } else {
                msg.what = msgId;
                if (msgId != MsgID.courier_location_upload_data) {
                    msgId = MsgID.courier_location_upload_data;// reset
                }
            }
            msg.obj = location;
            HandlerMgr.sendMessage(msg, 0);
        }
        //
        // int userId = ((BeeApplication) getApplication()).getUser().getId();
        // AppHttp.getInstance().beat(userId, location.getLatitude(), location.getLongitude());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        releaseWakeLock();
        if (mLocationClient != null) {
            mLocationClient.unRegisterLocationListener(this);
            mLocationClient.stopLocation();//中止定位後,本地定位服務並不會被銷燬
            mLocationClient.onDestroy();//銷燬定位客戶端,同時銷燬本地定位服務。
        }
        pausePlayer();
    }

    /**
     * PARTIAL_WAKE_LOCK:保持CPU 運轉,屏幕和鍵盤燈有多是關閉的。
     * SCREEN_DIM_WAKE_LOCK:保持CPU 運轉,容許保持屏幕顯示但有多是灰的,容許關閉鍵盤燈
     * SCREEN_BRIGHT_WAKE_LOCK:保持CPU 運轉,容許保持屏幕高亮顯示,容許關閉鍵盤燈
     * FULL_WAKE_LOCK:保持CPU 運轉,保持屏幕高亮顯示,鍵盤燈也保持亮度
     * ACQUIRE_CAUSES_WAKEUP:強制使屏幕亮起,這種鎖主要針對一些必須通知用戶的操做.
     * ON_AFTER_RELEASE:當鎖被釋放時,保持屏幕亮起一段時間
     */
    private void acquireWakeLock() {
        if (null == wakeLock) {
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
                    | PowerManager.ON_AFTER_RELEASE, getClass()
                    .getCanonicalName());
            if (null != wakeLock) {
                //   Log.i(TAG, "call acquireWakeLock");
                Log.d("33333", "call acquireWakeLock");
                wakeLock.acquire();
            }
        }
    }

    // 釋放設備電源鎖
    private void releaseWakeLock() {
        if (null != wakeLock && wakeLock.isHeld()) {
            Log.d("33333", "call releaseWakeLock");
            //   Log.i(TAG, "call releaseWakeLock");
            wakeLock.release();
            wakeLock = null;
        }
    }

//    /**
//     * 設置應用進入後臺,播放音頻來進行cpu不休眠,進行應用保活
//     */
//    private void setAppBackgroundPlayer() {
//        MediaPlayerUtils.getInstance().playerMusic("courier_silence.mp3", true);
//    }

    private void pausePlayer() {
        MediaPlayerUtils.getInstance().destoryPlayer();
    }
}

複製代碼

後臺播放音視頻文件工具類app

public class MediaPlayerUtils {
    private static final String TAG = MediaPlayerUtils.class.getSimpleName();
    private static MediaPlayer mediaPlayer = null;

    private static MediaPlayerUtils mediaPlayerUtils = null;
    private PlayerMediaAsync playerMediaAsync = null;
    private boolean isLooping = true;

    public static MediaPlayerUtils getInstance() {
        if (mediaPlayerUtils == null) {
            synchronized (MediaPlayerUtils.class) {
                if (mediaPlayerUtils == null) {
                    mediaPlayerUtils = new MediaPlayerUtils();
                    mediaPlayer = new MediaPlayer();
                }
            }
        }
        return mediaPlayerUtils;
    }

    public MediaPlayer getMediaPlayer() {
        if (mediaPlayer == null) {
            mediaPlayer = new MediaPlayer();
        }
        return mediaPlayer;
    }

    public synchronized void playerMusic(String fileName, boolean isLooping) {
        this.isLooping = isLooping;
        if (playerMediaAsync != null) {
            playerMediaAsync.cancel(true);
            playerMediaAsync = null;
        }
        playerMediaAsync = new PlayerMediaAsync();
        playerMediaAsync.execute(fileName);
    }

    class PlayerMediaAsync extends AsyncTask<String, String, String> {

        @Override
        protected String doInBackground(String... params) {
            try {
                getMediaPlayer();
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.stop();
                    mediaPlayer.reset();
                }
                AssetFileDescriptor fileDescriptor = BeeApplication.getContext().getAssets().openFd(params[0]);
                mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),
                        fileDescriptor.getStartOffset(),
                        fileDescriptor.getLength());
                mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//STREAM_ALARM
                mediaPlayer.prepare();
                mediaPlayer.setLooping(isLooping);
                mediaPlayer.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    public void destoryPlayer() {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
        if (playerMediaAsync != null) {
            playerMediaAsync.cancel(true);
            playerMediaAsync = null;
        }
    }

}
複製代碼

使用定時器進行cpu喚醒socket

public class TimerService extends Service {

    public static final String ACTION = "com.iseastar.guojiang.app.TimerService";
    private Timer timer = null;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Notify.getInstance().startForeground(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (timer == null) {
            timer = new Timer(true);
        }
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Log.d("33333", "TimerService:onHandleIntent()");
                Log.d("33333", ActivityMgr.isServiceRunning(BeeApplication.getContext(), "com.iseastar.guojiang.newcabinet.DealTaskService"));
                if (!ActivityMgr.isServiceRunning(BeeApplication.getContext(), "com.iseastar.guojiang.newcabinet.DealTaskService")) {
                    Intent intent = new Intent(getApplication(), DealTaskService.class);
                    intent.setPackage(getPackageName());
                    intent.putExtra("isStop", false);
                    startService(intent);
                    DealTaskService.startWorkLocation(false);
                }

            }
        }, new Date(), 10000);
        flags = START_STICKY;
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
        super.onDestroy();
    }
}
複製代碼

通過我屢次驗證,發現使用timer喚醒cpu並很差使,你們能夠選擇不實用TimerService和PollingUtils這兩個類,使用上後效果並不明顯。ide

經過作這個項目,我也思考過一些問題,好比使用websocket長連接,考慮到定位是基於百度或者高德的,也是每隔幾秒定位一次,就直接使用了普通的接口。gps定位也不是每秒都定位一次的,而且還容易受到干擾,因此選擇了第三方的定位方式,增長定位的準確度。以前我作過一款基於gps定位進行滑雪測速相關項目,對於gps定位進行過一些研究,因此這裏不採用手機原始的gps定位。函數

相關文章
相關標籤/搜索