接收 NFC 的应用程序总是在前面弹出新实例

Application receiving NFC always pops up new instance in front

通过启动器启动我的应用程序时,我 运行 经历了以下生命周期:

onCreate..., onPostCreate...,  onResume..., onNewIntent..., act:android.intent.action.MAIN, mNfcAdapter.disableForegroundDispatch OK.

当我点击一个标签时,一个新的应用程序实例似乎在我 运行 通过以下生命周期时启动:

onPause..., onCreate..., onPostCreate...,  onResume..., onNewIntent..., act:android.nfc.action.TAG_DISCOVERED, myTag.mId:048a1382bd2384)

因为我尝试使用前台调度系统来禁用接收 NFC 事件,所以我希望我的应用程序忽略 NFC 标签。那么为什么我的 activity 被重新创建了呢?是因为AndroidManifest.xml允许吗?

package com.example.pdf.nfcaccess;

import android.annotation.SuppressLint;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;

// PDf import
import android.telephony.TelephonyManager;
import android.content.Intent;
import android.content.Context;
import android.content.IntentFilter;
import android.app.PendingIntent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.widget.TextView;
import android.widget.Toast;
import android.util.Log;
import android.nfc.tech.NfcF;
import android.nfc.Tag;

/**
 * An example full-screen activity that shows and hides the system UI (i.e.
 * status bar and navigation/system bar) with user interaction.
 */
public class FullscreenActivity extends AppCompatActivity {
    /**
     * Whether or not the system UI should be auto-hidden after
     * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
     */
    private static final boolean AUTO_HIDE = true;

    /**
     * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
     * user interaction before hiding the system UI.
     */
    private static final int AUTO_HIDE_DELAY_MILLIS = 3000;

    /**
     * Some older devices needs a small delay between UI widget updates
     * and a change of the status and navigation bar.
     */
    private static final int UI_ANIMATION_DELAY = 300;
    private final Handler mHideHandler = new Handler();
    private View mContentView;
    private final Runnable mHidePart2Runnable = new Runnable() {
        @SuppressLint("InlinedApi")
        @Override
        public void run() {
            // Delayed removal of status and navigation bar

            // Note that some of these constants are new as of API 16 (Jelly Bean)
            // and API 19 (KitKat). It is safe to use them, as they are inlined
            // at compile-time and do nothing on earlier devices.
            mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        }
    };
    private View mControlsView;

    // application
    static FullscreenActivity mInstance;

    NfcAdapter mNfcAdapter;
    Intent mNfcIntent;
    PendingIntent mNfcPendingIntent;
    IntentFilter mTagIntentFilter;
    IntentFilter[] mIntentFiltersArray;
    String[][] mTechLists;
    TextView mTagContentText;

    // log
    //java.util.ArrayList<String> mLogItems = new java.util.ArrayList<String>();
    java.util.ArrayList<String> mLogItems = new java.util.ArrayList<String>();
    android.widget.ArrayAdapter<String> mLogAdapter;
    android.widget.ListView mLogList;

    /**/
    private final Runnable mShowPart2Runnable = new Runnable() {
        @Override
        public void run() {
            // Delayed display of UI elements
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null) {
                actionBar.show();
            }
            mControlsView.setVisibility(View.VISIBLE);
        }
    };
    private boolean mVisible;
    private final Runnable mHideRunnable = new Runnable() {
        @Override
        public void run() {
            hide();
        }
    };
    /**
     * Touch listener to use for in-layout UI controls to delay hiding the
     * system UI. This is to prevent the jarring behavior of controls going away
     * while interacting with activity UI.
     */

    /* Enable NFC
     */
    private final View.OnTouchListener mFuncNfcEnable = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                traceTry("mFuncNfcEnable");
                if (mNfcAdapter != null) {
                    try {
                        traceTry("mNfcAdapter.enableForegroundDispatch");
                        mNfcAdapter.enableForegroundDispatch(FullscreenActivity.mInstance, mNfcPendingIntent, mIntentFiltersArray, mTechLists);
                        traceOk();
                    }
                    catch (Throwable t) {
                        traceFails(t);
                    }
                }
            }
            return false;
        }
    };

    /* read Intent
     */
    private final View.OnTouchListener mFuncNfcRead = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (mNfcAdapter != null) {
                try {
                    traceTry("onNewIntent");
                    onNewIntent(getIntent());
                    traceOk();
                }
                catch (Throwable t) {
                    traceFails(t);
                }
            }
            return false;
        }
    };

    /* Disable NFC
     */
    private final View.OnTouchListener mFuncNfcDisable = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){
                traceTry("mFuncNfcDisable");
                if (mNfcAdapter != null) {
                    try {
                        traceTry("mNfcAdapter.disableForegroundDispatch");
                        mNfcAdapter.disableForegroundDispatch(FullscreenActivity.mInstance);
                        traceOk();
                    }
                    catch (Throwable t) {
                        traceFails(t);
                    }
                }
            }
            return false;
        }
    };

    /* Quit
     */
    private final View.OnTouchListener mFuncBtnQuit = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            finish();
            return false;
        }
    };

    /**/
    private void trace(String m) {
        Log.d("NFCTags",m);
        /*TextView tv = (TextView)findViewById(R.id.logContent_value);
        String previous = tv.getText().toString();
        tv.setText(previous + "\n" + m);*/
        if (mLogAdapter != null) {
            mLogItems.add(m);
            mLogAdapter.notifyDataSetChanged();
            mLogList.setSelection(mLogList.getCount()-1);
        }
    }

    String mMessage = "";
    private void traceTry(String m) {
        trace(m + "...");
        mMessage = m;
    }

    private void traceOk() {
        trace(mMessage + " OK");
        mMessage = "";
    }

    private void traceFails(Throwable t) {
        String msg = mMessage + " fails";
        if (t != null) {
            msg += " exception:" + t.getMessage();
        }
        trace(msg);
        //Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
        mMessage = "";
    }

    /*
        */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        trace("onCreate...");

        // set global unique instance
        FullscreenActivity.mInstance = this;

        setContentView(R.layout.activity_fullscreen);

        // log
        mLogItems.add("starts");
        mLogAdapter = new android.widget.ArrayAdapter<String>(this, R.layout.support_simple_spinner_dropdown_item, mLogItems);
        mLogList = (android.widget.ListView) findViewById(R.id.logList_value);
        mLogList.setAdapter(mLogAdapter);

        mVisible = true;
        mControlsView = findViewById(R.id.fullscreen_content_controls);
        mContentView = findViewById(R.id.fullscreen_content);
        mTagContentText = (TextView) findViewById(R.id.tagContent_value);

        // Set up the user interaction to manually show or hide the system UI.
        mContentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                toggle();
            }
        });

        // Upon interacting with UI controls, delay any scheduled hide()
        // operations to prevent the jarring behavior of controls going away
        // while interacting with the UI.

        findViewById(R.id.nfcRead_btn).setOnTouchListener(mFuncNfcRead);

        //findViewById(R.id.nfcDisable_btn).setOnTouchListener(mFuncNfcDisable);

        findViewById(R.id.quit_btn).setOnTouchListener(mFuncBtnQuit);

        trace("onCreate > before initializing nfc");

        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        trace("ID:" + tm.getDeviceId());
        trace("Network Operator Name:" + tm.getNetworkOperatorName());
        trace("Sim Operator Name:" + tm.getSimOperatorName());
        trace("Sim Serial Number:" + tm.getSimSerialNumber());
        trace("Phone Type:" + tm.getPhoneType());
        trace("Initial Phone Number:" + tm.getLine1Number());

        boolean tryNfc = true;

        if (tryNfc) {

            try {
                mMessage = "NfcAdapter.getDefaultAdapter";
                mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
                if (mNfcAdapter == null) {
                    mMessage = "NFC is not available";
                    traceFails(null);
                    return;
                } else {
                    traceOk();
                }
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            // Check if NFC is enabled
            try {
                mMessage = "test NfcAdapter.isEnabled";
                if (!mNfcAdapter.isEnabled()) {
                    mMessage = "NFC is not enabled. do it manually";
                    traceFails(null);
                    return;
                } else {
                    trace("NFC is enabled.");
                }
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "create new Intent";
                mNfcIntent = new Intent(this, getClass());
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "mNfcIntent.addFlags, PendingIntent.getActivity";
                mNfcIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
                mNfcPendingIntent = PendingIntent.getActivity(this, 0, mNfcIntent, 0);
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "new IntentFilter";
                mTagIntentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
                mTagIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "addDataType, new IntentFilter[]";
                mTagIntentFilter.addDataType("*/*");
                //mTagIntentFilter.addDataType("text/plain");
                mIntentFiltersArray = new IntentFilter[]{mTagIntentFilter};
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            // Setup a tech list for all NfcF tags
            try {
                mMessage = "new tech list";
                //mTechLists = new String[][]{new String[]{NfcF.class.getName()}};
                mTechLists = new String[][]{};
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            /*
            if (mNfcAdapter != null) {
                try {
                    mMessage = "mNfcAdapter.enableForegroundDispatch";
                    mNfcAdapter.enableForegroundDispatch(FullscreenActivity.mInstance, mNfcPendingIntent, mIntentFiltersArray, mTechLists);
                    traceOk();
                }
                catch (Throwable t) {
                    traceFails(t);
                }
            }
            */
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        trace("onPostCreate...");

        // Trigger the initial hide() shortly after the activity has been
        // created, to briefly hint to the user that UI controls
        // are available.
        //delayedHide(100);
    }

    private void toggle() {

        trace("toggle...");

        if (mVisible) {
            hide();
        } else {
            show();
        }
    }

    private void hide() {

        trace("hide...");

        // Hide UI first
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.hide();
        }
        mControlsView.setVisibility(View.GONE);
        mVisible = false;

        // Schedule a runnable to remove the status and navigation bar after a delay
        mHideHandler.removeCallbacks(mShowPart2Runnable);
        mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
    }

    @SuppressLint("InlinedApi")
    private void show() {
        // Show the system bar
        mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        mVisible = true;

        // Schedule a runnable to display UI elements after a delay
        mHideHandler.removeCallbacks(mHidePart2Runnable);
        mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
    }

    /**
     * Schedules a call to hide() in [delay] milliseconds, canceling any
     * previously scheduled calls.
     */
    private void delayedHide(int delayMillis) {
        mHideHandler.removeCallbacks(mHideRunnable);
        mHideHandler.postDelayed(mHideRunnable, delayMillis);
    }

    /**/
    public String intentToText(Intent intent){

        String report = "?";
        try {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
                java.util.Set<String> keys = bundle.keySet();
                java.util.Iterator<String> it = keys.iterator();
                report = "Intent:{";
                while (it.hasNext()) {
                    String key = it.next();
                    report += "\n[" + key + ":" + bundle.get(key) + "],";
                }
                report += "}\n";
            }
        }
        catch(Throwable t){
            trace("intentToText > " + t.getMessage());
        }
        return report;
    }

    /**/
    public static String byteArrayToHex(byte[] a) {
        StringBuilder sb = new StringBuilder(a.length * 2);
        for(byte b: a)
            sb.append(String.format("%02x", b & 0xff));
        return sb.toString();
    }

    /**/
    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        trace("onNewIntent...");

        handleIntent(intent);
    }

    /**/
    void handleIntent(Intent intent) {

        if (intent == null) return;

        String sAction = intent.getAction();
        trace("act:" + sAction);

        if( (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))
            ||  (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()))
            ||  (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()))) {

            String payload = intent.getDataString();

            mTagContentText.setText("act:" + sAction + "\n" + "pload:" + payload + "\n" + intentToText(intent));

            Tag myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            if (myTag != null) {
                trace("myTag.mId:" + byteArrayToHex(myTag.getId()));
                mTagContentText.setText(mTagContentText.getText() + "\n" + "myTag.mId:" + byteArrayToHex(myTag.getId()));

                android.os.Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
                if (rawMsgs != null) {
                    for (android.os.Parcelable p : rawMsgs) {
                        NdefMessage msg = (NdefMessage) p;
                        NdefRecord[] records = msg.getRecords();
                        for (NdefRecord record : records) {
                            short tnf = record.getTnf();
                            byte[] id = record.getId();
                            byte[] payLoad = record.getPayload();
                        }
                    }
                }
            }
        }

    }

    /**/
    @Override
    protected void onResume() {
        super.onResume();

        trace("onResume...");
        if (mNfcAdapter != null) {
            try {
                // See if the Activity is being started/resumed due to an NFC event and handle it
                onNewIntent( getIntent());
            }
            catch (Throwable t) {
                traceFails(t);
            }

            try {
                mMessage = "mNfcAdapter.disableForegroundDispatch";
                mNfcAdapter.disableForegroundDispatch(FullscreenActivity.mInstance);
                traceOk();
            }
            catch (Throwable t) {
                traceFails(t);
            }
        }
    }

    /**/
    @Override
    protected void onPause() {
        super.onPause();

        trace("onPause...");
    }
}

AndroidManifest.xml 是:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pdf.nfcaccess">

    <uses-permission android:name="android.permission.NFC" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_pdflauncher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:debuggable="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FullscreenActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/app_name"
            android:theme="@style/FullscreenTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

    <!--intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain"/>
    </intent-filter-->

</manifest>

您收到 NFC 意图(操作 TAG_DISCOVERED),因为您在清单中注册了它:

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

这也是为什么您的 activity 在收到意图时被重新创建的原因。

您为前台调度(任何 MIME 类型的操作 NDEF_DISCOVERED)注册的意图过滤器似乎与您的标签不匹配(或者您尚未调用启用前台的代码段调度)。

请注意,调用 disableForegroundDispatch() 只会禁用之前通过 enableForegroundDispatch() 注册的前台调度。它不会影响清单中的意图过滤器。请参阅 Android: Can I enable/disable an activity's intent filter programmatically?,了解如何有选择地禁用在清单中注册的 Intent 过滤器。但是,关于 NFC 意图,您可能希望通过前台调度系统注册接收 all 标签的事件,然后在 onNewIntent() 中接收到事件后有选择地忽略您不想要的标签。

关于您的代码的更多信息(实际上只是关于 NFC 部分)

  1. 对于清单中的 NDEF_DISCOVERED 意图过滤器,您通常还希望指定一个与标签数据类型相匹配的 <data ... /> 元素。

  2. 不要在您的清单中使用TAG_DISCOVERED intent 过滤器(除非您真的理解并想要它的影响)。 TAG_DISCOVERED 意图过滤器(当在清单中使用时)只是 API 级别 9(在 Android 2.3.3 之前)的兼容模式,其中NFC 支持非常非常有限,并且可以使用后备模式创建处理 任何其他 应用程序不支持的 NFC 标签的应用程序。

  3. TECH_DISCOVERED 意图过滤器 需要 技术列表 XML 文件以匹配任何标签。因此,您清单中此过滤器的当前版本将永远不会匹配任何内容。

  4. onResume()中调用disableForegroundDispatch()没有任何意义。根据设计,在 activity 生命周期的这一点永远无法启用前台调度。这样做的原因是你不能在onResume()之前调用enableForegroundDispatch()并且你必须最迟在onPause().

    [=69=调用disableForegroundDispatch() ]
  5. 事实上,在 onResume() / onPause() 之外的任何地方使用 enableForegroundDispatch() / disableForegroundDispatch() 是没有意义的。如果您想停止监听其他事件(例如按下按钮)的标签,您只需在某些标志中记住您当前的状态(process/don不处理 NFC 事件),并且当您收到新的 NFC 意图时在 onNewIntent() 中,您将处理或静默忽略基于该标志的标签。

  6. 您的代码不应手动调用 activity 生命周期方法(正如您目前使用 onNewIntent(getIntent()); 所做的那样)。