拓冰建站拓冰建站
首页 / 资讯中心 / 正文

短信备份后如何插入一条新短信:ContentResolver + ContentValues 实战

1. 短信备份之后为什么还要单独补一条短信做 Android 短信备份恢复的同学大概率都遇到过这种需求备份文件已经生成好了但用户反馈“我那条验证码短信没进去”“我手动删掉的那条想补回来”。这时候你不可能让用户重新全量备份一遍最合理的做法就是在备份流程之外单独往系统短信库里插入一条记录然后再把它同步写进备份 XML。这个场景的核心链路其实就三件事用ContentResolver拿到短信库的操作入口用ContentValues组装一条短信的字段用insert把数据写进content://sms。听起来简单但真正动手时会发现字段名写错、date单位搞混、插入后查询读不回来、备份 XML 结构和数据库字段对不上各种小坑。这篇就围绕“短信备份后如何插入一条新短信”这个具体动作展开把ContentResolver、ContentValues、Uri和XmlSerializer的配合讲清楚。适合已经能跑通短信读取备份、现在要补录单条短信的 Android 开发者。下面给的字段骨架、insert 示例和 XML 结构都可以直接复制改。2. 前置准备权限、Uri 与 TaoToken 接入2.1 短信读写权限与运行时申请短信库属于系统敏感数据AndroidManifest.xml里至少要声明读取和写入权限uses-permission android:nameandroid.permission.READ_SMS / uses-permission android:nameandroid.permission.WRITE_SMS / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE /注意WRITE_SMS在部分高版本系统上对普通应用已经收紧实际能不能写入取决于系统策略和你的应用角色。如果你只是做本地备份恢复的演示或内部工具建议在真机上验证如果是上架应用要提前确认目标系统的短信权限政策。运行时申请用标准写法即可if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_SMS, Manifest.permission.WRITE_SMS}, 1001); }2.2 短信库的 Uri 到底写哪个content://sms/是短信会话的总入口查询时能拿到所有短信。插入单条时用content://sms或content://sms/inbox都可以区别在于Uri含义插入后归属content://sms全部短信由 type 字段决定content://sms/inbox收件箱强制为收件content://sms/sent发件箱强制为已发送如果你要补录的是一条收到的短信用content://sms/inbox更省心不用自己填type。要精确控制就用content://sms并显式带上type。2.3 用 TaoToken 辅助调试与模型验证在写插入逻辑之前我习惯先把字段结构和备份 XML 的对应关系理清楚避免写完才发现字段名对不上。这种“字段映射 结构校验”的活用 TaoToken 的模型对话快速过一遍很省时间把数据库字段和 XML 标签贴进去让它帮你核对。如果你在做长期的 Android 编码或 Agent 类项目需要稳定调用模型能力可以看 Coding Plan适合持续性的编码场景。接入前先在 API Keys 页面生成密钥具体调用方式参考接入文档。地址统一走 API 入口https://taotoken.net/api官网在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。3. 可复制配置ContentValues 字段骨架与 insert 调用3.1 一条短信最少要填哪些字段短信库的列很多但插入一条能正常显示、能被备份读回的记录核心字段就这几个ContentValues values new ContentValues(); values.put(address, 110); // 号码 values.put(body, 马上过来一趟); // 正文 values.put(date, System.currentTimeMillis()); // 时间戳毫秒 values.put(type, 1); // 1收件 2发件 values.put(read, 1); // 0未读 1已读 values.put(seen, 1); // 是否已查看几个容易踩的点date必须是毫秒级时间戳。如果你从备份 XML 里读回来的是字符串记得Long.parseLong转换别直接塞字符串否则查询排序会乱。type不填的话用content://sms插入可能落到默认分类回读时筛选条件对不上。补录收件短信就填 1。read和seen建议都填 1否则插入后短信在系统里显示为未读用户会以为出了 bug。3.2 insert 调用与返回值判断ContentResolver resolver getContentResolver(); Uri target Uri.parse(content://sms/inbox); Uri result resolver.insert(target, values); if (result ! null) { Toast.makeText(this, 插入成功: result.toString(), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, 插入失败, Toast.LENGTH_SHORT).show(); }insert返回的是新记录的 Uri形如content://sms/inbox/123末尾的数字就是_id。拿到这个_id非常有用后面回读验证、写进备份 XML 都靠它。如果返回 null基本就是权限不够或字段非法先查权限再查字段名。3.3 把新短信同步写进备份 XML插入成功后如果这条短信也要进备份文件就用XmlSerializer追加一个节点。备份 XML 的结构建议和数据库字段一一对应?xml version1.0 encodingutf-8 standaloneyes ? smss sms address110/address body马上过来一趟/body date1730000000000/date type1/type /sms /smss追加写入的代码骨架FileOutputStream fos new FileOutputStream( new File(Environment.getExternalStorageDirectory(), smsbackup.xml), true); XmlSerializer serializer Xml.newSerializer(); serializer.setOutput(fos, utf-8); serializer.startTag(null, sms); serializer.startTag(null, address); serializer.text(110); serializer.endTag(null, address); serializer.startTag(null, body); serializer.text(马上过来一趟); serializer.endTag(null, body); serializer.startTag(null, date); serializer.text(String.valueOf(System.currentTimeMillis())); serializer.endTag(null, date); serializer.startTag(null, type); serializer.text(1); serializer.endTag(null, type); serializer.endTag(null, sms); serializer.flush(); fos.close();注意这里用FileOutputStream(file, true)的追加模式别把原来的备份覆盖了。XmlSerializer追加节点时不要再调startDocument和endDocument否则会生成两个 XML 声明解析时直接报错。4. 验证请求查询回读确认插入结果插入完不能只看 Toast必须回读确认。用ContentResolver.query按_id或按date倒序查最新一条Cursor cursor resolver.query( Uri.parse(content://sms/inbox), new String[]{_id, address, body, date, type}, date ?, new String[]{String.valueOf(System.currentTimeMillis() - 60000)}, date desc); if (cursor ! null cursor.moveToFirst()) { String id cursor.getString(cursor.getColumnIndexOrThrow(_id)); String address cursor.getString(cursor.getColumnIndexOrThrow(address)); String body cursor.getString(cursor.getColumnIndexOrThrow(body)); long date cursor.getLong(cursor.getColumnIndexOrThrow(date)); Log.d(SMS, id id address address body body date date); cursor.close(); }预期结果是日志里能看到你刚插入的号码、正文和时间戳_id和insert返回的 Uri 末尾数字一致。如果查不到先确认查询的 Uri 和插入的 Uri 是不是同一个分类再确认date过滤条件有没有把记录排除掉。回读通过后再打开备份 XML 检查节点是否完整。可以用一个简单的解析循环把 XML 里的address、body、date读出来和数据库对比两边一致才算真正闭环。5. 本篇常见错排查5.1 insert 返回 null最常见的原因是权限没给全尤其是WRITE_SMS。其次是ContentValues里字段名拼错比如把address写成addr系统不会报错直接返回 null。排查方法把values的 key 打印出来和官方列名逐个核对。5.2 插入成功但查询查不到大概率是 Uri 分类不一致。你用content://sms/inbox插入却用content://sms/sent查询自然查不到。统一用content://sms查询最稳再按type过滤。5.3 date 显示成 1970 年说明你塞进去的是秒级时间戳或者字符串。System.currentTimeMillis()是毫秒从 XML 读回来时用Long.parseLong转换别用Integer.parseInt会溢出。5.4 备份 XML 解析报错追加节点时多写了startDocument导致文件里出现两个 XML 声明。检查追加逻辑只保留startTag到endTag的部分。另外XmlSerializer用完要flush否则缓冲区数据可能没落盘。5.5 高版本系统写入被拒部分系统对短信写入做了额外限制普通应用即使有权限也可能被拦截。这种情况先确认应用角色必要时改用系统签名或走用户手动导入的路径不要硬刚权限。6. 继续把链路跑通插入单条短信这件事拆开看就是ContentValues组装字段、ContentResolver.insert写入、query回读验证、XmlSerializer同步备份四步。真正容易翻车的地方不在 API 本身而在字段名、时间戳单位、Uri 分类和 XML 追加这几个细节上。如果你在核对字段映射或者调试备份 XML 结构时想快速验证思路可以用模型对话把字段和标签贴进去让它帮你比对。需要长期在 Android 编码和 Agent 场景里稳定调用模型走 Coding Plan 更合适。密钥在 API Keys 页面生成调用细节看接入文档接口入口统一用https://taotoken.net/api。把插入、回读、备份这三步串成一个小工具方法以后补录短信就是一次调用的事。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门