ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Android实例解析:身份证校验、手机归属地与区号查询APP实现

2026/9/9 13:42:35 拓冰建站 浏览量
Android实例解析:身份证校验、手机归属地与区号查询APP实现 简介一份面向Android初学者的实用查询工具实例集合了手机号码归属地、身份证号码归属地区和国内区号三项查询功能界面直观、操作简单且每个功能都与日常生活密切相关适合用来熟悉Android工程的基本组织方式与开发流程。压缩包内共69个文件其中25个Java源文件是核心业务逻辑18个XML文件承担界面布局、菜单与配置声明22张PNG图片用于按钮、背景等视觉资源另有classpath、properties、cfg和project等工程文件辅助项目运行整体压缩包大小仅76KB轻量易得。目前已有1656人学习浏览特别适合刚接触Android的学生或转行者对照练习。通过该项目可以掌握查询类APP的共性实现套路包括输入控件监听、号码段/身份证编码规则判断、结果页面呈现等基础技能也能从src、res、assets标准目录划分中体会Android项目规范从而为独立开发同类小工具打下坚实基础。 拿到“Android身份证、手机号码、区号查询APP实例.rar”这个项目包的时候我直接把它解压丢进Android Studio跑了一遍。先说结论这不是什么高大上的项目但作为Android入门阶段的练手工程它的知识覆盖相当完整。一个输入框、一个查询按钮、一套数据匹配逻辑、一个结果展示页这几乎是所有工具类APP的通用骨架。如果你刚学完Activity、Intent、ListView/RecyclerView正愁没有一个“能拿得出手”的小项目这个实例值得仔细拆一遍。整个APP的核心就三件事身份证号解析、手机号归属地查询、国内长途区号查询。看上去只是“输入一段号码返回一个结果”但里面牵扯到数据校验、编码规则、离线数据组织、界面状态处理等一堆细节。这篇文章我就把自己从解压到跑通、再到改造这个项目的完整过程写出来包括我对每个功能模块的实现思路、关键代码、踩过的坑以及后续可以怎么扩展。1. 项目到底做了什么三个查询功能的业务逻辑先说业务层也就是“每个查询到底在查什么”。这三个功能看起来都是“查询”但背后的规则和数据处理方式完全不同千万别一上来就写代码。1.1 身份证查询18位编码里藏着多少信息身份证号码是国内居民身份编码的缩影18位数字最后一位可能是X由四段信息组成前6位户籍所在地的行政区划代码格式是“省2位 地市2位 区县2位”比如110105代表北京市朝阳区。这部分可以映射到省市区名称。第7到14位出生日期格式为YYYYMMDD比如19900308。第15到17位顺序码表示同一地址码区域内同年同月同日出生的人的排列顺序。其中第17位倒数第二位是性别标识奇数表示男性偶数表示女性。第18位校验码由前17位通过加权因子计算得出可以是数字0到9也可能是X。这个查询功能的关键点不在“查到”而在“校验”。一个合格的身份证查询APP首先得能判断这个号码格式是否合法然后再去解析省市区、出生日期、性别。如果用户输入“11010519900308001X”这种明显不对的号码你得先拦住而不是硬去匹配数据。1.2 手机号归属地查询数据量越来越大手机号归属地查询的核心依据是“号段”。早期手机号是11位前3位是运营商号段如139、150、188前4位能初步判定省份但如果要精确到市一般要看前7位。这些年随着携号转网和虚拟运营商的出现号段归属已经出现了“号在人不在”的情况即号码注册地与实际使用地分离。做这个功能时要清楚无论APP本地库还是在线API能查到的都是“号码最初分配时的归属地”不是机主当前所在的位置。数据组织的常见方式有两种离线内置把号段和归属地做成一个映射表打包进APP。查询快、不耗流量、稳定但更新要靠发版而且号段数据文件会随着记录增多越来越大。在线接口调用第三方API数据实时且准确但依赖网络还要申请接口权限和Key免费接口往往有调用次数限制。作为教学实例离线内置是更合适的选择既不需要处理网络异步回调又能在包体积可控的范围内完成功能闭环。别贪多先把离线方案跑通。1.3 区号查询最简单但容易忽略细节区号查询就是输入一个城市名返回长途电话区号比如“北京”返回“010”“广州”返回“020”。反过来也可以输入区号查询城市。这个功能逻辑最简单数据结构就是一个“城市名—区号”的键值对但要注意两点城市名的输入法问题。用户可能输入“北京市”“北京”“beijing”等各种形式你需要做归一化处理最简单的做法是数据存储时就用“北京”作为标准名输入时做去“市/省”等后缀处理。区号不是全国统一长度有三位如010、021也有四位如0755展示时别硬凑格式。2. APP的架构设计思路小项目也要有层次很多初学者拿到这种“多查询功能”的需求第一反应是把所有代码塞进一个Activity里。我强烈不建议这么做。这项目虽然小但按功能分层能让后面加功能、换数据源都轻松不少。2.1 导航与页面结构三种常见方案三个查询功能摆在一个APP里页面结构有三种常见选择第一种一个MainActivity放三个Tab底部导航每个Tab对应一个Fragment查询结果各自展示。这是最主流的结构适合功能平行、互相独立的工具类APP。第二种一个MainActivity放三个入口按钮点击进入各自独立的Activity。优点是每个查询逻辑完全隔离缺点是页面切换重代码重复度高。第三种一个Activity内做状态切换靠ViewFlipper或条件判断显示不同查询界面。最简单但代码容易越来越乱只适合极致精简的场景。这个实例选哪种取决于原作者的工程习惯。从我解压后的项目结构来看主流做法是第一种即一个主界面 三个查询模块数据层单独抽出来做查询工具类。这样写的好处是以后加一个新的查询功能只需新增一个Fragment和对应的查询器不需要改动现有模块。2.2 数据从哪来本地数据组织的关键取舍我把项目里的数据文件翻了一遍发现数据的组织方式决定了查询性能。身份证区划代码和区号的数据量不算大用一个JSON文件或一个SQLite表就能搞定。手机号段归属地稍有不同如果收录了全国所有号段数据量会到几十万条级别。这里有个关键取舍全量号段放JSON里启动时一次性加载到内存查询时用HashMap做Key速度可以非常快但首次解析大JSON会有几百毫秒的延迟和一定的内存占用。放到SQLite里用“号段”字段建索引查询走索引效率很高但需要处理数据库文件的导入、初始化版本等问题对新手不太友好。我的建议是入门阶段用JSON加载到HashMap的方式代码简单、逻辑清晰。等控制到数据量瓶颈再考虑引入SQLite或者Room。这个项目本身就是学习用的没必要一开始就把持久化方案搞得太重。2.3 输入校验与交互设计小细节决定体验别小看一个输入框。我测试过这个实例的原始版本发现几个交互层面的细节问题很典型输入框没有类型限制。身份证输入框应该用android:inputTypetextPersonName或设置digits属性限制只能输入数字和X手机号输入框限制为数字且长度11位区号查询需要同时支持城市名和区号两种输入模式。查询按钮没有做空输入拦截。用户没输入内容直接点击查询程序会走到数据匹配逻辑结果自然是查不到还容易被误认为是Bug。结果展示区域没有空状态。查询成功后结果直接显示在TextView是常见的做法但查询前最好显示一个类似“输入号码后点击查询”的提示查询失败时也有明确的反馈而不是让界面干在那里没反应。这些小细节是区分“能跑的Demo”和“能用的工具”的重要标准后文我会在实现环节给出具体的处理方式。3. 从解压到跑通核心模块的实现细节下面进入正题把我实际运行这个项目、以及重新整理关键模块的过程逐步拆开。这里我不会贴全量代码而是挑每个模块里最有价值、最容易出错的部分详细说最后你拿这些片段组合到自己的工程里就能直接跑。3.1 身份证号码校验算法代码只有三十行却要处理三个坑身份证校验是这整个项目里“技术含量最高”的部分因为它涉及国家标准GB 11643-1999里的校验码生成算法。原理不复杂前17位号码分别乘以不同的加权因子求和后除以11得到的余数对应一个校验码。加权因子固定为7、9、10、5、8、4、2、1、6、3、7、9、10、5、8、4、2。余数0到10对应的校验码依次是1、0、X、9、8、7、6、5、4、3、2。也就是说余数为2时校验码是X。我写了个工具方法传入18位身份证号返回布尔值public static boolean isValidIdCard(String idCard) { if (idCard null || idCard.length() ! 18) { return false; } String code 10X98765432; int[] factors {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; int sum 0; for (int i 0; i 17; i) { char c idCard.charAt(i); if (c 0 || c 9) { return false; } sum (c - 0) * factors[i]; } char expected code.charAt(sum % 11); return expected Character.toUpperCase(idCard.charAt(17)); }这段代码看似简单实际踩坑点有三个第一最后一位X的大小写。用户可能输入小写的x所以校验前必须统一转成大写否则会把合法的身份证号判成非法。上面代码里已处理。第二只校验校验码还不够。GB 11643要求第7到14位必须是合法日期也就是不仅格式是数字还要通过月份和日期的真实天数校验。比如第7到14位如果是“19901301”13月本身就不存在生日解析应该失败。我补充了完整的生日判断public static boolean checkBirthday(String idCard) { int year Integer.parseInt(idCard.substring(6, 10)); int month Integer.parseInt(idCard.substring(10, 12)); int day Integer.parseInt(idCard.substring(12, 14)); if (year 1900 || year Calendar.getInstance().get(Calendar.YEAR)) { return false; } if (month 1 || month 12) { return false; } int[] monthDays {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (month 2) { boolean leap (year % 4 0 year % 100 ! 0) || (year % 400 0); return leap ? day 29 : day 28; } return day 1 day monthDays[month - 1]; }第三地址码前6位不能只在区划表里查到就完事还要考虑县级行政区划变更。实操中我建议对“查不到地址码”的情况做单独提示比如“号码格式合法但区划代码不在当前数据库中”而不是直接报“号码无效”这样可以避免把用户因旧地址码被判无效的身份证号误伤了。3.2 性别解析与结果组装一个字符判断就搞定通过校验后性别、出生日期、地区就可以准确解析了。性别看第17位下标16的奇偶性String gender (Integer.parseInt(String.valueOf(idCard.charAt(16))) % 2 1) ? 男 : 女; String birthday idCard.substring(6, 10) - idCard.substring(10, 12) - idCard.substring(12, 14);省份和城市信息从区划数据里匹配这步就要用到项目里的JSON文件。我通常把区划代码按省、市、区县三级拆成三层Map先匹配前2位定位省份再匹配前4位定位城市最后用完整6位匹配区县。逐级回退的好处是即便某个县级代码已经废弃至少还能返回省份和城市等级别信息不会让整个解析失败。这种“能返回多少是多少”的容错思路比直接返回空结果对用户友善得多。3.3 手机号归属地查询从JSON加载到HashMap匹配离线查询手机号归属地核心是把“号段前缀—归属地”的映射关系加载到内存。项目中数据文件一般放在assets/phone.json结构大概是{ 130: 河北石家庄, 1300: 安徽合肥, 1301: 北京, ... }加载代码建议用原生的AssetManager配合org.json.JSONObjectpublic static MapString, String loadPhoneLocation(Context context) { MapString, String map new HashMap(); try { InputStream is context.getAssets().open(phone.json); int size is.available(); byte[] buffer new byte[size]; is.read(buffer); is.close(); String json new String(buffer, UTF-8); JSONObject obj new JSONObject(json); IteratorString keys obj.keys(); while (keys.hasNext()) { String key keys.next(); map.put(key, obj.getString(key)); } } catch (Exception e) { e.printStackTrace(); } return map; }查询时先取手机号前7位做Key查不到再往前3位查。这里必须做“长度递减匹配”的逻辑因为不同运营商的号段分配粒度不同。我的实践经验是前7位查不到时依次尝试前6位、前4位、前3位越短匹配到的结果越粗糙但至少能给出运营商或省份级别的结果。另外补充一点查询前一定要判断手机号合法性。11位、1开头、第二位是3到9的才可能是正常手机号。否则像“12345678901”这种号码进去查询返回结果没有意义纯属浪费计算。3.4 区号查询简单但要做数据归一化区号模块我建议在HashMap基础上做一层“城市名清洗”。用户输入“北京市”你不能直接拿“北京市”做Key去查而是先去掉结尾的“市”“省”“自治区”“壮族”“回族”“维吾尔”等后缀再拿核心地名去查。反过来如果用户输入的是“010”这种区号就去查反查表。项目里我会同时维护两个Map一个是“城市名 — 区号”一个是“区号 — 城市名”用同一份数据源初始化private MapString, String cityToCode new HashMap(); private MapString, String codeToCity new HashMap(); public void init(ListAreaCodeItem dataList) { for (AreaCodeItem item : dataList) { cityToCode.put(item.getCityName(), item.getAreaCode()); codeToCity.put(item.getAreaCode(), item.getCityName()); } } public String search(String input) { input input.trim().replace(市, ).replace(省, ); if (cityToCode.containsKey(input)) { return cityToCode.get(input); } else if (codeToCity.containsKey(input.trim())) { return codeToCity.get(input.trim()); } else { return null; } }这段代码来自我自己的实现习惯原始项目可能用的是更朴素的遍历List方式。从效率上看Map在数据量增大后优势很明显而且代码可读性更好值得你直接替换原始实现。3.5 界面层输入框、按钮、结果文本的联动状态界面部分我用一个Fragment展示查询界面布局文件包含一个输入框EditText、一个查询按钮Button、一个结果区域TextView或LinearLayout。三个查询功能对应三个类似布局的Fragment只是查询器不同。一个关键细节查询按钮的点击事件里必须处理空输入和非法输入。我在项目中写了一个统一的处理工具private void doQuery() { String input editText.getText().toString().trim(); if (TextUtils.isEmpty(input)) { resultText.setText(请先输入查询内容); return; } if (input.length() 11 currentMode MODE_PHONE) { resultText.setText(手机号长度不正确应为11位); return; } String result queryHelper.query(input); resultText.setText(result null ? 未查询到对应结果请检查输入内容 : result); }界面状态处理上有个容易被忽略的体验点查询结果出来之后如果用户修改了输入内容旧的结果应该被清空或置灰否则用户会误以为修改后的内容已经查询过了。我在TextWatcher的onTextChanged里做了结果区域透明度变化算是一个很小但能提升质感的小技巧。4. 编译运行常见问题与排查实录这部分是我实际跑项目时踩过的坑都整理出来了。如果你在导入或运行这个工程时遇到问题大概率能在这里找到答案。4.1 SDK版本与Gradle版本不匹配这是导入任何老项目时最容易遇到的问题。解压后如果发现Gradle构建报错先检查build.gradleProject级别里声明的Gradle版本和你本机安装的Android Studio版本是否兼容。我遇到过项目声明Gradle 3.5.2但本机Android Studio是新版本直接同步就报错。处理方法我建议升级而不是降级打开gradle-wrapper.properties把distributionUrl改成当前Android Studio配套的版本然后点击Sync Now。一般Android Studio会自动提示升级DSL的写法例如compile改成implementation、androidx依赖补全等。这类机械性的升级工作量不大半小时内能处理完。4.2 资源文件编码导致中文乱码项目里如果涉及中文字符串或JSON数据最常见的坑是文件编码不是UTF-8。JSON文件用GBK编码保存的话在Android Studio中显示正常但APP运行到真机上加载时String里全是乱码查询结果直接没法看。彻底解决方法是打开出问题的文件在Android Studio右下角状态栏把文件编码强制改为UTF-8然后重新输入中文内容或转换编码。注意转换前备份原始文件防止编码转换过程中出现内容损坏。4.3 数据量过大导致的启动卡顿这个问题容易出现在手机归属地查询模块。如果作者把全国几百万条号段数据全放进去第一次加载JSON到内存时会明显卡顿甚至可能触发Application Not RespondingANR。如果你遇到这种情况尝试这样优化把数据按省份拆成多个JSON文件用户查询时先根据号码前三位判定大致省份只加载对应文件。或者在首次启动时把JSON数据导入SQLite后续查询走数据库索引速度可以快一个量级。再或者把查询逻辑放到子线程通过Handler或协程回传结果不让主线程承担大JSON解析工作。我实际测试发现对10万条左右的号段数据用HashMap全量加载大约耗时300到500毫秒真机启动时勉强可以接受但不如数据库方案稳定。作为学习项目可以接受后续如果要上线还是建议换SQLite。4.4 隐私与合规性提醒这里必须提一句涉及到身份证号、手机号归属地的工具类APP在上线前要注意个人信息保护相关要求。身份证号码本身是敏感个人信息这类查询工具在功能设计上应该不提供通过姓名反向查询身份证号的功能。不记录、不上传用户查询的完整号码。查询结果不包含除归属地之外的隐私字段。如果需要网络权限必须在隐私政策中说明用途。合规不是软件功能问题但它是这个项目能不能上线、能不能在应用商店存在的先决条件写代码的时候就要有这根弦。这也是为什么我坚持推荐离线本地查询方案的原因之一——不上传数据隐私风险天然低很多。5. 这个项目还能怎么玩三个扩展方向跑通现有的三个查询功能只是第一步。我在重构过程中发现这个工程稍加改造就可以变成更完整的练手项目这里提供三个可选的扩展方向。5.1 从“单次查询”升级为“批量查询与历史记录”给APP增加一个“查询历史”功能非常实用。用一个SQLite表记录查询时间、查询内容、查询结果在主界面下方用ListView或RecyclerView展示最近20条记录。这样涉及到的知识点包括数据库CRUD、列表展示、点击历史记录回填输入框等几乎覆盖了Android中“数据持久化 列表展示”的完整链路。5.2 把手机归属地查询改成API版本如果你觉得内置数据的更新太麻烦可以做一版使用聚合数据或天行数据等平台免费接口的在线查询。这里需要处理网络请求库用OkHttp和Retrofit都行、异步回调、权限声明INTERNET权限、JSON解析和数据缓存。在线版本与离线版本可以做一个设置开关用户可以选择用本地数据还是实时接口这样就把整个项目从“工具类APP”提升成“具备工程架构思维的网络应用”了。5.3 支持扫描二维码识别手机号/身份证号Android中可以用CameraX或ZXing库快速实现二维码扫描识别出文本内容后自动判断是手机号还是身份证号直接跳转到对应的查询模块。这个功能虽然有一定工作量但都是成熟库的集成应用做完后项目的完整度会提升一大截。以上三个方向选任何一个做下去这个实例就能从“照抄练习”变成“自己的作品”。代码量不大但每个方向都用到了真实App开发中的核心知识简历上写项目经历时也能写得更充实。最后说句实际操作层面的经验拿到任何一个项目压缩包别急着看代码先把README如果有的话和数据文件结构过一遍搞清楚数据规模和数据格式再跑代码。这个习惯能帮你省掉大量定位“是不是代码写错了”的时间——大部分情况下这类查询失效的问题都出在数据文件和格式上而不是逻辑本身。本文还有配套的精品资源点击获取