首页 > 解决方案 > 如何手动更改 DayNight 主题?

问题描述

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

DarkSwitch.setOnCheckedChangeListener((compoundButton, b) -> {
            if (b) {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
                p.setBoolean("NightMode", true); //P is an instance of a class I have called PrefUtils that saves the user preferences
            } else {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
                p.setBoolean("NightMode", false);
            }
            ((MainActivity) getActivity()).recreate();
        }); 

上面的代码工作正常,它使用所需的主题重新启动 Activity 并将布尔值保存在首选项中。但是,主要问题是当我重新启动应用程序时。Activity 之前更改了主题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)' 在空对象引用上

我的猜测是,不知何故,更改主题会导致 Context 为空,并且此空上下文被传递给片段,即尝试通过 AsyncTask 访问数据库的片段,它会引发异常。
这很奇怪,因为应用程序只在加载 Activity 时崩溃(它不会在第一个代码片段中崩溃),并且只有AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);在执行时才会崩溃,只有当用户将 Preference "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()中的活动相同:

public Fragment fragment1 = new FolderFragment();
private void initFragments()
{
     fm.beginTransaction().add(R.id.fragmentcontainer, fragment1, "1").commit();
}

这是我的 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;
    }

    }

这个类"DatabaseHelper"是管理所有选择和插入语句的类:

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;
    }

}

标签: javaandroid

解决方案


该视图未附加到内部的 Activity/Fragment onCreateView(这false也是通货膨胀中所说的。false意味着不要附加,因为当您从该方法返回视图时,系统将附加它)。

开始操作视图并在此时执行使用视图和上下文的任务本身就是危险的。

我会new RefreshData(v).execute();搬进onActivityCreated


但是您的问题是您的 ASyncTask 是非静态的,这就是您可以使用其中的getContext()方法的原因。所以问题是任务持有一个旧的引用并且它变成了空,或者它在生命周期中过早地请求一个引用并且它的空。

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

然后我会更改您的 ASyncTask 以使其成为静态:

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

然后将您使用的任何地方更改getContext()v.getContext()

new DatabaseHelper(getContext());

变成

new DatabaseHelper(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;
        }

等等..这样您就可以明确声明任务使用的内容,并从活动中传递它们。


边注:

您有很多以大写字符开头的变量,例如:

String Title = res.getString(2);

你应该避免这样做,以免混淆自己。Java 开发人员的惯用约定是用大写字符命名类,例如用小写字符命名DatabaseHelper变量,例如title. 然后用驼峰式的任何进一步的话。所以这变成:

String title = res.getString(2);


推荐阅读