Xamarin 中的 IntentService PCL 解决方案

IntentService in Xamarin PCL Solution

我正忙着编写一个应用程序,用户需要在其中捕获大量图像,然后将它们与一些文本数据打包在一起,然后将它们上传到本地服务器。我想通过 Intent Service 在 Android 平台上实现上传,但我找不到好的 Xamarin Forms PCL 示例来展示如何。

这是我初始化要传递给 IntentService 的 Intent 的方法:

public async Task<bool> UploadAsync(Uri serviceAddress, 
                       CaptureEntity capture, 
                       List<ImageEntity> images)
    {
        try
        {
            Intent uploadIntent = new Intent();
            uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
            uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
            StartService(uploadIntent);

            return true;
        }
        catch (Exception exc)
        {
            App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
            throw exc;
        }
    }

这是 IntentService 本身。

[Service]
public class ServiceIntent : IntentService
{
    public ServiceIntent() : base("ServiceIntent")
    {

    }

    //[return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        return base.OnStartCommand(intent, flags, startId);
    }

    public override void OnCreate()
    {
        base.OnCreate();
    }

    protected override void OnHandleIntent(Intent intent)
    {
        Uri serviceAddress = new Uri(intent.GetStringExtra("serviceAddress"));
        Guid captureId = Guid.Parse(intent.GetStringExtra("captureId"));
        CaptureEntity capture = new DatabaseConnection_Android().CreateConnection().Query<CaptureEntity>("SELECT * FROM [CaptureEntity]").Single(c => c.WorkflowId == captureId);
        var images = new DatabaseConnection_Android().CreateConnection().Query<ImageEntity>("SELECT * FROM [ImageEntity]").Where(i => i.CaptureEntityId == capture.Id);
        try
        {
            MultipartFormDataContent content = new MultipartFormDataContent();
            StringContent strContent = new StringContent(
                                        capture.XmlData,
                                        Encoding.UTF8,
                                        "text/xml");
            IImageHandler handler = new ImageHandler_Droid();

            HttpRequestMessage request = new HttpRequestMessage();
            request.Headers.Add("workflow", capture.WorkflowId.ToString());
            request.Method = HttpMethod.Post;
            request.RequestUri = serviceAddress;

            foreach (var image in images)
            {
                byte[] imageByte = handler.ReadAllBytes(image.ImagePath);
                ByteArrayContent byteContent = new ByteArrayContent(imageByte);
                byteContent.Headers.Add("Content-Type", "image/jpeg");
                content.Add(byteContent, "file", image.ImageName);
            }
            content.Add(strContent, "text/xml");

            request.Content = content;

            using (HttpClient client = new HttpClient())
            {
                client.Timeout = TimeSpan.FromSeconds(180);

                var response = client.SendAsync(
                                            request,
                                            HttpCompletionOption.ResponseContentRead).Result;

                var readResponse = response.Content.ReadAsStringAsync().Result;

                if (readResponse == "File uploaded.")
                    MessagingCenter.Send<CaptureEntity, string>(
                        capture,
                        "Completed",
                        "Success");
                else if (readResponse.Contains("An error has occurred."))
                    MessagingCenter.Send<CaptureEntity, string>(
                        capture,
                        "Uploader",
                        String.Format(CultureInfo.InvariantCulture,
                        "Failed: {0}",
                        readResponse));
                else
                    MessagingCenter.Send<CaptureEntity, string>(
                        capture,
                        "Uploader",
                        String.Format(CultureInfo.InvariantCulture,
                        "Failed: {0}",
                        readResponse));
            }
        }
        catch (WebException webExc)
        {
            MessagingCenter.Send<string, string>("Uploader", "Failed",
                        String.Format(CultureInfo.InvariantCulture,
                        "{0} upload failed.\n{1}",
                        capture.DisplayName,
                        webExc.Message));
        }
        catch (TimeoutException timeExc)
        {
            MessagingCenter.Send<string, string>("Uploader", "Failed",
                        String.Format(CultureInfo.InvariantCulture,
                        "{0} upload failed.\n{1}",
                        capture.DisplayName,
                        timeExc.Message));
        }
        catch (Exception exc)
        {
            MessagingCenter.Send<string, string>("Uploader", "Failed",
                        String.Format(CultureInfo.InvariantCulture,
                        "{0} upload failed.\n{1}",
                        capture.DisplayName,
                        exc.Message));
        }
    }
}

任何人都可以告诉我我做错了什么,因为当我想启动服务时出现以下错误:

Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName android.content.Context.startService(android.content.Intent)' on a null object reference

在您的 Intent 声明中,您需要告诉您要调用的服务

像这样:

var uploadIntent = new Intent(this, typeof(ServiceIntent));

注:this代表Context.

更新:

如评论中所述,您的接口实现不能派生自 Activity class。为了能够访问 Context 以便能够调用 StartService 方法并创建您的 Intent 您可以通过两种方式实现:

使用 Xamarin.Forms.Forms.Context:

public async Task<bool> UploadAsync(Uri serviceAddress, 
                    CaptureEntity capture, 
                    List<ImageEntity> images)
    {
        try
        {
            var context = Xamarin.Forms.Forms.Context;
            var uploadIntent = new Intent(context, typeof(ServiceIntent));
            uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
            uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
            context.StartService(uploadIntent);

            return true;
        }
        catch (Exception exc)
        {
            App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
            throw exc;
        }
    }

如果您使用的是最新版本的 Xamarin.Forms,则此全局上下文已被弃用,他们建议您使用本地上下文。您仍然可以使用它,但在 XF 的未来更新中,您的应用程序可能会损坏。

使用 CurrentActivity plugin:

public async Task<bool> UploadAsync(Uri serviceAddress, 
                    CaptureEntity capture, 
                    List<ImageEntity> images)
    {
        try
        {
            var context = CrossCurrentActivity.Current.Activity;
            var uploadIntent = new Intent(context, typeof(ServiceIntent));
            uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
            uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
            context.StartService(uploadIntent);

            return true;
        }
        catch (Exception exc)
        {
            App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
            throw exc;
        }
    }

这个插件可以从 nugget 安装,设置非常简单。基本上,它可以让您访问当前 activity,您可以将其用作调用 IntentService

的上下文

希望对您有所帮助。-

这里是 IntentService.

IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

在Android中,我们通常使用IntentService来做异步操作。众所周知,线程也是用来做异步操作的。 IntentServiceThread的区别是IntentServiceService,属于Android组件。所以,IntentService的优先级高于Thread.

例如,有一个 ActivityA 有一个 IntentService,有一个 ActivityB 有一个 ThreadIntentServiceThread 正在工作,ActivityAActivityB 都是背景 Activity。现在,如果你的 phone 的系统没有额外的资源,你的 ActivityB 将首先被杀死。

关于异常:

Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName android.content.Context.startService(android.content.Intent)' on a null object reference

也就是说你应该使用android.content.Context来调用StartService方法。在Android中,一共有三种ContextApplication, Activity and Service。所以可以直接调用这三个class中的StartService方法。如果你不在这三个class中,你需要将Context传递给你的class,然后使用Context调用StartService

I added Activity for this class' inheritance.

如果你这样做,你的 class 将是一个 Activity,你需要在你的清单中注册它,为你的 class 添加布局,它应该有lifecycle等等,不会是你想得到的class。在Android中,Activity是一个组件,不是普通的class,所以你不能继承它,除非你想让你的class成为一个Activity。

演示:

我给你做了一个demo