Xamarin.Forms NFC:在 Android 和 iOS 上自动启动应用程序并读取标签数据

问题描述 投票:0回答:1

我正在创建一个 Xamarin.Forms 应用程序,在手机上放置 NFC 卡后,该应用程序应自动启动并立即在一个周期内读取所有标签数据(至少是 ID 和有效负载)。

在某些 Android 设备上,应用程序按预期启动,但 MainActivity 中的 OnNewIntent 未触发。 它还启动我的应用程序而不读取任何数据。所以不知怎的,标签没有被检测到。

在其他 Android 手机上,将 NFC 卡放在设备上时,应用程序根本不会启动。

我正在使用以下改编自此项目的代码,但已进行了重大更改。

我的第一问题是如何确保跨 Android 设备可靠的应用程序启动和数据读取?是否需要进行任何修改才能使 OnNewIntent 触发一致?

我的第二个问题是关于在 iOS 上实现相同的行为。考虑到 iOS NFC 限制,是否可以使用 Xamarin.Forms 在 iOS 上启动应用程序并在一个周期内读取 NFC 数据。

本机NFC适配器服务

using System;
using System.Linq;
using System.Threading.Tasks;
using NfcAdapter = Android.Nfc.NfcAdapter;
using Android.Content;
using Android.Nfc.Tech;
using Android.Nfc;
using Android.OS;
using NFCTestApp.Interfaces;
using NFCTestApp.Droid.Services;
using Xamarin.Essentials;
using Xamarin.Forms;
using NFCTestApp.Droid.Enums;
using System.IO;

[assembly: Dependency(typeof(NativeNFCAdapterService))]

namespace NFCTestApp.Droid.Services
{
    class NativeNFCAdapterService : INfcAdapter
    {
        private readonly MainActivity mainActivity = (MainActivity)Platform.CurrentActivity;
        private Lazy<NfcAdapter> lazynfcAdapter = new Lazy<NfcAdapter>(() => NfcAdapter.GetDefaultAdapter(Platform.CurrentActivity));
        private NfcAdapter NfcAdapter => lazynfcAdapter.Value;
        private PendingIntent pendingIntent;
        private IntentFilter[] writeTagFilters;
        private string[][] techList;
        private ReaderCallback readerCallback;

        public event Action<string> TagDiscovered;
        public event Action<string> AllDataRead;

        private NfcStatus NfcStatus => NfcAdapter == null ?
                                       NfcStatus.Unavailable : NfcAdapter.IsEnabled ?
                                       NfcStatus.Enabled : NfcStatus.Disabled;

        public static Tag DetectedTag { get; set; }

        public NativeNFCAdapterService()
        {
            Platform.ActivityStateChanged += Platform_ActivityStateChanged;
        }

        private void Platform_ActivityStateChanged(object sender, ActivityStateChangedEventArgs e)
        {
            switch (e.State)
            {
                case ActivityState.Resumed:
                    EnableForegroundDispatch();
                    break;

                case ActivityState.Paused:
                    DisableForegroundDispatch();
                    break;
            }
        }

        public void ConfigureNfcAdapter()
        {
            IntentFilter tagdiscovered = new IntentFilter(NfcAdapter.ActionTagDiscovered);
            IntentFilter ndefDiscovered = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
            IntentFilter techDiscovered = new IntentFilter(NfcAdapter.ActionTechDiscovered);
            tagdiscovered.AddCategory(Intent.CategoryDefault);
            ndefDiscovered.AddCategory(Intent.CategoryDefault);
            techDiscovered.AddCategory(Intent.CategoryDefault);

            var intent = new Intent(mainActivity, mainActivity.Class).AddFlags(ActivityFlags.SingleTop);
            pendingIntent = PendingIntent.GetActivity(mainActivity, 0, intent, PendingIntentFlags.Immutable);

            techList = new string[][]
            {
                new string[] { nameof(NfcA) },
                new string[] { nameof(NfcB) },
                new string[] { nameof(NfcF) },
                new string[] { nameof(NfcV) },
                new string[] { nameof(IsoDep) },
                new string[] { nameof(NdefFormatable) },
                new string[] { nameof(MifareClassic) },
                new string[] { nameof(MifareUltralight) },
            };

            readerCallback = new ReaderCallback();
            readerCallback.OnTagDiscoveredEvent += HandleTagDiscovered;

            writeTagFilters = new IntentFilter[] { tagdiscovered, ndefDiscovered, techDiscovered };
        }

        public void DisableForegroundDispatch()
        {
            NfcAdapter?.DisableForegroundDispatch(Platform.CurrentActivity);
            NfcAdapter?.DisableReaderMode(Platform.CurrentActivity);
        }

        public void EnableForegroundDispatch()
        {
            if (pendingIntent == null || writeTagFilters == null || techList == null) { return; }

            NfcAdapter?.EnableForegroundDispatch(Platform.CurrentActivity, pendingIntent, writeTagFilters, techList);
            NfcAdapter?.EnableReaderMode(Platform.CurrentActivity, readerCallback, NfcReaderFlags.NfcA, null);

            Task.Run(async () => await ReadAllTagInfoAsync());
        }

        public void UnconfigureNfcAdapter()
        {
            Platform.ActivityStateChanged -= Platform_ActivityStateChanged;
        }

        public void HandleTagDiscovered(string tagId)
        {
            TagDiscovered?.Invoke(tagId);
        }

        public async Task SendAsync(byte[] bytes)
        {
            Ndef ndef = null;
            try
            {
                if (DetectedTag == null)
                    DetectedTag = await GetDetectedTag();

                ndef = Ndef.Get(DetectedTag);
                if (ndef == null) return;

                if (!ndef.IsWritable)
                {
                    await Application.Current.MainPage.DisplayAlert("Error", "Tag is readonly", "Ok");
                    return;
                }

                if (!ndef.IsConnected)
                {
                    await ndef.ConnectAsync();
                }

                await WriteToTag(ndef, bytes);
            }
            catch (IOException)
            {
                await Application.Current.MainPage.DisplayAlert("Error", "Transmission error - possibly due to movement.", "Ok");
            }
            catch (Exception)
            {
                await Application.Current.MainPage.DisplayAlert("Error", "Request error", "Ok");
            }
            finally
            {
                if (ndef?.IsConnected == true) ndef.Close();
                ndef = null;
                DetectedTag = null;
            }
        }

        private async Task<Tag> GetDetectedTag()
        {
            mainActivity.NfcTag = new TaskCompletionSource<Tag>();
            readerCallback.NFCTag = new TaskCompletionSource<Tag>();
            var tagDetectionTask = await Task.WhenAny(mainActivity.NfcTag.Task, readerCallback.NFCTag.Task);
            return await tagDetectionTask;
        }

        private async Task WriteToTag(Ndef ndef, byte[] chunkedBytes)
        {
            var ndefRecord = new NdefRecord(NdefRecord.TnfWellKnown, NdefRecord.RtdText?.ToArray(), Array.Empty<byte>(), chunkedBytes);
            NdefMessage message = new NdefMessage(new[] { ndefRecord });
            ndef.WriteNdefMessage(message);
            await Application.Current.MainPage.DisplayAlert("NFC", "Write Successful", "Ok");
        }

        public async Task<string> ReadAllTagInfoAsync()
        {
            if (DetectedTag == null)
            {
                DetectedTag = await GetDetectedTag();
            }

            var info = new System.Text.StringBuilder();
            info.AppendLine("Tech List:");
            foreach (var tech in DetectedTag.GetTechList())
            {
                info.AppendLine($"- {tech}");
            }

            Ndef ndef = Ndef.Get(DetectedTag);
            if (ndef != null)
            {
                info.AppendLine("NDEF Supported: Yes");
                info.AppendLine($"NDEF Type: {ndef.Type}");
                info.AppendLine($"Is Writable: {ndef.IsWritable}");
                info.AppendLine($"Max Size: {ndef.MaxSize} bytes");

                var ndefMessage = ndef.CachedNdefMessage;
                if (ndefMessage != null && ndefMessage.GetRecords().Any())
                {
                    foreach (var ndefRecord in ndefMessage.GetRecords())
                    {
                        info.AppendLine($"Payload: {System.Text.Encoding.UTF8.GetString(ndefRecord.GetPayload())}");
                    }
                }
            }
            else
            {
                info.AppendLine("NDEF Supported: No");
            }

            AllDataRead?.Invoke(info.ToString());

            return info.ToString();
        }
    }
}

ReaderCallback.cs

public class ReaderCallback : Java.Lang.Object, NfcAdapter.IReaderCallback
{
    public TaskCompletionSource<Tag> NFCTag { get; set; }
    public event Action<string> OnTagDiscoveredEvent;

    public void OnTagDiscovered(Tag tag)
    {
        var isSuccess = NFCTag?.TrySetResult(tag);
        if (NFCTag == null || !isSuccess.Value)
            NativeNFCAdapterService.DetectedTag = tag;

        byte[] tagIdBytes = tag.GetId();
        string tagId = BitConverter.ToString(tagIdBytes).Replace("-", "");
        OnTagDiscoveredEvent?.Invoke(tagId);
    }
}

MainActivity.cs

[MetaData(NfcAdapter.ActionTechDiscovered, Resource = "@xml/nfc_tech_filter")]
[IntentFilter(new[] { NfcAdapter.ActionTechDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "text/plain")]
[IntentFilter(new[] { NfcAdapter.ActionNdefDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "text/plain")]
[IntentFilter(new[] { NfcAdapter.ActionTagDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "text/plain")]
[Activity(Label = "NFCTestApp", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    public TaskCompletionSource<Tag> NfcTag { get; set; }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        LoadApplication(new App());
    }

    protected override void OnNewIntent(Intent intent)
    {
        System.Diagnostics.Debug.WriteLine("It is here");
        base.OnNewIntent(intent);

        Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
        {
            await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("NFC Tag Discovered", "A1", "OK");
        });
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="NFCTestApp.Droid">
    <uses-permission android:name="android.permission.NFC" />
    <application android:label="NFCTestApp" android:icon="@mipmap/icon">
        <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
    </application>
</manifest>

这是卡信息

android ios xamarin.forms nfc
1个回答
0
投票

还有另外两个可能的问题,通过清单过滤器启动应用程序可以将 Intent 通知传递到两个可能的代码位置之一,

OnNewIntent
是其中之一,但也可以仅传递到主 Activity。

这是因为还有各种其他因素影响 Intent 的传递位置。

来自 Android 文档:-

在其包中将 launchMode 设置为“singleTop”的活动,或者客户端在调用 startActivity(Intent) 时使用 Intent#FLAG_ACTIVITY_SINGLE_TOP 标志时,会调用此方法。在任何一种情况下,当活动在活动堆栈顶部重新启动而不是启动活动的新实例时,将使用用于重新启动的 Intent 在现有实例上调用 onNewIntent()它。

这里的主要词是“重新推出”

因此,如果您想通过 NFC 启动应用程序,那么您还应该检查启动时传递的 Main Activity 的 Intent。

您可以通过在主活动开始时调用

getIntent()
来完成此操作,这通常在
onCreate
中完成。然后,您可以在此 Intent 上
getParcelableExtra(NfcAdapter.EXTRA_TAG)
查看初始 Intent 是否包含有关 NFC 标签的数据(您可以检查
getParcelableExtra
的返回值)

如果您只对 NDEF 数据感兴趣,只要您没有关闭 NFC 系统服务对 NDEF 的解析,您就可以使用

NfcAdapter.EXTRA_NDEF_MESSAGES
而不是原始
Tag

这会导致第二个可能的问题。

您的代码似乎同时使用了

EnableForegroundDispatch
并且它是更新更好的替代品
EnableReaderMode
。您的代码应该只使用其中一种,其中
EnableReaderMode
是更好使用的一种。在各种不同的条件下,这可能意味着可以调用
ReaderCallback
而不是
onNewIntent()

我会删除所有与

EnableForegroundDispatch

相关的代码
© www.soinside.com 2019 - 2024. All rights reserved.