
1. 从一次图片丢失说起SQLite 存 BLOB 到底难在哪Android Studio 里做本地图片缓存很多人第一反应是「存路径不就行了」。但真到用户换机、清理相册、或者图片来自拍照临时目录时路径就失效了。这时候把图片二进制直接塞进 SQLite 的 BLOB 字段反而是最稳的方案。SQLite 存 BLOB 图片本质就是把 Bitmap 压缩成字节数组写进BLOB列读的时候再还原成 Drawable 或 Bitmap。听起来简单坑却集中在三处字节数组和 Bitmap 的转换、Cursor 取 Blob 的列索引、以及大数据量下的内存与事务。这篇面向的是刚上手 Android 本地存储的开发者也适合被「图片存了读不出来」折磨过的同学。我会给出一套可直接复制的DatabaseHelper与 DAO 代码从建表、Bitmap 转字节数组到 Cursor 读取回显再到 Logcat 验证。同时调试这类 SQL 与字节流问题时我会用 TaoToken 的统一 Key 和 API 通道接入 AI 补全让排查 SQL 语法、字节长度异常这些事快很多。核心检索词先摆出来Android Studio SQLite 存取 BLOB 图片是一套「建表 压缩 写入 读取 回显」的完整链路适合谁适合要做离线图片缓存、头像本地化、票据截图留存的 Android 开发者。先明确一个概念。BLOB 是 SQLite 的二进制大对象类型能存任意字节流。图片、音频、PDF 都能塞。但 SQLite 单行默认上限约 1GB实际工程里没人这么干通常单张图控制在几百 KB 以内。所以压缩格式和采样率的选择比「能不能存」更重要。我见过有人直接把 4K 原图 PNG 无损塞进去结果一个查询卡住主线程ANR 就来了。下面从建表开始一步步把这条链路走通。2. TaoToken 前置统一 Key 接入 AI 补全辅助排查在写 SQL 和字节流代码时最容易卡住的是两类问题一是 SQL 语句拼错比如列名iamge拼成image运行时才报no such column二是字节数组长度对不上读出来是空。这类问题靠肉眼盯代码效率低用 AI 补全和解释能省不少时间。我用 TaoToken 的统一 Key 来接入好处是一个 Key 走通多个模型通道不用在 Android Studio 里为不同服务商反复换配置。TaoToken 在这里扮演的是「统一 API 通道」的角色不是替代 Android Studio也不是让你把生产数据库直连出去。它只是把 AI 请求的 Base URL 和 Key 统一起来方便你在编码时调用补全、解释 SQL、分析 Logcat。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。具体怎么拿 Key进控制台创建 API Key路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。拿到 Key 后如果你想先在网页里验证模型能不能正常回话可以用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期做 Android 编码和 Agent 辅助的可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这里要强调三件套Base URL、Key、Model ID。任何 AI 补全工具接入缺一不可。Base URL 填https://taotoken.net/apiKey 填你创建的那串Model ID 按文档里支持的模型名填。下面第三节我会给出可复制的配置片段包括 Android Studio 里用到的 settings 和 JSON。别跳过这节配置错了后面全是 401。3. 可复制配置DatabaseHelper 与 DAO 完整代码先给配置片段。如果你在 Android Studio 里用支持 OpenAI 兼容接口的补全插件配置通常是一个 JSON 或 settings 文件。以常见的settings.json为例路径和原文保持一致放在插件配置目录下{ ai.provider: openai-compatible, ai.baseUrl: https://taotoken.net/api, ai.apiKey: sk-你的TaoTokenKey, ai.model: 你的ModelID, ai.timeoutMs: 30000 }如果你用的是 Codex 风格的auth.json结构类似{ base_url: https://taotoken.net/api, api_key: sk-你的TaoTokenKey, model: 你的ModelID }三件套再念一遍Base URL 是https://taotoken.net/apiKey 是你创建的Model ID 按文档填。配置好后AI 补全就能在你写 SQL 时给提示。接下来是正题建表。我用一个userimage表列名故意保留原 excerpt 里的iamge方便你对照排查拼写坑public class DatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME app.db; private static final int DB_VERSION 1; public DatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE IF NOT EXISTS userimage ( id INTEGER PRIMARY KEY AUTOINCREMENT, iamge BLOB)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS userimage); onCreate(db); } }注意iamge BLOB这个列名原 excerpt 就是拼错的我保留它因为第五节要拿它讲no such column报错。你自己写的时候建议改成image。DAO 层负责写入和读取。写入时把 Bitmap 压缩成 PNG 字节数组public class ImageDao { private final DatabaseHelper helper; public ImageDao(Context context) { this.helper new DatabaseHelper(context); } public long saveImage(Bitmap bitmap) { ByteArrayOutputStream outs new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, outs); byte[] bytes outs.toByteArray(); SQLiteDatabase db helper.getWritableDatabase(); db.beginTransaction(); try { db.execSQL(INSERT INTO userimage(iamge) VALUES(?), new Object[]{bytes}); db.setTransactionSuccessful(); } finally { db.endTransaction(); db.close(); } return bytes.length; } public Bitmap getImage() { SQLiteDatabase db helper.getReadableDatabase(); Cursor cursor db.rawQuery(SELECT iamge FROM userimage ORDER BY id DESC LIMIT 1, null); byte[] img null; if (cursor ! null) { if (cursor.moveToFirst()) { img cursor.getBlob(cursor.getColumnIndex(iamge)); } cursor.close(); } db.close(); if (img null || img.length 0) { return null; } return BitmapFactory.decodeByteArray(img, 0, img.length); } }这里有两个关键点。第一写入用beginTransaction包起来避免大字节数组写入时中途失败留下脏数据。第二读取用getBlob而不是getString列索引用getColumnIndex拿别硬编码 0。压缩格式用 PNG 无损如果你要省空间可以换JPEG并把质量降到 80。4. 验证请求Logcat 看字节长度与回显结果代码写完怎么确认真的存进去了最直接的办法是打 Logcat。在saveImage和getImage里加日志Log.d(BLOB_TEST, saved bytes length bytes.length);读取后Log.d(BLOB_TEST, read bytes length (img null ? -1 : img.length)); Log.d(BLOB_TEST, decoded bitmap (bitmap null ? null : bitmap.getWidth() x bitmap.getHeight()));在 Android Studio 的 Logcat 面板过滤BLOB_TEST正常应该看到写入长度和读取长度一致比如都是20480然后 decoded bitmap 打印出宽高。如果读取长度是 0 或 -1说明写入没成功或者列名对不上。回显到 ImageView 的调用Bitmap bitmap imageDao.getImage(); if (bitmap ! null) { imageView.setImageBitmap(bitmap); } else { Log.e(BLOB_TEST, bitmap is null, check blob column); }实测下来最容易出问题的是getColumnIndex返回 -1。如果列名拼错getColumnIndex(iamge)会返回 -1getBlob(-1)直接抛异常。所以我在读取前加一层判断int idx cursor.getColumnIndex(iamge); if (idx 0) { Log.e(BLOB_TEST, column not found); return null; } img cursor.getBlob(idx);另外如果你用 AI 补全辅助可以把这段 Logcat 输出贴给模型让它帮你判断是字节流问题还是 SQL 问题。TaoToken 的统一通道在这里的好处是你不用切换多个 Key一个配置就能问。验证模型是否正常回话可以先用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 试一句「解释 SQLite getBlob 返回空的原因」。5. 本篇常见错排查401、no such column 与空 Blob这一节对照真实报错来。第一个401 Unauthorized。如果你在 Android Studio 插件里配 AI 补全Base URL 或 Key 填错就会 401。检查三件套Base URL 必须是https://taotoken.net/apiKey 是sk-开头那串Model ID 别填成别的服务商的名字。401 基本就是 Key 无效或没带上。第二个android.database.sqlite.SQLiteException: no such column: iamge。这就是列名拼写问题。原 excerpt 里iamge是错的正确是image。如果你建表用了image查询却写iamge就报这个。解决办法统一列名或者用PRAGMA table_info(userimage)查实际列名。第三个CursorIndexOutOfBoundsException或getBlob返回空。原因通常是getColumnIndex返回 -1 后没判断直接拿去取。加判断即可。还有一种情况是写入时compress返回 false字节数组为空。检查 Bitmap 是否已被回收bitmap.isRecycled()。第四个local proxy failed。这个报错一般出现在网络请求层不是 SQLite 本身。如果你在 Android Studio 里配了 HTTP 代理去调 AI 接口代理配置不对就会这样。检查插件里的代理设置或者直接走 TaoToken 的 API 地址不要额外套代理。第五个reading choices相关报错。这通常出现在解析 AI 返回的 JSON 时字段结构对不上。比如你期望choices[0].message.content但返回体结构不同。用模型对话页先验证一次原始返回再对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 调整解析。第六个OAuth 相关报错。如果你用的是 Claude Code 类工具认证方式可能走 OAuth。这类工具接入时Base URL 和 Key 的填法要看具体文档ClaudeCodeAnthropic 的接入说明在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里有。别把 OAuth token 和 API Key 混用。排查顺序建议先看 Logcat 的字节长度再看 SQL 列名最后看网络层。SQLite 的问题基本在本地就能定位AI 补全只是加速。6. 继续用统一 Key 做 Android 编码辅助把 BLOB 存取跑通后你会发现这套模式能复用到很多场景头像缓存、离线票据、扫描件留存。核心就是「压缩成字节数组 → 写 BLOB → 读 Blob → 解码回显」。代码不复杂难在细节和排查。后续如果你要做更复杂的本地存储比如多表关联、批量图片导入、或者用 Room 封装 BLOB可以继续用 TaoToken 的统一 Key 做编码辅助。长期做 Android 编码和 Agent 辅助的Coding Plan 入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。一个实用技巧把常用的 SQL 建表语句和 DAO 模板存成代码片段配合 AI 补全下次写新表直接改列名就行省去重复劳动。最后提醒一句BLOB 单行别存太大超过 1MB 的图先压缩或分片否则查询和内存都吃不消。