【问题标题】:Shared Preferences Null Pointer Exception共享首选项空指针异常
【发布时间】:2014-11-18 15:37:47
【问题描述】:

我正在为我的笔记 Android 应用程序添加共享首选项,我遇到了一个特殊的空指针异常。我目前正在我的应用程序中测试字体大小和字体的设置,并且能够成功保存两者的共享首选项,但在检索部分遇到问题。更改设置后,它们会在应用程序打开的剩余时间内成功更改 Fragment 上的字体和字体大小,但如果重新启动应用程序,我将无法恢复它们。

第一个奇怪的空指针在 onCreate 中。

创建时:

    //create a new note fragment if one has not been created yet
    mNoteFragment = (NoteFragment) getFragmentManager().findFragmentById(R.id.container);
    if (mNoteFragment == null) {
        mNoteFragment = new NoteFragment();
        getFragmentManager().beginTransaction()
                .replace(R.id.container, mNoteFragment).commit();
    }

    //restore SharedPreferences
    SharedPreferences sharedPrefs = getPreferences(0);
    int stylePref = sharedPrefs.getInt(SharedPreferanceConstants.PREF_FONT_SIZE, 2);
    String fontPref = sharedPrefs.getString(SharedPreferanceConstants.PREF_TYPEFACE, "");
    Log.e("STYLEID", String.valueOf(stylePref));
    Log.e("FONTTYPE", fontPref);
    onStyleChange(null , stylePref); //NULL POINTER EXCEPTION HERE
    onFontChange(null, fontPref);

日志输出“3”和“Impact”,它们是正确的大小和字体值,表明 stylePref 和 fontPref 不为空。

下一个空指针如下:

    @Override
    public void onStyleChange(CustomStyleDialogFragment dialog, int styleId) {
        Log.d("NOTEFRAGMENT", String.valueOf(mNoteFragment));
        mNoteFragment.setCustomStyle(styleId); //NULL POINTER EXCEPTION HERE
    }

    @Override
    public void onFontChange(CustomStyleDialogFragment dialog, String fontName) {
        mNoteFragment.setCustomFont(fontName);
    }

我已测试记录 styleId 的值并得到“3”,所以这似乎不是问题。根据日志,mNoteFragment 也不为空。

这是 NoteFragment.java 中的第三个 NP 异常。这是我最终将 EditText 视图设置为所需设置的地方。如果没有共享首选项,我可以轻松更改字体和大小,所以我不确定这里的问题。

    public void setCustomFont(String fontName) {
        if(fontName.equals("Helvetica")) {
            mEditText.setTypeface(mHelvetica);
        }
        else if(fontName.equals("Helvetica-Neue")) {
            mEditText.setTypeface(mHelveticaNeue);
        }
        else if(fontName.equals("Impact")) {
            mEditText.setTypeface(mImpact);
        }
        else {
            mEditText.setTypeface(Typeface.DEFAULT);
        }
    }

    public void setCustomStyle(int styleId) {
        if(styleId == 1) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Small);
        }
        else if(styleId == 2) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium);
        }
        else if(styleId == 3) {
            //NULL POINTER EXCEPTION HERE
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Large);
        }
}

当然,这里是 logcat。提前致谢!

11-18 10:31:53.030  18966-18966/com.richluick.blocnotes I/Process﹕ Sending signal. PID: 18966 SIG: 9
11-18 10:35:32.838  19248-19248/com.richluick.blocnotes D/STYLEID﹕ 3
11-18 10:35:32.838  19248-19248/com.richluick.blocnotes D/FONTTYPE﹕ Impact
11-18 10:35:32.839  19248-19248/com.richluick.blocnotes D/ERROR﹕ NoteFragment{422972f8 id=0x7f090001}
11-18 10:35:32.840  19248-19248/com.richluick.blocnotes D/AndroidRuntime﹕ Shutting down VM
11-18 10:35:32.840  19248-19248/com.richluick.blocnotes W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x4197dd40)
11-18 10:35:32.842  19248-19248/com.richluick.blocnotes E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.richluick.blocnotes, PID: 19248
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.richluick.blocnotes/com.richluick.blocnotes.BlocNotes}: java.lang.NullPointerException
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2198)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2257)
            at android.app.ActivityThread.access$800(ActivityThread.java:139)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5097)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)
     Caused by: java.lang.NullPointerException
            at com.richluick.blocnotes.NoteFragment.setCustomStyle(NoteFragment.java:86)
            at com.richluick.blocnotes.BlocNotes.onStyleChange(BlocNotes.java:139)
            at com.richluick.blocnotes.BlocNotes.onCreate(BlocNotes.java:61)
            at android.app.Activity.performCreate(Activity.java:5248)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2162)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2257)
            at android.app.ActivityThread.access$800(ActivityThread.java:139)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5097)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)

NoteFragment.java

public class NoteFragment extends Fragment {

    public EditText mEditText;
    private Typeface mHelvetica;
    private Typeface mHelveticaNeue;
    private Typeface mImpact;

    private static final String TEXT = "text";

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putString(TEXT, mEditText.getText().toString());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_note, container, false);
        setHasOptionsMenu(true);

        //restore state of app when activity is destroyed and restarted
        mEditText = (EditText) rootView.findViewById(R.id.editText);
        if (savedInstanceState != null) {
            mEditText.setText(savedInstanceState.getString(TEXT));
        }

        //Store the font assets as variables
        mHelvetica = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Helvetica_Reg.ttf");
        mHelveticaNeue = Typeface.createFromAsset(getActivity().getAssets(), "fonts/HelveticaNeue_Lt.ttf");
        mImpact = Typeface.createFromAsset(getActivity().getAssets(), "fonts/impact.ttf");

        return rootView;
    }

    /**
     * This is a setter method for setting the font the user has selected from the spinner
     *
     * param fontName the name of the font the user selected
     * @return void
     * */
    public void setCustomFont(String fontName) {
        if(fontName.equals("Helvetica")) {
            mEditText.setTypeface(mHelvetica);
        }
        else if(fontName.equals("Helvetica-Neue")) {
            mEditText.setTypeface(mHelveticaNeue);
        }
        else if(fontName.equals("Impact")) {
            mEditText.setTypeface(mImpact);
        }
        else {
            mEditText.setTypeface(Typeface.DEFAULT);
        }
    }

    /**
     * This is a setter method for setting the font style the user has selected from custom menu
     *
     * param styleId the integer id of the font stlye selected (SMALL, MEDIUM, LARGE)
     * @return void
     * */
    public void setCustomStyle(int styleId) {
        if(styleId == 1) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Small);
        }
        else if(styleId == 2) {
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium);
        }
        else if(styleId == 3) {
            Log.d("EDITTEXT", String.valueOf(mEditText));
            mEditText.setTextAppearance(getActivity(), android.R.style.TextAppearance_Large);
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Inflate the menu; this adds items to the action bar if it is present.
        super.onCreateOptionsMenu(menu, inflater);

        inflater = getActivity().getMenuInflater();
        inflater.inflate(R.menu.bloc_notes, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_erase) {
            mEditText.setText("");
        }
        return super.onOptionsItemSelected(item);
    }
}

【问题讨论】:

  • 当你保存你的偏好时,你提交了吗?
  • 是的,我确实提交了他们
  • com.richluick.blocnotes.NoteFragment.setCustomStyle(NoteFragment.java:86) 这意味着mEditText ...
  • @Selvin 我认为这可能是问题所在,但是在我引入共享首选项并且没有空指针问题之前,我使用 mEditText 更改字体大小和类型没有问题。引入 Shared Preferences 会如何让 mEditText 突然为空?
  • 不会,但除非您对所有内容进行单元测试,否则您无法 100% 确定。你能发布 NoteFragment 类吗?

标签: java android nullpointerexception sharedpreferences


【解决方案1】:

在您的SomeActivity.onCreate() 中,您正在创建一个NoteFragment 的实例,但即使您通过事务提交它,它也不会立即膨胀和创建。这基本上意味着,NoteFragment.onCreateView() 没有执行,片段 UI 还没有创建。但是,您的 Activity.onCreate() 假定 mFragment.mEditText 已设置,但事实并非如此,因此您遇到了 NullPointerException

看看FragmentTransation.commit方法:http://developer.android.com/reference/android/support/v4/app/FragmentTransaction.html#commit()

Schedules a commit of this transaction. The commit does not happen immediately; it will 
be scheduled as work on the main thread to be done the next time that thread is ready.

【讨论】:

  • 很有趣......所以解决方案可能是:if(mEditText is null) {deferred the setting into onCreateView} else {do settings now}
  • 好的,我有点想onCreateView 从未被执行,所以感谢您澄清这一点。我真的不明白为什么。我对“通过事务提交”和一般的 FragmentTransation.commit 方法的意思有点困惑。你能给我举个例子吗?否则我很迷茫
  • 这意味着基本上你要求片段管理器替换一个片段,片段管理器会异步执行。这意味着它会“记住它必须替换你的片段”,并且会在 CPU 可以处理它时执行它(没有承受太多负载)。这意味着无论你在 commit() 行之后调用什么,都不能确保由 commit() 启动的操作已经完成。
  • 为了解决这个问题,我会将onStyleChange 方法和所有与片段视图直接交互的相关方法放入NoteFragment 本身,例如您可以将其放入NoteFragment.onCreateView() 片段可以创建自己的SharedPreferences 实例并读取相同的底层数据。
【解决方案2】:

此时您的 NoteFragment 中的 mEditText 为空,因此是 NPE。

只需在片段的 onCreate() 或 onActivityCreated() 中初始化您的成员 mEditText,而不是 onCreateView()。这应该确保当您调用您的设置器时 mEditText 不为空。

无论发生什么,您都应该在 setter 函数中防止 mEditText 出现空值。

编辑这是我的意思:

public void setCustomFont(String fontName) {
      if(mEditText != null){
        if(fontName.equals("Helvetica")) {
            mEditText.setTypeface(mHelvetica);
        }
        else if(fontName.equals("Helvetica-Neue")) {
            mEditText.setTypeface(mHelveticaNeue);
        }
        else if(fontName.equals("Impact")) {
            mEditText.setTypeface(mImpact);
        }
        else {
            mEditText.setTypeface(Typeface.DEFAULT);
        }
        }
    }

编辑 2:Oncreate 不是一个好地方。基本上,您想在片段管理器提交您的更改后调用您的设置器,以便 mEditText 已被初始化。

编辑 3 - 用你的初始代码试试这个:

    getFragmentManager().beginTransaction().replace(R.id.container, mNoteFragment).commit();
    getFragmentManager().executePendingTransactions();

【讨论】:

  • 我不确定我是否同意。 OnCreate 可能很危险,但 onActivityCreated 是正确的地方,不是吗?我相信此时尚未调用 OnCreateView。你的答案可以更详细一点吗?似乎您在没有给出充分理由的情况下四处投票......
  • 我仍然在 onCreate 中得到空指针。在 setter 函数中保护它的例子是什么?
  • if(mEditText!=null){use mEditText } ...但它只会掩盖问题 onActivityCreated 也是一个好地方,这就是为什么我没有在我的 cmets 中使用它
  • @Selvin 仍然使用 onActivityCreated 获取 NPE。我同意您上面的解决方案会掩盖问题并避免错误,但它不能解决 mEditText 为空的问题。
  • 不,我不是要告诉你如何掩盖问题。我只是说,无论您在哪里编写代码,都针对空指针进行测试是一种很好的做法。与解决此问题无关。
【解决方案3】:

好的,所以我不确定为什么会这样,但我能够根据这个问题找出答案:

onCreateView() in Fragment is not called immediately, even after FragmentManager.executePendingTransactions()

我添加了以下行

getFragmentManager().executePendingTransactions();

然后将所有内容移至 onStart 方法

@Override
    protected void onStart() {
        super.onStart();

        //create a new note fragment if one has not been created yet
        mNoteFragment = (NoteFragment) getFragmentManager().findFragmentById(R.id.container);
        if (mNoteFragment == null) {
            mNoteFragment = new NoteFragment();
            getFragmentManager().beginTransaction().replace(R.id.container, mNoteFragment).commit();
            getFragmentManager().executePendingTransactions();
        }

        //restore SharedPreferences
        SharedPreferences sharedPrefs = getPreferences(0);
        int stylePref = sharedPrefs.getInt(SharedPreferanceConstants.PREF_FONT_SIZE, 2);
        String fontPref = sharedPrefs.getString(SharedPreferanceConstants.PREF_TYPEFACE, "");
        Log.d("STYLEID", String.valueOf(stylePref));
        Log.d("FONTTYPE", fontPref);
        onStyleChange(null , stylePref);
        onFontChange(null, fontPref);
    }

由于某种原因,它现在工作正常。如果有人能解释为什么那会很棒

【讨论】:

  • 仅在 NoteFragment 中的变化怎么样:gist.github.com/SelvinPL/e9549885632669c7a37f(没有 executePendingTransactions())
  • 我不确定我是否完全遵循?默认样式在 MainActivity 中设置,任何更改实际上都在单独的设置片段中处理
  • no ...所有代码都来自 NoteFragment ...如果 mEditText 不为 null,则它以通常的方式工作 ...如果未调用 onCreateView mEditText == null 则再次调用 setCustomFont onCreateView(完全提交的片段)和 SOMEDEFAULTSTYLE = 1 或 2 或 3
【解决方案4】:

尝试使用 'apply' 而不是 'commit' ,理由在这里 -> What's the difference between commit() and apply() in Shared Preference by Lukas Knuth。

但基本上,apply() 会立即将其更改提交到内存中的 SharedPreferences。

强烈建议使用一个类来管理您的偏好,例如“MyPreferencesManager”。

【讨论】:

  • 这应该是一个评论,因为没有你的回答和问题中的 NPE 就没有任何联系......
  • 我听说创建一个类是要走的路,所以我会继续使用它。这是我第一次尝试 SharedPreferences,所以我想首先让它像这样工作。感谢您的提示!
  • 它与一个 SP 而非 Fragment 管理器连接......看看 Flo 的答案......也许你想到的是 FM 而不是 SP......
猜你喜欢
  • 2012-08-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-01
  • 2017-04-11
  • 1970-01-01
  • 2013-12-27
  • 2023-03-09
相关资源
最近更新 更多