DialogFragment你可能踩過或?qū)⒁鹊目?/h2>

前言

dialogfragment是google推出用來替換dialog的一種方案,相比較dialog,dialogfragment能更好的管理dialog的展示與消失,以及在屏幕旋轉(zhuǎn)時的一些狀態(tài)保存問題dialogfragment都會給你處理好,看過源碼其實都知道dialogfragment內(nèi)部就是通過dialog來對視圖進行管理。而且本質(zhì)上dialogfragment就是一個fragment,任何事情感覺和fragment扯上關(guān)系都會變得沒這么簡單,dialogfragment也不例外,文章主要來講下在使用dialogfragment過程中遇到的幾個比較坑的問題,以及解決方法。主要可分為三個

(1)dialogfragment展示時引起的崩潰問題
(2)內(nèi)存泄露問題
(3)加載含有fragment的view導致的崩潰

dialogfragment展示時引起的崩潰問題

這個問題比較簡單,就是在dialogfragment要展示的時候,如果按home鍵返回到了桌面,這時強行調(diào)用show方法就會導致app崩潰,拋出的異常大致如下

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

熟悉fragment應(yīng)該都知道,這是一個fragment導致的崩潰,可以通過調(diào)用commitAllowingStateLoss方法來避免崩潰。出現(xiàn)這種崩潰場景常見的比如app首頁進入后可能會彈出一些彈框廣告,這些彈框如果在網(wǎng)絡(luò)有一定延遲并且app返回到home后觸發(fā)彈框,如果app沒有做好處理就會導致崩潰,可以來看下dialogfragment相關(guān)的show方法

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

可以很明顯的看到,并沒有調(diào)用commitAllowingStateLoss,而是直接調(diào)用了commit方法。但是去翻看下dialogfragment的dismiss方法,你卻會發(fā)現(xiàn)

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

        }
    }

有相關(guān)調(diào)用commitAllowingStateLoss的方法,show的時候不允許commitAllowingStateLoss但是dismiss卻又允許commitAllowingStateLoss,不太理解google大佬這么做的意圖。既然問題根源找到了那么問題就好解決了,主要有兩個方法
(1)app代碼邏輯控制,在app返回home之后如果有相關(guān)的彈框就先屏蔽掉,等app重新展示再彈框。這個方法改動比較大,可能需要對彈框邏輯進行修改,不太推薦
(2)給dialogfragment增加調(diào)用commitAllowingStateLoss的方法,目前自己采用的方法,通過修改dialograment的show方法源碼即可,文末給出源碼

內(nèi)存泄露問題

這個應(yīng)該說是使用dialogfragment遇到的比較多的一個問題,產(chǎn)生泄露的原因和內(nèi)部使用的dialog有關(guān)以及一些三方庫線程中使用到message有關(guān),要把這個問題說明白首先看下和handler相關(guān)的message,這似乎回到了內(nèi)存泄露的經(jīng)典場景handler+message。但dialogfragment的內(nèi)存泄露比這個要復雜一些。

android系統(tǒng)為了提高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對象,最終執(zhí)行完畢后通過recycleUnchecked被回收回message池中,然后繼續(xù)next取出下一個要被處理的message對象,如果沒有要被執(zhí)行的message則next會被一直阻塞在這里。由于msg是一個局部變量,next被阻塞住會導致該局部變量無法被回收,但是最為關(guān)鍵的一點是,這個msg對象卻被放到了message池中,可被其他的線程使用到。這就會導致一旦該msg對象被其他線程使用到就可能導致msg成員變量一直持有某些引用對象最終引發(fā)內(nèi)存泄露問題。

而dialogfragment正好就踩到了這個雷上,dialogfragment內(nèi)部會使用到dialog來看下dialog內(nèi)部源碼實現(xiàn)

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對象被設(shè)置為了listener,listener對象實際上就是一個dialogfragment對象,而dialogfragment又和activity有著關(guān)聯(lián)。
個別三方庫會自己維護一個非ui線程的messagequeue,通過handleThread對消息進行處理,一旦三方庫處于空閑狀態(tài),被回收的message對象就被放入了消息池當中,如果此時正好觸發(fā)了上述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內(nèi)部源碼
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最后被消息隊列執(zhí)行完畢調(diào)用recycleUnchecked回收了也不影響dialog成員變量mDismissMessage上的結(jié)構(gòu)有任何改變。知道原因后解決起來心里就有數(shù)了,處理方式如下:

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相關(guān)的坑。我們的dialogfragment所展示的view是通過解析xml來獲取到的,一般而言這個xml中都是一些普通的view控件,但如果xml中包含了fragment節(jié)點那么問題就會稍微復雜一些。

比如如下場景,某個頁面需要彈出一個dialog來展示一些銀行卡信息。這些銀行卡信息通過一個個item來讓recyclerview進行展示,如圖


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

自己在開發(fā)中經(jīng)常遇到這種通過recyclerview來展示item的場景,無論是刷新操作還是加載更多都離不開那些通用的邏輯處理,所以封裝了一個fragment,給出需要展示的數(shù)據(jù)后該fragment就會自動幫你展示出來,避免了一些重復的邏輯判斷工作。其實google也封裝了一個類似的fragment叫ListFragment。

所以基于上述情況,我在dialogfragment需要展示的xml上使用了該fragment,導致的結(jié)果就是多次展示dialogfragment后引起了app崩潰問題。崩潰信息類似如下

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

造成這個問題的主要原因就是當dialogfragment被展示時,實際上總共生成了兩個fragment,而且都被添加到了同一個fragmentmanager之上,但是在remove的時候卻只移除了其中一個,導致再次展示dialogfragment時出現(xiàn)重復添加的異常。

這兩個fragment其中一個就是dialogfragment,而另一個就是xml中自己設(shè)置的fragment。dialogfragment本質(zhì)上就是一個fragment,這個看類結(jié)構(gòu)就明白

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時如果內(nèi)部有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的監(jiān)聽,在內(nèi)部處理邏輯

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嵌套問題時,一般內(nèi)部的fragment會被添加到外部fragment的childmanager上,如果是這種情況應(yīng)該不會存在上述dialogfragment崩潰的問題,可以看下fragment內(nèi)部源碼的處理,最終會調(diào)用到如下方法

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供內(nèi)部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");
            }
        }
    }

導致最終沒有調(diào)用到fragment中的生成childmanager的邏輯處理,這才是產(chǎn)生dialogfragment崩潰的主要原因。

結(jié)尾

文章到此就分析完了自己在開發(fā)過程中遇到的幾個關(guān)于dialogfragment問題,最后給出解決后的代碼,以下代碼是直接在dialogfragment的基礎(chǔ)上進行修改的,直接拷貝出dialogfragment源碼修改即可。完整的源碼如下,看著很多但實際上絕大部分都是dialogfragment本身的源碼,具體的改動點在上述文章都已經(jīng)說明不在重復講述

/**
 * 解決內(nèi)存泄露,彈框可能引起的崩潰問題
* @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);
            }
        }
    }
}

堅持寫文章不易,覺得有幫助的可以點個贊就是最大的支持

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容