烘焙应用Widget获取ROOM数据库实例时崩溃问题求助
嘿,我一眼就看出你这个问题的症结所在了——当主应用被关闭后,Widget所在的进程重启时,你的BakingApp静态上下文还没初始化,导致调用getApplicationContext()时踩了空指针!
问题根源拆解
当主应用进程被销毁,Widget单独运行(或系统重启Widget进程)时,你的BakingApp类的onCreate()方法还没来得及执行,mContext变量还是null。这时候你在onDataSetChanged()里调用BakingApp.getContext()拿到的就是null,把这个null传到AppDatabase.getInstance()里,自然就触发了NullPointerException。
靠谱的解决方案(推荐方案1)
方案1:改用Widget传入的Context获取数据库实例
你的BakingAppRemoteViewsFactory已经在构造方法里拿到了合法的mContext,直接用这个上下文就好,完全没必要依赖静态的Application上下文。
修改RecipeRepository的构造方法:
把原来接收Application改成接收Context,兼容性更强:public RecipeRepository(Context context){ AppDatabase appDatabase = AppDatabase.getInstance(context); recipesDao = appDatabase.recipesDao(); }调整RemoteViewsFactory的onDataSetChanged方法:
用持有的mContext代替BakingApp.getContext()初始化仓库:@Override public void onDataSetChanged() { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); recipeID = sharedPreferences.getInt(RECIPE_ID, 0); // 这里换成mContext,再也不用怕空指针了 recipeRepository = new RecipeRepository(mContext); recipe = recipeRepository.getCurrentRecipe(recipeID); ingredients = recipe.getRecipeIngredients(); }加固AppDatabase.getInstance()的逻辑:
添加上下文非空检查,并且确保使用ApplicationContext避免内存泄漏:public static AppDatabase getInstance(Context context){ if (context == null) { throw new IllegalArgumentException("Context must not be null!"); } if (sInstance == null){ synchronized (LOCK){ // 先拿到全局的ApplicationContext,避免持有Widget/Activity的上下文导致泄漏 Context appContext = context.getApplicationContext(); sInstance = Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME) .fallbackToDestructiveMigration() .build(); } } Log.d(LOG_TAG, "Getting the db instance"); return sInstance; }
方案2:修复静态Application上下文的可靠性(备选)
如果你非要用静态上下文,那得给BakingApp的getContext()加个安全检查,避免返回null:
public class BakingApp extends Application { private static BakingApp mContext; @Override public void onCreate() { super.onCreate(); mContext = this; } public static BakingApp getContext(){ if (mContext == null) { throw new IllegalStateException("Application context hasn't been initialized yet!"); } return mContext; } }
不过这个方案有隐患——Widget进程启动时,Application的onCreate()可能还没执行,所以还是方案1更稳妥。
额外提醒
Widget是运行在独立进程(或主应用进程重启后的环境)的,绝对不能依赖主应用里的静态变量状态,一定要用Widget自身提供的合法上下文来获取系统资源、数据库这些东西。另外用Room的时候,一定要用ApplicationContext创建实例,不然很容易踩内存泄漏的坑!
内容的提问来源于stack exchange,提问作者Pointyhat

