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

栏目: Android · 发布时间: 6年前

内容简介:之前公司需求做一款类似滴滴打车派单的app,其中需要app退到后台后能一直上传定位的坐标,当有任务派发时,可以进行语音提醒和dialog弹窗提醒。根据以上的需求,这个需求应用长期在后台定位,必须保证应用是存活的,之前做过一款滑雪测速的项目,也需要后台不停的采集gps坐标。关于应用保活我尝试过过一下几种方案:这里的几种方案我测试得出的结论是第四种方案存活时间最长,但不能保证100%的存活率,我使用的是提升service优先级和模仿百度导航来实现的,百度导航如果长期置于后台关屏运行也会被系统干掉我先说下百度导航

之前公司需求做一款类似滴滴打车派单的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内精度最高的一次定位结果。如果设置其为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();
    }
}

复制代码

后台播放音视频文件 工具

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定位。


以上所述就是小编给大家介绍的《Android客户端提高应用存活率方案》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

We Are the Nerds

We Are the Nerds

Christine Lagorio-Chafkin / Hachette Books / 2018-10-2 / USD 18.30

Reddit hails itself as "the front page of the Internet." It's the third most-visited website in the United States--and yet, millions of Americans have no idea what it is. We Are the Nerds is an eng......一起来看看 《We Are the Nerds》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具