• NFC简析与应用


    前言

    最近在玩NFC的功能,感觉NFC的蕴含了巨大的能量,脑海中浮现各种骚操作,心情有点小激动。当然网上不乏许多优秀的文章,这里笔者只是给出自己得理解,方便快速掌握。

    NFC 能做什么
    • 老人小孩 自动打电话功能
    • WIFI贴卡自动连接
    • 语音留言
    • 家长模式,监控小孩玩手机
    NFC示例

    这里从demo入手,站在巨人得肩膀上我们能走得更远 ,文末给出源码地址

    配置

    CardEmulation(卡片模拟)

    • AndroidManifest
      标准写法直接梭哈
    
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.example.android.cardemulation"
       android:versionCode="1"
       android:versionName="1.0">
       <uses-permission android:name="android.permission.NFC" />
    
       
       <uses-feature
           android:name="android.hardware.nfc"
           android:required="true" />
       
       <uses-feature
           android:name="android.hardware.nfc.hcef"
           android:required="true" />
       
       <uses-feature
           android:name="android.hardware.nfc.hce"
           android:required="true" />
    
    
       <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
       <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
       <uses-permission android:name="android.permission.NFC_TRANSACTION_EVENT"/>
       <uses-permission android:name="android.permission.BIND_NFC_SERVICE"/>
    
       <application android:allowBackup="true"
           android:label="@string/app_name"
           android:icon="@drawable/ic_launcher"
           android:theme="@style/AppTheme">
    
           
           <activity android:name=".MainActivity"
                     android:label="@string/app_name">
               <intent-filter>
                   <action android:name="android.intent.action.MAIN" />
                   <category android:name="android.intent.category.LAUNCHER" />
               intent-filter>
    
           activity>
    
    
           
           <service android:name=".CardService"
                    android:exported="true"
                    android:permission="android.permission.BIND_NFC_SERVICE">
               
               <intent-filter>
                   <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
               intent-filter>
               
               <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
                          android:resource="@xml/aid_list"/>
           service>
    
       application>
    manifest>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    aid_list
    作为模拟卡3000000002即为其卡号,也是建立连接得唯一标识,这里理解即可,不必深究。

    • category
      还有一个模式为payment,即付款应用,貌似是说可以自动启动,试了下没成功,此处先不管
    • android:description 自动唤起时,后台显示名称,当然还能配置图标,怎么配
    
    <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
        android:description="@string/service_name"
        android:requireDeviceUnlock="false">
    
        <aid-group android:description="@string/card_title" android:category="other">
            <aid-filter android:name="3000000002"/>
        aid-group>
    
    host-apdu-service>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    绑定服务
    下面是标准代码,且必须在onResumeonPause处开关nfc

    public class MainActivity extends SampleActivityBase {
    
        public static final String TAG = "MainActivity";
        private boolean mLogShown;
        private CardEmulation mCardEmulation;
        private ComponentName mService;
        private static List<String> AIDS = new ArrayList<>();
        private NfcAdapter mNfcAdapter;
        private PendingIntent mPendingIntent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
            mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
            mService = new ComponentName(this, CardService.class);
            AIDS.add(CardService.SAMPLE_LOYALTY_CARD_AID);
        }
    
    
        @Override
        protected void onResume() {
            super.onResume();
            mCardEmulation.setPreferredService(this, mService);
            mCardEmulation.registerAidsForService(mService, "other", AIDS);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            mNfcAdapter.disableForegroundDispatch(this);
            mCardEmulation.removeAidsForService(mService, "other");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    HostApduService
    开启服务,此服务主要是进行命令收发,着重讲一下其思路和流程

    • AID,即卡号需要与前面的aid-filter对应
        // AID for our loyalty card service.
        public static final String SAMPLE_LOYALTY_CARD_AID = "3000000002";
    
    • 1
    • 2
    • 理解为握手标志,验证用,其实也可以不要
        // Format: [Class | Instruction | Parameter 1 | Parameter 2]
        private static final String SELECT_APDU_HEADER = "00A40400";
    
    • 1
    • 2
    • 具体传输数据标志,比如笔者这里会加入一个JSON
        // Format: [Class | Instruction | Parameter 1 | Parameter 2]
        private static final String GET_DATA_APDU_HEADER = "00CA0000";
    
    • 1
    • 2
    • SELECT_OK_SW : 成功标志,加到实际数据数组尾部
    • UNKNOWN_CMD_SW : 错误标志,加到实际数据数组尾部
      因为全程为buffer传输,所以会有很多的数组切割和转换操作,这个应该不算复杂
        // "OK" status word sent in response to SELECT AID command (0x9000)
        private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000");
        // "UNKNOWN" status word sent in response to invalid APDU command (0x0000)
        private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000");
    
    • 1
    • 2
    • 3
    • 4
    • 具体交互是怎么做的呢?(processCommandApdu)
      对! 就是按照命令做一问一答
        @Override
        public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
            Toast.makeText(getApplicationContext(), "processCommandApdu", Toast.LENGTH_LONG);
            Log.i(TAG, "Received APDU: " + ByteArrayToHexString(commandApdu));
            if (Arrays.equals(SELECT_APDU, commandApdu)) {
                return ConcatArrays("OK".getBytes(), SELECT_OK_SW);
            } else if ((Arrays.equals(GET_DATA_APDU, commandApdu))) {
                JSONObject jsonObject = new JSONObject();
                try {
                    jsonObject.put("ssid", "cameraui");
                    jsonObject.put("password", "12345678");
                } catch (JSONException e) {
                }
                String account = jsonObject.toString();
                byte[] accountBytes = account.getBytes();
                Log.i(TAG, "Sending account number: " + account);
                return ConcatArrays(accountBytes, SELECT_OK_SW);
            } else {
                return UNKNOWN_CMD_SW;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    CardReader(读卡器)

    AndroidManifest

    标准写法,同样是梭哈

    <?xml version="1.0" encoding="UTF-8"?>
     
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.android.cardreader"
        android:versionCode="1"
        android:versionName="1.0">
    
        <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    
        <!-- NFC Reader Mode was added in API 19. -->
        <uses-permission android:name="android.permission.NFC" />
        <uses-feature android:name="android.hardware.nfc" android:required="true" />
    
        <application android:allowBackup="true"
            android:label="@string/app_name"
            android:icon="@drawable/ic_launcher"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity"
                      android:label="@string/app_name"
                      android:launchMode="singleTop">
                <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" />
                    <data android:mimeType="*/*" />
                    <!--<data-->
                    <!--android:host="ext"-->
                    <!--android:pathPrefix="/vndcn.com:nfc"-->
                    <!--android:scheme="vnd.android.nfc" />-->
                </intent-filter>
                <intent-filter>
                        <action android:name="android.nfc.action.TAG_DISCOVERED" />
                        <category android:name="android.intent.category.DEFAULT" />
                        <data android:mimeType="*/*" />
                </intent-filter>
                <intent-filter>
                    <action android:name="android.nfc.action.TECH_DISCOVERED"/>
                </intent-filter>
                <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
            </activity>
        </application>
    
    
    </manifest>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    nfc_tech_filter
    标签列表,全兼容就是了

    
    
    <resources xmlns:android="http://schemas.android.com/apk/res/android">
        <tech-list>
            <tech>android.nfc.tech.IsoDeptech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.NfcAtech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.NfcBtech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.NfcFtech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.NfcVtech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.Ndeftech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.NdefFormatabletech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.MifareClassictech>
        tech-list>
        <tech-list>
            <tech>android.nfc.tech.MifareUltralighttech>
        tech-list>
    resources>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    读卡页面
    笔者自己写的读卡页面,支持命令交互和系统启动即nfc卡片启动方式,直接继承即可

    /**
     * 文件名:NFCActivity
     * 描  述:
     * 作  者:05878mq
     * 时  间:2022/8/16 15:28
     */
    abstract public class NFCActivity implements LoyaltyCardReader.AccountCallback {
    
        final static String TAG = NFCActivity.class.getSimpleName();
    
        //支持的标签类型
        private final int nfcFlag = NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
                | NfcAdapter.FLAG_READER_NFC_A
                | NfcAdapter.FLAG_READER_NFC_B
                | NfcAdapter.FLAG_READER_NFC_BARCODE
                | NfcAdapter.FLAG_READER_NFC_F
                | NfcAdapter.FLAG_READER_NFC_V;
    
        @Override
        public void onPause() {
            disableReaderMode();
            super.onPause();
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.i(TAG, "getIntent()" +getIntent());
            disposeIntent(getIntent());
        }
    
        @Override
        public void onResume() {
            try {
                enableReaderMode();
            } catch (IntentFilter.MalformedMimeTypeException e) {
                e.printStackTrace();
            }
            super.onResume();
        }
    
        private void disableReaderMode() {
            Log.i(TAG, "Disabling reader mode");
            NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
            if (nfc != null) {
                //nfc.disableReaderMode(this);
                nfc.disableForegroundDispatch(this);
            }
        }
    
        private void enableReaderMode() throws IntentFilter.MalformedMimeTypeException {
            Log.i(TAG, "Enabling reader mode");
            NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
            if (nfc != null) {
                IntentFilter[] FILTERS = new IntentFilter[]{new IntentFilter(
                        NfcAdapter.ACTION_TECH_DISCOVERED, "*/*")};
                String[][] TECHLISTS = new String[][]{{IsoDep.class.getName()},
                        {NfcV.class.getName()}, {NfcF.class.getName()},};
    
                PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                        new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
                nfc.enableForegroundDispatch(this, pendingIntent, FILTERS, TECHLISTS);
                nfc.enableReaderMode(this, new LoyaltyCardReader(this), nfcFlag, new Bundle());
            }
        }
    
        @Override
        protected void onNewIntent(Intent intent) {
            super.onNewIntent(intent);
    //        if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.getAction()) {
    //            new LoyaltyCardReader(this).onTagDiscovered(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
    //        }
            disposeIntent(intent);
        }
    
        private void disposeIntent(Intent intent) {
            Log.d("NfcAdapter", getIntent().getAction());
            if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.getAction()) {
                String cardId = getCardId(intent);
                if (cardId != null) {
                    Log.d(TAG, String.format("NFC ID:%s", cardId));
                } else {
                    Log.d(TAG, "未读取到卡ID");
                }
            }
    
        }
    
        private void readNfcTag(Intent intent) {
            if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
                Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
                        NfcAdapter.EXTRA_NDEF_MESSAGES);
                NdefMessage msgs[] = null;
                int contentSize = 0;
                if (rawMsgs != null) {
                    msgs = new NdefMessage[rawMsgs.length];
                    for (int i = 0; i < rawMsgs.length; i++) {
                        msgs[i] = (NdefMessage) rawMsgs[i];
                        contentSize += msgs[i].toByteArray().length;
                    }
                }
                try {
                    if (msgs != null) {
                        NdefRecord record = msgs[0].getRecords()[0];
    
                        //byte[] payload = record.getPayload();
                        //String res = new String(payload);
    
                        //数据格式定义 前两字节表示编码类型
                        //https://www.cnblogs.com/sjjg/p/4782546.html
                        String res = TextRecord.parseCustom(record).getText();
                        Log.d(TAG, "content url is: " + res);
                        getNFCInfoSuccess(res);
                    }
                } catch (Exception e) {
                }
            }
        }
    
        protected abstract void getNFCInfoSuccess(String res);
    
    
        private String getCardId(Intent intent) {
            Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            byte[] bytesId = tagFromIntent.getId();
            readNfcTag(intent);
            Ndef ndef = Ndef.get(tagFromIntent);
            return LoyaltyCardReader.ByteArrayToHexString(bytesId);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    标签的解析
    TXT 数据格式定义 前两字节表示编码类型 参考下面连接
    https://www.cnblogs.com/sjjg/p/4782546.htm
    自定义格式 使用 parseCustom

    
    /**
     * 文件名:SS
     * 描  述:
     * 作  者:05878mq
     * 时  间:2022/8/19 17:20
     */
    public class TextRecord {
        private final String mText;
    
        private TextRecord(String text) {
            mText = text;
        }
    
        public String getText() {
            return mText;
        }
    
        public static TextRecord parse(NdefRecord ndefRecord) {
            /*
             * 1,判断数据是否为NDEF格式
             */
            // verify tnf
            //第一个判断,TNF(类型名格式,Type Name Format)必须是NdefRecord.TNF_WELL_KNOWN
            if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) {
                return null;
            }
            //第二个判断,可变的长度类型必须是NdefRecord.RTD_TEXT。
            if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
                return null;
            }
    
            try {
                /*
                 * 2,取得读到的ndef字节流,
                 * 第1个字节描述了数据的状态,然后若干个字节描述文本的语言编码,最后剩余字节表示文本数据
                 */
                byte[] payload = ndefRecord.getPayload();
    
                /*
                 * 3,解析第1个字节最高位,第7位:本流的字符编码值, 若值是0是UTF8,1是UTF16
                 * 注意 字符编码与语言编码不同.
                 */
                String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16";
                //第1个字节第6位总为0
                /*
                 * 4,解析第1个字节0-5位,它存放语言编码的长度值
                 * 注意 字符编码与语言编码不同.
                 */
                int languageCodeLength = payload[0] & 0x3f;
    
                /*
                 * 5,解析语言编码
                 */
                String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
    
                /*
                 * 6,解析出文本内容
                 */
                String text = new String(payload, languageCodeLength + 1,
                        payload.length - languageCodeLength - 1, textEncoding);
    
                /*
                 * 7,返回解析结果
                 */
                return new TextRecord(text);
    
            } catch (Exception e) {
                throw new IllegalArgumentException();
            }
        }
        public static TextRecord parseCustom(NdefRecord ndefRecord) {
            /*
             * 1,判断数据是否为NDEF格式
             */
            // verify tnf
            //第一个判断,TNF(类型名格式,Type Name Format)必须是NdefRecord.TNF_WELL_KNOWN
            if (ndefRecord.getTnf() != NdefRecord.TNF_MIME_MEDIA) {
                return null;
            }
    
            //第二个判断,可变的长度类型必须是NdefRecord.RTD_TEXT。
            byte[] CustomType = "application/xxx.xxx.xxx.android.nfc".getBytes();
            if (!Arrays.equals(ndefRecord.getType(), CustomType)) {
                return null;
            }
    
            try {
                /*
                 * 2,取得读到的ndef字节流,
                 * 第1个字节描述了数据的状态,然后若干个字节描述文本的语言编码,最后剩余字节表示文本数据
                 */
                byte[] payload = ndefRecord.getPayload();
    
                /*
                 * 6,解析出文本内容
                 */
                String text = new String(payload, "UTF-8");
    
                /*
                 * 7,返回解析结果
                 */
                return new TextRecord(text);
    
            } catch (Exception e) {
                throw new IllegalArgumentException();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    解析成功回调
    getNFCInfoSuccess

    小结

    到此一个标准的读写流程已经完成,此时我们仍然需要手动打开自己的APP,然后进行数据交互
    那么如何能不开启APP直接拿想要的json数据,并进行下一步操作,答案是直接卡刷启动, 前面代码中注意到

    byte[] CustomType = "application/xxx.xxx.xxx.android.nfc".getBytes();
    此处即自定义标签,可打开指定APP并传输自定义数据。

    番外
    写白卡
    • 工具写入
      拿到一张nfc白卡,推荐TagWriterTagInfo,故名思意,一读一写

    主要是TagWriter 可写入包名和自定义文本,当然只是辅助工具,我们最终还是要自己实现该功能

    • 代码实现
    
        private void writeNFC(Tag tag, String content) {
            // 这里是将数据写入NFC卡中
    //            NdefRecord record1 = NdefRecord.createApplicationRecord("xxx.xxx.xxx.xxx");
    //            NdefRecord record2 = NdefRecord.createExternal("xxx.xxx", "nfc", content.getBytes());
    
    //            NdefRecord record2 = NdefRecord.createExternal("xxx.xxx", "nfc", "{\"ssid\":\"cameraui\",\"password\":\"12345678\"}".getBytes());
    //            NdefRecord record1 = NdefRecord.createApplicationRecord("com.guide.capp");
    //            NdefRecord record2 = NdefRecord.createExternal("xxx.xxx.xxx", "nfc", "{\"ssid\":\"cameraui\",\"password\":\"12345678\"}".getBytes());
    
            String mimeType = "application/xxx.xxx.xxx.android.nfc";
            byte[] mimeBytes = mimeType.getBytes(Charset.forName("UTF-8"));
            byte[] dataBytes = "{\"ssid\":\"cameraui\",\"password\":\"12345678\"}".getBytes(Charset.forName("UTF-8"));
            byte[] id = new byte[0];
    
            NdefRecord record3 = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, id, dataBytes);
            NdefRecord[] nfcs = {record3};
    	    //NdefRecord[] nfcs = {record1, record2};
            NdefMessage ndefMessage = new NdefMessage(nfcs);
            int size = ndefMessage.toByteArray().length;
            try {
                Ndef ndef = Ndef.get(tag);
                if (ndef != null) {
                    ndef.connect();
                    if (!ndef.isWritable()) {
                        return;
                    }
                    if (ndef.getMaxSize() < size) {
                        return;
                    }
                    try {
                        ndef.writeNdefMessage(ndefMessage);
                        Toast.makeText(this, "写入成功", Toast.LENGTH_LONG).show();
                    } catch (FormatException e) {
                        e.printStackTrace();
                    }
                } else {
                    NdefFormatable format = NdefFormatable.get(tag);
                    format.connect();
                    format.format(ndefMessage);
                    if (format.isConnected()) {
                        format.close();
                    }
                    Toast.makeText(this, "写入成功", Toast.LENGTH_LONG).show();
                }
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "写入失败", Toast.LENGTH_LONG).show();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    这样只能写入一张实体卡,虽然能直接启动APP并执行相关操作,但是无卡的情况下,直接手机模拟ic卡岂不是更加完美

    模拟卡

    先分享一个软件 NFC卡模拟 主要是利用他的模拟卡功能,当然你的手机需要root,所以这是一条不归路,除非定制的手机,不建议这样玩,但这与笔者推出编译Android源码的想法不谋而合,Framework一直是笔者追求,所谓任重而道远,也祝学习的路上大家都更上一层楼,后会有期咯。

    https://github.com/championswimmer/NFC-host-card-emulation-Android

    https://github.com/android10/nfc_android_sample

    https://github.com/TracyEminem/NFCNDEF

  • 相关阅读:
    Qt 客户端和服务器通信【一对一版本】
    532.数组中的k-diff数对 Python&Java 哈希表、双指针双解
    运算符+分支+循环语句
    uniapp 部署h5,pdf预览
    前端开发——HTML5新增加的表单属性
    二、数据链路层
    嵌入式软件行业真的没前途吗?
    AI: 2021 年人工智能前沿科技报告(更新中……)
    String的intern()方法详解
    国际炒黄金策略,炒黄金要怎么炒?
  • 原文地址:https://blog.csdn.net/qq_20330595/article/details/126484215