I'm developing an Android application with Xamarin Forms that is composed of an interface and also a background service.
I need that the service works also when the interface application is closed.
If I add "IsolatedProcess = true" into the service the graphical interface still works but the service crashes.
I read a lot of posts with possible solutions but they don't work. (I tried to compile in release mode and also to remove "Use Shared Runtime" flag).
I'm compiling with Android 8.1 (Oreo) as Target Framework.
The target environment is Android 4.2.
I start the service into OnCreate method of the MainActivity class:
Intent testIntent = new Intent(this.BaseContext, typeof(TestService));
StartService(testIntent);
The service class:
[Service(IsolatedProcess = true, Exported = true, Label = "TestService")]
public class TestService : Service
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override void OnCreate()
{
base.OnCreate();
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
Device.StartTimer(new TimeSpan(0, 0, 40), () =>
{
//Code executed every 40 seconds
});
base.OnStartCommand(intent, flags, startId);
return StartCommandResult.Sticky;
}
public override bool StopService(Intent name)
{
return base.StopService(name);
}
}
If I remove "IsolatedProcess = true" the service works but it will be stopped when I will close the application interface process.
I solved the issue by changing the value of the attribute IsolatedProcess to true, removing the Device.StartTimer instruction and by introducing a BroadcastReceiver.
MainActivity class:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public static Intent testServiceIntent;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
testServiceIntent = new Intent(this.BaseContext, typeof(TestService));
LoadApplication(new App());
}
}
The service class:
[Service(IsolatedProcess = false, Exported = true, Label = "TestService")]
public class TestService : Service
{
System.Threading.Timer _timer;
public override IBinder OnBind(Intent intent)
{
return null;
}
public override void OnCreate()
{
base.OnCreate();
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
businessLogicMethod();
base.OnStartCommand(intent, flags, startId);
return StartCommandResult.Sticky;
}
public void businessLogicMethod()
{
//My business logic in a System.Threading.Timer
}
}
The Broadcast Receiver class:
[BroadcastReceiver]
[IntentFilter(new[] { Intent.ActionBootCompleted })]
public class TestApplicationBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
Log.Info("TestApp", "******* Loading Application *******");
try
{
if (intent.Action.Equals(Intent.ActionBootCompleted))
{
Intent service = new Intent(context, typeof(TestService));
service.AddFlags(ActivityFlags.NewTask);
context.StartService(service);
}
}
catch (Exception ex)
{
Log.Error("TestApp", "******* Error message *******: " + ex.Message);
}
}
}
I hope that can be useful for someone.
Related
I am using Xamarin Android to build an app which should allow the app to keep sending a driver's location every 15 minutes so that I can keep track of his movement. I used JobScheduler to get this done. My project is very simple now and only contains the following 3 files:
MainActivity.cs
AttendancePage.cs (Content page, interact with UI button to start the service)
ServiceClass.cs
Methods in Main Activity.cs
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
scheduler = (JobScheduler)GetSystemService(JobSchedulerService);
LoadApplication(new App()); //This line will then jump to AttendancePage.cs
}
public void ScheduleJob()
{
ComponentName componentName = new ComponentName(this, Java.Lang.Class.FromType(typeof(ServiceClass)));
JobInfo info = new JobInfo.Builder(123, componentName)
.SetPersisted(true)
.SetPeriodic(60000)
.Build();
int resultCode = scheduler.Schedule(info); //The error show when hit this line.
if (resultCode == JobScheduler.ResultSuccess)
{
Log.Info("Message", "Job Schedule!");
}
else
{
Log.Info("Message", "Job shceduling failed");
}
}
public void CancelJob()
{
scheduler.Cancel(123);
}
AttendancePage.cs
public partial class AttendancePage : ContentPage
{
MainActivity main = new MainActivity();
public AttendancePage()
{
InitializeComponent();
Title = "Attendance App";
}
//Button OnClickEvent
async void ScheduleJob(object s, EventArgs e)
{
main.ScheduleJob();
}
//Button OnClickEvent
async void CancelJob(object s, EventArgs e)
{
main.CancelJob();
}
}
ServiceClass.cs
[Service(Name = "com.SampleApp.AttendanceApp.ServiceClass", Permission = "android.permission.BIND_JOB_SERVICE")]
public class ServiceClass : JobService
{
public ServiceClass()
{
}
public override bool OnStartJob(JobParameters jobParamsOnStart)
{
doBackgroundWork(jobParamsOnStart);
return true;
}
private void doBackgroundWork(JobParameters jobParam)
{
//My code to send driver's location
TestingPage.GetGPS();
JobFinished(jobParam, false);
}
public override bool OnStopJob(JobParameters jobParamsOnStop)
{
return false;
}
}
I have added the service tag inside AndroidManifest.xml as well.
<service android:name=".ServiceClass"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
I have no idea why the error is still there. The error is from the line scheduler.Schedule(JobInfo). Anyone has another possible solution? I am frustrated on solving this. Will the reason be I can't debug on the service but only can straight away run in release mode? Please help.
Froms shared code it works in Xamarin.Android project , however here is a Xamarin.Forms project . It can not work.
In AttendancePage.cs , you create a new MainActivity to invoke the native method ScheduleJob and CancelJob . This will not find the JobScheduler in native ,then it will return null .
No such service ComponentInfo{/com.SampleApp.AttendanceApp.ServiceClass}
If you want to invoke native method , you can have a try with DependencyService in Xamarin Forms .
At least need to create a Interface in Forms to invoke :
public interface IJobSchedulerService
{
//Start JobSchedule
void StartJobSchedule();
//Cancel JobSchedule
void CancelJobSchedule();
}
Then can invoke in Xamarin Forms as follow :
async void ScheduleJob(object s, EventArgs e)
{
DependencyService.Get<IJobSchedulerService>().StartJobSchedule();
}
//Button OnClickEvent
async void CancelJob(object s, EventArgs e)
{
DependencyService.Get<IJobSchedulerService>().CancelJobSchedule();
}
Now you need to implement the IJobSchedulerService in native android .Create the JobSchedulerDependcenyService:
public class JobSchedulerDependcenyService : IJobSchedulerService
{
JobScheduler jobScheduler;
public JobSchedulerDependcenyService()
{
jobScheduler = (JobScheduler)MainActivity.Instance.GetSystemService(Android.Content.Context.JobSchedulerService);
}
public void StartJobSchedule()
{
ComponentName componentName = new ComponentName(MainActivity.Instance, Java.Lang.Class.FromType(typeof(DownloadJob)));
JobInfo jobInfo = new JobInfo.Builder(1, componentName)
.SetOverrideDeadline(0)
.Build();
//var jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
var scheduleResult = jobScheduler.Schedule(jobInfo);
if (JobScheduler.ResultSuccess == scheduleResult)
{
var snackBar = Snackbar.Make(MainActivity.Instance.FindViewById(Android.Resource.Id.Content), "jobscheduled_success", Snackbar.LengthShort);
snackBar.Show();
}
else
{
var snackBar = Snackbar.Make(MainActivity.Instance.FindViewById(Android.Resource.Id.Content), "jobscheduled_failure", Snackbar.LengthShort);
snackBar.Show();
}
}
public void CancelJobSchedule()
{
jobScheduler.CancelAll();
}
}
Here you will find the MainActivity.Instance inside it , that's a static instance defined by self in MainActivity.
public static MainActivity Instance { set; get; }
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Instance = this;
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
I'm Creating a Call Diverting app using Xamarin. What i am doing is with the press of a button a command is triggerd and using DependencyService i call the Android specific implementaion of the Call Diverter Method.
Now I'm having a problam where the instance of the MainActivity Context for the StartActivity Method is null. I'm suspecting that this is the cause of the error i get when i running the app: 'Target of StartActivity is null (NullReferenceException)'
but i dont know how to fix this. i thought i'm doing everything right but i'm getting an exception.
this is my code so far: Android Project Call diverting method implementation
[assembly: Dependency(typeof(CallDiverter_Android))]
namespace CallDiverter2.Droid
{
public class CallDiverter_Android : ICallDiverter
{
public void DivertCall(string callForwardString)
{
var context = MainActivity.Instance;
//Divert call code
try
{
//String callForwardString = "**21*1234567890#";
Intent callIntent = new Intent(Intent.ActionCall); // ACTION_CALL
Android.Net.Uri uri = Android.Net.Uri.Parse(callForwardString);
callIntent.SetData(uri);
context.StartActivity(callIntent);
//Forms.Context.StartActivity(callIntent);
}
catch (Exception)
{
throw;
}
}
public void StopCallDiverting()
{
//Stop the call diverting action
}
}
}
The instance in the MainActivity class:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle bundle)
{
Instance = this;
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
}
How can I call method defined in my PCL project to Android project ?
I have a method DoWork() defined in my PCL and I want this method to continuously be run in a service defined in my android project as follows:
public class BroadcastService : Service
{
IBinder mBinder;
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
Toast.MakeText(this, "BroadcastService is running ", ToastLength.Long).Show();
Task.Run(() =>
{
var counter = new Counter();
counter.DoWork().Wait();
});
base.OnStartCommand(intent, flags, startId);
return StartCommandResult.Sticky;
}
The toast is appearing.
However, the DoWork() is not running. Can someone enlighten what is wrong please?
The full method signature for DoWork() :
private async void DoWork()
{
StartDetect();
Device.StartTimer(TimeSpan.FromSeconds(20), () =>
{
_foundTags = _truck.GetAvailableTrucks();
DoWork();
});
}
I think you can use a MessagingCenter. You can try something like this
public class BroadcastService : Service
{
IBinder mBinder;
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
Toast.MakeText(this, "BroadcastService is running ", ToastLength.Long).Show();
Xamarin.Forms.MessagingCenter.Send<App>((App)Xamarin.Forms.Application.Current, "dowork");
base.OnStartCommand(intent, flags, startId);
return StartCommandResult.Sticky;
}
and in your PCL project
protected override void OnStart()
{
MessagingCenter.Subscribe<App>(this, "dowork", (sender) =>
{
// Do something here
});
// Handle when your app starts
}
Hello, I want to build an app, in which you can start a service, which runs intependenly and creates a notification, and this service should constantly proof, if the DateTime.Now.Date is bigger than a spezific Date.
When I execute the code below, the notification gets displayed, but when I am closing the app, a few secondes later I get two times an information that the app crashed and I dont know why.
I cant even debug the code because this anly happens when the application is closed....
I hope you can help me thanks!
Here is my code:
namespace App
{
[Activity(Label = "App", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
int count = 1;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
Button button = FindViewById<Button>(Resource.Id.MyButton);
button.Click += delegate {
button.Text = string.Format("{0} clicks!", count++);
StartService(new Intent(this, typeof(backgroudservice)));
};
}
}
public class backgroudservice : Service
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
newnotification("Title", "Text: ", 0);
new Task(() => {
DoWork();
Thread.Sleep(1000);
}).Start();
return StartCommandResult.Sticky;
}
public void DoWork()
{
if (DateTime.Now.Date > Convert.ToDateTime("2016-03-29").Date)
{
cancelnotification(0);
StopSelf();
}
}
public override void OnDestroy()
{
base.OnDestroy();
cancelnotification(0);
}
private void newnotification(string titel, string text, int id)
{
Notification.Builder builder = new Notification.Builder(this)
.SetContentTitle(titel)
.SetContentText(text)
.SetSmallIcon(Resource.Drawable.droidlogo_small)
.SetAutoCancel(false)
.SetVisibility(NotificationVisibility.Public)
.SetContentIntent(PendingIntent.GetActivity(this, 0, new Intent(this, typeof(MainActivity)), PendingIntentFlags.OneShot));
// Build the notification:
Notification notification = builder.Build();
notification.Flags = NotificationFlags.NoClear;
//notification.ContentIntent = new Intent(this,typeof(login));
// Get the notification manager:
NotificationManager notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
// Publish the notification:
notificationManager.Notify(id, notification);
}
private void cancelnotification(int id)
{
NotificationManager notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
notificationManager.Cancel(id);
}
}
}
I solved it, I forgot the [Service] above my class, now it works!
[Service]
public class backgroudservice : Service
{
...
}
You might try moving the call to cancelnotification in your service's OnDestroy to before the call to the base method, i.e.:
public override void OnDestroy()
{
cancelnotification(0);
base.OnDestroy();
}
As in title broadcast from IntentService is not received.
Here's how I have it set up:
[Service]
[IntentFilter(new[] { MyService.ToUploadCountNotification })]
public class MyService : IntentService
{
public const string ToUploadCountNotification = "MyService.ToUploadCountNotification";
public const string ToUploadCount = "MyService.ToUploadCount";
protected override void OnHandleIntent(Intent intent)
{
while (Count > 0)
{
var uploadCount = new Intent();
uploadCount.SetAction(ToUploadCountNotification);
uploadCount.PutExtra(ToUploadCount, localHarryys.Count);
Log.Debug("Sync service", "sending broadcast");
SendBroadcast(intent, null);
}
}
}
public class MyServiceBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
//throw new NotImplementedException();
Toast.MakeText(context,
string.Format("Uploading...({0})", intent.Extras.GetInt(MyService.ToUploadCount)),
ToastLength.Short).Show();
}
}
In activity I register receiver:
protected override void OnResume()
{
base.OnResume();
RegisterReceiver(_receiver, new IntentFilter(MyHarryysSyncService.ToUploadCountNotification));
Log.Debug("Browse", "Receiver registered");
}
protected override void OnPause()
{
base.OnPause();
UnregisterReceiver(_receiver);
Log.Debug("Browse", "Receiver unregistered");
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (requestCode == 0)
{
StartService(new Intent(this, typeof(MyService)));
}
base.OnActivityResult(requestCode, resultCode, data);
}
In my research all I could find was
make sure that you register it correctly in activity - I believe I do I've also put it in OnCreate/OnDestroy see below
activity might not be visible/active at the time service sends broadcast - From debug window I get correct order of messages Receiver registered / sending broadcast so I'm assuming that activity is active and receiver registered.
I've tested couple more things.
After line StartService(new Intent(this, typeof(MyService)))
I've put SendBroadcast(new Intent(MyService.ToUploadCountNotification), null);
that didn't work till I've move registration to OnCreate/OnDestroy. Unfortunatelly broadcast from service was still not received
I've registered receiver in androidmanifest.xml that works as well but I wanted to have it registered dynamically by activity as there might be different behaviour depending on active activity.
So my question is what else am I doing wrong there. What else can I check.