Java 如何手动更改DayNight主题?

Java 如何手动更改DayNight主题?,java,android,Java,Android,我已经成功地在我的应用程序中实现了NightMode,但就在它>API29(Android Q)时。 在旧的API中,我没有自动昼夜主题的功能(根据操作系统),因此我使用开关手动触发主题,然后将主题保存在首选项中,以便在用户再次启动活动时加载主题。这是开关代码(此代码在片段中): 上面的代码工作正常,它使用所需的主题重新启动活动,并将布尔值保存在首选项中。然而,主要问题是当我重新启动应用程序时。该活动在setContentView()之前更改主题,并加载主题,但在尝试从AsyncTask访问我的

我已经成功地在我的应用程序中实现了NightMode,但就在它>API29(Android Q)时。
在旧的API中,我没有自动昼夜主题的功能(根据操作系统),因此我使用开关手动触发主题,然后将主题保存在首选项中,以便在用户再次启动活动时加载主题。这是开关代码(此代码在片段中):

上面的代码工作正常,它使用所需的主题重新启动活动,并将布尔值保存在首选项中。然而,主要问题是当我重新启动应用程序时。该活动在
setContentView()
之前更改主题,并加载主题,但在尝试从AsyncTask访问我的数据库后,会引发异常:

java.lang.RuntimeException:执行时出错 doInBackground()由以下原因引起:java.lang.NullPointerException:尝试 调用虚拟方法“android.database.sqlite.SQLiteDatabase” android.content.Context.openOrCreateDatabase(java.lang.String,int, android.database.sqlite.SQLiteDatabase$CursorFactory, android.database.DatabaseErrorHandler)在空对象引用上

我的猜测是,在某种程度上,更改主题可能会导致上下文为空,而此空上下文将传递给片段,这些片段与尝试通过AsyncTask访问数据库的片段相同,并引发异常。
这很奇怪,因为应用程序只有在加载活动时才会崩溃(在第一个代码段中不会崩溃),并且只有在
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE\u NIGHT\u YES)时才会崩溃被执行,只有当用户的首选项“NightMode”设置为true时才会发生这种情况

这是我的活动代码:

private void initTheme() {
        PrefUtils p = new PrefUtils(this);
        if (p.getBoolean("NightMode", false) || AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
            getDelegate().applyDayNight();
        }
    }

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initTheme();
        setContentView(R.layout.activity_main);
        initFragments();
}
编辑
我通过FolderFragment访问数据库值,与initFragments()中附加到活动的值相同:

这是我的FolderFragment的代码:

 @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.folderfragment, container, false);
        new RefreshData(v).execute();


        return v;
    }

 private class RefreshData extends AsyncTask<Void, Void, Void> {

        private View v;
        private String title;
        private Drawable drawable;

        public RefreshData(View v) {
            this.v = v;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Progress = v.findViewById(R.id.FFProgress); //Progress is my ProgressBar
            Progress.setVisibility(View.VISIBLE);
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            PAdapter = new ListAdapter(mList, getContext(), Mode);
            Pager.setAdapter(PAdapter);
            Progress.setVisibility(View.GONE); //Hides the ProgressBar because has finished 
        }

        @Override
        protected Void doInBackground(Void... voids) {
            mList = getLists();
            return null;
        }

private void getLists()
{
        ArrayList<ListItem> l = new ArrayList<>();
        DatabaseHelper d = new DatabaseHelper(getContext());

        Cursor res = d.getAllLists();

        try
        {
            while (res.moveToNext()) {
                int ID = res.getInt(0);
                int FolderID = res.getInt(1);
                String Title = res.getString(2);
                String Description = res.getString(3);
                String ColorHex = res.getString(4);
                int Tag = res.getInt(5);

                l.add(new ListItem(ID, FolderID, Title, Description, ColorHex, Tag));
            }
        }
        catch(Exception ignored)
        {

        }
        return l;
    }

    }

视图未附加到
onCreateView
中的活动/片段(这也是膨胀中的
false
所说的。
false
表示不附加,因为当您从该方法返回视图时,系统将附加它)

这本身就是危险的,如果开始操纵视图,并在此时执行使用视图和上下文的任务

我会移动
新的刷新数据(v.execute()导入已创建的活动中


然而,您的问题是ASyncTask是非静态的,这就是为什么您可以在其中使用
getContext()
方法的原因。所以问题是要么任务持有一个旧的引用,它变为null,要么任务在生命周期中过早地请求引用,它变为null

我认为上面提到的移动到activitycreated上的
修复可能会修复它,但它仍然不是最好的

然后,我将更改您的ASyncTask,使其成为静态:

私有静态类RefreshData扩展异步任务{

然后将您使用的
getContext()
更改为
v.getContext()

newdatabasehelper(getContext());

变成

newdatabasehelper(v.getContext());

您还可以显式地传递上下文,就像传递视图一样。这会更好。实际上最好是这样做:

@Override
public void onActivityCreated (Bundle savedInstanceState) {
    DatabaseHelper dbHelper = new DatabaseHelper(getContext());
    ProgressBar progressView = findViewById(R.id.FFProgress);
    new RefreshData(dbHelper, progressView).execute();
}

 private static class RefreshData extends AsyncTask<Void, Void, Void> {

        private final View progress;
        private final DatabaseHelper dbHelper;
        private String title;
        private Drawable drawable;

        public RefreshData(DatabaseHelper dbHelper, View progress) {
            this.dbHelper = dbHelper;
            this.progress = progress;
        }
您应该避免这样做,以免混淆自己。java开发人员的idomatic协议是使用大写字符命名类,如
DatabaseHelper
,使用小写字符命名变量,如
title
。然后使用camelcase任何其他单词。因此,这就变成:


String title=res.getString(2);

视图未附加到
onCreateView
中的活动/片段(这也是膨胀中的
false
的意思。
false
表示不附加,因为当您从该方法返回视图时,系统将附加它)

这本身就是危险的,如果开始操纵视图,并在此时执行使用视图和上下文的任务

我会将
新的刷新数据(v).execute();
移动到
onActivityCreated


然而,您的问题是您的异步任务是非静态的,这就是为什么您可以在其中使用
getContext()
方法的原因。因此,问题要么是任务持有旧的引用并且变为null,要么是它在生命周期中过早地请求引用并且为null

我认为上面提到的移动到activitycreated上的
修复可能会修复它,但它仍然不是最好的

然后,我将更改您的ASyncTask,使其成为静态:

私有静态类RefreshData扩展异步任务{

然后将您使用的
getContext()
更改为
v.getContext()

newdatabasehelper(getContext());

变成

newdatabasehelper(v.getContext());

您还可以显式地传递上下文,就像传递视图一样。这会更好。实际上最好是这样做:

@Override
public void onActivityCreated (Bundle savedInstanceState) {
    DatabaseHelper dbHelper = new DatabaseHelper(getContext());
    ProgressBar progressView = findViewById(R.id.FFProgress);
    new RefreshData(dbHelper, progressView).execute();
}

 private static class RefreshData extends AsyncTask<Void, Void, Void> {

        private final View progress;
        private final DatabaseHelper dbHelper;
        private String title;
        private Drawable drawable;

        public RefreshData(DatabaseHelper dbHelper, View progress) {
            this.dbHelper = dbHelper;
            this.progress = progress;
        }
您应该避免这样做,以免混淆自己。java开发人员的idomatic协议是使用大写字符命名类,如
DatabaseHelper
,使用小写字符命名变量,如
title
。然后使用camelcase任何其他单词。因此,这就变成:


String title=res.getString(2);

访问数据库的代码在哪里?是否在initFragments中?显示给我们that@Blundell刚刚用代码的其余部分更新了这个问题。访问数据库的代码在哪里?在initFragments中吗?展示给我们that@Blundell刚刚用剩下的代码更新了这个问题
public class DatabaseHelper extends SQLiteOpenHelper{
public Cursor getAllLists() {
        try
        {
            SQLiteDatabase db = this.getWritableDatabase(); //This is where throws the exception
            return db.rawQuery(Db.Query.SELECT_LISTS, null);
        }
        catch(Exception e)
        {

        }
        return null;
    }

}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
    DatabaseHelper dbHelper = new DatabaseHelper(getContext());
    ProgressBar progressView = findViewById(R.id.FFProgress);
    new RefreshData(dbHelper, progressView).execute();
}

 private static class RefreshData extends AsyncTask<Void, Void, Void> {

        private final View progress;
        private final DatabaseHelper dbHelper;
        private String title;
        private Drawable drawable;

        public RefreshData(DatabaseHelper dbHelper, View progress) {
            this.dbHelper = dbHelper;
            this.progress = progress;
        }
String Title = res.getString(2);