阅读 823

Android客户端提高应用存活率方案

之前公司需求做一款类似滴滴打车派单的app,其中需要app退到后台后能一直上传定位的坐标,当有任务派发时,可以进行语音提醒和dialog弹窗提醒。根据以上的需求,这个需求应用长期在后台定位,必须保证应用是存活的,之前做过一款滑雪测速的项目,也需要后台不停的采集gps坐标。关于应用保活我尝试过过一下几种方案:

  • 1.使用aidl双进程守护
  • 2.使用系统的alarmmanager机制进行唤醒保活
  • 3.提升应用的优先级
  • 4.模仿百度导航进行保活

这里的几种方案我测试得出的结论是第四种方案存活时间最长,但不能保证100%的存活率,我使用的是提升service优先级和模仿百度导航来实现的,百度导航如果长期置于后台关屏运行也会被系统干掉


我先说下百度导航的方案和思路,我反复查看了百度导航app在后台运行的效果,发现百度导航在后台播放音频文件,来让系统得知它在运行,提升了应用的优先级,不让系统kill掉它。在不播放路线的时候,它在后台播放无声的音频文件。在得知它的保活方式后,我也开始仿照它的做法来进行实现,发现这样的方式确实可以让应用存活的时间更长,但是这样会比较费电,系统会进行提示高耗电。

首先进入应用我会判断当前状态是否为可接收任务状态,如果可接收则开启service,在后台进行定位,每两秒上传一次定位坐标给后台。直接上代码:

管理是否开启定位服务

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);
    }
} 
复制代码

定位service

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();
    }
}

复制代码

后台播放音视频文件工具类

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唤醒

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这两个类,使用上后效果并不明显。

通过做这个项目,我也思考过一些问题,比如使用websocket长链接,考虑到定位是基于百度或者高德的,也是每隔几秒定位一次,就直接使用了普通的接口。gps定位也不是每秒都定位一次的,并且还容易受到干扰,所以选择了第三方的定位方式,增加定位的准确度。之前我做过一款基于gps定位进行滑雪测速相关项目,对于gps定位进行过一些研究,所以这里不采用手机原始的gps定位。

关注下面的标签,发现更多相似文章
评论