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 } } }
问题根因&修复方案
按优先级逐个排查:
- 资源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) }
- 数据库存储的图片名不符合Android资源规范
- 数据库里如果存的图片名带后缀(比如
china.png),必须去掉后缀,getIdentifier只识别不带后缀的资源名 - Android drawable资源名必须全小写,不能有大写字母、特殊符号,数据库里如果存了大写、空格、中文的文件名,会直接匹配失败
- 核对数据库里存的图片名和drawable目录下的文件名完全一致,避免拼写错误、多空格的问题
- 数据库拷贝逻辑硬编码路径,可能读到脏数据
写死的数据库路径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) } } }
- 避免全局持有Activity引用的坏实践
在Base单例里存储Activity引用很容易造成内存泄漏,如果Activity被回收后再触发Adapter渲染,会直接触发空指针。Adapter本身已经持有了context参数,直接用传入的context拿资源即可,不需要依赖Base里存的Activity。
相关参考截图
- SQLite Browser数据库配置截图

- drawable目录资源截图

内容的提问来源于stack exchange,提问作者aaallliii
相关产品推荐
相关产品推荐

