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

news2024/11/26 0:57:15

问题背景

最近有需求,在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在后台,周期循环获取位置信息并进行上报,有兴趣的同学可以进一步深入研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/785063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sui Builder House巴黎站精彩集锦

Sui Builder House巴黎站于7月19日圆满结束&#xff0c;Mysten Labs联合创始人兼CTO的Sam Blackshear在活动上发表了主题演讲。两天的Builder House活动还邀请了Mysten Labs的其他杰出成员分享Sui的发展情况和近期进展&#xff0c;社区成员展示了自己项目并提供见解&#xff0c…

C++继承体系中,基类析构函数请加上virtual,设置为虚函数

为什么建议在存在继承体系时刻我们的类的析构函数加上virtual呢&#xff1f; 大家看段代码。 咋一看&#xff0c;没什么毛病这段代码&#xff0c;让我们画图理解下。 紫框中的前4个字节指向new开辟的空间。 我们知道&#xff0c;当基类A指针指向基类B时候会发生切片 当我们del…

小程序如何修改商品

​商家可能会遇到需要修改产品信息的情况。无论是价格调整、库存更新还是商品描述的修改&#xff0c;小程序提供了简便的方式来帮助你们完成这些操作。下面是一些简单的步骤和注意事项&#xff0c;帮助你们顺利地修改商品。 一、进入商品管理页面 在个人中心点击管理入口&…

工厂电力监控解决方案

1、概述 电力监控系统实现对变压器、柴油发电机、断路器以及其它重要设备进行监视、测量、记录、报警等功能&#xff0c;并与保护设备和远方控制中心及其他设备通信&#xff0c;实时掌握供电系统运行状况和可能存在的隐患&#xff0c;快速排除故障&#xff0c;提高工厂供电可靠…

2023年Q2京东环境电器市场数据分析(京东数据产品)

今年Q2&#xff0c;环境电器市场中不少类目表现亮眼&#xff0c;尤其是以净水器、空气净化器、除湿机等为代表的环境健康电器。此外&#xff0c;像冷风扇这类具有强季节性特征的电器也呈现出比较好的增长态势。 接下来&#xff0c;结合具体数据我们一起来分析Q2环境电器市场中…

承接箱体透明拼接屏项目时,需要注意哪些事项?

承接箱体透明拼接屏项目时&#xff0c;需要注意以下事项&#xff1a; 确定需求&#xff1a;在承接箱体透明拼接屏项目之前&#xff0c;需要明确客户的需求&#xff0c;包括屏幕的大小、分辨率、亮度、色彩等参数&#xff0c;以及使用的环境、观看距离和观看角度等。 材料选择&…

图文教程:如何在 3DS Max 中创建3D迷你卡通房屋

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 在本教程中&#xff0c;我们将学习如何创建一个有趣的、低多边形的迷你动画房子&#xff0c;你可以在自己的插图或视频游戏项目中使用它。您将学习的一些技能将包括创建基本的3D形状和基本的建模技术。让我…

最简单的固定表格列实现

ref: https://dev.to/nicolaserny/table-with-a-fixed-first-column-2c5b 假设我们现在有这样一个表格 <table><thead><tr><th>姓名</th><th>性别</th><th>民族</th><th>年龄</th><th>籍贯</th>…

好用的敏捷开发项目管理工具有哪些?这3款真的绝绝子!

随着数字化的转型和企业团队成员不断追求高效的工作效率&#xff0c;越来越多优质的敏捷开发项目管理工具&#xff0c;深受广大管理者的青睐。今天我将通过这篇文章为大家介绍3款非常好用的开发项目管理工具&#xff0c;建议收藏起来&#xff01; ​ 1.boardmix boardmix博思…

Jmeter 中 Beanshell 的使用

目录 前言&#xff1a; Beanshell 介绍 常用内置变量 log vars 和 props vars 常用方法&#xff1a; props 常用方法&#xff1a; prev 综合运用 前言&#xff1a; JMeter 是一个广泛使用的性能测试工具&#xff0c;它支持许多不同的测试技术和方法。其中&#xff0c…

浏览器协议TCP详解

浏览器协议TCP详解 浏览器进程负责存储、界面、下载等管理。在渲染进程中&#xff0c;运行着熟知的主线程、合成线程、JavaScript 解释器、排版引擎等。 浏览器进程处理用户在地址栏的输入&#xff0c;然后将 URL 发送给网络进程。网络进程发送 URL 请求&#xff0c;在接收到响…

【Yolov8自动标注数据集完整教程】

Yolov8自动标注数据集完整教程 1 前言2 先手动标注数据集&#xff0c;训练出初步的检测模型2.1 手动标注数据集2.2 Yolov8环境配置2.2.1 Yolov8下载2.2.2 Yolov8环境配置 2.3 Yolov8模型训练&#xff0c;得到初步的检测模型2.3.1 训练方式 3 使用初步的检测模型实现自动数据集标…

STM32 I2C OVR 错误

一、问题 STM32 I2C 用作从机时&#xff0c;开启如下中断并启用 callback 回调函数。 每一次复位后&#xff0c;从机都可以正常触发地址匹配中断ADDR&#xff0c;之后在该中断的回调函数中启用接收中断去收取数据时&#xff0c;却无法进入RXNE中断&#xff0c;而是触发了 OVR …

《数据分析-JiMuReport08》JiMuReport报表开发-报表列数量开发限制调整

JiMuReport报表开发列数量限制调整 1.开发列数限制 JiMuReport报表在开发的时候&#xff0c;需要100-200列的数据&#xff0c;但是在设计到一定数量的时候&#xff0c;水平下拉框就不能滑动了 2.报表参数调整 col: n 在application.yml文件的jmreport配置处&#xff0c;如果想…

【C++】特殊类的设计 | 类型转换

文章目录 1. 特殊类的设计单例模式饿汉模式具体代码 懒汉模式具体代码 懒汉模式和饿汉模式的优缺点 2. C的类型转换C语言的类型转换C的类型转换static_castreinterpret_castconst_castdynamic_cast 1. 特殊类的设计 单例模式 设计模式是 被反复使用 多数人知晓 经过分类的、代…

【Docker】Docker中容器之间通信方式

文章目录 1. Docker容器之间通信的主要方式1.1 通过容器ip访问1.2. 通过宿主机的ip:port访问1.3. 通过link建立连接&#xff08;官方不推荐使用&#xff09;1.4. 通过 User-defined networks&#xff08;推荐&#xff09; 2. 参考资料 1. Docker容器之间通信的主要方式 1.1 通…

OpenCV图像处理-视频分割静态背景-MOG/MOG2/GMG

视频分割背景 1.概念介绍2. 函数介绍MOG算法MOG2算法GMG算法 原视频获取链接 1.概念介绍 视频背景扣除原理&#xff1a;视频是一组连续的帧&#xff08;一幅幅图组成&#xff09;&#xff0c;帧与帧之间关系密切(GOP/group of picture)&#xff0c;在GOP中&#xff0c;背景几乎…

前端面试题汇总大全!

文章目录 前端面试题汇总大全&#xff08;含答案超详细&#xff09;-- 持续更新一、HTML 篇1.xhtml和html有什么区别2.行内元素有哪些&#xff1f;块级元素有哪些&#xff1f; 空(void)元素有那些&#xff1f;行内元素和块级元素有什么区别3. 简述一下你对 HTML 语义化的理解&a…

sql进阶 之case表达式

case表达式 CASE表达式是SQL里非常重要而且使用起来非常便利的技术&#xff0c;我们应该学会用它来描述条件分支。本节将通过行列转换、已有数据重分组&#xff08;分类&#xff09;、与约束的结合使用、针对聚合结果的条件分支等例题&#xff0c;来介绍CASE表达式的用法。标红…

在 3dsmax 中创建电影场景

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 有时&#xff0c;通过简单的图像和简单的技术&#xff0c;我们可以创建有效的输出。在本教程中&#xff0c;您将学习如何在 3ds max 中使用三个简单图像创建电影场景。 步骤-1 让我们在 photoshop 中为场…