如何在一个Fragment中为两个数据库实例化两个ViewModel
问题根源与解决方案
核心问题
Android应用只能存在一个自定义Application子类,你当前定义了RosterDbApplication和MatchDbApplication两个类,而系统运行时只会初始化Manifest中配置的那一个。当你尝试把已初始化的Application实例强制转换成另一个未被实例化的Application类型时,必然抛出类型转换异常。
具体修复步骤
1. 合并Application类
将两个数据库的初始化逻辑合并到同一个Application类中:
class AppApplication : Application() { // 花名册数据库(持久化存储) val playerDatabase: PlayerDatabase by lazy { PlayerDatabase.getDatabase(this) } // 比赛内存数据库(仅应用运行期间存在) val teamsDatabase: TeamsDatabase by lazy { TeamsDatabase.getMatchDb(this) } }
2. 更新Manifest配置
确保AndroidManifest.xml中的application标签指向合并后的类:
<application android:name=".AppApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.YourAppName"> <!-- 你的Activity、Service等组件配置 --> </application>
3. 调整Fragment中的ViewModel实例化
修改Fragment中获取Dao的逻辑,从合并后的Application获取对应数据库的Dao:
val leagueViewModel: RosterViewModel by activityViewModels { RosterViewModelFactory( (activity?.application as AppApplication).playerDatabase.itemDao() ) } val matchViewModel: MatchViewModel by activityViewModels { MatchViewModelFactory( (activity?.application as AppApplication).teamsDatabase.matchDao() ) }
4. (可选)允许主线程访问数据库
由于你提到数据库规模很小,可以在Room数据库构建时开启主线程查询权限,避免额外的线程处理:
// PlayerDatabase的getDatabase示例 fun getDatabase(context: Context): PlayerDatabase { return Room.databaseBuilder(context, PlayerDatabase::class.java, "player_db") .allowMainThreadQueries() // 允许主线程操作数据库 .build() } // TeamsDatabase的getMatchDb示例 fun getMatchDb(context: Context): TeamsDatabase { return Room.inMemoryDatabaseBuilder(context, TeamsDatabase::class.java) .allowMainThreadQueries() .build() }
可选优化方案(依赖注入)
如果后续项目复杂度提升,推荐使用Hilt实现依赖注入,避免手动传递Dao和ViewModelFactory,简化代码:
- 在Application类添加
@HiltAndroidApp注解 - 为数据库和Dao提供注入绑定
- 在ViewModel中通过构造函数注入Dao
- 在Fragment中用
by viewModels()直接获取ViewModel
不过对于当前小型项目,合并Application的方案已经足够解决问题。
内容的提问来源于stack exchange,提问作者PaulWolt
相关产品推荐
相关产品推荐

