【问题标题】:Async Method not running properly in service mode in Xamarin异步方法在 Xamarin 的服务模式下无法正常运行
【发布时间】:2017-12-01 16:05:07
【问题描述】:

我定义了以下服务。该服务应该在设备启动时运行。但是,服务已成功启动,但似乎没有调用DoBackgroundJob() 方法中的方法。当我从 MainActivity 启动服务并放置断点时,所有方法都正确执行,我得到了预期的结果。有人可以告诉这里有什么问题吗?

我已将DoBackgroundJob() 包装在一个新线程中,以便该服务在手动启动时不会阻塞应用程序的 UI。该应用程序应该在两个单独的进程中工作。

我想知道的是,像 DoBackgroundJob() 这样的异步方法是否应该包含其中的所有方法 async ?请帮我解决这个问题,因为我怀疑某些方法不足以正确调用。

    [return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        try
        {

            Thread t = new Thread(() =>
            {
                dsa = new DataServiceAccess();
                Task.Run(async () =>
                {                     
                    await DoBackgroundJob();
                });
            });

            t.Start();

        }
        catch (Exception ex)
        {

        }

        base.OnStartCommand(intent, flags, startId);
        return StartCommandResult.Sticky;

    }

    public void GetGps()
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            try
            {
                LocationService locService = new LocationService();
                GpsData = locService.GetLocation();
                a = GpsData.Latitude;
                b = GpsData.Longitude;
            }
            catch (Exception ex)
            {

            }
        });        
    }

    public async Task DoBackgroundJob()
    {
        GetGps();

        try
        {

            ObservableCollection<Trucks> Trucks = new ObservableCollection<Trucks>();
            Trucks truck = new Trucks();
            TrucksService ds = new TrucksService();

            beacon.DetectAvailableTrucks();

            await Task.Delay(100000);

            Trucks = beacon.GetAvailableTrcuks();

            await MatchRequiredTrayBeacon(Truckss);


            await Task.Delay(8000);
            await DoBackgroundJob();


        } catch (Exception e)
        {

        }
        finally
        {


        }
    }


    private ObservableCollection<TrucksLoaded> ConstructTrucks(ObservableCollection<Trucks> trucksMatch)
    {
        string address = "";

        List<ConfigDataModel> conf = new List<ConfigDataModel>();
        conf =  dsa.GetAllConfigData();

        foreach (var item in conf)
        {
            address = item.address;
        }

        ObservableCollection<TrucksLoaded> TrucksLoadedFound = new ObservableCollection<TrucksLoaded>();

        foreach (var item in matchedBeacon)
        {

            TrucksLoadedFound.Add(new TrucksLoaded(item.Id, item.Name, address, latitude,
            longitude, DateTime.Now.ToString(), false, item.BatteryLife));

        }

        return TrucksLoadedFound;

    }


    public ObservableCollection<TrucksLoaded> MatchTrucks(ObservableCollection<Trucks> foundTrucks)
    {
        TrucksLoaded = new ObservableCollection<TrucksLoaded>();

        foundTrucks = new ObservableCollection<Trucks>();

        foundTrucks = HandleTrucks(foundTrucks);

        TrucksLoaded = ConstructTrucks(foundTrucks);

        SavetoLocalDB(TrucksLoaded);

        return TrucksLoaded;
    }

    private ObservableCollection<Trucks> CheckMatching(ObservableCollection<Trucks> foundTrucks)
    {
          MatchedTrucks = new ObservableCollection<Trucks>();

          // matching logic implemeted here.. 

     return MatchedTrucks;

    }

    private void SavetoLocalDB(ObservableCollection<TrucksLoaded> TrucksLoaded)
    { 
        foreach (var item in TrucksLoaded)
        {
            dsa.AddTrucksLoaded(item).ConfigureAwait(false);
        }
    }

需要在主线程上运行的位置服务部分代码

 public void InitLocation()
    {
        if (_locationManager.AllProviders.Contains(LocationManager.NetworkProvider) && _locationManager.IsProviderEnabled(LocationManager.NetworkProvider))
        {
            _locationManager.RequestLocationUpdates(LocationManager.NetworkProvider, 2000, 5, this);
        }

【问题讨论】:

  • 我假设这是在 Android 上,而您正在使用 XF。服务中使用的任何代码是否需要初始化 Xamarin 表单(例如 Device.BeginInvokeOnMainThread)?如果是这样,您将不得不在应用程序未运行时手动初始化 XF。可能是异步代码正在吞噬异常。
  • 否,代码在调试模式下正常运行。嫌疑人正在等待等。

标签: c# xamarin xamarin.android


【解决方案1】:

不能Service 中使用Xamarin.Forms 调用。

  • 调用Device.BeginInvokeOnMainThread 将导致您的Service 在您的服务收到Intent.ActionBootCompleted 时被终止,因为您没有UI 和已启动的Xamarin.Forms 应用程序...因为您有服务返回StartCommandResult.Sticky操作系统将不断尝试重新启动您的服务(按不断减少的时间表)。

OnStartCommand 中不需要双线程,只需一个即可。

  • 只启动一次工作线程,因为OnStartCommand 可能会被多次调用...

我不知道您的 LocationService 方法包含什么,但再次确保您没有在其中使用 Xamarin.Forms 调用。

  • Android 定位服务,或者最好是 Google Fused Location 服务,无需任何 Forms 调用即可正常工作。

使用adb 测试您在ActionBootCompleted 上的服务启动,具体取决于设备/模拟器类型/API/等...您可以使用以下adb cmd 之一:

  • adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -p UseYourPackageNameHere
  • adb shell am broadcast -a android.intent.action.ACTION_BOOT_COMPLETED
  • adb reboot

示例:

添加您的启动权限(在清单中手动或通过属性)

[assembly: UsesPermission(Manifest.Permission.ReceiveBootCompleted)]

服务代码:

[Service]
public class BootService : Service
{
    const string TAG = "SushiHangover";
    public const string SERVICE = "com.sushihangover.BootService";
    Thread serviceThread;

    public IBinder Binder { get; private set; }

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        Log.Debug(TAG, $"OnStartCommand : {Thread.CurrentThread.ManagedThreadId}");

        serviceThread = serviceThread ?? new Thread(ServiceRun); // Many ways... use a Task with a cancellation token, etc... 
        if (!serviceThread.IsAlive)
            try
            {
                serviceThread.Start();
            }
            catch (ThreadStateException ex) when (ex.Message == "Thread has already been started.")
            {
                // Xamarin.Android bug: isAlive always returns false, so eat the Start() exception if needed
            }

        base.OnStartCommand(intent, flags, startId);
        return StartCommandResult.Sticky;
    }

    public override void OnTrimMemory(TrimMemory level)
    {
        // Stop serviceThread? free resources? ...
        base.OnTrimMemory(level);
    }

    public override void OnLowMemory()
    {
        // Stop serviceThread? free resources? ...
        base.OnLowMemory();
    }

    public override void OnDestroy()
    {
        serviceThread.Abort(); // Handle ThreadAbortException in your thread, cleanup resources if needed...
        base.OnDestroy();
    }

    public override IBinder OnBind(Intent intent)
    {
        Log.Debug(TAG, $"OnBind");
        Binder = new BootBinder(this);
        return Binder;
    }

    public class BootBinder : Binder
    {
        public BootBinder(BootService service)
        {
            Service = service;
        }

        public BootService Service { get; private set; }
    }

    async void ServiceRun()
    {
        int i = 0;
        while (true) // Handle cancellations...
        {
            await Task.Delay(1000);
            Log.Debug(TAG, $"{i} : {Thread.CurrentThread.ManagedThreadId}");
            i++;
        }
    }
}

广播接收方代码:

[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Intent.ActionBootCompleted })]    
public class BootBroadcastReceiver : BroadcastReceiver
{
    const string TAG = "SushiHangover";

    public override void OnReceive(Context context, Intent intent)
    {
        Log.Debug(TAG, $"OnReceive");
        var serviceIntent = new Intent(context, typeof(BootService));
        context.StartService(serviceIntent);
    }
}

测试

只要您至少启动了一次应用程序,就可以通过调试会话安装应用程序。

现在通过上面的adb cmd 触发ActionBootCompleted 意图。

监控logcat,你应该会看到(我发出了多个ActionBootCompleted):

 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 0 : 6
 D SushiHangover: 1 : 7
 D SushiHangover: 2 : 6
 D SushiHangover: 3 : 7
 D SushiHangover: 4 : 6
 D SushiHangover: 5 : 7
 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 6 : 6
 D SushiHangover: 7 : 7
 D SushiHangover: 8 : 6
 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 9 : 7
 D SushiHangover: 10 : 6
 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 11 : 7
 D SushiHangover: 12 : 6
 D SushiHangover: 13 : 7
 D SushiHangover: 14 : 6
 D SushiHangover: 15 : 6
 D SushiHangover: 16 : 7
 D SushiHangover: 17 : 6
 D SushiHangover: 18 : 7

【讨论】:

  • 我从来没有想过Device.BeginInvokeOnMainThread会在你的服务收到Intent.ActionBootCompleted时导致我的服务被杀死。但经过大量研究,我发现我的位置服务需要在主线程上运行才能获取 GPS 数据。在 Android 项目中使用 Device.BeginInvokeOnMainThread 的替代方法是什么?
  • 我已经通过添加位置服务的一部分代码来编辑我的代码。请告知在主线程上运行此代码的正确替代方法。
  • @ExpertGenie RunOnUiThread 是在 UI 线程处理程序上放置操作的 Android 方式,但由于您在设备启动时启动服务,因此您没有 UI 上下文。仅供参考:您不需要 UI 线程即可使用基于 Android.Location 的侦听器(或用于 Google 位置服务 API)。
  • 但是根据你所说的,我也不能使用RunOnUiThread,因为我在设备启动时开始我的服务。而这个RinOnUiThread 会杀死我的服务。但是我的代码需要在 UI 线程上运行。见pastebin.com/m0KAG3kz中的代码如果我在ServiceThread之前调用StartCommand中的方法呢?
  • @ExpertGenie 您仍在使用 Xamarin.Forms 代码 (Forms.Context),这将是 Activitycontext(托管您的 Xamarin.Forms 页面的代码),因此不是在设备启动时可用(同样你没有 UI)。您可以在Service 中访问ApplicationContext(Android 应用程序上下文),只需将其传递给您的LocationService,这样您就可以调用GetSystemService。 (仅供参考:删除所有引用这些文件中表单的 using 语句,以便您可以找到需要更正的任何其他区域)
猜你喜欢
  • 2011-12-06
  • 2022-11-16
  • 2022-01-03
  • 1970-01-01
  • 2014-01-11
  • 2021-01-02
  • 1970-01-01
  • 1970-01-01
  • 2014-12-01
相关资源
最近更新 更多