【问题标题】:Android sensors don't gather data when phone is idle手机空闲时,Android 传感器不会收集数据
【发布时间】:2020-02-10 11:11:29
【问题描述】:

我正在尝试开发一个适用于 Android 的应用程序,在该应用程序中,我从多个传感器(如果设备上可用)获取数据并将其写入一个文件,稍后将针对某些用途进行分析。 我面临着几个问题,一个我可以忽略的小问题和一个我无法解决并导致应用无法正常运行的大问题。

- 小问题

我从以下位置收集数据:加速度计、线性加速度计、陀螺仪和磁力计以及 GPS,但它们的工作方式完全不同,只能以低得多的频率进行采样,所以我暂时忽略它。 我通过为每个传感器实现一个监听器来收集数据:

public class AccelerometerWatcher implements SensorEventListener 
{
    private SensorManager sm;
    private Sensor accelerometer;

    AccelerometerWatcher(Context context) {

        sm = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);

        assert sm != null;
        if (sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
            accelerometer = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        }
    }
}

我正在使用以下方法将频率设置为 ~50Hz:

sm.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);

在收集数据时,我知道频率不可能 100% 稳定,但奇怪的是它在每个传感器(大约 50Hz)上都或多或少地保持稳定,除了加速度计上,大部分时间它都在采样100Hz,有时会降至 50Hz。

我可能做错了什么或有什么方法可以控制它吗?到目前为止,我尝试过的每台设备都发生了这种情况,尽管它们的行为方式并不完全相同。

- 主要问题

我正在将信息写入文件,首先将我从传感器获取的所有内容写入字符串,然后每隔 X 秒,将字符串上的内容写入文件并清除它,以便传感器侦听器可以继续写在上面,但不会变得无限长。

我在字符串上这样写:

   @Override
    public void onSensorChanged(SensorEvent event) {

        if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
            return;


            if(initTime == -1)
                initTime = event.timestamp;

            MyConfig.SENSOR_ACCEL_READINGS += ((event.timestamp - initTime) / 1000000L) + MyConfig.DELIMITER + event.values[0] + MyConfig.DELIMITER + event.values[1] + MyConfig.DELIMITER + event.values[2] + "\n";
}

然后使用以下命令将其保存到文件中:

public class Utils {

    private static Timer timer;
    private static TimerTask timerTask;

    public static void startRecording() {
        timer = new Timer();
        timerTask = new TimerTask()
        {
            @Override
            public void run()
            {
                // THIS CODE RUNS EVERY x SECONDS
                writeDataToFile();
            }
        };
        timer.scheduleAtFixedRate(timerTask, 0, MyConfig.SAVE_TIMER_PERIOD);
    }

    public static void stopRecording()
    {
        if(timer != null)
            timer.cancel();
        if(timerTask != null)
            timerTask.cancel();

        writeDataToFile();
    }

    private static void writeDataToFile()
    {
        String temp_accel = String.copyValueOf(MyConfig.SENSOR_ACCEL_READINGS.toCharArray());
        WriteData.write(MyConfig.RECORDING_FOLDER, MyConfig.FILENAME_ACCEL, temp_accel);
        MyConfig.SENSOR_ACCEL_READINGS = MyConfig.SENSOR_ACCEL_READINGS.replaceFirst(temp_accel, ""); 
    }

在监听器中,每次我停止监听时,我都会将“initTime”设置为 -1,因此样本总是从 0 开始,一直到监听周期的持续时间(以毫秒为单位)。 (忽略DELIMITER,只是格式问题)。

我的主要应用崩溃问题如下:

在大多数手机中(少数幸运的手机可以完美运行),只有 1 或 2 件事情会失败。

在某些情况下,在闲置一段时间后(例如锁定并放在口袋里),传感器会停止记录数据,因此应用只会写入空白值,直到我再次唤醒手机。

在其他情况下,更糟糕的是,不仅传感器停止记录数据,而且计时器/写入文件似乎也停止工作,当手机再次唤醒时,它会尝试写入它应该写入的内容在它不工作的时候编写并弄乱所有时间戳,在“过去”的不同点写入相同的样本,直到它赶上当前时间。 (如果您将其可视化为图表,它基本上看起来就像收集的数据回到了过去)。

有什么方法可以确保应用程序在任何情况下都能继续工作,无论是手机锁定、打瞌睡、应用程序最小化、后台还是前台等?强>

我尝试了一种我在谷歌上搜索的方法,该方法包括设置和警报,以每隔 X 秒“唤醒进程”(无论我设置什么时间,它每分钟最多只能工作一次)。 我看到每次警报响起的几毫秒内,它再次捕获样本但随后立即进入睡眠状态,它并没有让手机在更长的时间内保持“清醒”。 它没有解决任何问题,甚至在短时间内强制传感器收集数据,它只是帮助唤醒传感器,定时器/写入文件的问题仍然存在。

希望有人能阐明如何让手机无论如何都收集数据,我一直在尝试我能想到的一切,但我没有得到任何结果。对不起,文字砖,但我真的不知道如何用更短的方式解释它。

P.S:我发现打开省电模式会使情况变得更糟,即使在通常可以正常工作的手机上,它也开始把事情搞砸了。所以另一个问题是......我怎样才能阻止它干扰?

【问题讨论】:

    标签: android background-process android-sensors battery-saver android-doze-and-standby


    【解决方案1】:

    许多问题合二为一。让我来解决手机空闲时不收集数据的大问题。

    如果您在 onCreate、onStart 或 onResume 中的活动中注册传感器侦听器,则当您关闭屏幕或应用程序进入后台时,侦听器将不会出现。然后将调用事件 onPause、onStop 和最终 onDestroy。所以没有可以监听这些事件的活动。 :(

    According to the documentation you can add a partial wake lock to your app.

    另一种方法是启动将在后台运行的服务,然后获取数据并写入文件。但要保持服务运行,您需要使其成为前台服务。为此,您可以添加一个通知,该通知向用户显示某事正在后台运行。

    因此,当您启动服务时,显示通知并启动传感器侦听器。然后,当您停止服务时,关闭通知并取消注册侦听器。希望您不介意 kotlin 示例。

    class TrackService : Service() {
    
        private lateinit var notificationBuilder: NotificationCompat.Builder
    
    
        /**
         * Start command of service. Contains all starting commands needed to start the tracking
         *
         * @param intent used to call startService
         * @param flags flags used for service
         * @param startId id of service
         * @return type of service, in our case STICKY
         */
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    
            notificationBuilder = createNotificationBuilder()
            showNotification()
    
            // register your sensor listener here
    
            return START_REDELIVER_INTENT
        }
    
    
        /**
         * Called when service is being stopped. This is where we stop all listeners and set the status
         * to "offline"
         */
        override fun onDestroy() {
            dismissNotification()
    
            // un register your sensor listener here
    
            super.onDestroy()
        }
    
    
        /**
         * Not needed for our use case
         * @param intent Intent
         * @return null
         */
        override fun onBind(intent: Intent): IBinder? = null
    
    
    
        /**
         * Shows the notification that makes the service more STICKY
         */
        private fun showNotification() {
            if (App.showNotificationContent())
                notificationBuilder.setContentText("Time: - | Distance: -")
            startForeground(ONGOING_NOTIFICATION_ID, notificationBuilder.build())
        }
    
    
        /**
         * Generates the notification to be shown
         *
         * @return NotificationCompat.Builder
         */
    
        private fun createNotificationBuilder(): NotificationCompat.Builder {
            if (pendingIntent == null) {
                val notificationIntent = Intent(this, DashboardActivity::class.java)
                notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
                notificationIntent.putExtra("ticket_flag", FLAG_TICKET_RUNNING)
                notificationIntent.putExtra("event", event)
                notificationIntent.putExtra("course", course)
                pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
    
                @SuppressLint("NewApi")
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    val notificationChannel = NotificationChannel("o-track-channel-4", "Tracking Channel 4", NotificationManager.IMPORTANCE_LOW)
                    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                    notificationManager.createNotificationChannel(notificationChannel)
                    notificationChannel.setSound(null, null)
                }
            }
    
            return NotificationCompat.Builder(this, "channel-4")
                    .setSmallIcon(R.drawable.ic_notification_orienteering)
                    .setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark))
                    .setContentTitle(event.name + " - " + course.name)
                    .setAutoCancel(false)
                    .setSound(null)
                    .setOngoing(true)
                    .setOnlyAlertOnce(true)
                    .setContentIntent(pendingIntent)
                    .setPriority(NotificationCompat.PRIORITY_MAX)
        }
    
    
        /**
         * This is the method that can be called to update the Notification
         */
        private fun updateNotification(time: String, distance: String) {
            if (App.showNotificationContent()) {
                notificationBuilder.setContentText("Time: $time | Distance: $distance")
                val mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                mNotificationManager.notify(ONGOING_NOTIFICATION_ID, notificationBuilder.build())
            }
        }
    
    
        /**
         * Dismisses the notification
         */
        private fun dismissNotification() {
            stopForeground(true)
        }
    
    }
    

    编辑:

    对于较小的问题,read this,您可能需要在注册监听器时设置一个 maxReportLatencyUs。

    【讨论】:

    • 感谢您的回复 just_user。我已经尝试了所有这些,但我仍然无法让它工作。当我变得如此绝望时,我创建了一个新帖子,其中包含一个更简单的示例来说明我正在尝试做的事情,忘记了传感器和所有这些东西。我什至无法以我想要的方式获得最简单的示例,请看一下,希望您可能知道我做错了什么:stackoverflow.com/questions/60211491/…
    猜你喜欢
    • 1970-01-01
    • 2014-06-27
    • 1970-01-01
    • 2011-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多