Android 身份证号校验

时间:2016年5月25日18:31:01

作者:JustDo23

说明:在录入用户信息的时候有时候会需要填写居民身份证号信息,对于身份证号信息的校验,看到同事在项目中塞入了这个判断的工具类,本人在简单浏览和修改之后第二次进行总结。

01. 居民身份证号

  • 首先,就目前而言基本所有人的身份证都升级到了二代身份证。
  • 其次,身份证的编码是有规则的,这个真的需要百科一下,进行简单理解。
  • 公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。
  • 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
  • 身份证的尾号可以是X这个是大写的X。最后一位是0-10的校验位,大写的X代表了10。其实是罗马数字。
  • 更多信息在百度百科有介绍。

本次总结中,用户输入的最后一位不区分大小写X,感觉不太对。哈哈。

02. EditText输入身份证号

使用EditText来接收用户的输入,同时需要限制用户输入的字符,可以对EditText进设置输入类型和监听来实现。示例代码如下:

// 居民身份证号的组成元素
String[] IDCARD = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "x", "X", };

对EditText设置输入的监听

// 在这里设置才有用![对EditText可以输入的字符进行了限制][使用Android原生的键盘会停留在数字键盘无切换到英文键盘][第三方的键盘正常使用]
// et_idcard.setInputType(InputType.TYPE_CLASS_TEXT);
// et_idcard.setKeyListener(DigitsKeyListener.getInstance("1234567890Xx"));
final List<String> idCardList = Arrays.asList(ConstantValues.IDCARD);
InputFilter inputFilter = new InputFilter() {

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        // 返回空字符串,就代表匹配不成功,返回null代表匹配成功
        for (int i = 0; i < source.toString().length(); i++) {
            if (!idCardList.contains(String.valueOf(source.charAt(i)))) {
                return "";
            }
            if (et_idcard.getText().toString().length() < 17) {
                if ("x".equals(String.valueOf(source.charAt(i))) || "X".equals(String.valueOf(source.charAt(i)))) {
                    return "";
                }
            }
        }
        return null;
    }
};
et_idcard.setFilters(new InputFilter[]{new InputFilter.LengthFilter(18), inputFilter});// 长度的限制和字符的限制

03. 校验工具类

工具类的封装主要也是根据身份证号的定义规则进行的。里边包含了15位和18位的身份证号的校验。主要的核心是根据前边的数字计算出最后一位校验位,计算结果和真实最后一位进行对比。工具类具体代码如下:

import android.text.TextUtils;

/**
 * 身份证的工具类
 */
public class IdCardUtil {

    private String idCardNum = null;

    private static int IS_EMPTY = 1;
    private static int LEN_ERROR = 2;
    private static int CHAR_ERROR = 3;
    private static int DATE_ERROR = 4;
    private static int CHECK_BIT_ERROR = 5;

    private String[] errMsg = new String[]{"身份证完全正确!",
            "身份证为空!",
            "身份证长度不正确!",
            "身份证有非法字符!",
            "身份证中出生日期不合法!",
            "身份证校验位错误!"};

    private int error = 0;

    /**
     * 构造方法。
     *
     * @param idCardNum
     */
    public IdCardUtil(String idCardNum) {
        // super();
        this.idCardNum = idCardNum.trim();
        if (!TextUtils.isEmpty(this.idCardNum)) {
            this.idCardNum = this.idCardNum.replace("x", "X");
        }
    }

    public String getIdCardNum() {
        return idCardNum;
    }

    public void setIdCardNum(String idCardNum) {
        this.idCardNum = idCardNum;
        if (!TextUtils.isEmpty(this.idCardNum)) {
            this.idCardNum = this.idCardNum.replace("x", "X");
        }
    }

    /**
     * 得到身份证详细错误信息。
     *
     * @return 错误信息。
     */
    public String getErrMsg() {
        return this.errMsg[this.error];
    }

    /**
     * 是否为空。
     *
     * @return true: null  false: not null;
     */
    public boolean isEmpty() {
        if (this.idCardNum == null)
            return true;
        else
            return this.idCardNum.trim().length() > 0 ? false : true;
    }

    /**
     * 身份证长度。
     *
     * @return
     */
    public int getLength() {
        return this.isEmpty() ? 0 : this.idCardNum.length();
    }

    /**
     * 身份证长度。
     *
     * @return
     */
    public int getLength(String str) {
        return this.isEmpty() ? 0 : str.length();
    }

    /**
     * 是否是15位身份证。
     *
     * @return true: 15位  false:其他。
     */
    public boolean is15() {
        return this.getLength() == 15;
    }

    /**
     * 是否是18位身份证。
     *
     * @return true: 18位  false:其他。
     */
    public boolean is18() {
        return this.getLength() == 18;
    }

    /**
     * 得到身份证的省份代码。
     *
     * @return 省份代码。
     */
    public String getProvince() {
        return this.isCorrect() == 0 ? this.idCardNum.substring(0, 2) : "";
    }

    /**
     * 得到身份证的城市代码。
     *
     * @return 城市代码。
     */
    public String getCity() {
        return this.isCorrect() == 0 ? this.idCardNum.substring(2, 4) : "";
    }

    /**
     * 得到身份证的区县代码。
     *
     * @return 区县代码。
     */
    public String getCountry() {
        return this.isCorrect() == 0 ? this.idCardNum.substring(4, 6) : "";
    }

    /**
     * 得到身份证的出生年份。
     *
     * @return 出生年份。
     */
    public String getYear() {
        if (this.isCorrect() != 0)
            return "";

        if (this.getLength() == 15) {
            return "19" + this.idCardNum.substring(6, 8);
        } else {
            return this.idCardNum.substring(6, 10);
        }
    }

    /**
     * 得到身份证的出生月份。
     *
     * @return 出生月份。
     */
    public String getMonth() {
        if (this.isCorrect() != 0)
            return "";

        if (this.getLength() == 15) {
            return this.idCardNum.substring(8, 10);
        } else {
            return this.idCardNum.substring(10, 12);
        }
    }

    /**
     * 得到身份证的出生日子。
     *
     * @return 出生日期。
     */
    public String getDay() {
        if (this.isCorrect() != 0)
            return "";

        if (this.getLength() == 15) {
            return this.idCardNum.substring(10, 12);
        } else {
            return this.idCardNum.substring(12, 14);
        }
    }

    /**
     * 得到身份证的出生日期。
     *
     * @return 出生日期。
     */
    public String getBirthday() {
        if (this.isCorrect() != 0)
            return "";

        if (this.getLength() == 15) {
            return "19" + this.idCardNum.substring(6, 12);
        } else {
            return this.idCardNum.substring(6, 14);
        }
    }

    /**
     * 得到身份证的出生年月。
     *
     * @return 出生年月。
     */
    public String getBirthMonth() {
        return getBirthday().substring(0, 6);
    }

    /**
     * 得到身份证的顺序号。
     *
     * @return 顺序号。
     */
    public String getOrder() {
        if (this.isCorrect() != 0)
            return "";

        if (this.getLength() == 15) {
            return this.idCardNum.substring(12, 15);
        } else {
            return this.idCardNum.substring(14, 17);
        }
    }

    /**
     * 得到性别。
     *
     * @return 性别:1-男  2-女
     */
    public String getSex() {
        if (this.isCorrect() != 0)
            return "";

        int p = Integer.parseInt(getOrder());
        if (p % 2 == 1) {
            return "男";
        } else {
            return "女";
        }
    }

    /**
     * 得到性别值。
     *
     * @return 性别:1-男  2-女
     */
    public String getSexValue() {
        if (this.isCorrect() != 0)
            return "";

        int p = Integer.parseInt(getOrder());
        if (p % 2 == 1) {
            return "1";
        } else {
            return "2";
        }
    }

    /**
     * 得到校验位。
     *
     * @return 校验位。
     */
    public String getCheck() {
        if (!this.isLenCorrect())
            return "";

        String lastStr = this.idCardNum.substring(this.idCardNum.length() - 1);
        if ("x".equals(lastStr)) {
            lastStr = "X";
        }
        return lastStr;
    }

    /**
     * 得到15位身份证。
     *
     * @return 15位身份证。
     */
    public String to15() {
        if (this.isCorrect() != 0)
            return "";

        if (this.is15())
            return this.idCardNum;
        else
            return this.idCardNum.substring(0, 6) + this.idCardNum.substring(8, 17);
    }

    /**
     * 得到18位身份证。
     *
     * @return 18位身份证。
     */
    public String to18() {
        if (this.isCorrect() != 0)
            return "";

        if (this.is18())
            return this.idCardNum;
        else
            return this.idCardNum.substring(0, 6) + "19" + this.idCardNum.substring(6) + this.getCheckBit();
    }

    /**
     * 得到18位身份证。
     *
     * @return 18位身份证。
     */
    public static String toNewIdCard(String tempStr) {
        if (tempStr.length() == 18)
            return tempStr.substring(0, 6) + tempStr.substring(8, 17);
        else
            return tempStr.substring(0, 6) + "19" + tempStr.substring(6) + getCheckBit(tempStr);
    }

    /**
     * 校验身份证是否正确
     *
     * @return 0:正确
     */
    public int isCorrect() {
        if (this.isEmpty()) {
            this.error = IdCardUtil.IS_EMPTY;
            return this.error;
        }

        if (!this.isLenCorrect()) {
            this.error = IdCardUtil.LEN_ERROR;
            return this.error;
        }

        if (!this.isCharCorrect()) {
            this.error = IdCardUtil.CHAR_ERROR;
            return this.error;
        }

        if (!this.isDateCorrect()) {
            this.error = IdCardUtil.DATE_ERROR;
            return this.error;
        }

        if (this.is18()) {
            if (!this.getCheck().equals(this.getCheckBit())) {
                this.error = IdCardUtil.CHECK_BIT_ERROR;
                return this.error;
            }
        }

        return 0;
    }


    private boolean isLenCorrect() {
        return this.is15() || this.is18();
    }

    /**
     * 判断身份证中出生日期是否正确。
     *
     * @return
     */
    private boolean isDateCorrect() {

        /*非闰年天数*/
        int[] monthDayN = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        /*闰年天数*/
        int[] monthDayL = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

        int month;
        if (this.is15()) {
            month = Integer.parseInt(this.idCardNum.substring(8, 10));
        } else {
            month = Integer.parseInt(this.idCardNum.substring(10, 12));
        }

        int day;
        if (this.is15()) {
            day = Integer.parseInt(this.idCardNum.substring(10, 12));
        } else {
            day = Integer.parseInt(this.idCardNum.substring(12, 14));
        }

        if (month > 12 || month <= 0) {
            return false;
        }

        if (this.isLeapyear()) {
            if (day > monthDayL[month - 1] || day <= 0)
                return false;
        } else {
            if (day > monthDayN[month - 1] || day <= 0)
                return false;
        }

        return true;
    }

    /**
     * 得到校验位。
     *
     * @return
     */
    private String getCheckBit() {
        if (!this.isLenCorrect())
            return "";

        String temp = null;
        if (this.is18())
            temp = this.idCardNum;
        else
            temp = this.idCardNum.substring(0, 6) + "19" + this.idCardNum.substring(6);


        String checkTable[] = new String[]{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
        int[] wi = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
        int sum = 0;

        for (int i = 0; i < 17; i++) {
            String ch = temp.substring(i, i + 1);
            sum = sum + Integer.parseInt(ch) * wi[i];
        }

        int y = sum % 11;

        return checkTable[y];
    }


    /**
     * 得到校验位。
     *
     * @return
     */
    private static String getCheckBit(String str) {

        String temp = null;
        if (str.length() == 18)
            temp = str;
        else
            temp = str.substring(0, 6) + "19" + str.substring(6);


        String checkTable[] = new String[]{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
        int[] wi = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
        int sum = 0;

        for (int i = 0; i < 17; i++) {
            String ch = temp.substring(i, i + 1);
            sum = sum + Integer.parseInt(ch) * wi[i];
        }

        int y = sum % 11;

        return checkTable[y];
    }


    /**
     * 身份证号码中是否存在非法字符。
     *
     * @return true: 正确  false:存在非法字符。
     */
    private boolean isCharCorrect() {
        boolean iRet = true;

        if (this.isLenCorrect()) {
            byte[] temp = this.idCardNum.getBytes();

            if (this.is15()) {
                for (int i = 0; i < temp.length; i++) {
                    if (temp[i] < 48 || temp[i] > 57) {
                        iRet = false;
                        break;
                    }
                }
            }

            if (this.is18()) {
                for (int i = 0; i < temp.length; i++) {
                    if (temp[i] < 48 || temp[i] > 57) {
                        if (i == 17 && temp[i] != 88) {
                            iRet = false;
                            break;
                        }
                    }
                }
            }
        } else {
            iRet = false;
        }
        return iRet;
    }

    /**
     * 判断身份证的出生年份是否未闰年。
     *
     * @return true :闰年  false 平年
     */
    private boolean isLeapyear() {
        String temp;

        if (this.is15()) {
            temp = "19" + this.idCardNum.substring(6, 8);
        } else {
            temp = this.idCardNum.substring(6, 10);
        }

        int year = Integer.parseInt(temp);

        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
            return true;
        else
            return false;
    }
}

04. 使用方法

将使用方法封装在一个名为RegexUtil的工具类中。

/**
 * 校验工具类
 *
 * @author JustDo23
 */
public class RegexUtil {    

    /**
     * 比较真实完整的判断身份证号码的工具
     *
     * @param IdCard 用户输入的身份证号码
     * @return [true符合规范, false不符合规范]
     */
    public static boolean isRealIDCard(String IdCard) {
        if (IdCard != null) {
            int correct = new IdCardUtil(IdCard).isCorrect();
            if (0 == correct) {// 符合规范
                return true;
            }
        }
        return false;
    }
}

04. 正则校验

除了上边的具体工具之外,同时可以使用正则表达式进行身份证的简单校验。可以在上边g工具类RegexUtil中添加真正表达式的代码。

// 判断是否符合身份证号码的规范
public static boolean isIDCard(String IDCard) {
    if (IDCard != null) {
        String IDCardRegex = "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x|Y|y)$)";
        return IDCard.matches(IDCardRegex);
    }
    return false;
}

后记

  • 对于代码中大小写X没有进行修改,以上同时兼容输入大小写。
  • 代码并没有写上完整的注释,很多方法本人没有用到。
  • 核心工具类代码并非本人原创。如果找到网络地址,会后期附上。
  • 文档中复制了太多的代码,似乎写个demo更好一些。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,064评论 25 707
  • 为什么有的人身份证最后一位的号码是0、1或2、3、4、5、6、7、8、9或X,而有的人是X这是怎么回事的呢,又有多...
    吴富良阅读 7,366评论 0 1
  • 申明:本文除特别说明外,身份证号码专指18位公民身份号码 一、身份证号码结构 早期‘身份证号码’叫‘社会保障号’,...
    安东的漫长岁月阅读 21,411评论 0 4
  • 笑来老师一直是在各种不同场合不停的重复传递价值的人,不害怕别人说啰嗦,坚持要做这个世界的好人。 其实大家都知道一个...
    Zhou_b739阅读 71评论 0 1