商米介绍地址:https://www.sunmi.com/
商米是一个提供手持PDA的一个很好的解决方案厂商,
也有其他的一些桌面设备。
其中商米提供的软件服务中,比较特别的是 身份证云解功能。
此处重点说明一下,身份证云解功能。
以往市面上的身份证读卡功能,都是找公安申请身份证读卡器硬件模块。比较贵。
商米的身份证读卡,是利用商米的NFC功能,配合身份证云解功能来实现
单独实现身份证读卡,和单独实现NFC刷IC卡,在商米提供的SDK很容易能实现
但是同时实现兼容 读IC卡和身份证,现在的商米SDK 就兼容性做的很差。
这里主要说明一下同时兼容 读IC卡和身份证,
1、引用jar包:implementation ‘com.sunmi:SunmiEID-SDK:1.3.16’
2、主界面上,初始化NFC,NfcUtils
package com.smk.travelpda.common.nfc;
import android.app.Activity;
import android.content.Context;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.util.Log;
import android.widget.Toast;
import com.smk.travelpda.common.util.RString;
/**
* luoyang
*/
public class NfcUtils {
private static final String TAG = "NfcUtils";
private static NfcAdapter mNfcAdapter;
private NfcUtils(){}
private static NfcUtils nfcUtils = null;
private static boolean isOpen = false;
/**
* 获取NFC的单例
* @return NfcUtils
*/
public static NfcUtils getInstance(){
if (nfcUtils == null){
synchronized (NfcUtils.class){
if (nfcUtils == null){
nfcUtils = new NfcUtils();
}
}
}
return nfcUtils;
}
private NfcListener nfcListener;
public void setNfcListener(NfcListener listener){
nfcListener = listener;
}
/**
* 在onStart中检测是否支持nfc功能
* @param context 当前页面上下文
*/
public void onStartNfcAdapter(Context context){
//设备的NfcAdapter对象
mNfcAdapter = NfcAdapter.getDefaultAdapter(context);
}
/**
* 在onResume中开启nfc功能
* @param activity
*/
public void onResumeNfcAdapter(final Activity activity){
if(mNfcAdapter==null){//判断设备是否支持NFC功能
RString.showDia(activity,"提醒","设备不支持NFC功能");
return;
}
if (!mNfcAdapter.isEnabled()){//判断设备NFC功能是否打开
RString.showDia(activity,"提醒","请到系统设置中打开NFC功能!");
return;
}
if (!isOpen) {
mNfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() {
@Override
public void onTagDiscovered(final Tag tag) {
if (nfcListener != null){
(activity).runOnUiThread(new Runnable() {
@Override
public void run() {
nfcListener.nfcReadHander(tag);
}
});
}
}
},
(NfcAdapter.FLAG_READER_NFC_A |
NfcAdapter.FLAG_READER_NFC_B |
NfcAdapter.FLAG_READER_NFC_F |
NfcAdapter.FLAG_READER_NFC_V |
NfcAdapter.FLAG_READER_NFC_BARCODE ),
null);
isOpen = true;
}
}
/**
* 在onPause中关闭nfc功能
* @param activity
*/
public void onPauseNfcAdapter(Activity activity){
if(mNfcAdapter!=null && mNfcAdapter.isEnabled()){
if (isOpen){
mNfcAdapter.disableReaderMode(activity);
}
isOpen = false;
}
}
}
NFC监听组件
package com.smk.travelpda.common.nfc;
import android.nfc.Tag;
/**
* 自定义的NFC接口
*/
public interface NfcListener{
/**
* 用于扫到nfc后的后续操作
*/
void nfcReadHander(Tag tag);
}
设置商米SDK的回调方法
package com.smk.travelpda.common.yidcard;
import android.util.Log;
import com.smk.travelpda.MainActivity;
import com.smk.travelpda.common.util.RJson;
import com.sunmi.eidlibrary.EidCall;
import com.sunmi.eidlibrary.EidConstants;
import com.sunmi.eidlibrary.EidSDK;
import java.util.Map;
//云解身份证读卡回调方法
public class EidCardReadCall implements EidCall {
private String TAG = "IdCardHander";
private EidcardListener eidcardListener;
private String eventType;
public EidCardReadCall(EidcardListener eidcardListener,String eventType){
this.eidcardListener = eidcardListener;
this.eventType = eventType;
}
@Override
public void onCallData(int code, String msg) {
if(this.eventType.endsWith(EidHanderEo.EID_INIT)){
initEidHanderCall(code, msg);
}else if(this.eventType.endsWith(EidHanderEo.Eid_READY_NFC)){
readerForNfc(code, msg);
} else if (this.eventType.endsWith(EidHanderEo.GETIDCARDINFO)) {
getIDCardInfo(code, msg);
}
}
public void initEidHanderCall(int i, String s){
EidHanderEo inttEo = new EidHanderEo(this.eventType);
inttEo.setEventCode(i);
switch (i) {
case EidConstants.EID_INIT_SUCCESS:
inttEo.setSucces(true);
break;
default:
inttEo.setSucces(false);
inttEo.setMsg(s);
break;
}
eidcardListener.handEidEvent(inttEo);
}
public void getIDCardInfo(int i, String s) {
EidHanderEo eo = new EidHanderEo(this.eventType);
eo.setEventCode(i);
if (i == EidConstants.DECODE_SUCCESS) {
eo.setSucces(true);
eo.setCardType("sfz");
try{
Map<String,Object> sfzMap = RJson.parseJson2Map(s);
String idnum = (String)((Map<String,Object>)sfzMap.get("base_info")).get("idnum");
eo.setCardId(idnum);
}
catch (Exception e){
eo.setSucces(false);
eo.setMsg("身份证解析失败,请重试");
}
} else {
//解码失败,code 为错误吗,data为错误原因
// typetext.setText("解析失敗:code:"+i);
//typetext.setText("解析失敗:data:"+s);
eo.setSucces(false);
eo.setMsg(s);
}
eidcardListener.handEidEvent(eo);
}
public void readerForNfc(int code, String msg) {
System.out.println("code:" + code + ":msg:" + msg);
EidHanderEo eidHanderEo = new EidHanderEo(this.eventType);
eidHanderEo.setEventCode(code);
switch (code) {
case EidConstants.EID_INIT_SUCCESS:
eidHanderEo.setSucces(true);
break;
case EidConstants.ERR_NFC_NOT_SUPPORT:
// 该机器不支持NFC功能,无法使用SDK
eidHanderEo.setSucces(false);
eidHanderEo.setMsg("机器不支持NFC");
break;
case EidConstants.ERR_NETWORK_NOT_CONNECTED:
eidHanderEo.setSucces(false);
eidHanderEo.setMsg("网络未连接,请联网后重试");
// *** 异常处理: 连接网络后,需要重新调用 startCheckCard 方法 (手动触发,非自动)***
break;
case EidConstants.ERR_NFC_CLOSED:
eidHanderEo.setSucces(false);
eidHanderEo.setMsg("NFC 未打开,打开后重试 ");
// *** 异常处理: 打开NFC后,需要重新调用 startCheckCard 方法 (手动触发,非自动)***
break;
case EidConstants.READ_CARD_READY:
//Step 3 读卡准备完成 -> 业务方可以引导用户开始进行刷卡操作
eidHanderEo.setSucces(true);
eidHanderEo.setMsg("SDK准备完成,请刷卡");
break;
case EidConstants.READ_CARD_START:
//Step 4 读卡中 -> 业务方可以提醒用户"读卡中,请勿移动卡片"
eidHanderEo.setSucces(true);
eidHanderEo.setMsg("开始读卡,请勿移动");
break;
case EidConstants.READ_CARD_SUCCESS:
//Step 5 读卡成功 -> 返回的msg为reqId,通过 reqId 业务方走云对云方案获取身份证信息
//注:如不需要循环读卡,可在此处调用stopCheckCard方法
eidHanderEo.setSucces(true);
eidHanderEo.setMsg("读卡成功");
eidHanderEo.setCardId(msg);//设置读取的身份证ID
break;
case EidConstants.READ_CARD_FAILED:
//*** 异常处理: 读卡失败,请重新读卡 ***
eidHanderEo.setSucces(false);
eidHanderEo.setMsg("读卡失败,请重试"+ msg);
break;
case EidConstants.ERR_ACCOUNT_EXCEPTION:
//*** 异常处理: 读卡失败,请重新读卡 ***
eidHanderEo.setSucces(false);
eidHanderEo.setMsg("该设备未开通身份证读卡权限!");
break;
default:
//*** 异常处理: 其他失败 - code为错误码,msg为详细错误原因 需要重新调用 startCheckCard 方法 (手动触发,非自动)***
eidHanderEo.setSucces(false);
eidHanderEo.setMsg("读卡异常,请重试code:"+code+ msg);
break;
}
eidcardListener.handEidEvent(eidHanderEo);
}
}
商米SDK的监听方法
package com.smk.travelpda.common.yidcard;
/**
* 处理EID的事件
*/
public interface EidcardListener {
public void handEidEvent(EidHanderEo eidHanderEo);
}
主界面
package com.smk.travelpda;
import android.content.IntentFilter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcA;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.smk.travelpda.common.macinfo.MacInfoHander;
import com.smk.travelpda.common.nfc.NfcListener;
import com.smk.travelpda.common.nfc.NfcReadHander;
import com.smk.travelpda.common.nfc.NfcUtils;
import com.smk.travelpda.common.scan.ScanEventEo;
import com.smk.travelpda.common.scan.ScanListener;
import com.smk.travelpda.common.util.BusinessException;
import com.smk.travelpda.common.util.RString;
import com.smk.travelpda.common.util.VersionCheckUtil;
import com.smk.travelpda.common.yidcard.EidCardReadCall;
import com.smk.travelpda.common.yidcard.EidHanderEo;
import com.smk.travelpda.common.scan.ScanMachinReceive;
import com.smk.travelpda.common.yidcard.EidcardListener;
import com.sunmi.eidlibrary.EidCall;
import com.sunmi.eidlibrary.EidConstants;
import com.sunmi.eidlibrary.EidReader;
import com.sunmi.eidlibrary.EidSDK;
/**
* luoyang 2024-04-17
*/
public class MainActivity extends AppCompatActivity implements NfcListener, ScanListener, EidcardListener {
public final static String APP_ID = "商米的APPID";
public final static String APP_KEY = "商米的APPKEY";
private TextView datatext;
//绑定的归属方,景区名称
private TextView tourname;
private TextView sntext;
private TextView visionnum;
private NfcUtils nfcUtils = NfcUtils.getInstance();
private EidReader eid;
private ScanMachinReceive myReceiver ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
datatext = findViewById(R.id.data);
tourname = findViewById(R.id.tourname);
sntext = findViewById(R.id.sntext);
visionnum = findViewById(R.id.visionnum);
sntext.setText("序列号:"+MacInfoHander.getSN());
visionnum.setText("V1.0.0");
tourname.setText("景区");
//初始化NFC读卡器
nfcUtils.setNfcListener(this);
//商米的扫码监听
initScanReceiveLislen();//初始话监听
//初始化身份证读卡
initCardEidSdk();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销扫码接收广播
unregisterReceiver(myReceiver);
//注销读卡服务
EidSDK.destroy();
}
@Override
protected void onStart() {
super.onStart();
System.out.println("onStart................................");
nfcUtils.onStartNfcAdapter(this); //初始化Nfc对象
}
@Override
protected void onResume() {
super.onResume();
System.out.println("onResume................................");
nfcUtils.onResumeNfcAdapter(this); //activity激活的时候开始扫描
datatext.append("NFC初始化成功\n");
}
@Override
protected void onPause() {
super.onPause();
System.out.println("onPause................................");
nfcUtils.onPauseNfcAdapter(this); //activity切换到后台的时候停止扫描
}
//读NFC卡后的返回事件
@Override
public void nfcReadHander(Tag tag) {
datatext.setText("start new card");
String tl[] = tag.getTechList();
//POS机器,只支持2代和3代卡,身份证,其他证件不支持
try {
boolean isHandCard = false;
for (String s:tl) {
if(s.equals("android.nfc.tech.MifareClassic")){
//String data = NfcReadHander.readMifareTag(tag,MifareClassic.get(tag));
datatext.setText(RString.byteToString(tag.getId()));
isHandCard = true;
break;
//旅游卡PDA 无需支持M1卡类型
}else if(s.equals("android.nfc.tech.IsoDep")){
String cardType = "smk";
//读取IC卡
String cardId = NfcReadHander.readIsoDepTag(tag, IsoDep.get(tag));
System.out.println("cardId"+cardId);
datatext.setText(cardId);
isHandCard = true;
break;
}else if(s.equals("android.nfc.tech.NfcB")){
//跳转到身份证云解读取,NFCB为身份证模块,此处调用身份证云解,其他的为IC卡读取
eid.nfcReadCard(tag);
isHandCard = true;
break;
}
}
if(!isHandCard){
throw new BusinessException("请刷IC卡或身份证");
}
}catch (BusinessException e){
//捕获自定义异常
datatext.setText(e.getMessage());
}
}
/**
* 实现扫码事件
* @return
*/
@Override
public void scanEventHander(ScanEventEo eventEo) {
String eventType = eventEo.getEventType();
if(eventType.equals(ScanEventEo.SCAN_START)){
//typetext.setText("开始扫码");
datatext.setText("开始扫码");
} else if (eventType.equals(ScanEventEo.SCAN_RECEVIE)) {
//typetext.setText("扫码成功");
datatext.setText("");
datatext.append(eventEo.isAllow()+":"+ eventEo.getQrData());
} else if (eventType.equals(ScanEventEo.SCAN_END)) {
datatext.setText("扫码结束");
}
}
//实现云解身份证的事件
@Override
public void handEidEvent(EidHanderEo eidHanderEo) {
String eventType = eidHanderEo.getEventType();
if(eventType.equals(EidHanderEo.EID_INIT)){
//typetext.setText("init success");
//init初始化监听时间
if(eidHanderEo.isSucces() && eidHanderEo.getEventCode() == EidConstants.EID_INIT_SUCCESS){
eid = EidSDK.getEidReaderForNfc(3, new EidCardReadCall(this,EidHanderEo.Eid_READY_NFC));
}else {
//其他都是失败的,
RString.showDia(this,"身份证模块初始化失败",eidHanderEo.getMsg());
}
} else if (eventType.equals(EidHanderEo.Eid_READY_NFC)) {
// typetext.setText("Eid_READY_NFC");
//读卡的回调
if(eidHanderEo.isSucces() && eidHanderEo.getEventCode() == EidConstants.READ_CARD_SUCCESS){
datatext.setText(eidHanderEo.getCardId());
//读卡成功的回调
EidSDK.getIDCardInfo(eidHanderEo.getCardId(), MainActivity.APP_KEY, new EidCardReadCall(this,EidHanderEo.GETIDCARDINFO));
}else if(!eidHanderEo.isSucces()){
RString.showDia(this,"提醒",eidHanderEo.getMsg());
}else{
//有些成功,是否需要干预流程处理,不需要就不做任何事情
//datatext.setText(eidHanderEo.getMsg());
datatext.append("身份证模块初始化成功");
}
} else if (eventType.equals(EidHanderEo.GETIDCARDINFO)) {
//typetext.setText("GETIDCARDINFO");
//调用
if(eidHanderEo.isSucces() && eidHanderEo.getEventCode() == EidConstants.DECODE_SUCCESS){
//解析成功
datatext.setText("");
datatext.append(eidHanderEo.getCardId());
}else {
RString.showDia(this,"提醒",eidHanderEo.getMsg());
}
}
}
private void initScanReceiveLislen(){
myReceiver = new ScanMachinReceive(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ScanEventEo.SCAN_RECEVIE); // 监听网络变化广播
intentFilter.addAction(ScanEventEo.SCAN_START); // 监听网络变化广播
intentFilter.addAction(ScanEventEo.SCAN_END); // 监听网络变化广播
// 注册广播 只从原生获取,不从外部APP获取
registerReceiver(myReceiver, intentFilter);
datatext.append("扫码初始化成功\n");
}
private void initCardEidSdk(){
EidSDK.init(this, APP_ID, new EidCardReadCall(this,EidHanderEo.EID_INIT));
// EidSDK.startCheckCard不要使用这个API
}
public void visionClick(View view){
//startActivity(VersionCheckUtil.check(false));
}
}
这个兼容身份证和IC读卡的关键点在于,不要使用SDK里面 EidSDK.startCheckCard();
1、EidSDK.init方法,
2、在EidSDK.init的成功回调函数中,获取NFC读卡的
EidReader eid = EidSDK.getEidReaderForNfc(3, new EidCardReadCall(this,EidHanderEo.Eid_READY_NFC));