安卓开发后台应用周期循环获取位置信息上报服务器

问题背景

最近有需求,在APP启动后,退到后台,还要能实现周期获取位置信息上报服务器,研究了一下实现方案。

问题分析

一、APP退到后台后网络请求实现

APP退到后台后,实现周期循环发送网络请求。目前尝试了两种方案是OK,如下:

(1)AlarmManager + 前台服务 +广播的方案,可以正常实现,大体思路是,启动一个前台服务,使用AlarmManager发起一个定时广播,然后广播接收器接收到广播后,循环去执行service的操作。

(2)使用jetpeck库提供的worker实现,基于PeriodicWorkRequest实现一个周期执行的任务,比如周期设置为15分钟,可以在后台稳定执行。

二、APP退到后台后获取地理位置实现

APP申请位置时,用户选择了列表中的始终允许后,APP在后台是可以正常获取到位置信息的。不过这里有个坑,因为安卓11版本后退位置信息管控策略进行了更新,如果APP的target sdk版本大于29时,需要分别申请前台位置权限和后台位置权限,本APPtargetsdk是小于等于29的,可以同时申请前台位置权限和后台位置权限。(compileSdkVersion小于29时,会没有这个后台位置权限,需要最好升级到29)

问题解决

下面展示大概的代码,可以参考实现。

(1)引入依赖

复制代码
api('androidx.work:work-runtime:2.0.1')

(2)manifest文件中增加申请权限

复制代码
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 这个权限用于访问GPS定位 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位 -->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

(3)权限申请(同时申请位置权限和后台位置权限)

复制代码
        RxPermissionHelper helper = new RxPermissionHelper(this);
        helper.requestEach(new RxPermissionHelper.PermissionCallback() {
            @Override
            public void granted(String permissionName) {
                LogUtil.writerLog("ACCESS_FINE_LOCATION granted");
            }

            @Override
            public void denied(String permissionName, boolean forever) {

            }

            @Override
            public void result(boolean allGranted) {

            }
        }, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION);

(4)work类:去执行前台服务

复制代码
/**
 * work类执行定时任务
 */
public class SimpleWorker extends Worker {
    private CurPosUtil curPosUtil;

    public SimpleWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }
    @NonNull
    @Override
    public Result doWork() {
        Log.d("baorant", "执行调度任务");
        LogUtil.writerLog("执行调度任务");
        startService();
        return Result.success();
    }

    private void startService() {
//        curPosUtil = new CurPosUtil(getApplicationContext());
        Intent intent = new Intent(getApplicationContext(), RecordService.class);
        getApplicationContext().startService(intent);
    }
}

(5)RecordService前台服务类(需要在manifest文件中配置)

复制代码
/**
 * 一个定时任务
 *
 * 方案:使用前台服务去执行网络请求,定时发送广播,然后在广播接收器中重新启动服务,实现循环后台服务。
 */
public class RecordService extends Service {
    private CurPosUtil curPosUtil;

    /**
     * 每30秒更新一次数据
     */
    private static final int ONE_Miniute= 30 * 1000;
    private static final int PENDING_REQUEST=0;
    int count = 0;

    public RecordService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        curPosUtil = new CurPosUtil(getApplicationContext());
        LogUtil.writerLog("RecordService onCreate");

        if (Build.VERSION.SDK_INT >=    Build.VERSION_CODES.O) {
            String NOTIFICATION_CHANNEL_ID = "package_name";
            String channelName = "My Background Service";
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,channelName, NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.createNotificationChannel(channel);
            Notification notification = new Notification.Builder(this,NOTIFICATION_CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_dial_icon)  // the status icon
                    .setWhen(System.currentTimeMillis())  // the time stamp
                    .setContentText("定时服务正在运行")  // the contents of the entry
                    .build();

            startForeground(2, notification);
        }
    }

    /**
     * 调用Service都会执行到该方法
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtil.writerLog("RecordService:  onStartCommand");

        // 这里模拟后台操作
        initPos();

        return super.onStartCommand(intent, flags, startId);
    }

    private void initPos() {
        curPosUtil = new CurPosUtil(this);
        curPosUtil.getCurPos(new CurPosUtil.CurPosCallback() {
            @Override
            public void getCurPos(double s, double s1, String s2) {
                LogUtil.writerLog(DateUtil.timeToDate(String.valueOf(System.currentTimeMillis())));
                LogUtil.writerLog("getCurPos: " + s + " " + s1 + " " + s2);
                commonLogin(s + " " + s1 + " " + s2);
            }
        });
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public void commonLogin(String position) {
        RetrofitHelper.getInstance().login(position, "", "", "",
                        "", "", "", "")
                .subscribe(new Observer<Boolean>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Boolean aBoolean) {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
}

(6)activity中启动周期任务,周期15分钟循环执行

复制代码
 PeriodicWorkRequest.Builder request =
                        new PeriodicWorkRequest.Builder(SimpleWorker.class, 15
                                , TimeUnit.MINUTES).addTag("simpleTask");
                LogUtil.writerLog(DateUtil.timeToDate(String.valueOf(System.currentTimeMillis())));
                LogUtil.writerLog("点击执行task");
                WorkManager.getInstance().enqueue(request.build() );

(7)LogUtil工具类,输出日志到文件,方便定位

复制代码
/**
 * 日志工具,输出日志到文件
 */
public class LogUtil {

    /**
     * 路径 "/data/data/com包名/files/backLogTest"
     *
     * @param msg 需要打印的内容
     */
    public static void writerLog(String msg) {
        Log.d("baorant", msg);
        // 保存到的文件路径
        final String filePath = App.getContext().getFilesDir().getPath();
        FileWriter fileWriter;
        BufferedWriter bufferedWriter = null;

        try {
            // 创建文件夹
            File dir = new File(filePath, "backLogTest");
            if (!dir.exists()) {
                dir.mkdir();
            }
            // 创建文件
            File file = new File(dir, "lowTemperature.txt");
            if (!file.exists()) {
                file.createNewFile();
            }
            // 写入日志文件
            fileWriter = new FileWriter(file, true);
            bufferedWriter = new BufferedWriter(fileWriter);
            bufferedWriter.write( msg + "=======时间 :"+ getCurrentTime()+ "\n");
            bufferedWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }


    }

    private static String getCurrentTime() {
        Calendar calendar = Calendar.getInstance();
        @SuppressLint("SimpleDateFormat")
        SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return  sdf.format(calendar.getTime());
    }
}

问题总结

运行结果如下:

如结果所示,基于该方案可以实现APP在后台,周期循环获取位置信息并进行上报,有兴趣的同学可以进一步深入研究。

相关推荐
Sinclair1 小时前
内网服务器离线安装 Nginx+PHP+MySQL 的方法
运维
叶落阁主1 小时前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
雨白1 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk1 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING2 小时前
RN容器启动优化实践
android·react native
恋猫de小郭5 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
茶杯梦轩9 小时前
从零起步学习RabbitMQ || 第二章:RabbitMQ 深入理解概念 Producer、Consumer、Exchange、Queue 与企业实战案例
服务器·后端·消息队列
Kapaseker10 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴10 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭20 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter