Forum Xamarin.Android

Any Xamarin Android C# sample for JobIntentService?

fbs419fbs419 USMember ✭✭

Now that I have to target Oreo, I ran into an issue where I crash the first time I'm trying to process a GCM message in my app. That is because there are new restrictions on background services, so I can't start my OnMessageReceived code from the background. All the docs say to use the JobIntentService instead of IntentService. The examples are generally in Java. One question revolves around enqueueWork, which is a static method. Adjusting their samples for C#, I get the compiler error about requiring an object reference for non static fields, etc. This is for the second parameter -- the Class parameter.

Is there a Xamarin sample anywhere that implements a RegistrationIntentService inheriting from JobIntentService instead of IntentService?

Best Answer

  • fbs419fbs419 US ✭✭
    Accepted Answer

    OK -- I have solved this with Xamarin code. There are a number of things that need to be done. Make sure to upgrade to a later Xamarin.GooglePlayServices.Gcm Nuget package, and endure the nightmares that come with that. There will also be some "using" changes that come with this. For example:

    using Android.Gms.Iid; instead ofusing Android.Gms.Gcm.Iid;

    You might have to upgrade your SDK to 26 also. As for the code, the documentation is partly right, but they leave things out. Here is what I did:

    RegistrationIntentService:
    Change RegistrationIntentService to inherit from JobIntentService:
    public class RegistrationIntentService : Android.Support.V4.App.JobIntentService

    Put this line above it to avoid the BIND_JOB_SERVICE permission error:
    [Service(Name = "com.MyPackageName.RegistrationIntentService", Permission = "android.permission.BIND_JOB_SERVICE", Exported = true)]

    Declare a static for the job id:
    private static int MY_JOB_ID = 1000;

    Implement a static EnqueueWork method:

    public static void EnqueueWork(Context context, Intent work)
    {
        Java.Lang.Class cls = Java.Lang.Class.FromType(typeof(RegistrationIntentService));
        try
        {
            EnqueueWork(context, cls, MY_JOB_ID, work);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
        }
    }
    

    Change OnHandleIntentto OnHandleWork

    Places in the code where you used to call StartService:
    Instead of StartService (intent), do RegistrationIntentService.EnqueueWork(this, intent);

    Manifest:
    Put this in the manifest (probably necessary -- I'm certainly not going to mess with it)
    <service android:name="RegistrationIntentService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>

    That should do it.

Answers

  • jezhjezh Member, Xamarin Team Xamurai

    Hi,fbs419

    The following link is the suggested steps of using JobIntentService, you could try it.
    https://android.jlelse.eu/keep-those-background-services-working-when-targeting-android-oreo-sdk-26-cbf6cc2bdb7f

    The JobIntentService works in the same way as a Service , so that you could try to convert your Services and IntentServices into a JobIntentService step by step.
    I Hope this could help you.

  • fbs419fbs419 USMember ✭✭

    Right -- I had read that post, and a number of others too. This post had nothing about EnqueueWork, which is needed so that OnHandleWork will be called. And that's the method I am having trouble with. There are many posts on those, but none of them are really complete, and none of them deal with C#. I haven't solved this yet, but I think I'm getting closer. If I solve it, I will post some code.

  • fbs419fbs419 USMember ✭✭
    Accepted Answer

    OK -- I have solved this with Xamarin code. There are a number of things that need to be done. Make sure to upgrade to a later Xamarin.GooglePlayServices.Gcm Nuget package, and endure the nightmares that come with that. There will also be some "using" changes that come with this. For example:

    using Android.Gms.Iid; instead ofusing Android.Gms.Gcm.Iid;

    You might have to upgrade your SDK to 26 also. As for the code, the documentation is partly right, but they leave things out. Here is what I did:

    RegistrationIntentService:
    Change RegistrationIntentService to inherit from JobIntentService:
    public class RegistrationIntentService : Android.Support.V4.App.JobIntentService

    Put this line above it to avoid the BIND_JOB_SERVICE permission error:
    [Service(Name = "com.MyPackageName.RegistrationIntentService", Permission = "android.permission.BIND_JOB_SERVICE", Exported = true)]

    Declare a static for the job id:
    private static int MY_JOB_ID = 1000;

    Implement a static EnqueueWork method:

    public static void EnqueueWork(Context context, Intent work)
    {
        Java.Lang.Class cls = Java.Lang.Class.FromType(typeof(RegistrationIntentService));
        try
        {
            EnqueueWork(context, cls, MY_JOB_ID, work);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
        }
    }
    

    Change OnHandleIntentto OnHandleWork

    Places in the code where you used to call StartService:
    Instead of StartService (intent), do RegistrationIntentService.EnqueueWork(this, intent);

    Manifest:
    Put this in the manifest (probably necessary -- I'm certainly not going to mess with it)
    <service android:name="RegistrationIntentService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>

    That should do it.

  • hopesureshhopesuresh Member

    Wow! this is awesome answers... Thanks for Sharing your Valuable information.....

  • HospifyHospify GBMember ✭✭

    I came to this post trying to migrate a Service. Just in case anyone else struggles with this. It's important to remove the OnBind override. Otherwise on API 26/27/28 the Service will not run.

    I also thought it might be useful to provide a full example:

    Note that I have intentionally NOT set a Name in the Service line. Xamarin will automatically set one in the manifest. If you do want to set a name then make sure it's all lower case

    using System;
    using Android.App;
    using Android.Content;
    using Android.OS;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Droid.BackgroundTasks
    {
        [Service (Permission = "android.permission.BIND_JOB_SERVICE", Exported = true)]
        public class AppService : Android.Support.V4.App.JobIntentService
        {
            private static string Tag = typeof(AppService) + ": ";
            private CancellationTokenSource _cts;
            private static int MY_JOB_ID = 1000;
    
            public static void EnqueueWork(Context context, Intent work)
            {
                Java.Lang.Class cls = Java.Lang.Class.FromType(typeof(AppService));
                try
                {
                    EnqueueWork(context, cls, MY_JOB_ID, work);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"{Tag}{e.Message}");
                }
            }
    
            protected override void OnHandleWork(Intent intent)
            {
                _cts = new CancellationTokenSource(30*1000);
    
                    Task.Run(async () =>
                    {
                        try
                        {
                // do your work here
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine($"{Tag}{e.Message}");
                        }
                    });
                }
            }
    
            public override void OnDestroy()
            {
                if (_cts != null)
                {
                    _cts.Cancel();
                }
                base.OnDestroy();
            }
        }
    }
    

    Start the service using

    Bundle jobParameters = new Bundle();
    jobParameters.PutInt("AnINTParam", 1);
    jobParameters.PutString("AStringParam", "filename.txt");
    
    var intent = new Intent(this, typeof(AppService));
    intent.PutExtras(jobParameters);
    AppService.EnqueueWork(this, intent);
    
  • Greg767Greg767 CHMember ✭✭

    For testing purposes I created a JobIntentService which doesn't do anything and the whole app crashes after about 30 seconds. If I don't launch it then its OK, the app runs.

    [libc] Fatal signal 11 (SIGSEGV), code 1, fault addr 0xbc in tid 16178 (AsyncTask #1)

    Any ideas?? I have created it as per the snippets in this post.

  • Greg767Greg767 CHMember ✭✭

    Here is a sample project to reproduce the issue.

  • fbs419fbs419 USMember ✭✭

    Of course the best way around this is to switch to FCM and forget GCM. Then all this JobIntent stuff goes away.

Sign In or Register to comment.