在Android开发中,数据库连接泄露是常见但易被忽视的性能杀手。本文将深入探讨多种检测方法,从基础原理到高级技巧,助你彻底解决这一隐患。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
setupStrictMode()
}
private fun setupStrictMode() {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() // 检测数据库泄露
.detectLeakedClosableObjects() // 检测未关闭资源
.detectActivityLeaks() // 检测Activity泄露
.penaltyLog()
.penaltyDeath() // 测试环境直接崩溃便于定位
.build()
)
}
}
}
AndroidManifest.xml
中注册自定义ApplicationStrictMode
和leaked
关键字的日志E/StrictMode: A resource was acquired at attached stack trace but never released.
See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'close' not called
at android.database.sqlite.SQLiteDatabase.(SQLiteDatabase.java:218)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1017)
at com.example.MyDBHelper.getWritableDatabase(MyDBHelper.kt:25)
class TracedDBHelper(
context: Context,
name: String,
factory: SQLiteDatabase.CursorFactory?,
version: Int
) : SQLiteOpenHelper(context, name, factory, version) {
companion object {
private val openCounter = AtomicInteger(0)
private val openStackTraces = ConcurrentHashMap<Int, String>()
fun getOpenCount() = openCounter.get()
fun printOpenConnections() {
if (openCounter.get() > 0) {
Log.w("DB_TRACKER", "⚠️ 未关闭的数据库连接: ${openCounter.get()}")
openStackTraces.values.forEach {
Log.w("DB_TRACKER", "打开堆栈:\n$it")
}
}
}
}
override fun getWritableDatabase(): SQLiteDatabase {
return trace(super.getWritableDatabase())
}
override fun getReadableDatabase(): SQLiteDatabase {
return trace(super.getReadableDatabase())
}
private fun trace(db: SQLiteDatabase): SQLiteDatabase {
openCounter.incrementAndGet()
val stack = Throwable().stackTrace
.drop(1) // 去掉当前方法
.take(10) // 取前10个堆栈帧
.joinToString("\n") { " at ${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" }
openStackTraces[db.hashCode()] = stack
Log.d("DB_TRACKER", " 数据库连接打开. 总数: ${openCounter.get()}\n$stack")
return db
}
override fun close() {
super.close()
openCounter.decrementAndGet()
Log.d("DB_TRACKER", " 数据库连接关闭. 剩余: ${openCounter.get()}")
}
}
class MainActivity : AppCompatActivity() {
private lateinit var dbHelper: TracedDBHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dbHelper = TracedDBHelper(this, "my_db", null, 1)
// 测试使用
val db = dbHelper.writableDatabase
// 模拟忘记关闭
}
override fun onDestroy() {
TracedDBHelper.printOpenConnections()
// 实际项目中应该在这里关闭数据库
// dbHelper.close()
super.onDestroy()
}
}
// 在Application类中
class DebugApp : Application() {
override fun onCreate() {
super.onCreate()
setupLeakCanary()
}
private fun setupLeakCanary() {
if (BuildConfig.DEBUG) {
// 自定义配置
val config = LeakCanary.config.copy(
dumpHeap = true,
retainedVisibleThreshold = 3,
referenceMatchers = listOf(
// 特别监控SQLiteDatabase
IgnoredMatcher(
className = "android.database.sqlite.SQLiteDatabase"
)
)
)
LeakCanary.config = config
}
}
}
// 在数据库操作处
fun performDatabaseOperation() {
val db = dbHelper.writableDatabase
try {
// 数据库操作
db.execSQL("CREATE TABLE IF NOT EXISTS Users (id INTEGER PRIMARY KEY, name TEXT)")
} finally {
db.close()
// 主动监控数据库对象
AppWatcher.objectWatcher.watch(
watchedObject = db,
description = "SQLiteDatabase实例应被回收"
)
}
}
try (val db = dbHelper.writableDatabase) {
// 执行操作 - 自动关闭
db.insert("Users", null, ContentValues().apply {
put("name", "John")
})
} // 自动调用db.close()
fun <T> SQLiteOpenHelper.useDatabase(block: (SQLiteDatabase) -> T): T {
val db = writableDatabase
try {
return block(db)
} finally {
db.close()
}
}
// 使用示例
dbHelper.useDatabase { db ->
db.query("Users", null, null, null, null, null, null)
.use { cursor ->
while (cursor.moveToNext()) {
// 处理数据
}
}
}
class DatabaseLifecycleObserver(private val dbHelper: SQLiteOpenHelper) : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun closeDatabase() {
dbHelper.close()
Log.d("DB_LIFECYCLE", "数据库连接已关闭")
}
}
// 在Activity/Fragment中
lifecycle.addObserver(DatabaseLifecycleObserver(dbHelper))
dbHelper.writableDatabase.use { db ->
try {
db.beginTransaction()
// 批量操作
repeat(100) { i ->
val values = ContentValues().apply {
put("name", "User$i")
}
db.insert("Users", null, values)
}
db.setTransactionSuccessful()
} catch (e: Exception) {
Log.e("DB_ERROR", "事务失败", e)
} finally {
db.endTransaction()
}
}
检测方法 | 适用场景 | 检测精度 | 性能影响 | 实现复杂度 | 推荐指数 |
---|---|---|---|---|---|
StrictMode | 开发阶段 | ★★★☆☆ | 低 | 低 | ★★★★★ |
手动追踪 | 关键模块调试 | ★★★★★ | 中 | 中 | ★★★★☆ |
LeakCanary | 全应用内存监测 | ★★★★☆ | 高 | 中 | ★★★★☆ |
静态代码分析 | 编码阶段预防 | ★★☆☆☆ | 无 | 低 | ★★★☆☆ |
选择建议:
object DatabaseManager {
private const val MAX_POOL_SIZE = 5
private val connectionPool = ArrayBlockingQueue<SQLiteDatabase>(MAX_POOL_SIZE)
fun getConnection(dbHelper: SQLiteOpenHelper): SQLiteDatabase {
return connectionPool.poll() ?: dbHelper.writableDatabase.apply {
// 新连接初始化设置
enableWriteAheadLogging()
}
}
fun releaseConnection(db: SQLiteDatabase) {
if (!connectionPool.offer(db)) {
db.close() // 连接池满时直接关闭
}
}
}
// 使用示例
val db = DatabaseManager.getConnection(dbHelper)
try {
// 使用数据库
} finally {
DatabaseManager.releaseConnection(db)
}
fun getUsers(): List<User> {
val users = mutableListOf<User>()
dbHelper.readableDatabase.use { db ->
db.query("Users", null, null, null, null, null, null).use { cursor ->
val idIndex = cursor.getColumnIndex("id")
val nameIndex = cursor.getColumnIndex("name")
while (cursor.moveToNext()) {
users.add(User(
id = cursor.getLong(idIndex),
name = cursor.getString(nameIndex)
))
}
}
}
return users
}
try-finally
或use
确保资源释放Cursor.use{}
或在finally
中关闭使用Room时,泄露检测更简单:
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "app_db"
)
.addCallback(object : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
// 连接打开追踪
}
})
.build()
.also { INSTANCE = it }
}
}
}
}
// 检测关闭情况
val db = AppDatabase.getDatabase(context)
// ...使用数据库...
db.close() // 显式关闭
db.isOpen // 检查状态
Room自动管理连接,但仍需注意:
通过结合传统SQLite和现代ORM库的检测技术,可以构建全方位的数据库连接安全保障体系。