我想将 .pdf 文件存储到 SQLite 数据库中。我知道首选方法是将 .pdf 本地存储在 Android 设备上,然后将路径存储在数据库中,但我希望用户能够下载 apk,而不必担心进行任何其他设备设置。我还读到,为了将 .pdf 存储在数据库中,需要将其转换为字节,然后存储在 BLOB 中。然而,我是用 Kotlin 写的,实际上我不确定从哪里开始转换为字节然后存储在 BLOB 中。谁能提供示例代码来执行此操作?
我知道首选方法是将 .pdf 本地存储在 Android 设备上,然后将路径存储在数据库中,但我希望用户能够下载 apk,而不必担心进行任何其他设备设置。
APK 不仅限于数据库文件,各种文件都可以作为资产或资源包含在包中。
您在尝试检索存储在数据库中的 pdf 文件时很可能会遇到由于文件大小而导致的问题。
我还读到,为了将 .pdf 存储在数据库中,需要将其转换为字节,然后存储在 BLOB 中。
文件是字节流(下面的演示展示了这一点)。
因此,仍然建议您仅在数据库中存储一个值(例如文件名),然后从合适的位置检索 pdf 文件。
演示
首先有 2 个 PDF 存在于任何地方(仅在 PC 上):-
SQLite 工具(本例中为 Navicat for SQLite)用于使用以下 SQL 创建预填充数据库:-
DROP TABLE IF EXISTS pdf;
CREATE TABLE IF NOT EXISTS pdf (id INTEGER PRIMARY KEY, pdf_title TEXT, pdf_name TEXT);
INSERT INTO pdf VALUES (1,'Blah PDF','pdf001.pdf'),(2,'Not Blah PDF','pdf002.pdf');
导致:-
数据库关闭,连接关闭,Navitcat for SQLite 关闭。生成一个文件,也位于 PC 上:-
现阶段已有基础数据。
在 Android Studio 中创建一个新的空 Kotlin 项目,并创建 assets 文件夹。复制 3 个文件(2 个 PDF 和数据库)(数据库文件重命名为 mypdfstore.db):-
创建了一个扩展 SQLiteOpenHelper 类的类,名为 DBHelper:-
const val DATABASE_NAME = "mypdfstore.db"
const val DATABASE_VERSION = 1
const val PDF_TABLE_NAME = "pdf"
const val PDF_ID_COLUMN = "id"
const val PDF_TITLE_COLUMN = "pdf_title"
const val PDF_NAME_COLUMN = "pdf_name"
const val PDF_CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS $PDF_TABLE_NAME ($PDF_ID_COLUMN INTEGER PRIMARY KEY, $PDF_TITLE_COLUMN TEXT, $PDF_NAME_COLUMN TEXT);"
class DBHelper(context: Context): SQLiteOpenHelper(context, DATABASE_NAME,null, DATABASE_VERSION) {
override fun onCreate(p0: SQLiteDatabase?) {
if (p0 != null) p0.execSQL(PDF_CREATE_TABLE_SQL)
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
TODO("Not yet implemented")
}
companion object {
private var instance: DBHelper?=null
fun getInstance(context: Context): DBHelper {
if (instance==null) {
if(!ifDBExists(DATABASE_NAME,context)) {
getDBFromAsset(DATABASE_NAME, DATABASE_NAME,context)
}
instance = DBHelper(context)
}
return instance as DBHelper
}
fun ifDBExists(databaseName: String, context: Context): Boolean {
val db_file = context.getDatabasePath(databaseName)
if (db_file.exists()) return true
val db_directory = db_file.parentFile
if (db_directory != null) {
if (!db_directory.exists()) db_directory.mkdirs()
}
return false
}
fun getDBFromAsset(assetName: String, databaseName: String, context: Context) {
val i = context.assets.open(assetName)
val o = context.getDatabasePath(databaseName).outputStream()
val bufferSize = 1024 * 8
val buffer = ByteArray(bufferSize)
while (i.read(buffer) > 0) {
o.write(buffer)
}
o.flush()
o.close()
i.close()
}
}
fun getAllFromPDF(): Cursor {
return this.writableDatabase.query(PDF_TABLE_NAME,null,null,null,null,null,null)
}
}
getInstance
返回实例,仅实例化一次。ifDBExists
函数检查数据库是否存在。
getDBFromAsset
函数来执行它所说的操作。 (注意如何将其读入 ByteArray(Room 中的 BLOB))instance
变量将被实例化并返回。onCreate
函数将创建唯一的表,但实际上永远不应该调用该函数,因为数据库是资产中预先填充/预先存在的数据库。另外还创建了另一个类
PDFReader
:-
class PDFReader() {
fun doesPDFExist(pdfName: String, context: Context): Boolean {
try {
val pdf = context.assets.open(pdfName)
val o = context.getDatabasePath(pdfName).outputStream()
pdf.copyTo(o)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
}
显然,如果不使用上述类,什么也不会完成,因此一些活动代码MainActivity:-
class MainActivity : AppCompatActivity() {
lateinit var db: DBHelper
lateinit var csr: Cursor
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/* Get the database instance */
db = DBHelper.getInstance(this)
/* extract the data from the database*/
csr = db.getAllFromPDF()
val id_ix = csr.getColumnIndex(PDF_ID_COLUMN)
val title_ix = csr.getColumnIndex(PDF_TITLE_COLUMN)
val name_ix = csr.getColumnIndex(PDF_NAME_COLUMN)
/* for each row copy the file from the asset to the databases folder */
while (csr.moveToNext()) {
val r = PDFReader().doesPDFExist(csr.getString(name_ix),this)
}
csr.close()
}
}
运行时然后使用应用程序检查:-
即mypdfstore.db 数据库存在,并且具有根据 SQLite 工具中创建的原始数据库的数据。此外,这 2 个 pdf 文件存在于同一目录中(请注意,为了演示的简洁性,使用了数据库目录)。
使用设备资源管理器:-
即文件存在并且 pdf 文件是预期大小。该数据库有两个附加文件 mypdfstore.db-wal 和 mypdfstore.db-shm;这些在使用 WAL 日志记录时存在。