Android Kotlin库中String扩展复用ArrayList的单例Context问题
解决方案:安全复用ArrayList的String扩展方法(基于Context加载)
嘿,这个问题我之前做Android Library的时候也碰到过!核心是要解决单例安全持有Context同时只加载一次ArrayList的问题,毕竟频繁调用扩展方法,重复加载太浪费性能了。我给你几个实用的方案,都是经过项目验证的:
方案1:ApplicationContext注入式单例(适合需调用方主动初始化的场景)
这种方案需要Library的使用者在Application初始化时传入ApplicationContext,好处是逻辑清晰,可控性强。
第一步:改造单例类,安全持有ApplicationContext
我们用双重校验锁实现单例,并且只持有ApplicationContext(避免内存泄漏),同时用by lazy延迟加载ArrayList,确保只初始化一次:
class StringSearchManager private constructor(private val appContext: Context) { // 延迟加载列表,线程安全且仅初始化一次 val searchList: ArrayList<String> by lazy { loadSearchListFromSource(appContext) } companion object { @Volatile private var instance: StringSearchManager? = null // 对外暴露初始化方法,要求传入ApplicationContext fun initialize(appContext: Context) { if (instance == null) { synchronized(this) { instance ?: StringSearchManager(appContext.applicationContext).also { instance = it } } } } // 获取单例实例,确保已初始化 fun getInstance(): StringSearchManager { return checkNotNull(instance) { "StringSearchManager not initialized! Call initialize() with ApplicationContext first." } } } // 这里写你的列表加载逻辑,比如从assets、raw资源读取 private fun loadSearchListFromSource(context: Context): ArrayList<String> { return try { context.assets.open("search_dictionary.txt") .bufferedReader() .readLines() .toCollection(ArrayList()) } catch (e: Exception) { // 加载失败返回空列表或做错误处理 ArrayList() } } }
第二步:实现String扩展方法
扩展方法直接调用单例的已加载列表,不需要每次都初始化:
fun String.searchMatchingItem(): String? { val searchList = StringSearchManager.getInstance().searchList // 这里替换成你的搜索逻辑,比如模糊匹配、精确匹配 return searchList.firstOrNull { it.contains(this, ignoreCase = true) } }
第三步:提醒调用方初始化
在Library的文档里说明,需要在Application的onCreate()中初始化:
class MyApp : Application() { override fun onCreate() { super.onCreate() StringSearchManager.initialize(this) } }
方案2:ContentProvider自动初始化(适合无侵入式Library)
如果不想让调用方手动初始化,Android的ContentProvider机制可以帮我们在App启动时自动完成初始化,完全无侵入。
第一步:改造单例类,支持内部初始化
调整单例,新增内部初始化方法,对外暴露无参的getInstance:
class StringSearchManager private constructor(private val appContext: Context) { val searchList: ArrayList<String> by lazy { loadSearchListFromSource(appContext) } companion object { @Volatile private var instance: StringSearchManager? = null // 供ContentProvider内部调用的初始化方法 internal fun initialize(appContext: Context) { if (instance == null) { synchronized(this) { instance ?: StringSearchManager(appContext.applicationContext).also { instance = it } } } } fun getInstance(): StringSearchManager { return checkNotNull(instance) { "StringSearchManager failed to initialize! Check if the ContentProvider is registered." } } } private fun loadSearchListFromSource(context: Context): ArrayList<String> { // 同方案1的加载逻辑 return try { context.assets.open("search_dictionary.txt") .bufferedReader() .readLines() .toCollection(ArrayList()) } catch (e: Exception) { ArrayList() } } }
第二步:创建初始化用的ContentProvider
ContentProvider会在App启动时被系统实例化,我们可以在这里完成单例的初始化:
class SearchListInitProvider : ContentProvider() { override fun onCreate(): Boolean { context?.let { // 初始化单例,传入ApplicationContext StringSearchManager.initialize(it.applicationContext) } return true } // 以下方法不需要实现,返回默认值即可 override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? = null override fun getType(uri: Uri): String? = null override fun insert(uri: Uri, values: ContentValues?): Uri? = null override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0 override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int = 0 }
第三步:在Library的Manifest中注册Provider
在你的Library模块的AndroidManifest.xml中添加这个Provider的注册:
<provider android:name=".SearchListInitProvider" android:authorities="${applicationId}.search_list_init_provider" android:exported="false" />
第四步:实现String扩展方法
和方案1一样,直接调用单例即可,调用方无需任何额外操作:
fun String.searchMatchingItem(): String? { val searchList = StringSearchManager.getInstance().searchList return searchList.firstOrNull { it.equals(this, ignoreCase = true) } }
关键注意事项
- 永远用ApplicationContext:单例是全局生命周期,持有Activity/Fragment的Context会导致内存泄漏,必须用
applicationContext。 - lazy加载确保只初始化一次:
by lazy是线程安全的,保证ArrayList只在第一次使用时加载,之后复用。 - 错误处理:加载列表时要捕获异常(比如文件不存在、读取失败),避免Crash。
内容的提问来源于stack exchange,提问作者Duncan Hill
相关产品推荐
相关产品推荐

