Android系统级应用连续读取NFC标签实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android系统级应用连续读取NFC标签实现相关的知识,希望对你有一定的参考价值。

参考技术A 在网上找了好久,发现没有能够连续读取NFC标签的方法,目前我的实现还有一定的局限性,如下:

要实现连续读取NFC标签,在不改变源码的情况下,需要你能够在应用内开关NFC,查看源码后发现NfcAdapter的disable与enable都属于系统api并且是hide的

所以如果要使用的话必须要使用反射调用并且是系统App。

当你满足这个条件后(或者你有其他黑科技),你就可以在App里开关NFC了。

然后你会发现,当你处于同一个Activity时,不停开关NFC,系统确实每次都会读取到NFC标签(滴的一声),但是只有第一次你的Activity里的onNewIntent会被调用,之后不会被调用,所以也无法再次接收到NFC标签,但是如果把标签拿远再靠近,又会走onNewIntent,感觉是android系统设置的。

我的做法是在关闭NFC的同时,移除Activity的监听,然后再开启的地方,重新绑定监听

这样每次开启NFC时,都会接收到TAG的Intent,执行onNewIntent(),在其中获得标签内容即可。

在全屏启动器应用程序来自后台后,Android NFC 读取失败

【中文标题】在全屏启动器应用程序来自后台后,Android NFC 读取失败【英文标题】:Android NFC read fails after fullscreen launcher app comes from background 【发布时间】:2019-07-25 12:37:09 【问题描述】:

我的应用程序是一个启动器,我在安装后立即设置为默认值,它在第一次启动时确实读取 NFC 标签没有问题,但如果我锁定手机并且他们解锁它(此时我的应用程序已经在屏幕上并且onResume() 被调用)应用停止读取标签(手机读取标签时播放不同的声音,我猜是 NFC 失败的声音)并且 onNewIntent(Intent intent) 没有被调用。

我已经尝试让我的过滤器和意图成为活动范围而不是本地(从 setupForegroundDispatch 中删除),但仍然存在同样的问题。如果我重新打开应用程序(杀死并重新启动),那么 NFC 阅读器确实可以正确读取。

所以下面是我的逻辑:

NfcAdapter nfcAdapter;

@Override
    protected void onResume() 
        super.onResume();

        if (nfcAdapter == null)
            nfcAdapter=NfcAdapter.getDefaultAdapter(this);
        
        setupForegroundDispatch(this, nfcAdapter);
    
public static void setupForegroundDispatch(Activity activity, NfcAdapter adapter) 
        final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());

        final PendingIntent pendingIntent = PendingIntent.getActivity(
                activity.getApplicationContext(), 0, intent, 0);

        IntentFilter[] filters = new IntentFilter[2];
        String[][] techList = new String[][];

        filters[0] = new IntentFilter();
        filters[0].addAction(NfcAdapter.ACTION_TAG_DISCOVERED);
        filters[1] = new IntentFilter();
        filters[1].addAction(NfcAdapter.ACTION_TECH_DISCOVERED);

        adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
    
@Override
    protected void onNewIntent(Intent intent) 
        super.onNewIntent(intent);
        String action = intent.getAction();
        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) ||
                NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) 
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            if (tag != null) 
                 //do my logic here
            
        
    
@Override
    protected void onPause() 
        super.onPause();
        nfcAdapter.disableForegroundDispatch(this);
    

我的清单

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="br.org.company.app">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> 
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
    <uses-permission android:name="android.permission.STATUS_BAR" />
    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.PREVENT_POWER_KEY" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    <uses-permission android:name="android.permission.REORDER_TASKS" />
    <uses-permission android:name="android.permission.NFC" />

    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:sharedUserId="android.uid.system"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar"
        android:versionName="1.2.1xdk"
        android:usesCleartextTraffic="true">
        <meta-data
            android:name="com.google.android.actions"
            android:resource="@xml/actions" />

        <receiver android:name=".receivers.GpsLocationReceiver">
            <intent-filter>
                <action android:name="android.location.PROVIDERS_CHANGED" />

                <category android:name="android.intent.category.DEFAULT" />

                <action android:name="android.location.LocationManager.KEY_LOCATION_CHANGED" />
            </intent-filter>
        </receiver>
        <receiver
            android:name=".receivers.PassiveLocationChangedReceiver"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.location.LocationManager.KEY_LOCATION_CHANGED" />
            </intent-filter>
        </receiver>
        <receiver
            android:name=".receivers.BootReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.BOOTUP_COMPLETE" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>
        <receiver android:name=".receivers.MyWakefulReceiver" />
        <receiver android:name=".receivers.InternetConnectionReceiver">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                <action android:name="android.net.wifi.WIFI_STATE_CHANGE" />
            </intent-filter>
        </receiver>
        <receiver android:name=".receivers.IncomingCall">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE" />
            </intent-filter>
        </receiver>

        <activity
            android:name=".FirstScreenActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:screenOrientation="portrait"
            android:sharedUserId="android.uid.system"
            android:soundEffectsEnabled="true"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".FullscreenActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:screenOrientation="portrait"
            android:sharedUserId="android.uid.system"
            android:soundEffectsEnabled="true"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.CALL_BUTTON" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />

                <data android:mimeType="text/pg" />
            </intent-filter>

            <meta-data
                android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_type" />
        </activity>

        <receiver
            android:name=".AppDeviceAdmin"
            android:description="@string/app_name"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_DEVICE_ADMIN">
            <meta-data
                android:name="android.app.device_admin"
                android:resource="@xml/app_device_admin" />
            <intent-filter>
                <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE" />
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>
        </receiver>

    </application>

</manifest>

在 logcat 上没有例外,看到 NFC 读取甚至没有传递到我的应用程序。 (在它从后台返回并且手机读取 NFC 后,甚至 onPause(),onResume() 都不会在我的应用程序上触发)

到目前为止,它与我的应用程序作为启动器有关,尽管我没有发现任何关于此限制的信息。

【问题讨论】:

请显示清单文件代码 @PJain 添加了清单 在这里我可以在你的代码中看到在 onNewIntent 中你只使用了 ACTION_TAG_DISCOVERED 和 ACTION_TECH_DISCOVERED 但根据 nfc 的文档,首先调度系统将尝试读取 ACTION_NDEF_DISCOVERED 所以请添加代码并重试 @PJain 添加但错误仍然存​​在 【参考方案1】:

所以我找到了问题的答案;这个应用程序是全屏的(公司的内部应用程序)所以在我的onCreate() 我有各种标志来保持应用程序在屏幕上并尽可能避免锁定屏幕上的键盘保护(以避免用户去其他应用程序)和这就是问题所在,我的应用程序有

WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
WindowManager.LayoutParams.FLAG_FULLSCREEN

并且由于某种原因阻止键盘保护出现(在锁定屏幕上运行应用程序)会禁用向应用程序的 NFC 传送(我猜是出于安全原因)。由于这是一个内部应用程序(并且所有手机都是由 IT 团队预先设置的),因此解决方案是通过手机设置手动从锁定屏幕中删除键盘保护并删除标志

WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD

因此,每次用户锁定和解锁手机时,系统都会再次启用 NFC 读取器。

【讨论】:

以上是关于Android系统级应用连续读取NFC标签实现的主要内容,如果未能解决你的问题,请参考以下文章

Android 简单 NFC 阅读器应用程序源代码错误。它不会读取任何 nfc 标签?

在全屏启动器应用程序来自后台后,Android NFC 读取失败

如何在靠近 NFC(智能卡)时停止我的 Android NFC 应用程序循环

如何以编程方式播放 Android NFC 通知声音?

Android获取NFC标签和NFC十进制16进制ID

NFC Basics(基本NFC)——翻译自developer.android.com