尝试在空对象引用上调用虚拟方法 'void android.nfc.tech.MifareClassic.connect()'

Attempt to invoke virtual method 'void android.nfc.tech.MifareClassic.connect()' on a null object reference

我一直在 Android Studio 中开发一个应用程序来读写 NFC 标签,特别是 Mifare Classic 标签。我在 2016 年初(一年前)设法在我的智能手机(使用 S.O.KitKat)上开发和测试它。

正如我提到的,将应用程序放在一边,在更新 Android Studio、SDK 和 S.O 的版本之后。从我的智能手机到 MarshMallow,尝试写入标签时出现此错误:"java.lang.NullPointerException: Attempt to invoke virtual method 'void android.nfc.tech.MifareClassic.connect()' on a null object reference".

这个错误显然是在尝试连接到 MifareClassic 标签时产生的。

附上我的代码 activity 将一些我认为无关紧要的部分替换为...。

import android.annotation.SuppressLint;
import android.app.Activity;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.os.Bundle;
import android.view.View.OnClickListener;
...

import java.io.IOException;
import java.io.UnsupportedEncodingException;

@SuppressLint("Escribir")
public class escribir extends Activity {
    NfcAdapter adapter;
    PendingIntent pendingIntent;
    IntentFilter writeTagFilters[];
    boolean writeMode;
    Tag myTag;
    MifareClassic mfc;
    Context context;
    ...

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

        setContentView(R.layout.activity_datospropietario);
        context = this;
        ...

        Button btnWrite = (Button)findViewById(R.id.button);
        final String b = getIntent().getExtras().getString("datos");

        btnWrite.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                final String mensaje = (b + ...);

                if (first.getText().toString().isEmpty()) {
                    Toast.makeText(context, context.getString(R.string.missing_fields), Toast.LENGTH_SHORT).show();
                } else {
                    if (myTag == null) {
                        Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
                    } else {
                    MifareClassic tmpMFC = null;
                    try {
                        tmpMFC = MifareClassic.get(myTag);
                    } catch (Exception e) {
                        Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
                        e.printStackTrace();
                    }
                    mfc = tmpMFC;

                    int sect;
                    if (mfc != null) {
                        sect = mfc.getSectorCount();
                    }
                    try {
                        mfc.connect();
                        ...
                    } catch (IOException e) {
                        Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
                        e.printStackTrace();
                        myTag = null; 
                    }
                }
            }
        });

        adapter = NfcAdapter.getDefaultAdapter(this);
        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
        tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
        writeTagFilters = new IntentFilter[]{tagDetected};
    }

    private void write(String text, Tag tag, int sector) throws IOException, FormatException {

        NdefRecord[] records = {createRecord(text), NdefRecord.createApplicationRecord("my_app")};
        NdefMessage mensaje = new NdefMessage(records);

        NdefFormatable formatable = NdefFormatable.get(tag);

        if (formatable != null) {
            formatable.connect();
            formatable.format(mensaje);
            formatable.close();
        } else {
            Ndef ndef = Ndef.get(tag);
            ndef.connect();
            ndef.writeNdefMessage(mensaje);
            ndef.close();
        }

        MifareClassic mfc = MifareClassic.get(tag);
        ...
    }

    @SuppressLint("Escribir") private NdefRecord createRecord(String text) throws UnsupportedEncodingException{
        String lang = "es";
        byte[] textBytes = text.getBytes();
        byte[] langBytes = lang.getBytes("US-ASCII");
        int langLength = langBytes.length;
        int textLength = textBytes.length;
        byte[] payLoad = new byte[1 + langLength + textLength];

        payLoad[0] = (byte) langLength;

        System.arraycopy(langBytes, 0, payLoad, 1, langLength);
        System.arraycopy(textBytes, 0, payLoad, 1 + langLength, textLength);

        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payLoad);
    }

    @SuppressLint("Escribir") protected void onNewIntent(Intent intent){
        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
            myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        }
    }

    public void onPause(){
        super.onPause();
        WriteModeOff();
    }
    public void onResume(){
        super.onResume();
        WriteModeOn();
    }

    @SuppressLint("Escribir") private void WriteModeOn(){
        writeMode = true;
        adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
    }

    @SuppressLint("Escribir") private void WriteModeOff(){
        writeMode = false;
        adapter.disableForegroundDispatch(this);
    }
}

错误可能是在您执行以下操作时:

int sect = mfc.getSectorCount();

因为 mfc 在执行

后可能为空
MifareClassic mfc = MifareClassic.get(myTag);

上网查了一下这个问题,发现NFC标签写入问题出现在各种设备上,比如一些HTC或Sony Xperia机型。这些问题是在 Android 版本升级到 SDK 5.1 (Lollipop) 后出现的。

这个问题的起源是因为一些制造商修改了堆栈顺序,发现不同的标签类型在请求 TechExtras 并返回 SAK 空值或错误值时导致冲突。

我找到的详细解释如下:

HTC One:看来,这个bug的原因是NfcA的TechExtras为空。 但是,TechList 包含 MifareClassic。

Sony Xperia Z3(+ 仿真 MIFARE Classic 标签):越野车标签在 TechList 中有两个具有不同 SAK 值的 NfcA 和一个 MifareClassic(带有第二个 NfcA 的 Extra ).第二个 NfcA 和 MifareClassic 技术都具有 0x20 的 SAK。根据恩智浦关于识别 MIFARE 标签的指南(第 11 页),这是一个 MIFARE Plus 或 MIFARE DESFire 标签。此方法使用两个 NfcA 出现的 SAK 值或运算(如 NXP 的 MIFARE 类型识别过程指南中所述)创建一个新的 extra,并将第一个 NfcA 的 Extra 替换为新的。

更多信息请参考https://github.com/ikarus23/MifareClassicTool/issues/52

以及解决我问题的 bildin 用户提出的补丁:

public Tag patchTag(Tag oTag)
{
    if (oTag == null) 
        return null;

    String[] sTechList = oTag.getTechList();

    Parcel oParcel, nParcel;

    oParcel = Parcel.obtain();
    oTag.writeToParcel(oParcel, 0);
    oParcel.setDataPosition(0);

    int len = oParcel.readInt();
    byte[] id = null;
    if (len >= 0)
    {
        id = new byte[len];
        oParcel.readByteArray(id);
    }
    int[] oTechList = new int[oParcel.readInt()];
    oParcel.readIntArray(oTechList);
    Bundle[] oTechExtras = oParcel.createTypedArray(Bundle.CREATOR);
    int serviceHandle = oParcel.readInt();
    int isMock = oParcel.readInt();
    IBinder tagService;
    if (isMock == 0)
    {
        tagService = oParcel.readStrongBinder();
    }
    else
    {
        tagService = null;
    }
    oParcel.recycle();

    int nfca_idx=-1;
    int mc_idx=-1;

    for(int idx = 0; idx < sTechList.length; idx++)
    {
        if(sTechList[idx] == NfcA.class.getName())
        {
            nfca_idx = idx;
        }
        else if(sTechList[idx] == MifareClassic.class.getName())
        {
            mc_idx = idx;
        }
    }

    if(nfca_idx>=0&&mc_idx>=0&&oTechExtras[mc_idx]==null)
    {
        oTechExtras[mc_idx] = oTechExtras[nfca_idx];
    }
    else
    {
        return oTag;
    }

    nParcel = Parcel.obtain();
    nParcel.writeInt(id.length);
    nParcel.writeByteArray(id);
    nParcel.writeInt(oTechList.length);
    nParcel.writeIntArray(oTechList);
    nParcel.writeTypedArray(oTechExtras,0);
    nParcel.writeInt(serviceHandle);
    nParcel.writeInt(isMock);
    if(isMock==0)
    {
        nParcel.writeStrongBinder(tagService);
    }
    nParcel.setDataPosition(0);

    Tag nTag = Tag.CREATOR.createFromParcel(nParcel);

    nParcel.recycle();

    return nTag;
}

此补丁由 bildin (https://github.com/bildin) 提供。

虽然我测试的设备不是早期品牌,但该补丁在我的 Moto X(第一代)上运行良好,带有修改后的 ROM Marshmallow,所以我想它也适用于广泛的范围使用 NXP 芯片 PN544

的设备
MifareClassic mfc = MifareClassic.get(myTag);

仅当卡技术列表具有 android.nfc.tech.MifareClassic 时才创建 mfc 对象,否则 returns MifareClassic 对象为 null。

您可以通过myTag.getTechList();

查看技术列表

使用其技术列表中包含上述技术的卡片,您就可以开始了。

也可以参考这个解释mifareclassic卡内存结构的答案:

如果有人来这里弄清楚如何读取 MifareClassic 卡,那么这里有一个存储库可以这样做。 https://github.com/codes29/RFIDReader/blob/master/app/src/main/java/com/codes29/rfidreader/MainActivity.java

在我的种姓中,必须首先格式化 NFC 标签。

String[] techList = tag.getTechList();

没有包含所需的技术:

TagTechnology.NDEF

但是

TagTechnology.NDEF_FORMATABLE

已列出。我将标签格式化为:

NdefFormatable ndefFormatable = NdefFormatable.get(tag);
                    ndefFormatable.connect();
                    ndefFormatable.format(message);
                    ndefFormatable.close();