如何从手机内部存储选取PDF存入SQLite并读取打开
搞定这个需求很简单,你已经迈出了第一步——选取PDF文件,接下来咱们一步步完成将PDF转为Blob存入SQLite和从数据库取出并打开的功能:
1. 完善文件选取后的处理:将PDF转为Blob
你已经通过ACTION_GET_CONTENT获取了PDF的Uri,接下来需要把这个Uri对应的文件转换成字节数组(也就是SQLite能存储的Blob类型),然后存入数据库。
首先补全你的onActivityResult方法:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 100 && resultCode == RESULT_OK && data != null) { Uri fileUri = data.getData(); try { // 通过ContentResolver获取文件输入流 InputStream inputStream = getContentResolver().openInputStream(fileUri); // 将输入流转换为字节数组(Blob) byte[] pdfBlob = convertInputStreamToByteArray(inputStream); // 调用方法将Blob存入SQLite savePdfToDatabase(pdfBlob); Toast.makeText(this, "PDF已成功存入数据库", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "读取PDF文件失败", Toast.LENGTH_SHORT).show(); } } } // 辅助方法:把输入流转换成字节数组 private byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; int len; while ((len = inputStream.read(buffer)) != -1) { byteBuffer.write(buffer, 0, len); } return byteBuffer.toByteArray(); }
2. 创建SQLite数据库帮助类,实现Blob存储
接下来需要一个SQLite帮助类来创建表、插入和查询Blob数据:
public class PdfDatabaseHelper extends SQLiteOpenHelper { // 数据库和表的常量定义 private static final String DATABASE_NAME = "PdfStorage.db"; private static final int DATABASE_VERSION = 1; private static final String TABLE_PDFS = "pdfs"; private static final String COLUMN_ID = "_id"; private static final String COLUMN_PDF_BLOB = "pdf_blob"; public PdfDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 创建存储PDF Blob的表 String createTableSql = "CREATE TABLE " + TABLE_PDFS + " (" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_PDF_BLOB + " BLOB NOT NULL)"; db.execSQL(createTableSql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 升级时删除旧表(实际项目建议做数据迁移,避免丢失数据) db.execSQL("DROP TABLE IF EXISTS " + TABLE_PDFS); onCreate(db); } // 插入PDF Blob到数据库 public long insertPdfBlob(byte[] pdfBlob) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(COLUMN_PDF_BLOB, pdfBlob); // 插入数据并返回行ID(可以保存这个ID,后续用来查询对应PDF) long rowId = db.insert(TABLE_PDFS, null, values); db.close(); return rowId; } // 根据行ID查询PDF Blob public byte[] getPdfBlobById(long rowId) { SQLiteDatabase db = this.getReadableDatabase(); byte[] pdfBlob = null; Cursor cursor = db.query(TABLE_PDFS, new String[]{COLUMN_PDF_BLOB}, COLUMN_ID + "=?", new String[]{String.valueOf(rowId)}, null, null, null); if (cursor != null && cursor.moveToFirst()) { pdfBlob = cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_PDF_BLOB)); cursor.close(); } db.close(); return pdfBlob; } }
然后实现刚才调用的savePdfToDatabase方法:
private void savePdfToDatabase(byte[] pdfBlob) { PdfDatabaseHelper dbHelper = new PdfDatabaseHelper(this); long savedRowId = dbHelper.insertPdfBlob(pdfBlob); // 这里可以把savedRowId保存到SharedPreferences或者变量中,方便后续查看时使用 }
3. 实现「查看」按钮功能:从数据库取出Blob并打开PDF
因为第三方PDF应用无法直接读取SQLite里的Blob,所以我们需要把Blob转换成临时文件,再通过Intent打开它。
首先给「查看」按钮添加点击事件:
Button viewPdfBtn = findViewById(R.id.btn_view_pdf); viewPdfBtn.setOnClickListener(v -> { // 这里用之前保存的rowId,示例中用1代替实际ID PdfDatabaseHelper dbHelper = new PdfDatabaseHelper(this); byte[] pdfBlob = dbHelper.getPdfBlobById(1); if (pdfBlob != null) { openPdfFromBlob(pdfBlob); } else { Toast.makeText(this, "数据库中未找到对应PDF", Toast.LENGTH_SHORT).show(); } });
然后实现openPdfFromBlob方法,处理Blob转临时文件和打开逻辑:
private void openPdfFromBlob(byte[] pdfBlob) { try { // 在应用缓存目录创建临时PDF文件 File tempPdfFile = new File(getExternalCacheDir(), "temp_view.pdf"); FileOutputStream fos = new FileOutputStream(tempPdfFile); fos.write(pdfBlob); fos.close(); // 构建打开PDF的Intent Intent viewIntent = new Intent(Intent.ACTION_VIEW); Uri fileUri; // Android 7.0及以上必须用FileProvider避免FileUriExposedException if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { fileUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", tempPdfFile); viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { fileUri = Uri.fromFile(tempPdfFile); } viewIntent.setDataAndType(fileUri, "application/pdf"); viewIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); // 检查是否有应用能打开PDF if (viewIntent.resolveActivity(getPackageManager()) != null) { startActivity(viewIntent); } else { Toast.makeText(this, "未找到支持打开PDF的应用", Toast.LENGTH_SHORT).show(); } } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "打开PDF失败", Toast.LENGTH_SHORT).show(); } }
4. 配置FileProvider(Android 7.0+必备)
为了避免Android 7.0及以上的FileUriExposedException,需要配置FileProvider:
- 在
AndroidManifest.xml的<application>标签内添加:
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
- 在
res/xml目录下创建file_paths.xml文件:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 允许访问应用的外部缓存目录 --> <external-cache-path name="external_cache" path="." /> </paths>
注意事项
- 权限问题:Android 10及以上使用分区存储,无需申请
READ_EXTERNAL_STORAGE权限;Android 10以下需要在Manifest中添加权限,并动态申请。 - 临时文件清理:临时文件会存在应用缓存目录,应用卸载时会自动删除,也可以在合适的时机手动清理。
- 多文件管理:如果需要存储多个PDF,建议在数据库表中添加文件名、存储时间等字段,方便区分和查询。
内容的提问来源于stack exchange,提问作者Jayashri




