美文网首页Android
DialogFragment你可能踩过或将要踩的坑

DialogFragment你可能踩过或将要踩的坑

作者: mandypig | 来源:发表于2022-06-03 21:42 被阅读0次

    前言

    dialogfragment是google推出用来替换dialog的一种方案,相比较dialog,dialogfragment能更好的管理dialog的展示与消失,以及在屏幕旋转时的一些状态保存问题dialogfragment都会给你处理好,看过源码其实都知道dialogfragment内部就是通过dialog来对视图进行管理。而且本质上dialogfragment就是一个fragment,任何事情感觉和fragment扯上关系都会变得没这么简单,dialogfragment也不例外,文章主要来讲下在使用dialogfragment过程中遇到的几个比较坑的问题,以及解决方法。主要可分为三个

    (1)dialogfragment展示时引起的崩溃问题
    (2)内存泄露问题
    (3)加载含有fragment的view导致的崩溃

    dialogfragment展示时引起的崩溃问题

    这个问题比较简单,就是在dialogfragment要展示的时候,如果按home键返回到了桌面,这时强行调用show方法就会导致app崩溃,抛出的异常大致如下

    java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    

    熟悉fragment应该都知道,这是一个fragment导致的崩溃,可以通过调用commitAllowingStateLoss方法来避免崩溃。出现这种崩溃场景常见的比如app首页进入后可能会弹出一些弹框广告,这些弹框如果在网络有一定延迟并且app返回到home后触发弹框,如果app没有做好处理就会导致崩溃,可以来看下dialogfragment相关的show方法

    public void show(FragmentManager manager, String tag) {
            this.mDismissed = false;
            this.mShownByMe = true;
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        }
    

    可以很明显的看到,并没有调用commitAllowingStateLoss,而是直接调用了commit方法。但是去翻看下dialogfragment的dismiss方法,你却会发现

    void dismissInternal(boolean allowStateLoss) {
            if (!this.mDismissed) {
                this.mDismissed = true;
                this.mShownByMe = false;
                if (this.mDialog != null) {
                    this.mDialog.dismiss();
                }
    
                this.mViewDestroyed = true;
                if (this.mBackStackId >= 0) {
                    this.getFragmentManager().popBackStack(this.mBackStackId, 1);
                    this.mBackStackId = -1;
                } else {
                    FragmentTransaction ft = this.getFragmentManager().beginTransaction();
                    ft.remove(this);
                    if (allowStateLoss) {
                        ft.commitAllowingStateLoss();
                    } else {
                        ft.commit();
                    }
                }
    
            }
        }
    

    有相关调用commitAllowingStateLoss的方法,show的时候不允许commitAllowingStateLoss但是dismiss却又允许commitAllowingStateLoss,不太理解google大佬这么做的意图。既然问题根源找到了那么问题就好解决了,主要有两个方法
    (1)app代码逻辑控制,在app返回home之后如果有相关的弹框就先屏蔽掉,等app重新展示再弹框。这个方法改动比较大,可能需要对弹框逻辑进行修改,不太推荐
    (2)给dialogfragment增加调用commitAllowingStateLoss的方法,目前自己采用的方法,通过修改dialograment的show方法源码即可,文末给出源码

    内存泄露问题

    这个应该说是使用dialogfragment遇到的比较多的一个问题,产生泄露的原因和内部使用的dialog有关以及一些三方库线程中使用到message有关,要把这个问题说明白首先看下和handler相关的message,这似乎回到了内存泄露的经典场景handler+message。但dialogfragment的内存泄露比这个要复杂一些。

    android系统为了提高message的复用,使用到了一个message池来管理那些被回收的message对象,可以通过Message的obtain方法从message池中获取到message,而message最终会被放到messagequeue中等待被处理,源码如下

    for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                ...
    
                msg.recycleUnchecked();
            }
    

    通过next获取到message对象,最终执行完毕后通过recycleUnchecked被回收回message池中,然后继续next取出下一个要被处理的message对象,如果没有要被执行的message则next会被一直阻塞在这里。由于msg是一个局部变量,next被阻塞住会导致该局部变量无法被回收,但是最为关键的一点是,这个msg对象却被放到了message池中,可被其他的线程使用到。这就会导致一旦该msg对象被其他线程使用到就可能导致msg成员变量一直持有某些引用对象最终引发内存泄露问题。

    而dialogfragment正好就踩到了这个雷上,dialogfragment内部会使用到dialog来看下dialog内部源码实现

    public void setOnDismissListener(@Nullable OnDismissListener listener) {
            if (mCancelAndDismissTaken != null) {
                throw new IllegalStateException(
                        "OnDismissListener is already taken by "
                        + mCancelAndDismissTaken + " and can not be replaced.");
            }
            if (listener != null) {
                mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
            } else {
                mDismissMessage = null;
            }
        }
    

    obtainMessage从message池中获取到了一个message,并且到message的obj对象被设置为了listener,listener对象实际上就是一个dialogfragment对象,而dialogfragment又和activity有着关联。
    个别三方库会自己维护一个非ui线程的messagequeue,通过handleThread对消息进行处理,一旦三方库处于空闲状态,被回收的message对象就被放入了消息池当中,如果此时正好触发了上述setOnDismissListener,此时message对象就会存在两个地方引用,一个是dialog的mDismissMessage变量,另一个则是三方库的局部变量message,再来看下dialog中是如何使用mDismissMessage的

    private void sendDismissMessage() {
            if (mDismissMessage != null) {
                // Obtain a new message so this dialog can be re-used
                Message.obtain(mDismissMessage).sendToTarget();
            }
        }
    
    message内部源码
    public static Message obtain(Message orig) {
            Message m = obtain();
            m.what = orig.what;
            m.arg1 = orig.arg1;
            m.arg2 = orig.arg2;
            m.obj = orig.obj;
            m.replyTo = orig.replyTo;
            m.sendingUid = orig.sendingUid;
            if (orig.data != null) {
                m.data = new Bundle(orig.data);
            }
            m.target = orig.target;
            m.callback = orig.callback;
    
            return m;
        }
    

    obtain(Message orig)是直接拷贝了一份message出来,所以即使该message最后被消息队列执行完毕调用recycleUnchecked回收了也不影响dialog成员变量mDismissMessage上的结构有任何改变。知道原因后解决起来心里就有数了,处理方式如下:

    private static class DialogDismissListener extends WeakReference<FixDialogFragment> implements DialogInterface.OnDismissListener {
    
            private DialogDismissListener(FixDialogFragment referent) {
                super(referent);
            }
    
            @Override
            public void onDismiss(DialogInterface dialog) {
                FixDialogFragment dialogFragment = get();
                if (dialogFragment != null) {
                    dialogFragment.onDismissDialog(dialog);
                }
            }
        }
    

    将message的what对象最终指向一个弱引用对象

    mDialog.setOnDismissListener(new DialogDismissListener(this));
    

    同理CancelListener处理过程类似。上述解决方法需要修改dialogfragment源码进行解决。文末给出完整代码

    加载含有fragment的view导致的崩溃

    这又是一个和fragment相关的坑。我们的dialogfragment所展示的view是通过解析xml来获取到的,一般而言这个xml中都是一些普通的view控件,但如果xml中包含了fragment节点那么问题就会稍微复杂一些。

    比如如下场景,某个页面需要弹出一个dialog来展示一些银行卡信息。这些银行卡信息通过一个个item来让recyclerview进行展示,如图


    {C11650A9-AA87-4292-8125-37CB2F0CE32D}_20200610225133.jpg

    自己在开发中经常遇到这种通过recyclerview来展示item的场景,无论是刷新操作还是加载更多都离不开那些通用的逻辑处理,所以封装了一个fragment,给出需要展示的数据后该fragment就会自动帮你展示出来,避免了一些重复的逻辑判断工作。其实google也封装了一个类似的fragment叫ListFragment。

    所以基于上述情况,我在dialogfragment需要展示的xml上使用了该fragment,导致的结果就是多次展示dialogfragment后引起了app崩溃问题。崩溃信息类似如下

     Caused by: java.lang.IllegalArgumentException: Binary XML file line #30: Duplicate id 0x7f080047
    

    造成这个问题的主要原因就是当dialogfragment被展示时,实际上总共生成了两个fragment,而且都被添加到了同一个fragmentmanager之上,但是在remove的时候却只移除了其中一个,导致再次展示dialogfragment时出现重复添加的异常。

    这两个fragment其中一个就是dialogfragment,而另一个就是xml中自己设置的fragment。dialogfragment本质上就是一个fragment,这个看类结构就明白

    public class DialogFragment extends Fragment implements OnCancelListener, OnDismissListener 
    

    dialogfragment的show源码如下

    public void show(FragmentManager manager, String tag) {
            this.mDismissed = false;
            this.mShownByMe = true;
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        }
    

    manager对象就是通过Activity获取到的,当dialogfragment去解析xml时如果内部有fragment,也会将该fragment添加到该fragmentmanager上,但是看下dialogfragment的dismiss源码

    void dismissInternal(boolean allowStateLoss) {
            if (!this.mDismissed) {
                this.mDismissed = true;
                this.mShownByMe = false;
                if (this.mDialog != null) {
                    this.mDialog.dismiss();
                }
    
                this.mViewDestroyed = true;
                if (this.mBackStackId >= 0) {
                    this.getFragmentManager().popBackStack(this.mBackStackId, 1);
                    this.mBackStackId = -1;
                } else {
                    FragmentTransaction ft = this.getFragmentManager().beginTransaction();
                    ft.remove(this);
                    if (allowStateLoss) {
                        ft.commitAllowingStateLoss();
                    } else {
                        ft.commit();
                    }
                }
    
            }
        }
    

    很明显,它只会remove掉dialogfragment,而xml中的fragment会被一直保留在manager之上。明白崩溃原因之后解决方法也就简单了,在dialogfragment被dismiss的时候remove掉那个xml中的fragment即可,dialogfragment提供了dismiss的监听,在内部处理逻辑

    FragmentActivity context = (FragmentActivity) getContext();
            if (context != null) {
                FragmentManager manager = context.getSupportFragmentManager();
                Fragment fragment = manager.findFragmentById(XXX);
                if (fragment != null) {
                    FragmentTransaction transaction = manager.beginTransaction();
                    transaction.remove(fragment);
                    transaction.commitAllowingStateLoss();
                }
            }
    

    实际上当存在fragment嵌套问题时,一般内部的fragment会被添加到外部fragment的childmanager上,如果是这种情况应该不会存在上述dialogfragment崩溃的问题,可以看下fragment内部源码的处理,最终会调用到如下方法

    public LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState) {
            if (this.mHost == null) {
                throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the Fragment is attached to the FragmentManager.");
            } else {
                LayoutInflater result = this.mHost.onGetLayoutInflater();
                this.getChildFragmentManager();
                LayoutInflaterCompat.setFactory2(result, this.mChildFragmentManager.getLayoutInflaterFactory());
                return result;
            }
        }
    

    getChildFragmentManager会生成一个childmanager供内部fragment'使用,但是dialogfragment却复写了onGetLayoutInflater方法

    @NonNull
        public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
            if (!this.mShowsDialog) {
                return super.onGetLayoutInflater(savedInstanceState);
            } else {
                this.mDialog = this.onCreateDialog(savedInstanceState);
                if (this.mDialog != null) {
                    this.setupDialog(this.mDialog, this.mStyle);
                    return (LayoutInflater)this.mDialog.getContext().getSystemService("layout_inflater");
                } else {
                    return (LayoutInflater)this.mHost.getContext().getSystemService("layout_inflater");
                }
            }
        }
    

    导致最终没有调用到fragment中的生成childmanager的逻辑处理,这才是产生dialogfragment崩溃的主要原因。

    结尾

    文章到此就分析完了自己在开发过程中遇到的几个关于dialogfragment问题,最后给出解决后的代码,以下代码是直接在dialogfragment的基础上进行修改的,直接拷贝出dialogfragment源码修改即可。完整的源码如下,看着很多但实际上绝大部分都是dialogfragment本身的源码,具体的改动点在上述文章都已经说明不在重复讲述

    /**
     * 解决内存泄露,弹框可能引起的崩溃问题
    * @author stupid pig mandy
     * */
    public class FixDialogFragment extends Fragment
            implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
    
        /** @hide */
        @RestrictTo(LIBRARY_GROUP)
        @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
        @Retention(RetentionPolicy.SOURCE)
        private @interface DialogStyle {}
    
        /**
         * Style for {@link #setStyle(int, int)}: a basic,
         * normal dialog.
         */
        public static final int STYLE_NORMAL = 0;
    
        /**
         * Style for {@link #setStyle(int, int)}: don't include
         * a title area.
         */
        public static final int STYLE_NO_TITLE = 1;
    
        /**
         * Style for {@link #setStyle(int, int)}: don't draw
         * any frame at all; the view hierarchy returned by {@link #onCreateView}
         * is entirely responsible for drawing the dialog.
         */
        public static final int STYLE_NO_FRAME = 2;
    
        /**
         * Style for {@link #setStyle(int, int)}: like
         * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
         * The user can not touch it, and its window will not receive input focus.
         */
        public static final int STYLE_NO_INPUT = 3;
    
        private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
        private static final String SAVED_STYLE = "android:style";
        private static final String SAVED_THEME = "android:theme";
        private static final String SAVED_CANCELABLE = "android:cancelable";
        private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
        private static final String SAVED_BACK_STACK_ID = "android:backStackId";
    
        int mStyle = STYLE_NORMAL;
        int mTheme = 0;
        boolean mCancelable = true;
        boolean mShowsDialog = true;
        int mBackStackId = -1;
    
        Dialog mDialog;
        boolean mViewDestroyed;
        boolean mDismissed;
        boolean mShownByMe;
    
        public FixDialogFragment() {
        }
    
        /**
         * Call to customize the basic appearance and behavior of the
         * fragment's dialog.  This can be used for some common dialog behaviors,
         * taking care of selecting flags, theme, and other options for you.  The
         * same effect can be achieve by manually setting Dialog and Window
         * attributes yourself.  Calling this after the fragment's Dialog is
         * created will have no effect.
         *
         * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
         * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
         * {@link #STYLE_NO_INPUT}.
         * @param theme Optional custom theme.  If 0, an appropriate theme (based
         * on the style) will be selected for you.
         */
        public void setStyle(@DialogStyle int style, @StyleRes int theme) {
            mStyle = style;
            if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
                mTheme = android.R.style.Theme_Panel;
            }
            if (theme != 0) {
                mTheme = theme;
            }
        }
    
        /**
         * Display the dialog, adding the fragment to the given FragmentManager.  This
         * is a convenience for explicitly creating a transaction, adding the
         * fragment to it with the given tag, and {@link FragmentTransaction#commit() committing} it.
         * This does <em>not</em> add the transaction to the fragment back stack.  When the fragment
         * is dismissed, a new transaction will be executed to remove it from
         * the activity.
         * @param manager The FragmentManager this fragment will be added to.
         * @param tag The tag for this fragment, as per
         * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
         */
        public void show(FragmentManager manager, String tag) {
            mDismissed = false;
            mShownByMe = true;
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commitAllowingStateLoss();
        }
    
        /**
         * Display the dialog, adding the fragment using an existing transaction
         * and then {@link FragmentTransaction#commit() committing} the transaction.
         * @param transaction An existing transaction in which to add the fragment.
         * @param tag The tag for this fragment, as per
         * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
         * @return Returns the identifier of the committed transaction, as per
         * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
         */
        public int show(FragmentTransaction transaction, String tag) {
            mDismissed = false;
            mShownByMe = true;
            transaction.add(this, tag);
            mViewDestroyed = false;
            mBackStackId = transaction.commit();
            return mBackStackId;
        }
    
        /**
         * Display the dialog, immediately adding the fragment to the given FragmentManager.  This
         * is a convenience for explicitly creating a transaction, adding the
         * fragment to it with the given tag, and calling {@link FragmentTransaction#commitNow()}.
         * This does <em>not</em> add the transaction to the fragment back stack.  When the fragment
         * is dismissed, a new transaction will be executed to remove it from
         * the activity.
         * @param manager The FragmentManager this fragment will be added to.
         * @param tag The tag for this fragment, as per
         * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
         */
        public void showNow(FragmentManager manager, String tag) {
            mDismissed = false;
            mShownByMe = true;
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commitNow();
        }
    
        /**
         * Dismiss the fragment and its dialog.  If the fragment was added to the
         * back stack, all back stack state up to and including this entry will
         * be popped.  Otherwise, a new transaction will be committed to remove
         * the fragment.
         */
        public void dismiss() {
            dismissInternal(false);
        }
    
        /**
         * Version of {@link #dismiss()} that uses
         * {@link FragmentTransaction#commitAllowingStateLoss()
         * FragmentTransaction.commitAllowingStateLoss()}. See linked
         * documentation for further details.
         */
        public void dismissAllowingStateLoss() {
            dismissInternal(true);
        }
    
        void dismissInternal(boolean allowStateLoss) {
            if (mDismissed) {
                return;
            }
            mDismissed = true;
            mShownByMe = false;
            if (mDialog != null) {
                mDialog.dismiss();
            }
            mViewDestroyed = true;
            if (mBackStackId >= 0) {
                getFragmentManager().popBackStack(mBackStackId,
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                mBackStackId = -1;
            } else {
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.remove(this);
                if (allowStateLoss) {
                    ft.commitAllowingStateLoss();
                } else {
                    ft.commit();
                }
            }
        }
    
        public Dialog getDialog() {
            return mDialog;
        }
    
        @StyleRes
        public int getTheme() {
            return mTheme;
        }
    
        /**
         * Control whether the shown Dialog is cancelable.  Use this instead of
         * directly calling {@link Dialog#setCancelable(boolean)
         * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
         * its behavior based on this.
         *
         * @param cancelable If true, the dialog is cancelable.  The default
         * is true.
         */
        public void setCancelable(boolean cancelable) {
            mCancelable = cancelable;
            if (mDialog != null) mDialog.setCancelable(cancelable);
        }
    
        /**
         * Return the current value of {@link #setCancelable(boolean)}.
         */
        public boolean isCancelable() {
            return mCancelable;
        }
    
        /**
         * Controls whether this fragment should be shown in a dialog.  If not
         * set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
         * and the fragment's view hierarchy will thus not be added to it.  This
         * allows you to instead use it as a normal fragment (embedded inside of
         * its activity).
         *
         * <p>This is normally set for you based on whether the fragment is
         * associated with a container view ID passed to
         * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
         * If the fragment was added with a container, setShowsDialog will be
         * initialized to false; otherwise, it will be true.
         *
         * @param showsDialog If true, the fragment will be displayed in a Dialog.
         * If false, no Dialog will be created and the fragment's view hierarchy
         * left undisturbed.
         */
        public void setShowsDialog(boolean showsDialog) {
            mShowsDialog = showsDialog;
        }
    
        /**
         * Return the current value of {@link #setShowsDialog(boolean)}.
         */
        public boolean getShowsDialog() {
            return mShowsDialog;
        }
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            if (!mShownByMe) {
                // If not explicitly shown through our API, take this as an
                // indication that the dialog is no longer dismissed.
                mDismissed = false;
            }
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            if (!mShownByMe && !mDismissed) {
                // The fragment was not shown by a direct call here, it is not
                // dismissed, and now it is being detached...  well, okay, thou
                // art now dismissed.  Have fun.
                mDismissed = true;
            }
        }
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mShowsDialog = mContainerId == 0;
    
            if (savedInstanceState != null) {
                mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
                mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
                mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
                mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
                mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
            }
        }
    
        @Override
        public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
            if (!mShowsDialog) {
                return super.onGetLayoutInflater(savedInstanceState);
            }
    
            mDialog = onCreateDialog(savedInstanceState);
    
            if (mDialog != null) {
                setupDialog(mDialog, mStyle);
    
                return (LayoutInflater) mDialog.getContext().getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
            }
            return (LayoutInflater) mHost.getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
        }
    
        /** @hide */
        @RestrictTo(LIBRARY_GROUP)
        public void setupDialog(Dialog dialog, int style) {
            switch (style) {
                case STYLE_NO_INPUT:
                    dialog.getWindow().addFlags(
                            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                    // fall through...
                case STYLE_NO_FRAME:
                case STYLE_NO_TITLE:
                    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            }
        }
    
        /**
         * Override to build your own custom Dialog container.  This is typically
         * used to show an AlertDialog instead of a generic Dialog; when doing so,
         * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need
         * to be implemented since the AlertDialog takes care of its own content.
         *
         * <p>This method will be called after {@link #onCreate(Bundle)} and
         * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.  The
         * default implementation simply instantiates and returns a {@link Dialog}
         * class.
         *
         * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
         * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
         * Dialog.setOnDismissListener} callbacks.  You must not set them yourself.</em>
         * To find out about these events, override {@link #onCancel(DialogInterface)}
         * and {@link #onDismiss(DialogInterface)}.</p>
         *
         * @param savedInstanceState The last saved instance state of the Fragment,
         * or null if this is a freshly created Fragment.
         *
         * @return Return a new Dialog instance to be displayed by the Fragment.
         */
        @NonNull
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new Dialog(getActivity(), getTheme());
        }
    
        @Override
        public void onCancel(DialogInterface dialog) {
        }
    
        @Override
        public void onDismiss(DialogInterface dialog) {
            if (!mViewDestroyed) {
                // Note: we need to use allowStateLoss, because the dialog
                // dispatches this asynchronously so we can receive the call
                // after the activity is paused.  Worst case, when the user comes
                // back to the activity they see the dialog again.
                dismissInternal(true);
            }
        }
    
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
    
            if (!mShowsDialog) {
                return;
            }
    
            View view = getView();
            if (view != null) {
                if (view.getParent() != null) {
                    throw new IllegalStateException(
                            "DialogFragment can not be attached to a container view");
                }
                mDialog.setContentView(view);
            }
            final Activity activity = getActivity();
            if (activity != null) {
                mDialog.setOwnerActivity(activity);
            }
            mDialog.setCancelable(mCancelable);
            mDialog.setOnCancelListener(new DialogCancelListener(this));
            mDialog.setOnDismissListener(new DialogDismissListener(this));
            if (savedInstanceState != null) {
                Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
                if (dialogState != null) {
                    mDialog.onRestoreInstanceState(dialogState);
                }
            }
        }
    
        @Override
        public void onStart() {
            super.onStart();
    
            if (mDialog != null) {
                mViewDestroyed = false;
                mDialog.show();
            }
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            if (mDialog != null) {
                Bundle dialogState = mDialog.onSaveInstanceState();
                if (dialogState != null) {
                    outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
                }
            }
            if (mStyle != STYLE_NORMAL) {
                outState.putInt(SAVED_STYLE, mStyle);
            }
            if (mTheme != 0) {
                outState.putInt(SAVED_THEME, mTheme);
            }
            if (!mCancelable) {
                outState.putBoolean(SAVED_CANCELABLE, mCancelable);
            }
            if (!mShowsDialog) {
                outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
            }
            if (mBackStackId != -1) {
                outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
            }
        }
    
        @CallSuper
        protected void onDismissDialog(DialogInterface dialog) {
            onDismiss(dialog);
        }
    
        protected void onCancelDialog(DialogInterface dialog) {
        }
    
        @Override
        public void onStop() {
            super.onStop();
            if (mDialog != null) {
                mDialog.hide();
            }
        }
    
        /**
         * Remove dialog.
         */
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            if (mDialog != null) {
                // Set removed here because this dismissal is just to hide
                // the dialog -- we don't want this to cause the fragment to
                // actually be removed.
                mViewDestroyed = true;
                mDialog.dismiss();
                mDialog = null;
            }
        }
    
        private static class DialogDismissListener extends WeakReference<FixDialogFragment> implements DialogInterface.OnDismissListener {
    
            private DialogDismissListener(FixDialogFragment referent) {
                super(referent);
            }
    
            @Override
            public void onDismiss(DialogInterface dialog) {
                FixDialogFragment dialogFragment = get();
                if (dialogFragment != null) {
                    dialogFragment.onDismissDialog(dialog);
                }
            }
        }
    
        private static class DialogCancelListener extends WeakReference<FixDialogFragment> implements DialogInterface.OnCancelListener {
    
            private DialogCancelListener(FixDialogFragment referent) {
                super(referent);
            }
    
            @Override
            public void onCancel(DialogInterface dialog) {
                FixDialogFragment dialogFragment = get();
                if (dialogFragment != null) {
                    dialogFragment.onCancelDialog(dialog);
                }
            }
        }
    }
    

    坚持写文章不易,觉得有帮助的可以点个赞就是最大的支持

    相关文章

      网友评论

        本文标题:DialogFragment你可能踩过或将要踩的坑

        本文链接:https://www.haomeiwen.com/subject/rdmiprtx.html