Android设备读写NFC标签

2016就这样静悄悄的过去了,最近也是颓废的不行,都没怎么充实自己了,每天练练车,打打游戏和麻将,书都基本没翻个几页,家里完全没有状态。2017,加油吧,为了事业和家庭,Fighting!

关于接触到NFC这东西,是我2年前导师项目中认识到的,当时自己也是刚学Android,由于项目需要,所以自己当时承担了这方面的工作,由此也开启了我Android生涯。想当初我也是翻遍了网上的所有资料,基本上没有一个是完完全全写好的,能直接拿来用的,当时能读出一个标签的id也是乐的不行,所以我当时在想,要是我弄好了,就分享下经验,顺便共享下代码。但是为什么现在才写呢,因为当时做完后也没有继续做这个了,导致这个事情也是一拖再拖,直到最近导师又需要这个,又拿出来把代码改了改,才想起以前说要记录的这事。

NFC(Near Field Communication)近场通信,属于RFID中的一种,它是一种无源、低频、近距离的通信方式。通过扫描方提供的电能,利用电磁感应原理来驱动标签与扫描方进行通信。是一种快速,且安全的通信方式。现在广泛运用于公交卡、地铁票、门禁卡、校园卡等场景。现在应用比较广泛的是MifareClassic 1K卡,属于NfcA型标签,NFC标签共有4种类型的标签,具体类型和用途不一,这里就不说了。而当时我项目中用到的就是NfcA型卡,对NfcA卡进行数据读取和写入。至于NFC标签内部数据存放格式,读写的过程以及标签扇区(sector)和块(block)的概念,也就不说了,相信你可以通过其他途径可以获取到,这里我就只介绍NFC读写的代码了。

完成后整个功能界面如下,由于主要是测试的玩,所以界面什么的就不用太在意了,功能实现就好了。

                  Android设备读写NFC标签_第1张图片

整个应用分为3大功能模块:读取标签内存,将内存数据转为Ascii码,写入数据。

        后面的是sector和block是写入数据时,自己要将数据写入的扇区和块,data是要写入的数据。

首先你得拥有NFC标签,相信这个不难,校园卡,地铁票,公交卡什么的,都可以的。其次你得有读写的设备,作为Android开发者,相信你也不会专门买个读写的设备,不然就和Android联系不上了,我们要做的是开发一个简易版的NFC标签读写软件,所以你得有一个NFC读写的手机或Pad。好了,准备好了之后就可以测试玩一玩了。你也可以先从应用市场上下载NFC的应用,来测试你的设备是否支持NFC功能或者你的标签有没有问题。

1).新建Android项目,在res文件夹下新建一个xml的文件夹,里面放的是Android支持的NFC类型的配置数据。nfc_tech_filter.xml如下:

 

 
    
 
     android.nfc.tech.IsoDep  
        android.nfc.tech.NfcA  
        android.nfc.tech.NfcB  
        android.nfc.tech.NfcF  
        android.nfc.tech.NfcV  
        android.nfc.tech.Ndef  
        android.nfc.tech.NdefFormatable  
        android.nfc.tech.MifareUltralight 
     android.nfc.tech.MifareClassic
 
 

2)在AndroidManifest中进行权限声明:

 

 




    

    

    

    
        
            
                

                
            
            
                
            
            
                
                

                
                
            
            
                
            

            
        
    

3)布局界面代码如下activity_nfc_main:

 

 


    
    
   
   
    
    
   
       
        

        

        
        
    
    
       
     
       
           
           
           
           
        
       
         
           
           
           
           
          
          
     
     
         
           
           
            
          
          
         
    

    
    
    
    
  
      

 


4)主界面 NfcRW.java

 

 

 

package com.example.nfc_read_write;

import java.io.IOException;
import java.util.logging.Logger;

import com.example.nfc_test3.R;

import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcA;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;

public class NfcRW extends Activity {
	TextView tv1, tv2, tv3;
	EditText etSector, etBlock, etData;
	// private NfcAdapter nfcAdapter;
	private PendingIntent mPendingIntent;
	private IntentFilter[] mFilters;
	private String[][] mTechLists;
	private int mCount = 0;
	String info = "";

	private int bIndex;
	private int bCount;

	private int BlockData;
	private String BlockInfo;
	private RadioButton mRead, mWriteData, mChange;
	private byte[] b3;
	byte[] code=MifareClassic.KEY_NFC_FORUM;//读写标签中每个块的密码
	private byte[] data3, b0;
	private String temp = "";
	private NfcAdapter mNfcAdapter;
	private Context mContext;
	int block[] = { 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24,
			25, 26, 28, 29, 30, 32, 33, 34, 36, 37, 38, 40, 41, 42, 44, 45, 46,
			48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 62 };
	// private StringBuilder metaInfo=new StringBuilder();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_nfc_main);
		mContext = this;
		checkNFCFunction(); // NFC Check
		init();

		mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
				getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
		// Setup an intent filter for all MIME based dispatches
		IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
		try {
			ndef.addDataType("*/*");
		} catch (MalformedMimeTypeException e) {
			throw new RuntimeException("fail", e);
		}
		mFilters = new IntentFilter[] { ndef, };

		// 根据标签类型设置
		mTechLists = new String[][] { new String[] { NfcA.class.getName() } };
	}

	private void checkNFCFunction() {
		// TODO Auto-generated method stub
		mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
		// check the NFC adapter first
		if (mNfcAdapter == null) {
			// mTextView.setText("NFC apdater is not available");
			Dialog dialog = null;
			CustomDialog.Builder customBuilder = new CustomDialog.Builder(
					mContext);
			customBuilder
					.setTitle("很遗憾")
					.setMessage("没发现NFC设备,请确认您的设备支持NFC功能!")
					.setIcon(R.drawable.dialog_icon2)
					.setPositiveButton("是",
							new DialogInterface.OnClickListener() {
								public void onClick(DialogInterface dialog,
										int which) {
									dialog.dismiss();
									finish();
								}
							});
			dialog = customBuilder.create();
			dialog.setCancelable(false);// back
			dialog.setCanceledOnTouchOutside(false);
			SetDialogWidth(dialog).show();
			return;
		} else {
			if (!mNfcAdapter.isEnabled()) {
				Dialog dialog = null;
				CustomDialog.Builder customBuilder = new CustomDialog.Builder(
						mContext);
				customBuilder
						.setTitle("提示")
						.setMessage("请确认NFC功能是否开启!")
						.setIcon(R.drawable.dialog_icon2)
						.setPositiveButton("现在去开启......",
								new DialogInterface.OnClickListener() {
									public void onClick(DialogInterface dialog,
											int which) {
										dialog.dismiss();
										Intent setnfc = new Intent(
												Settings.ACTION_NFC_SETTINGS);
										startActivity(setnfc);
									}
								});
				dialog = customBuilder.create();
				dialog.setCancelable(false);// back
				dialog.setCanceledOnTouchOutside(false);
				SetDialogWidth(dialog).show();
				return;
			}
		}
	}

	private Dialog SetDialogWidth(Dialog dialog) {
		// TODO 自动生成的方法存根
		DisplayMetrics dm = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(dm);
		int screenWidth = dm.widthPixels;
		int screenHeight = dm.heightPixels;
		WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
		if (screenWidth > screenHeight) {
			params.width = (int) (((float) screenHeight) * 0.875);

		} else {
			params.width = (int) (((float) screenWidth) * 0.875);
		}
		dialog.getWindow().setAttributes(params);

		return dialog;
	}

	private void init() {
		// TODO 自动生成的方法存根
		tv1 = (TextView) findViewById(R.id.tv1);
		tv2 = (TextView) findViewById(R.id.tv2);
		tv3 = (TextView) findViewById(R.id.tv3);
		etSector = (EditText) findViewById(R.id.etSector);
		etBlock = (EditText) findViewById(R.id.etBlock);
		etData = (EditText) findViewById(R.id.etData);

		mRead = (RadioButton) findViewById(R.id.rb_read);
		mWriteData = (RadioButton) findViewById(R.id.rb_write);
		mChange = (RadioButton) findViewById(R.id.rb_Change);
	}

	@Override
	protected void onResume() {
		// TODO 自动生成的方法存根
		super.onResume();
		enableForegroundDispatch();
		// mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters,
		// mTechLists);
	}

	private void enableForegroundDispatch() {
		// TODO 自动生成的方法存根
		if (mNfcAdapter != null) {
			mNfcAdapter.enableForegroundDispatch(this, mPendingIntent,
					mFilters, mTechLists);
		}
	}

	@Override
	protected void onNewIntent(Intent intent) {
		// TODO 自动生成的方法存根
		super.onNewIntent(intent);
		tv1.setText("发现新的 Tag:  " + ++mCount + "\n");// mCount 计数
		String intentActionStr = intent.getAction();// 获取到本次启动的action
		if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intentActionStr)// NDEF类型
				|| NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentActionStr)// 其他类型
				|| NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentActionStr)) {// 未知类型

			// 在intent中读取Tag id
			Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
			byte[] bytesId = tag.getId();// 获取id数组

			info += ByteArrayChange.ByteArrayToHexString(bytesId) + "\n";
			tv2.setText("标签UID:  " + "\n" + info);

			// 读取存储信息
			if (mRead.isChecked()) {
				// mChange=false;
				tv3.setText("读取成功! " + readTag(tag));
				// readNfcVTag(tag);
				etSector.setText("");
				etBlock.setText("");
				etData.setText("");
			}

			// 写数据
			if (mWriteData.isChecked()) {
				//writeTag(tag);
				String str= etData.getText().toString();
				writeTag(tag,str);
			}

			// 转换为ASCll
			if (mChange.isChecked()) {
				tv3.setText(change(tag));
				Toast.makeText(getBaseContext(), "转换成功", 1).show();
				etSector.setText("");
				etBlock.setText("");
				etData.setText("");
			}
		}
	}

	// 写数据
		public void writeTag(Tag tag, String str) {
			MifareClassic mfc = MifareClassic.get(tag);
				
			try {
				if (mfc != null) {
					mfc.connect();
				} else {
					Toast.makeText(mContext, "写入失败", 1).show();
					return;
				}
				Log.i("write", "----connect-------------");
				boolean CodeAuth = false;
				byte[] b1 = str.getBytes();
				if (b1.length <= 720) {
					//System.out.println("------b1.length:" + b1.length);
					int num = b1.length / 16;
					System.out.println("num= " + num);
					int next = b1.length / 48 + 1;
					System.out.println("扇区next的值为" + next);
					b0 = new byte[16];
					if (!(b1.length % 16 == 0)) {
						for (int i = 1, j = 1; i <= num; i++) {
							CodeAuth = mfc.authenticateSectorWithKeyA(j, code);
							System.arraycopy(b1, 16 * (i - 1), b0, 0, 16);
							mfc.writeBlock(block[i - 1], b0);
							if (i % 3 == 0) {
								j++;
							}
						}
						//Log.d("下一个模块", "测试");
						CodeAuth = mfc.authenticateSectorWithKeyA(next,// 非常重要------
								code);
						//Log.d("获取第5块的密码", "---成功-------");
						byte[] b2 = { 0 };
						b0 = new byte[16];
						System.arraycopy(b1, 16 * num, b0, 0, b1.length % 16);
						System.arraycopy(b2, 0, b0, b1.length % 16, b2.length);
						mfc.writeBlock(block[num], b0);
						mfc.close();
						Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
						return;
					} else {
						for (int i = 1, j = 1; i <= num; i++) {
							if (i % 3 == 0) {
								j++;
								System.out.println("扇区j的值为:" + j);
							}
							CodeAuth = mfc.authenticateSectorWithKeyA(j,// 非常重要---------
									code);
							System.arraycopy(b1, 16 * (i - 1), b0, 0, 16);
							mfc.writeBlock(block[i - 1], b0);
							str += ByteArrayChange.ByteArrayToHexString(b0);
							System.out.println("Block" + i + ": " + str);
						}
						mfc.close();
						Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show();
						return;
					}
				} else {
					Toast.makeText(getBaseContext(), "字符过长,内存不足", 1).show();
					return;
				}

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

	// //读取数据
	public String readTag(Tag tag) {
		MifareClassic mfc = MifareClassic.get(tag);
		for (String tech : tag.getTechList()) {
			System.out.println(tech);// 显示设备支持技术
		}
		boolean auth = false;
		// 读取TAG

		try {
			// metaInfo.delete(0, metaInfo.length());//清空StringBuilder;
			StringBuilder metaInfo = new StringBuilder();
			// Enable I/O operations to the tag from this TagTechnology object.
			mfc.connect();
			int type = mfc.getType();// 获取TAG的类型
			int sectorCount = mfc.getSectorCount();// 获取TAG中包含的扇区数
			String typeS = "";
			switch (type) {
			case MifareClassic.TYPE_CLASSIC:
				typeS = "TYPE_CLASSIC";
				break;
			case MifareClassic.TYPE_PLUS:
				typeS = "TYPE_PLUS";
				break;
			case MifareClassic.TYPE_PRO:
				typeS = "TYPE_PRO";
				break;
			case MifareClassic.TYPE_UNKNOWN:
				typeS = "TYPE_UNKNOWN";
				break;

			}
			metaInfo.append("  卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共"
					+ mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize()
					+ "B\n");
			for (int j = 0; j < sectorCount; j++) {
				// Authenticate a sector with key A.
				auth = mfc.authenticateSectorWithKeyA(j,
						MifareClassic.KEY_NFC_FORUM);// 逐个获取密码
				/*
				 * byte[]
				 * codeByte_Default=MifareClassic.KEY_DEFAULT;//FFFFFFFFFFFF
				 * byte[]
				 * codeByte_Directory=MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY
				 * ;//A0A1A2A3A4A5 byte[]
				 * codeByte_Forum=MifareClassic.KEY_NFC_FORUM;//D3F7D3F7D3F7
				 */if (auth) {
					metaInfo.append("Sector " + j + ":验证成功\n");
					// 读取扇区中的块
					bCount = mfc.getBlockCountInSector(j);
					bIndex = mfc.sectorToBlock(j);
					for (int i = 0; i < bCount; i++) {
						byte[] data = mfc.readBlock(bIndex);
						metaInfo.append("Block " + bIndex + " : "
								+ ByteArrayChange.ByteArrayToHexString(data)
								+ "\n");
						bIndex++;
					}
				} else {
					metaInfo.append("Sector " + j + ":验证失败\n");
				}
			}
			return metaInfo.toString();
		} catch (Exception e) {
			Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
			e.printStackTrace();
		} finally {
			if (mfc != null) {
				try {
					mfc.close();
				} catch (IOException e) {
					Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG)
							.show();
				}
			}
		}
		return null;

	}

	// 转换Hex为字符串
	public String change(Tag tag) {
		MifareClassic mfc = MifareClassic.get(tag);
		Log.d("----------", "change----------");
		boolean auth = false;
		// 读取TAG

		String ChangeInfo = "";
		String Ascll = "";
		// Enable I/O operations to the tag from this TagTechnology object.
		try {
			mfc.connect();
			int sectorCount = mfc.getSectorCount();// 获取TAG中包含的扇区数
			for (int j = 1; j < sectorCount; j++) {
				// Authenticate a sector with key A.
				auth = mfc.authenticateSectorWithKeyA(j,
						MifareClassic.KEY_NFC_FORUM);
				if (auth) {
					Log.i("change 的auth验证成功", "开始读取模块信息");

					byte[] data0 = mfc.readBlock(4 * j);
					byte[] data1 = mfc.readBlock(4 * j + 1);
					byte[] data2 = mfc.readBlock(4 * j + 2);
					data3 = new byte[data0.length + data1.length + data2.length];
					System.arraycopy(data0, 0, data3, 0, data0.length);
					System.arraycopy(data1, 0, data3, data0.length,
							data1.length);
					System.arraycopy(data2, 0, data3, data0.length
							+ data1.length, data2.length);

					ChangeInfo = ByteArrayChange.ByteArrayToHexString(data3);
					temp = "扇区" + (j) + "里的内容为:"
							+ ToStringHex.decode(ChangeInfo) + "\n";
				}
				Ascll += temp;
			}
			return Ascll;
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
			Toast.makeText(getBaseContext(), "转换失败", 1).show();
		} finally {
			if (mfc != null) {
				try {
					mfc.close();
				} catch (IOException e) {
					Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG)
							.show();
				}
			}
		}
		return "";
	}

	public int StringToInt(String s) {
		if (!(TextUtils.isEmpty(s)) || s.length() > 0) {
			BlockData = Integer.parseInt(s);
		} else {
			Toast.makeText(NfcRW.this, "Block输入有误", 1).show();
		}
		System.out.println(BlockData);
		return BlockData;
	}

	@Override
	public void onPause() {
		super.onPause();
		disableForegroundDispatch();
	}

	private void disableForegroundDispatch() {
		// TODO 自动生成的方法存根
		if (mNfcAdapter != null) {
			mNfcAdapter.disableForegroundDispatch(this);
		}
	}
}

5)由于标签中的数据都是byte型,需要转为16进制Ascii码数据。

 

ByteArrayChange.java

 

package com.example.nfc_read_write;

public class ByteArrayChange {
	
	//转换法1   格式为0xabcd1234  字母小写
	/* public static  String ByteArrayToHexString(byte[] src) {
	      StringBuilder stringBuilder = new StringBuilder("0x");
	      if (src == null || src.length <= 0) {
	          return null;
	      }
	      char[] buffer = new char[2];
	      for (int i = 0; i < src.length; i++) {
	          buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);
	          buffer[1] = Character.forDigit(src[i] & 0x0F, 16);
	          System.out.println(buffer);
	          stringBuilder.append(buffer);
	      }
	      return stringBuilder.toString();
	  }*/
	  
	  //转换法2   格式为  ABCD1234 字母大写
	public static  String ByteArrayToHexString(byte[] bytesId) {   //Byte数组转换为16进制字符串
				// TODO 自动生成的方法存根
			 int i, j, in;
		        String[] hex = {
		                "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
		        };
		        String output = "";

		        for (j = 0; j < bytesId.length; ++j) {
		            in = bytesId[j] & 0xff;
		            i = (in >> 4) & 0x0f;
		            output += hex[i];
		            i = in & 0x0f;
		            output += hex[i];
		        }
		        return output;
			}
}


6)在进行读取16进制数据时,我们将其内容转换(翻译)为我们比较熟悉的中文

 

 ToStringHex.java

 

package com.example.nfc_read_write;

import java.io.ByteArrayOutputStream;

public class ToStringHex {

	// 将字符串编码成16进制数字,适用于所有字符(包括中文)
	private static String hexString = "0123456789ABCDEF";

	/**
	 * 将字符串编码成16进制数字,适用于所有字符(包括中文)
	 */
	public static String encode(String str) {
		// 根据默认编码获取字节数组
		byte[] bytes = str.getBytes();
		StringBuilder sb = new StringBuilder(bytes.length * 2);
		// 将字节数组中每个字节拆解成2位16进制整数
		for (int i = 0; i < bytes.length; i++) {
			sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4));
			sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0));
		}
		return sb.toString();
	}

	/** 将16进制数字解码成字符串,适用于所有字符(包括中文)
	 * 
	 * @param bytes
	 * @return
	 */
	public static String decode(String bytes) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream(
				bytes.length() / 2);
		// 将每2位16进制整数组装成一个字节
		for (int i = 0; i < bytes.length(); i += 2)
			baos.write((hexString.indexOf(bytes.charAt(i)) << 4 | hexString
					.indexOf(bytes.charAt(i + 1))));
		return new String(baos.toByteArray());
	}

	// 转化十六进制编码为ASCll字符串
	public static String toStringHex(String s) {
		byte[] baKeyword = new byte[s.length() / 2]; // ------------------
		for (int i = 0; i < baKeyword.length; i++) {
			try {
				baKeyword[i] = (byte) (0xff & Integer.parseInt(
						s.substring(i * 2, i * 2 + 2), 16));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		try {
			s = new String(baKeyword, "utf-8");// UTF-16le:Not
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		return s;
	}
}

 

项目源码地址,点我下载

PS:现已更新,可以下载了,好像现在CSDN上传文件时,必须说要下载积分,以前不要积分的全部不能下载了,现在更新了。2017-08-12

 

注:关于NFC的Android 软件,大家可以去应用市场下载   NFC玩家   和   Mifare Classic  Tool(MCT)   这2个app,然后对比下,基本原理差不多的。

  其中在主界面activity中进行标签的数据读写时,要切记如何读取失败的话,很可能是块密码不对,要更换密码。一般的标签,块密码是默认的,默认的有3种,可以根据实际情况进行更换。新手,两年前的代码中写的比较乱了,不想改了,有不懂的,或者有问题的,可以留言探讨。

 

 

 

 

你可能感兴趣的:(Android)