Android:为啥 DialogFragment 在方向更改时返回空指针

Posted

技术标签:

【中文标题】Android:为啥 DialogFragment 在方向更改时返回空指针【英文标题】:Android: Why DialogFragment return nullpointer on orientation changeAndroid:为什么 DialogFragment 在方向更改时返回空指针 【发布时间】:2013-03-23 09:34:54 【问题描述】:

在更改方向后,我遇到了正确对话框片段解散的问题。我猜这是由于旧的上下文,因为在重新创建该活动之后,创建了一个新的活动和相应的上下文。我知道我可以在 onSaveInstance 中设置一些变量来保存对话框状态并在必要时重新创建对话框,或者只是将一个属性放在清单“方向”中。但是,也许有更好的方法可以做到不在清单中硬编码它而不是手动保存在 onSaveIntance 中?我也尝试在主片段和对话框片段中使用 setRetainInstance 但它对我没有帮助。

片段:

public class MainFragment extends Fragment implements ServiceExecutorListener, OnClickListener, DialogClickListener 

private static final String TAG = MainFragment.class.getName();

private TextView serviceStatus;
Intent intent;
Boolean bound = false;
ServiceConnection sConn;
RESTService service;
ProgressDialogFragment pd;
private Button btnSend, btnCheck;

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    setRetainInstance(true);
    intent = new Intent(getActivity(), RESTService.class);
    getActivity().startService(intent);
    sConn = new ServiceConnection() 

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) 
            Log.d(TAG, "MainFragment onServiceConnected");
            service = ((RESTService.MyBinder) binder).getService();
            service.registerListener(MainFragment.this);
            if (service.tasksAreDone())
                serviceStatus.setText(service.getResult());
            bound = true;
        

        public void onServiceDisconnected(ComponentName name) 
            Log.d(TAG, "MainFragment onServiceDisconnected");
            bound = false;
        

    ;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    View rootView = inflater.inflate(R.layout.main_fragment, container, false);
    serviceStatus = (TextView) rootView.findViewById(R.id.tvServiceStatusValue);
    btnSend = (Button) rootView.findViewById(R.id.btnSend);
    btnCheck = (Button) rootView.findViewById(R.id.btnCheck);

    btnSend.setOnClickListener(this);
    btnCheck.setOnClickListener(this);
    return rootView;


@Override
public void onClick(View v) 
    switch (v.getId()) 
        case R.id.btnSend:
            pd = new ProgressDialogFragment();
            pd.newInstance(null);
            pd.setDialogClickListener(this);
            pd.show(getActivity().getSupportFragmentManager(), "ProgressDialog");
            service.run(RESTService.REQUEST, 7);
            service.run(RESTService.REQUEST, 2);
            service.run(RESTService.REQUEST, 4);
            break;
        case R.id.btnCheck:
            if (service != null)
                serviceStatus.setText(String.valueOf(service.tasksAreDone()) + service.getTasksCount());
            break;
    


@Override
public void onStart() 
    super.onStart();
    Log.d(TAG, "onStart: Bind service");
    getActivity().bindService(intent, sConn, 0);


@Override
public void onPause() 
    super.onPause();
    Log.d(TAG, "onPause: Unbind service");
    if (!bound)
        return;
    getActivity().unbindService(sConn);
    service.unregisterListener();
    bound = false;


@Override
public void onComplete(int taskID, int action, String result) 
    Log.d(TAG, "Task #" + taskID + ", action = " + action + " Completed");
    pd.dismiss();
    serviceStatus.setText(result);


@Override
public void onDialogClick(int action) 
    switch (action) 
        case Dialog.BUTTON_POSITIVE:
            Log.d(TAG, "POSITIVE BUTTON");
            break;
        case Dialog.BUTTON_NEGATIVE:
            Log.d(TAG, "NEGATIVE BUTTON");
            service.removeTasks();
            break;
        case Dialog.BUTTON_NEUTRAL:
            Log.d(TAG, "NEUTRAL BUTTON");
            break;
    


@Override
public void onDestroy() 
    super.onDestroy();
    Log.d(TAG, "onDestroy");


对话框:

public class ProgressDialogFragment extends DialogFragment implements OnClickListener 

final String TAG = ProgressDialogFragment.class.getName();
private DialogClickListener listener;

public ProgressDialogFragment newInstance(Bundle args) 
    ProgressDialogFragment pdf = new ProgressDialogFragment();
    pdf.setArguments(args);
    return pdf;


public void setDialogClickListener(DialogClickListener listener) 
    this.listener = listener;


@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
    Log.d(TAG, "onCreate");


public Dialog onCreateDialog(Bundle savedInstanceState) 
    AlertDialog.Builder adb = new AlertDialog.Builder(getActivity())
            .setTitle("Title!")
            .setPositiveButton(R.string.yes, this)
            .setNegativeButton(R.string.no, this)
            .setNeutralButton(R.string.maybe, this)
            .setCancelable(false)
            .setMessage(R.string.message_text)
            .setOnKeyListener(new OnKeyListener() 
                @Override
                public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) 
                    return true;
                
            );
    return adb.create();


public void onClick(DialogInterface dialog, int which) 
    if (listener != null)
        listener.onDialogClick(which);


public void onDismiss(DialogInterface dialog) 
    Log.d(TAG, "Dialog: onDismiss, dialog = " + getDialog() + ", retainInstance = " + getRetainInstance());
    // Fix to avoid simple dialog dismiss in orientation change
    if ((getDialog() != null) && getRetainInstance())  
        getDialog().setDismissMessage(null);



public void onCancel(DialogInterface dialog) 
    super.onCancel(dialog);
    Log.d(TAG, "Dialog: onCancel");

LOgCat:

04-29 06:17:17.860: E/androidRuntime(4202): FATAL EXCEPTION: main
04-29 06:17:17.860: E/AndroidRuntime(4202): java.lang.NullPointerException
04-29 06:17:17.860: E/AndroidRuntime(4202):     at android.support.v4.app.DialogFragment.dismissInternal(DialogFragment.java:184)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at android.support.v4.app.DialogFragment.dismiss(DialogFragment.java:155)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at com.example.restservice.fragments.MainFragment.onComplete(MainFragment.java:108)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at com.example.restservice.service.RESTService$1.run(RESTService.java:79)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at android.os.Handler.handleCallback(Handler.java:605)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at android.os.Handler.dispatchMessage(Handler.java:92)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at android.os.Looper.loop(Looper.java:137)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at android.app.ActivityThread.main(ActivityThread.java:4514)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at java.lang.reflect.Method.invokeNative(Native Method)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at java.lang.reflect.Method.invoke(Method.java:511)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
04-29 06:17:17.860: E/AndroidRuntime(4202):     at dalvik.system.NativeStart.main(Native Method)

编辑:

如果我将 setRetainInstance(true) 放在调用 dialogFragment 的主片段和 onCreate 方法的 DialogFragment 中,那么我看到 getRetainInstance 返回 true 并且 getDialog 在方向更改过程中具有对象(否则为 NPE)。在这种情况下,我也没有 NPE,但会出现以下奇怪的行为:创建并呈现对话框,重新创建对话框(方向更改)并关闭(为什么?),重新创建对话框(再次更改方向)并呈现(wtf?上次它被解除了)等等,即对话在一侧被解除,但请记住,它应该在另一侧呈现。那是什么?

【问题讨论】:

【参考方案1】:

onCreateDialog(Bundle savedInstanceState) 中删除setRetainInstance(true); 并将其保存在 onCreate(Bundle savedInstance) 中,如下所示:

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setRetainInstance(true);

【讨论】:

【参考方案2】:

已找到修复程序。也许,它已经写在谷歌社区的修复之一中,这有助于避免简单地忽略方向变化,我想念它。

我还不知道。就我而言,我在DialogFragmentonCreate 方法中设置了setRetainInstance(true)

通过简单的修复实现了onDismiss 方法,我的最后一个小问题是我没有在 onDismiss 方法中删除super.onDestroyView()。现在它就像一个魅力。

【讨论】:

以上是关于Android:为啥 DialogFragment 在方向更改时返回空指针的主要内容,如果未能解决你的问题,请参考以下文章

为啥要使用 DialogFragment?

Android:DialogFragment.dismissInternal 处 DialogFragment.dismissAllow 处的 NullPointerException

Android 撸起袖子,自己封装 DialogFragment

Android 撸起袖子,自己封装 DialogFragment

Android中的全屏DialogFragment

主题不适用于 Android 上的 DialogFragment