0

我正在尝试开发一个适用于 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 秒“唤醒进程”(无论我设置什么时间,它每分钟最多只能工作一次)。我看到每次警报响起的几毫秒内,它再次捕获样本但随后立即进入睡眠状态,它并没有让手机在更长的时间内保持“清醒”。它没有解决任何问题,即使在短时间内强制传感器收集数据,它也只能帮助唤醒传感器,定时器/写入文件的问题仍然存在。

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

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

4

1 回答 1

3

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

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

根据文档,您可以为您的应用添加部分唤醒锁定

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

因此,当您启动服务时,显示通知并启动传感器侦听器。然后,当您停止服务时,关闭通知并取消注册侦听器。希望您不介意 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)
    }

}

编辑:

至于较小的问题,请阅读本文,您可能需要在注册监听器时设置一个 maxReportLatencyUs。

于 2020-02-10T11:37:16.853 回答