Android后台多传感器数据推送至通知栏实现求助
Hey there! I see you're getting started with Android sensors and notifications, and you've already got the basics of displaying sensor data in card views—great work! Let's fix those two issues you're facing, step by step.
First, let's quickly note a small bug in your sensor code: you've got two checks for Sensor.TYPE_GYROSCOPE! The second one should be Sensor.TYPE_PROXIMITY if you want to handle the proximity sensor data. That's probably why your proximity sensor values aren't showing up correctly right now.
1. Keeping the App Running in Background After Being Closed from Task Manager
On Android, regular background services get killed pretty aggressively (especially on Android 8.0+), so the only reliable way to keep sensor monitoring going after the app is swiped away is to use a Foreground Service. This type of service shows a persistent notification to the user, which gives it higher priority and prevents the system from killing it easily.
Here's how to implement it:
Step 1: Create a Foreground Service Class
Create a new class that extends Service, and handle sensor registration/unregistration here:
public class SensorForegroundService extends Service implements SensorEventListener { private SensorManager sensorManager; private Sensor lightSensor, proximitySensor, accelSensor, gyroSensor; private NotificationManager notificationManager; private static final int NOTIFICATION_ID = 1001; @Override public void onCreate() { super.onCreate(); sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); // Initialize sensors lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); // Register sensor listeners registerSensors(); // Start foreground service with a notification startForeground(NOTIFICATION_ID, createNotification("Initializing sensors...")); } private void registerSensors() { if (lightSensor != null) sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); if (proximitySensor != null) sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL); if (accelSensor != null) sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_NORMAL); if (gyroSensor != null) sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_NORMAL); } @Override public void onSensorChanged(SensorEvent event) { // Handle sensor data changes here—we'll update the notification with this data next String sensorData = getFormattedSensorData(event); updateNotification(sensorData); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} private Notification createNotification(String contentText) { // Create notification channel (required for Android 8.0+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "SENSOR_CHANNEL", "Sensor Monitoring", NotificationManager.IMPORTANCE_LOW ); notificationManager.createNotificationChannel(channel); } return new NotificationCompat.Builder(this, "SENSOR_CHANNEL") .setContentTitle("Sensor Monitor Running") .setContentText(contentText) .setSmallIcon(R.drawable.ic_sensor) // Replace with your own icon .setPriority(NotificationCompat.PRIORITY_LOW) .build(); } private void updateNotification(String sensorData) { Notification updatedNotification = createNotification(sensorData); notificationManager.notify(NOTIFICATION_ID, updatedNotification); } private String getFormattedSensorData(SensorEvent event) { // Format the sensor data into a readable string for the notification StringBuilder sb = new StringBuilder(); switch (event.sensor.getType()) { case Sensor.TYPE_LIGHT: sb.append("Light: ").append(String.format("%.2f", event.values[0])).append("\n"); break; case Sensor.TYPE_PROXIMITY: sb.append("Proximity: ").append(String.format("%.2f", event.values[0])).append("\n"); break; case Sensor.TYPE_ACCELEROMETER: sb.append("Accel: X=").append(String.format("%.2f", event.values[0])) .append(" Y=").append(String.format("%.2f", event.values[1])) .append(" Z=").append(String.format("%.2f", event.values[2])).append("\n"); break; case Sensor.TYPE_GYROSCOPE: sb.append("Gyro: X=").append(String.format("%.2f", event.values[0])) .append(" Y=").append(String.format("%.2f", event.values[1])) .append(" Z=").append(String.format("%.2f", event.values[2])).append("\n"); break; } return sb.toString(); } @Override public void onDestroy() { super.onDestroy(); // Unregister sensor listeners to save battery sensorManager.unregisterListener(this); } @Nullable @Override public IBinder onBind(Intent intent) { return null; // We don't need binding for this service } }
Step 2: Declare Permissions and Service in Manifest
Add these to your AndroidManifest.xml:
<!-- For Android 12+, need notification permission to show foreground service notification --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <application ...> <service android:name=".SensorForegroundService" android:foregroundServiceType="sensor" /> <!-- Specify service type for sensors --> </application>
Step 3: Start the Service from Your Activity
In your main activity, start the service when needed (e.g., on a button click or when the app starts):
// Check notification permission first for Android 12+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) startSensorService(); } ); requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); } else { startSensorService(); } } else { startSensorService(); } // Helper method to start the service private void startSensorService() { Intent serviceIntent = new Intent(this, SensorForegroundService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } }
2. Displaying Real-Time Sensor Data in the Notification Bar
As you can see in the service code above, we're already updating the notification every time sensor data changes. The updateNotification() method takes the formatted sensor data and refreshes the notification content.
If you want a more polished layout (instead of plain text), you can use a custom notification layout:
Step 1: Create a Custom Notification Layout
Create an XML file in res/layout/notification_sensor_layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/tv_light" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Light: 0.0" /> <TextView android:id="@+id/tv_proximity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Proximity: 0.0" android:layout_marginTop="4dp"/> <TextView android:id="@+id/tv_accel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Accel: X=0.0 Y=0.0 Z=0.0" android:layout_marginTop="4dp"/> <TextView android:id="@+id/tv_gyro" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Gyro: X=0.0 Y=0.0 Z=0.0" android:layout_marginTop="4dp"/> </LinearLayout>
Step 2: Update the Notification to Use the Custom Layout
Modify the createNotification() and updateNotification() methods in the service:
private Notification createNotification() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "SENSOR_CHANNEL", "Sensor Monitoring", NotificationManager.IMPORTANCE_LOW ); notificationManager.createNotificationChannel(channel); } RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification_sensor_layout); // Initialize default values remoteViews.setTextViewText(R.id.tv_light, "Light: 0.0"); remoteViews.setTextViewText(R.id.tv_proximity, "Proximity: 0.0"); remoteViews.setTextViewText(R.id.tv_accel, "Accel: X=0.0 Y=0.0 Z=0.0"); remoteViews.setTextViewText(R.id.tv_gyro, "Gyro: X=0.0 Y=0.0 Z=0.0"); return new NotificationCompat.Builder(this, "SENSOR_CHANNEL") .setContentTitle("Sensor Monitor Running") .setSmallIcon(R.drawable.ic_sensor) .setPriority(NotificationCompat.PRIORITY_LOW) .setCustomContentView(remoteViews) .build(); } private void updateNotification(SensorEvent event) { RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification_sensor_layout); switch (event.sensor.getType()) { case Sensor.TYPE_LIGHT: remoteViews.setTextViewText(R.id.tv_light, "Light: " + String.format("%.2f", event.values[0])); break; case Sensor.TYPE_PROXIMITY: remoteViews.setTextViewText(R.id.tv_proximity, "Proximity: " + String.format("%.2f", event.values[0])); break; case Sensor.TYPE_ACCELEROMETER: remoteViews.setTextViewText(R.id.tv_accel, "Accel: X=" + String.format("%.2f", event.values[0]) + " Y=" + String.format("%.2f", event.values[1]) + " Z=" + String.format("%.2f", event.values[2])); break; case Sensor.TYPE_GYROSCOPE: remoteViews.setTextViewText(R.id.tv_gyro, "Gyro: X=" + String.format("%.2f", event.values[0]) + " Y=" + String.format("%.2f", event.values[1]) + " Z=" + String.format("%.2f", event.values[2])); break; } Notification updatedNotification = new NotificationCompat.Builder(this, "SENSOR_CHANNEL") .setContentTitle("Sensor Monitor Running") .setSmallIcon(R.drawable.ic_sensor) .setPriority(NotificationCompat.PRIORITY_LOW) .setCustomContentView(remoteViews) .build(); notificationManager.notify(NOTIFICATION_ID, updatedNotification); }
Quick Notes:
- Always unregister sensor listeners when they're not needed (like in
onDestroy()of the service) to save battery life. - The
SENSOR_DELAY_NORMALis a good balance between responsiveness and battery usage—adjust it if you need faster updates. - On some devices, even foreground services might be killed by aggressive battery optimizers. You can guide users to add your app to the battery optimization exception list if needed.
内容的提问来源于stack exchange,提问作者Nurul Haque Saad

