zoukankan      html  css  js  c++  java
  • 【转】Android NFC学习笔记

    一:NFC的tag分发系统

    如果想让android设备感应到NFC标签,你要保证两点

    1:屏幕没有锁住 2:NFC功能已经在设置中打开

    当系统检测到一个NFC标签的时候,他会自动去寻找最合适的activity去处理这个intent.

    他所发出的这个Intent将会有三种action:

    ACTION_NDEF_DISCOVERED:当系统检测到tag中含有NDEF格式的数据时,且系统中有activity声明可以接受包含NDEF数据的Intent的时候,系统会优先发出这个action的intent。

    ACTION_TECH_DISCOVERED:当没有任何一个activity声明自己可以响应ACTION_NDEF_DISCOVERED时,系统会尝试发出TECH的intent.即便你的tag中所包含的数据是NDEF的,但是如果这个数据的MIME type或URI不能和任何一个activity所声明的想吻合,系统也一样会尝试发出tech格式的intent,而不是NDEF.

    ACTION_TAG_DISCOVERED:当系统发现前两个intent在系统中无人会接受的时候,就只好发这个默认的TAG类型的

    二:NFC相关androidManifest文件设置

    首先是权限:<uses-permission android:name="android.permission.NFC" />

    然后是sdk级别限制:我个人建议API10开始比较合适:<uses-sdk android:minSdkVersion="10"/>

    接着是特殊功能限制<uses-feature android:name="android.hardware.nfc"android:required="true" />这个生命可以让你的应用在google play上被声明使用者必须拥有nfc功能。

    三:NFC标签过滤

    在activity的intent过滤xml声明中,你可以同时声明过滤这三种action.但是由之前所说,你应该知道系统在发送intent的时候是有优先级的,所以你最好清楚自己最想处理哪个。

    1:过滤ACTION_TAG_DISCOVERED:

        <intent-filter>
            <action android:name="android.nfc.action.TAG_DISCOVERED"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
    这个最简单,也是最后一个被尝试接受intent的选项。

    2:过滤ACTION_NDEF_DISCOVERED:

    <intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
    </intent-filter>
    其中最重要的应该算是data的mimeType类型了,这个定义的越准确,intent指向你这个activity的成功率就越高,否则系统可能不会发出你想要的NDEF intent了。下面在讲如何使用NDEF写入NFC标签的时候会多举几个类型的例子。
    3:过滤ACTION_TECH_DISCOVERED:
    你首先需要在你的<project-path>/res/xml下面创建一个过滤规则文件。名字任取,比如可以叫做nfc_tech_filter.xml。这个里面定义的是nfc实现的各种标准,每一个nfc卡都会符合多个不同的标准,个人理解为这些标准有些相互之间也是兼容的。你可以在检测到nfc标签后使用getTechList()方法来查看你所检测的tag到底支持哪些nfc标准。
    一个nfc_tech_filter.xml中可以定义多个<tech-list>结构组。每一组代表我声明我只接受同时满足这些标准的nfc标签。比如A组表示,只有同时满足IsoDep,NfcA,NfcB,NfcF这四个标准的nfc标签的intent才能进入。A与B组之间的关系就是只要满足其中一个就可以了。换句话说,你的nfc标签技术,满足A的声明也可以,满足B的声明也可以。
    <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list> --------------------------------A组
    <tech>android.nfc.tech.IsoDep</tech> <tech>android.nfc.tech.NfcA</tech><tech>android.nfc.tech.NfcB</tech> <tech>android.nfc.tech.NfcF</tech>
    </tech-list>
    <tech-list>-----------------------------------------B组
    <tech>android.nfc.tech.NfcV</tech> <tech>android.nfc.tech.Ndef</tech><tech>android.nfc.tech.NdefFormatable</tech><tech>android.nfc.tech.MifareClassic</tech><tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
    </resources>

    在androidManifest文件中声明xml过滤的举例如下

    <activity>
    <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>
    4:nfc标签前台分发系统
    之所以把他也归类在nfc的过滤里面,主要是因为他跟解析nfc标签到不是那么的紧密,他解决的是接受哪些nfc标准的标签问题。所以更接近nfc的过滤。
    什么叫nfc的前台发布系统?就是说当我们已经打开我们的应用的时候,那么通过这个前台发布系统的设置,我们可以让我们已经启动的activity拥有更高的优先级来依据我们在代码中定义的标准来过滤和处理intent,而不是让别的声明了intent filter的activity来干扰,甚至连自己声明在androidManifest中的intent filter都不会来干扰。也就是说foreground Dispatch的优先级大于intent filter。
    第一种情况:当你的activity没有启动的时候,去扫描tag,那么系统中所有的intent filter都将一起参与过滤。
    第二种情况:当你的actiity启动了,去扫描tag时,那么将直接使用你在foreground dispatch中代码写入的过滤标准。如果这个标准没有命中任何intent,那么系统将使用所有activity声明的intent filter xml来过滤。
    在OnCreate中你可以添加如下代码        
    // Create a generic PendingIntent that will be deliver to this activity. The NFC stack will fill in the intent with the details of the discovered tag before delivering to this activity.
       mPendingIntent = PendingIntent.getActivity(this, 0,
                 new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
            
            // 做一个IntentFilter过滤你想要的action 这里过滤的是ndef
            IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
    //如果你对action的定义有更高的要求,比如data的要求,你可以使用如下的代码来定义intentFilter
    //        try {
    //            ndef.addDataType("*/*");
    //        } catch (MalformedMimeTypeException e) {
    //            // TODO Auto-generated catch block
    //            e.printStackTrace();
    //        }
         //生成intentFilter   
            mFilters = new IntentFilter[] {
                    ndef,
            };
            
            // 做一个tech-list。可以看到是二维数据,每一个一维数组之间的关系是或,但是一个一维数组之内的各个项就是与的关系了
            mTechLists = new String[][] { 
                    new String[] { NfcF.class.getName()},
                    new String[]{NfcA.class.getName()},
                    new String[]{NfcB.class.getName()},
                    new String[]{NfcV.class.getName()}
                    };
    在onPause和 onResume中需要加入相应的代码。
    public void onPause() {
    super.onPause();
    //反注册 mAdapter.disableForegroundDispatch(this);
    }
     
    public void onResume() {
    super.onResume();
    //设定intentfilter和tech-list。如果两个都为null就代表优先接收任何形式的TAG action。也就是说系统会主动发TAG intent。
    mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists);
    }


    一:解析NDEF

       一直在说ndef格式,这到底是个啥格式?据文档说这是google参考nfc论坛而提出和支持的格式。也就是说nfc的格式和标准肯定有很多,google只不过比较支持其中的这一种NDEF。而如果之后apple也搞nfc的话,我怀疑他肯定就不会去支持google已经支持的ndef了。我浅薄的如此认为,以后再看看吧呵呵

      ndef格式,就是数据被包裹在一个NdefMessage里,而同时一个NdefMessage里面又可以包含多个NdefRecord.当然你也应该还记得,nfc的tag里面是可以不包含Ndef数据的,他也可以包含android.nfc.tech所定义的多种标签。Google推荐开发人员使用ndef格式的数据来处理android相关的nfc格式数据。

       当一个android设备检测到nfc标签包含ndef格式的数据的时候,他就会尝试的去解析数据中的MIME type或者是URI。为了做这个事情,他需要解析NdefMessage中的第一个NdefRecord。第一个NdefRecord中的结构一般如下:

    3-bit TNF (Type Name Format)

    如何解释variable length type的数据。并依据不同的解析,来确定发送什么样的intent。如果是这里定义的是TNF_WELL_KNOWN则还要参考variable length type来进一步确定。如果是网络地址URL则直接去调用浏览器程序了。

    Variable length type
    进一步定义record的类型。具体的请参看android文档。不过话在这里多说一句,TNF和TYPE这两个参数非常的重要,直接决定了你NDEF格式的命中程度。定义的越规范,越清楚,同时在androidmanifest文件中写的越正确,就越好,否则系统就会被你发TECH_ACTION
    比如:构造的时候写
    new NdefRecord(
                    NdefRecord.TNF_WELL_KNOWN ,
                    NdefRecord.RTD_TEXT,
                    new byte[0],
                    data.getBytes(Charset.forName("UTF-8")));
    androidmanifest文件中写
          <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:mimeType="text/plain"/>     
        </intent-filter>
        这一对就可以正确的过滤一个text/plain格式的ndef

    Variable length ID

        不经常使用.....是用来唯一标示一个record的。 

    Variable length payload

        actual这个才是真正的数据区域。你的读写操作一般应该主要是对这个区域的操作。一个NdefMessage可以有多个NdefRecord所以你不要假设所有的数据都仅仅存储在第一个NdefRecord中。

    已经知道了Ndef的格式了,那么解析带有Ndef数据的Intent也就简单很多了。底下这个解析的方法,通常放在onResume或者onNewIntent中。

       
        private boolean readFromTag(Intent in){
            Parcelable[] rawArray = in.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            //we just have noly one NdefMessage,如果你不止一个的话,那么你要遍历了。
            NdefMessage mNdefMsg = (NdefMessage)rawArray[0];
            //we just have only one NdefRecord,如果你不止一个record,那么你也要遍历出来所有的record
            NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];
            try {
                if(mNdefRecord != null){
                    readResult = new String(mNdefRecord.getPayload(),"UTF-8");
                    readJson = new JSONObject(readResult);//我举的例子中用的是json格式,所以这里要把payload中的数据封装成json格式的。
                    return true;
                }
            } catch (JSONException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            };
            return false;
        }

    二:解析其他tech标签

       现在你已经会解析ndef标签了,但是你也应该知道,大多数nfc标签其实并不是ndef格式的。比如你的公交卡,各个城市的公交卡会支持什么格式,并不一定。至少我理解是这样的。所以你还要会解析各种tech格式的nfc数据。

      如果你还记得我们再tech-list中定义了多少种格式,你就应该知道我们如果要全部解析的话,应该要解析多少种不同的nfc格式了。底下这段代码可以显示出你所扫描的nfc卡到底支持哪几种格式。

            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            String[] temp = tag.getTechList();
            for(String s :temp){
              Log.i("xxx","s = "+s);
            }

       这是一段最常用的(我个人理解啊)的nfc解析代码,主要是解析MifareClassic格式。貌似大多数nfc标签都会支持这种格式。先普及下关于他的简单常识:不知道各个格式的数据是怎么构成的,你压根就没法解析。

    一般来说,给予MifareClassic的射频卡,一般内存大小有3种:

    1K: 16个分区(sector),每个分区4个块(block),每个块(block) 16个byte数据

    2K: 32个分区,每个分区4个块(block),每个块(block) 16个byte数据

    4K:64个分区,每个分区4个块(block),每个块(block) 16个byte数据

     对于所有基于MifareClassic的卡来说,每个区最后一个块(block)叫Trailer,16个byte,主要来存放读写该区的key,可以有A,B两个KEY,每个key长6byte,默认的key一般是FF 或 0,最后一个块的内存结构如下:

    Block 0  Data 16bytes
    Block 1  Data 16 bytes
    Block 2  Data 16 bytes
    Block 3  Trailer 16 bytes
     
    Trailer:
    Key A: 6 bytes
    Access Conditions: 4 bytes
    Key B: 6 bytes 
     
    所以在写卡的内存的时候,一般不能写每个sector的最后一个block,除非你有要修改KEY和访问权限的需求。如果KEY A 被你不小心修改掉了,而你不知道修改成什么,那与之对应的那个sector你就没有办法访问了。因为在MifareClassic中,如果你要读取数据,那么必须要有这个数据地址所在的sector的权限,这个权限就是这个sector的trailer的KEY A或KEY B。
     
    这就是解析MifareClassic的代码了

        private void readTechPayloadMC(Tag tag){
            MifareClassic mc = MifareClassic.get(tag);//通过intent拿到EXTRA_TAG并转化成MirareClassic格式。

            int bCount = 0 ;
            int bIndex = 0 ;
            try {
                mc.connect();
                int sectorCount = mc.getSectorCount();//获得sector总数
                Log.i("liyufei3","sectorCount = "+sectorCount);
                for(int i =0;i<sectorCount;i++){
                    //尝试去获得每个sector的认证,只有认证通过才能访问
                    auth = mc.authenticateSectorWithKeyA(i, MifareClassic.KEY_DEFAULT);
                    if(auth){
                        //这句其实不是必须的,因为每个sector中本来就只有4个block
                        bCount = mc.getBlockCountInSector(i);
                       
                        //all blocks are consecutively numbered ,0-64,so we can not use number of cycles to be index.
                        //we just can get the first block in iTH sector

    //我们可以得到每一个sector中的第一个block的编号
                        bIndex = mc.sectorToBlock(i);

                        for(int j =0;j<bCount;j++){//循环四次拿出一个sector中所有的block
    //每次循环bIndex会去++,然后可以得出每一个block的数据。这些数据是字节码,所以你还有一个翻译的工作要做。

                            byte[] data = mc.readBlock(bIndex);           
                           String s = readByteArray(data);
                            bIndex++;
                        }
                    }else{
                        Log.i("xxx","   auth = false !!!  in sectorCount = "+i);

                       
                    }
                }

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    一:将数据写入NFC 标签

       往可读写的nfc标签中写tag相比读什么的要简单一点。当然这主要是因为,我在这里只讲如何写ndef数据。简单概括一下就是自己构造一个或多个NdefRecord然后将他们封装到一个NdefMessage中。然后将这个message写入就可以了。

        private void writeNdefTag(Intent in){
            Tag tag = in.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            Ndef ndef = Ndef.get(tag);
            try {

    //这一句别丢了,读nfc标签的时候不需要这句,因为那时数据直接就在intent中。
                ndef.connect(); 

    //构造一个合适的NdefMessage。你可以看到代码里用了NdefRecord数组,只不过这个数组里只有一个record

                NdefMessage ndefMsg = new NdefMessage(new NdefRecord[]{createRecord()});
                ndef.writeNdefMessage(ndefMsg);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (FormatException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    创建一个Record的举例,注释掉的是举例写一个url进去,到时候一扫描就会直接调用浏览器程序。没有注释的是尝试调用一个本地应用,只要一扫描就会直接启动应用

        private NdefRecord createRecord(){
    //               return new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI,
    //                       "http://www.sohu.com".getBytes(Charset.forName("US-ASCII")),
    //                       new byte[0], new byte[0]);
                            
                   return new NdefRecord(
                           NdefRecord.TNF_MIME_MEDIA ,
                           "application/com.android.TestNfc".getBytes(Charset.forName("US-ASCII")),
                           new byte[0],  "com.android.yufeimusic".getBytes(Charset.forName("US-ASCII")));
        }

     

    二:Android Beam

        再来讲讲android 4.0之后的新特性,android beam。如果你和你的朋友都恰好有一部android NFC手机,而且都是4.0的系统,那你们就可以玩一下了。把nfc功能打开,然后打开联系人界面,然后将两个手机背靠背放在一起,手指轻轻一点。你的联系人数据就会唰的一下飞到对方手机里去了。这个非常有意思,而且实现也很简单。

    主要是在代码中的onCreate中要设置回调。       

     mNfcAdapter.setNdefPushMessageCallback(this, this);

    其中第一个this就是CreateNdefMessageCallback 回调函数。他中间你要实现的也就是下面的createNdefMessage

    第二个this,貌似指的是由哪一个activity来接受系统回调...好吧...没做实验我就不多说了怕误导自己

    以下的代码全部为android文档中的示例代码,比较简单,也就是创建了一个字符串NdefMessage然后传过去。到时候,另外一个设备收到Intent后如何解析,你可以参看之前的过滤和解析博文

        public NdefMessage createNdefMessage(NfcEvent event) {
            Time time = new Time();
            time.setToNow();
            String text = ("Beam me up! " +
                    "Beam Time: " + time.format("%H:%M:%S"));
            NdefMessage msg = new NdefMessage(
                    new NdefRecord[] { createMimeRecord(
                            "application/com.example.android.beam", text.getBytes())
            
    //          ,NdefRecord.createApplicationRecord("com.example.android.beam")
            });
            return msg;
        }

    public NdefRecord createMimeRecord(String mimeType, byte[] payload) { byte[] mimeBytes= mimeType.getBytes(Charset.forName("US-ASCII")); NdefRecord mimeRecord = newNdefRecord( NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload); returnmimeRecord; }
     
    当然系统中有关android beam的不会只有这一个回调。比如还有
    mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
    这个回调主要是用来当你的NdefMessage发送到对方成功之后,你要干什么的一个回调函数。
    比如你可以向用户主线程发一个handler或者自己弹出个toast都是可以的。   
    @Override
        public void onNdefPushComplete(NfcEvent arg0) {
            // A handler is needed to send messages to the activity when this
            // callback occurs, because it happens from a binder thread
            mHandler.obtainMessage(MESSAGE_SENT).sendToTarget();
        }


  • 相关阅读:
    bch算法生成nand flash中512byte校验和
    CFileDialog用法总结
    c++修改打印机名称
    c++连接打印机(转载)
    转发:for /f命令之—Delims和Tokens用法&总结
    c++中DLL文件的编写与实现——三步走
    GhostScript说明
    打印机API
    c++中DLL文件的编写与实现——三步走(2)
    windows程序设计基础知识
  • 原文地址:https://www.cnblogs.com/l2rf/p/4384551.html
Copyright © 2011-2022 走看看