以前公司需求作一款相似滴滴打車派單的app,其中須要app退到後臺後能一直上傳定位的座標,當有任務派發時,能夠進行語音提醒和dialog彈窗提醒。根據以上的需求,這個需求應用長期在後臺定位,必須保證應用是存活的,以前作過一款滑雪測速的項目,也須要後臺不停的採集gps座標。關於應用保活我嘗試過過一下幾種方案:git
我先說下百度導航的方案和思路,我反覆查看了百度導航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內精度最高的一次定位結果。若是設置其爲true,setOnceLocation(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定位。函數