You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin开发Android应用外部SQLite数据库关联drawable图片不显示问题

Android 外部SQLite接入列表图片不显示问题排查

问题现象

接入外部导入的SQLite数据库实现图文列表时,文本内容可正常渲染,图片始终无法加载,运行时无相关错误日志输出。

相关实现代码

数据实体类

class model {
    var id:Int?=null
    var name:String?=null
    var image:String?=null
}

数据库配置常量

object info_db {
    val dbName="country"
    val dbversion=2
    val dbId="id"
    val dbcname="name"
    val dbimage="image"
    val packageData="data/data/com.alialim.countrylist_sample/databases/"
    val source ="country.sqlite"
}

数据库操作类

class Database(val context: Context): SQLiteOpenHelper(context,info_db.dbName,null,info_db.dbversion)  {
    override fun onCreate(p0: SQLiteDatabase?) {
    }

    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {

    }

    init {
        initDatabase()
    }
    private fun initDatabase() {

        var file = File(info_db.packageData)

        if (file.exists()) {

        } else {
            file.mkdirs()
        }

        file = context.getDatabasePath(info_db.dbName)
        if (file.exists()) {

        } else {
            copyDataBase()
        }

    }

    private fun copyDataBase() {
        val inputStream = context.assets.open(source)
        val outFile = packageData + dbName
        val uotPutStream = FileOutputStream(outFile)
        val buffer = ByteArray(1024)
        var lenght = 0
        while (inputStream.read(buffer).also({ lenght = it }) > 0)
            uotPutStream.write(buffer, 0, lenght)
        uotPutStream.flush()
        uotPutStream.close()
        inputStream.close()
    }

    @SuppressLint("Range")
    fun getAll(): ArrayList<model> {
        val db = readableDatabase
        val query = "SELECT * FROM countrylist"
        var list: ArrayList<model> = ArrayList()
        val cursor = db.rawQuery(query, null)
        if (cursor.moveToFirst()) {
            do {
                val lmodel = model()
                lmodel.id=cursor.getInt(cursor.getColumnIndex(info_db.dbId))
                lmodel.name=cursor.getString(cursor.getColumnIndex(info_db.dbcname))

                lmodel.image=cursor.getString(cursor.getColumnIndex(info_db.dbimage))
                list.add(lmodel)
                Log.d(
                    "massage",
                    "log Database name ==>${lmodel.name} and category ==> ${lmodel.image}  "
                )
            } while (cursor.moveToNext())
        }
        cursor.close()
        db.close()
        return list
    }
}

全局Application类

object Base: Application() {
    var database: Database?=null
    var activity: Activity?=null

    override fun onCreate() {
        super.onCreate()
    }
}

列表适配器

class AdapterCountry(val context: Context, val countrylists:ArrayList<model>): RecyclerView.Adapter<AdapterCountry.HolderCountry>() {

    class HolderCountry(itemView: View): RecyclerView.ViewHolder(itemView){
        val imagerecy=itemView.findViewById<ImageView>(R.id.recycler_main_image)
        val txtfname=itemView.findViewById<TextView>(R.id.recycler_main_text)

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HolderCountry {
        val layout= LayoutInflater.from(parent.context).inflate(R.layout.item,parent,false)
        return HolderCountry(layout)
    }
    override fun onBindViewHolder(holder: HolderCountry, position: Int) {
        val countrylist=countrylists.get(position)
        holder.txtfname.text=countrylist.name
        //图片加载逻辑(当前失效)
        val img=countrylist.image
        val Image: Int = Base.activity!!.getResources()
            .getIdentifier(img, "drawable", Base.activity!!.getPackageName())
        holder.imagerecy.setImageResource(Image)
        //////////////
    }
    override fun getItemCount()=countrylists.size
}

首页Activity

class MainActivity : AppCompatActivity() {

    var btnclick: Button?=null
    var recycler: RecyclerView?=null
    var adaptecounry: AdapterCountry?=null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnclick=findViewById(R.id.showcountrye)
        recycler=findViewById(R.id.mainactivity_recy_image)
        Base.activity=this
        Base.database= Database(this)
        btnclick!!.setOnClickListener {
            recycler!!.setHasTransientState(true)
            recycler!!.layoutManager= LinearLayoutManager(Base.activity)
            val list= Base.database!!.getAll()
            adaptecounry = AdapterCountry(Base.activity!!,list)
            recycler!!.adapter=adaptecounry

        }

    }

}

问题根因&修复方案

按优先级逐个排查:

  1. 资源ID获取逻辑无容错,返回0时无感知
    getIdentifier() 找不到匹配资源时会返回0,直接传入setImageResource(0)不会抛出错误,只会清空图片内容,这也是看不到报错的核心原因。
    修复:加日志打印拿到的资源ID值,如果是0直接定位是资源匹配问题:
val imgId = context.resources.getIdentifier(
    countrylist.image?.lowercase()?.substringBefore("."), 
    "drawable", 
    context.packageName
)
Log.d("img_check", "图片名:${countrylist.image},拿到的资源ID:$imgId")
if (imgId != 0) {
    holder.imagerecy.setImageResource(imgId)
} else {
    // 可替换为项目内的默认占位图
    holder.imagerecy.setImageResource(R.drawable.default_placeholder)
}
  1. 数据库存储的图片名不符合Android资源规范
  • 数据库里如果存的图片名带后缀(比如china.png),必须去掉后缀,getIdentifier只识别不带后缀的资源名
  • Android drawable资源名必须全小写,不能有大写字母、特殊符号,数据库里如果存了大写、空格、中文的文件名,会直接匹配失败
  • 核对数据库里存的图片名和drawable目录下的文件名完全一致,避免拼写错误、多空格的问题
  1. 数据库拷贝逻辑硬编码路径,可能读到脏数据
    写死的数据库路径data/data/com.alialim.countrylist_sample/databases/在高版本安卓、多用户/应用分身场景下失效,而且数据库版本升级时没有做旧库删除逻辑,很可能APP一直读取第一次安装时拷入的旧数据库,里面的image字段值是错误的。
    修复:修改拷贝数据库的路径获取逻辑,加版本判断:
private fun initDatabase() {
    val dbFile = context.getDatabasePath(info_db.dbName)
    // 版本号变更时删除旧库重新拷贝
    if (dbFile.exists()) {
        val curVersion = context.getSharedPreferences("db_config", Context.MODE_PRIVATE)
            .getInt("db_version", 0)
        if (curVersion != info_db.dbversion) {
            dbFile.delete()
        }
    }
    if (!dbFile.exists()) {
        // 确保父目录存在
        dbFile.parentFile?.mkdirs()
        copyDataBase(dbFile.absolutePath)
        // 存储当前数据库版本
        context.getSharedPreferences("db_config", Context.MODE_PRIVATE).edit()
            .putInt("db_version", info_db.dbversion).apply()
    }
}

private fun copyDataBase(outPath: String) {
    context.assets.open(info_db.source).use { input ->
        FileOutputStream(outPath).use { output ->
            input.copyTo(output)
        }
    }
}
  1. 避免全局持有Activity引用的坏实践
    在Base单例里存储Activity引用很容易造成内存泄漏,如果Activity被回收后再触发Adapter渲染,会直接触发空指针。Adapter本身已经持有了context参数,直接用传入的context拿资源即可,不需要依赖Base里存的Activity。

相关参考截图

  • SQLite Browser数据库配置截图
    SQLitebrowser数据库配置
  • drawable目录资源截图
    drawable目录资源列表

内容的提问来源于stack exchange,提问作者aaallliii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:01:05