Я использую службу переднего плана, если версия ОС> = 8.0. На Android 8.1 и 9.0, когда мое приложение шагомер открыто или на заднем плане оно считает шаги, но не когда приложение закрыто. Также после закрытия приложения значок уведомления исчезает с экрана телефона. Протестировано на Oreo 8.0 с использованием службы переднего плана, работает нормально и подсчитывает шаги, когда приложение закрыто. Проверено также на андроиде Nougat 7.0 с фоновым сервисом и работающим без проблем.
BootReceiver.cs
[BroadcastReceiver(Enabled = true, Exported = true, DirectBootAware = true)]
[IntentFilter(new string[] { Intent.ActionBootCompleted, Intent.ActionLockedBootCompleted, "android.intent.action.QUICKBOOT_POWERON", "com.htc.intent.action.QUICKBOOT_POWERON" })]
public class BootReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var stepServiceIntent = new Intent(context, typeof(StepService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
context.StartForegroundService(stepServiceIntent);
}
else
{
context.StartService(stepServiceIntent);
}
}
}
StepServiceBinder.cs
public class StepServiceBinder : Binder
{
StepService stepService;
public StepServiceBinder(StepService service)
{
this.stepService = service;
}
public StepService StepService
{
get { return stepService; }
}
}
StepServiceConnection.cs
public class StepServiceConnection : Java.Lang.Object, IServiceConnection
{
MainActivity activity;
public StepServiceConnection(MainActivity activity)
{
this.activity = activity;
}
public void OnServiceConnected(ComponentName name, IBinder service)
{
var serviceBinder = service as StepServiceBinder;
if (serviceBinder != null)
{
activity.Binder = serviceBinder;
activity.IsBound = true;
}
}
public void OnServiceDisconnected(ComponentName name)
{
activity.IsBound = false;
}
}
* Manifest.xml *
<manifest android:versionName="1.1" package="com.PedometerApp" android:installLocation="internalOnly" android:versionCode="11">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27" />
<application android:label="My Pedometer" android:icon="@drawable/logo"> </application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>
StepService.cs
[Service(Enabled = true)]
[IntentFilter(new String[] { "com.PedometerApp.StepService" })]
public class StepService : Service, ISensorEventListener, INotifyPropertyChanged
{
private SensorManager sManager;
private bool isRunning;
private long stepsToday = 0;
public bool WarningState
{
get;
set;
}
public long StepsToday
{
get { return stepsToday; }
set
{
if (stepsToday == value)
return;
stepsToday = value;
OnPropertyChanged("StepsToday");
Settings.CurrentDaySteps = value;
MessagingCenter.Send<object, long>(this, "Steps", Settings.CurrentDaySteps);
}
}
public const string PRIMARY_NOTIF_CHANNEL = "exampleChannel";
public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
RegisterForService();
var warning = false;
if (intent != null)
warning = intent.GetBooleanExtra("warning", false);
Startup();
return StartCommandResult.Sticky;
}
private void RegisterForService()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channel = new NotificationChannel(PRIMARY_NOTIF_CHANNEL, "Pedometer Service Channel", NotificationImportance.Low)
{
Description = "Foreground Service Channel"
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
var notification = new Notification.Builder(this, PRIMARY_NOTIF_CHANNEL)
.SetContentTitle("Service")
.SetContentText("Running")
.SetSmallIcon(Resource.Drawable.ic_stat_name)
.SetContentIntent(BuildIntentToShowMainActivity())
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
else
{
BuildIntentToShowMainActivity();
}
}
PendingIntent BuildIntentToShowMainActivity()
{
var alarmManager = ((AlarmManager)ApplicationContext.GetSystemService(AlarmService));
var intent2 = new Intent(this, typeof(StepService));
intent2.PutExtra("warning", WarningState);
var stepIntent = PendingIntent.GetService(ApplicationContext, 200, intent2, PendingIntentFlags.UpdateCurrent);
alarmManager.Set(AlarmType.Rtc, Java.Lang.JavaSystem
.CurrentTimeMillis() + 1000 * 60 * 60, stepIntent);
return stepIntent;
}
public override void OnTaskRemoved(Intent rootIntent)
{
base.OnTaskRemoved(rootIntent);
UnregisterListeners();
var intent = new Intent(this, typeof(StepService));
intent.PutExtra("warning", WarningState);
((AlarmManager)GetSystemService(AlarmService)).Set(AlarmType.Rtc, Java.Lang.JavaSystem
.CurrentTimeMillis() + 500,
PendingIntent.GetService(this, 201, intent, 0));
}
private void Startup(bool warning = false)
{
CrunchDates(true);
if (!isRunning)
{
RegisterListeners();
WarningState = warning;
}
isRunning = true;
}
public override void OnDestroy()
{
base.OnDestroy();
UnregisterListeners();
isRunning = false;
CrunchDates();
}
void RegisterListeners()
{
sManager = GetSystemService(SensorService) as SensorManager;
sManager.RegisterListener(this, sManager.GetDefaultSensor(SensorType.StepCounter), SensorDelay.Ui);
}
void UnregisterListeners()
{
if (!isRunning)
return;
try
{
var sensorManager = (SensorManager)GetSystemService(Context.SensorService);
sensorManager.UnregisterListener(this);
isRunning = false;
}
catch (Exception ex)
{
}
}
StepServiceBinder binder;
public override Android.OS.IBinder OnBind(Android.Content.Intent intent)
{
binder = new StepServiceBinder(this);
return binder;
}
public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
{
//do nothing here
}
public void AddSteps(long count)
{
if (lastSteps == 0)
{
lastSteps = count;
}
newSteps = count - lastSteps;
if (newSteps < 0)
newSteps = 1;
else if (newSteps > 100)
newSteps = 1;
lastSteps = count;
CrunchDates();
Settings.TotalSteps += newSteps;
StepsToday = Settings.TotalSteps - Settings.StepsBeforeToday;
}
long newSteps = 0;
long lastSteps = 0;
public void OnSensorChanged(SensorEvent e)
{
if (lastSteps < 0)
lastSteps = 0;
var count = (long)e.Values[0];
WarningState = false;
AddSteps(count);
}
private void CrunchDates(bool startup = false)
{
if (!Utils.IsSameDay)
{
var yesterday = Settings.CurrentDay;
var dayEntry = StepEntryManager.GetStepEntry(yesterday);
if (dayEntry == null || dayEntry.Date.DayOfYear != yesterday.DayOfYear)
{
dayEntry = new StepEntry();
}
dayEntry.Date = yesterday;
dayEntry.Steps = Settings.CurrentDaySteps;
Settings.CurrentDay = DateTime.Today;
Settings.CurrentDaySteps = 0;
Settings.StepsBeforeToday = Settings.TotalSteps;
StepsToday = 0;
try
{
StepEntryManager.SaveStepEntry(dayEntry);
}
catch (Exception ex)
{
Console.WriteLine("Error {0}", ex.Message);
}
}
else if (startup)
{
StepsToday = Settings.TotalSteps - Settings.StepsBeforeToday;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
MainActivity.cs
[Activity(Label = "My Pedometer", Icon = "@mipmap/icon", Theme = "@style/MainTheme", LaunchMode = LaunchMode.SingleTask, MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public bool IsBound { get; set; }
private StepServiceBinder binder;
private bool registered;
private Handler handler;
private bool firstRun = true;
private StepServiceConnection serviceConnection;
public StepServiceBinder Binder
{
get { return binder; }
set
{
binder = value;
if (binder == null)
return;
HandlePropertyChanged(null, new System.ComponentModel.PropertyChangedEventArgs("StepsToday"));
if (registered)
binder.StepService.PropertyChanged -= HandlePropertyChanged;
binder.StepService.PropertyChanged += HandlePropertyChanged;
registered = true;
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState);
StartStepService();
handler = new Handler();
handler.PostDelayed(() => UpdateUI(), 500);
LoadApplication(new App());
}
private void StartStepService()
{
try
{
var service = new Intent(this, typeof(StepService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
StartForegroundService(service);
}
else
{
StartService(service);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception {0}.", ex.Message);
}
}
protected override void OnStop()
{
base.OnStop();
if (IsBound)
{
UnbindService(serviceConnection);
IsBound = false;
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (IsBound)
{
UnbindService(serviceConnection);
IsBound = false;
}
}
protected override void OnStart()
{
base.OnStart();
if (!firstRun)
StartStepService();
if (IsBound)
return;
var serviceIntent = new Intent(this, typeof(StepService));
serviceConnection = new StepServiceConnection(this);
BindService(serviceIntent, serviceConnection, Bind.AutoCreate);
}
protected override void OnPause()
{
base.OnPause();
if (registered && binder != null)
{
binder.StepService.PropertyChanged -= HandlePropertyChanged;
registered = false;
}
}
protected override void OnResume()
{
base.OnResume();
if (!firstRun)
{
if (handler == null)
handler = new Handler();
handler.PostDelayed(() => UpdateUI(), 500);
}
firstRun = false;
if (!registered && binder != null)
{
binder.StepService.PropertyChanged += HandlePropertyChanged;
registered = true;
}
}
void HandlePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != "StepsToday")
return;
UpdateUI();
}
private void UpdateUI()
{
RunOnUiThread(() =>
{
long steps = 0;
var showWaring = false;
if (Binder == null)
{
if (Utils.IsSameDay)
steps = Settings.CurrentDaySteps;
}
else
{
steps = Binder.StepService.StepsToday;
showWaring = binder.StepService.WarningState;
}
Settings.CurrentDaySteps = steps;
});
}
}