Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android源碼解析(十九)--)Dialog加載繪制流程

android源碼解析(十九)--)Dialog加載繪制流程

編輯:關於Android編程

前面兩篇文章,我們分析了Activity的布局文件加載、繪制流程,算是對整個Android系統中界面的顯示流程有了一個大概的了解,其實Android系統中所有的顯示控件(注意這裡是控件,而不是組件)的加載繪制流程都是類似的,包括:Dialog的加載繪制流程,PopupWindow的加載繪制流程,Toast的顯示原理等,上一篇文章中,我說在介紹了Activity界面的加載繪制流程之後,就會分析一下剩余幾個控件的顯示控制流程,這裡我打算先分析一下Dialog的加載繪制流程。

可能有的同學問這裡為什麼沒有Fragment?其實嚴格意義上來說Fragment並不是一個顯示控件,而只是一個顯示組件。為什麼這麼說呢?其實像我們的Activity,Dialog,PopupWindow以及Toast類的內部都管理維護著一個Window對象,這個Window對象不但是一個View組件的集合管理對象,它也實現了組件的加載與繪制流程,而我們的Fragment組件如果看過源碼的話,嚴格意義上來說,只是一個View組件的集合並通過控制變量實現了其特定的生命周期,但是其由於並沒有維護Window類型的成員變量,所以其不具備組件的加載與繪制功能,因此其不能單獨的被繪制出來,這也是我把它稱之為組件而不是控件的原因。(在分析完這幾個控件的加載繪制流程之後,有時間的話,也會分析一下Fragment的相關源碼)

好吧,開始我們今天關於Dialog的講解,相信大家在平時的開發過程中經常會使用到Dialog彈窗,使用Dialog可以在Activity彈出彈窗,確認消息等。為了更好的分析Dialog的源碼,我們這裡暫時寫一個簡單的demo,看一下Dialog的使用實例。

title.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setIcon(R.mipmap.ic_launcher);
                builder.setMessage("this is the content view!!!");
                builder.setTitle("this is the title view!!!");
                builder.setView(R.layout.activity_second);
                builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        alertDialog.dismiss();
                    }
                });
                alertDialog = builder.create();
                alertDialog.show();
            }
        });

我們在Activity中獲取一個textView組件,並監聽TextView的點擊事件,並在點擊事件中,初始化一個AlertDialog彈窗,並執行AlertDialog的show方法展示彈窗,在彈窗中定義一個按鈕,並監聽彈窗按鈕的點擊事件,若用戶點擊了彈窗的按鈕,則執行AlertDialog的dismiss方法,取消展示AlertDialog。好吧,我們來看一下這個彈窗彈出的展示結果:
這裡寫圖片描述
可以看到我們定義的icon,title,message和button都已經顯示出來了,這時候我們點擊彈窗按鈕知道了,這時候彈窗就會消失了。

一般我們使用Dialog的大概流程都是這樣的,可能定制Dialog的時候有一些定制化的操作,但是基本操作流程還是這樣的。

那麼我們先來看一下AlertDialog.Builder的構造方法,這裡的Builder是AlertDialog的內部類,用於封裝AlertDialog的構造過程,看一下Builder的構造方法:

public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }

好吧,這裡調用的是Builder的重載構造方法:

public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
        }

那麼這裡的P是AlertDialog.Builder中的一個AlertController.AlertParams類型的成員變量,可見在這裡執行了P的初始化操作。

public AlertParams(Context context) {
            mContext = context;
            mCancelable = true;
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

可以看到這裡主要執行了AlertController.AlertParams的初始化操作,初始化了一些成員變量。這樣執行了一系列操作之後我們的代碼:

AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);

就已經執行完成了,然後我們調用了builder.setIcon方法,這裡看一下setIcon方法的具體實現:

public Builder setIcon(@DrawableRes int iconId) {
            P.mIconId = iconId;
            return this;
        }

可以看到AlertDialog的Builder的setIcon方法,這裡執行的就是給類型為AlertController.AlertParams的P的mIconId賦值為傳遞的iconId,並且這個方法返回的類型就是Builder。

然後我們調用了builder.setMessage方法,可以看一下builder.setMessage方法的具體實現:

public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this;
        }

好吧,這裡跟setIcon方法的實現邏輯類似,都是給成員變量的mMessage賦值為我們傳遞的Message值,且和setIcon方法類似的,這個方法返回值也是Builder。

再看一下builder.setTitle方法:

public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

可以發現builder的setIcon、setMessage、setTitle等方法都是給Builder的成員變量P的icon,message,title賦值。

然後我們看一下builder.setView方法:

public Builder setView(int layoutResId) {
            P.mView = null;
            P.mViewLayoutResId = layoutResId;
            P.mViewSpacingSpecified = false;
            return this;
        }

可以發現這裡的setView和setIcon,setMessage,setTitle等方法都是類似的,都是將我們傳遞的數據值賦值給Builder的成員變量P。

然後我們調用了builder.setPositiveButton方法:

builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        alertDialog.dismiss();
                    }
                });

好吧,這裡我們看一下builder的setPositiveButton的源碼:

public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
            P.mPositiveButtonText = text;
            P.mPositiveButtonListener = listener;
            return this;
        }

好吧,可以發現跟上面幾個方法還是類似的,都是為Builder的成員變量P的相應成員變量賦值。。。

上面的幾行代碼我們都是調用的builder.setXXX等方法,主要就是為Builder的成員變量P的相應成員變量值賦值。並且setXX方法返回值都是Builder類型的,因此我們可以通過消息琏的方式連續執行:

builder.setIcon().setMessage().setTitle().setView().setPositiveButton()...

這樣代碼顯得比較簡潔,set方法的執行順序是沒有固定模式的,這裡多說一下,這種編程方式很優秀,平時我們在設計構造類工具類的時候也可以參考這種模式,構造類有不同的功能或者特性,並且都不是必須的,我們可以通過set方法設置不同的特性值並返回構造類本身。

然後我們調用了builder.create方法,並且這個方法返回了AlertDialog。

public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

可以看到這裡首先構造了一個AlertDialog,我們可以看一下這個構造方法的具體實現:

AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = new AlertController(getContext(), this, getWindow());
    }

可以看到這裡首先調用了super的構造方法,而我們的AlertDialog繼承於Dialog,所以這裡執行的就是Dialog的構造方法,好吧,繼續看一下Dialog的構造方法:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == 0) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

可以發現在Dialog的構造方法中直接直接構造了一個PhoneWindow,並賦值給Dialog的成員變量mWindow,從這裡可以看出其實Dialog和Activity的顯示邏輯都是類似的,都是通過對應的Window變量來實現窗口的加載與顯示的。然後我們執行了一些Window對象的初始化操作,比如設置回調函數為本身,然後調用了Window類的setWindowManager方法,並傳入了WindowManager,可以發現這裡的WindowManager對象是通過方法:

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

獲取的,而我們的context傳入的是Activity對象,所以這裡的WindowManager對象其實和Activity獲取的WindowManager對象是一致的。然後我們看一下window類的setWindowManager方法:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

可以看到跟Activity的Window對象的windowManager的獲取方式是相同的,都是通過new的方式創建一個新的WindowManagerImpl對象。好吧,繼續回到我們的AlertDialog的構造方法中,在構造方法中,我們除了調用Dialog的構造方法之外還執行了:

mAlert = new AlertController(getContext(), this, getWindow());

相當於初始化了AlertDiaog的成員變量mAlert。

繼續回到我們的AlertDialog.Builder.create方法,在創建了一個AlertDialog之後,又執行了P.apply(dialog.mAlert);
我們知道這裡的P是一個AlertController.AlertParams的變量,而dialog.mAlert是我們剛剛創建的AlertDialog中的一個AlertController類型的變量,我們來看一下apply方法的具體實現:

ublic void apply(AlertController dialog) {
            if (mCustomTitleView != null) {
                dialog.setCustomTitle(mCustomTitleView);
            } else {
                if (mTitle != null) {
                    dialog.setTitle(mTitle);
                }
                if (mIcon != null) {
                    dialog.setIcon(mIcon);
                }
                if (mIconId != 0) {
                    dialog.setIcon(mIconId);
                }
                if (mIconAttrId != 0) {
                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
                }
            }
            if (mMessage != null) {
                dialog.setMessage(mMessage);
            }
            if (mPositiveButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                        mPositiveButtonListener, null);
            }
            if (mNegativeButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                        mNegativeButtonListener, null);
            }
            if (mNeutralButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                        mNeutralButtonListener, null);
            }
            if (mForceInverseBackground) {
                dialog.setInverseBackgroundForced(true);
            }
            // For a list, the client can either supply an array of items or an
            // adapter or a cursor
            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
                createListView(dialog);
            }
            if (mView != null) {
                if (mViewSpacingSpecified) {
                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                            mViewSpacingBottom);
                } else {
                    dialog.setView(mView);
                }
            } else if (mViewLayoutResId != 0) {
                dialog.setView(mViewLayoutResId);
            }
        }

看到了麼?就是我們在初始化AlertDialog.Builder的時候設置的icon、title、message賦值給了AlertController.AlertParams,這裡就是將我們初始化時候設置的屬性值賦值給我們創建的Dialog對象的mAlert成員變量。。。。

繼續我們的AlertDialog.Builder.create方法,在執行了AlertController.AlertParams.apply方法之後又調用了:

dialog.setCancelable(P.mCancelable);

可以發現這個也是AertController.AlertParams的一個成員變量,我們在初始化AlertDialog.Builder的時候也可以通過設置builder.setCancelable賦值,由於該屬性為成員變量,所以默認值為false,而我們並沒有通過builder.setCancelable修改這個屬性值,所以這裡設置的dialog的cancelable的值為false。然後我們的create方法有設置了dialog的cancelListener和dismissListener並返回了我們創建的Dialog對象。這樣我們就獲取到了我們的Dialog對象,然後我們調用了dialog的show方法用於顯示dialog,好吧,這裡我們看一下show方法的具體實現:

public void show() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;

        if (!mCreated) {
            dispatchOnCreate(null);
        }

        onStart();
        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new WindowDecorActionBar(this);
        }

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;

            sendShowMessage();
        } finally {
        }
    }

方法體的內容比較多,我們慢慢看,由於一開始mShowing變量用於表示當前dialog是否正在顯示,由於我們剛剛開始調用執行show方法,所以這裡的mShowing變量的值為false,所以if分支的內容不會被執行,繼續往下看:

if (!mCreated) {
            dispatchOnCreate(null);
        }

mCreated這個控制變量控制dispatchOnCreate方法只被執行一次,由於我們是第一次執行,所以這裡會執行dispatchOnCreate方法,好吧,我們看一下dispatchOnCreate方法的執行邏輯:

void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            onCreate(savedInstanceState);
            mCreated = true;
        }
    }

好吧,可以看到代碼的執行邏輯很簡單就是回調了Dialog的onCreate方法,那麼onCreate方法內部又執行了那些邏輯呢?由於我們創建的是AlertDialog對象,該對象繼承於Dialog,所以我們這時候需要看一下AlertDialog的onCreate方法的執行邏輯:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }

可以看到這裡面除了調用super.onCreate方法之外就是調用了mAlert.installContent方法,而這裡的super.onCreate方法就是調用的Dialog的onCreate方法,Dialog的onCreate方法只是一個空的實現邏輯,所以我們具體來看一下mAlert.installContent的實現邏輯。

public void installContent() {
        /* We use a custom title so never request a window title */
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
        setupDecor();
    }

可以看到這裡實現Window窗口的頁面設置布局初始化等操作,這裡設置了mWindow對象為NO_TITLE,然後通過調用selectContentView設置Window對象的布局文件。

private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        // TODO: use layout hint side for long messages/lists
        return mAlertDialogLayout;
    }

可以看到這裡通過執行selectContentView方法返回布局文件的id值,這裡的默認值是mAlertDialogLayout。從這個方法開始我們就把指定布局文件的內容加載到內存中的Window對象中。我們這裡看一下具體的布局文件。

mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);

也就是R.layout.alert_dialog的布局文件,有興趣的同學可以看一下該布局文件的源碼,O(∩_∩)O哈哈~

繼續回到我們的installContent方法,在執行了mWindow.setContentView方法之後,又調用了setupView方法和setupDector方法,這兩個方法的主要作用就是初始化布局文件中的組件和Window對象中的mDector成員變量,這裡就不在詳細的說明。

然後回到我們的show方法,在執行了dispatchOnCreate方法之後我們又調用了onStart方法,這個方法主要用於設置ActionBar,這裡不做過多的說明,然後初始化WindowManager.LayoutParams對象,並最終調用我們的mWindowManager.addView()方法。

O(∩_∩)O哈哈~,到了這一步大家如果看了上一篇Acitivty布局繪制流程的話,就應該知道順著這個方法整個Dialog的界面就會被繪制出來了。

最後我們調用了sendShowMessage方法,可以看一下這個方法的實現:

private void sendShowMessage() {
        if (mShowMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mShowMessage).sendToTarget();
        }
    }

這裡會發送一個Dialog已經顯示的異步消息,該消息最終會在ListenersHandler中的handleMessage方法中被執行:

private static final class ListenersHandler extends Handler {
        private WeakReference mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

由於我們的msg.what = SHOW,所以會執行OnShowListener.onShow方法,那麼這個OnShowListener是何時賦值的呢?還記得我們構造AlertDialog.Builder麼?

alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
                    @Override
                    public void onShow(DialogInterface dialog) {

                    }
                });

這樣就為我們的AlertDialog.Builder設置了OnShowListener,可以看一下setOnShowListener方法的具體實現:

public void setOnShowListener(OnShowListener listener) {
        if (listener != null) {
            mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
        } else {
            mShowMessage = null;
        }
    }

這樣就為我們的Dialog中的mListenersHandler構造了Message對象,並且當我們在Dialog中發送showMessage的時候被mListenersHandler所接收。。。。

注:
這裡說一下我們平時開發中若創建的Dialog使用的Context對象不是Activity,就會報出:

Process: com.example.aaron.helloworld, PID: 11948                                                                         android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:690)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:282)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:298)
at com.example.aaron.helloworld.MainActivity$1.onClick(MainActivity.java:59)
at android.view.View.performClick(View.java:4811)
at android.view.View$PerformClick.run(View.java:20136)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5552)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)

的異常,這是由於WindowManager.addView方法最終會調用ViewRootImpl.setView方法,而這時候會有mToken的檢查,若我們傳入的Context對象不是Activity,這時候的mToken為空,就會出現上述問題。。。

總結:

Dialog和Activity的顯示邏輯是相似的都是內部管理這一個Window對象,用WIndow對象實現界面的加載與顯示邏輯;

Dialog中的Window對象與Activity中的Window對象是相似的,都對應著一個WindowManager對象;

Dialog相關的幾個類:Dialog,AlertDialog,AlertDialog.Builder,AlertController,AlertController.AlertParams,其中Dialog是窗口的父類,主要實現Window對象的初始化和一些共有邏輯,而AlertDialog是具體的Dialog的操作實現類,AlertDialog.Builder類是AlertDialog的內部類,主要用於構造AlertDialog,AlertController是AlertDialog的控制類,AlertController.AlertParams類是控制參數類;

構造顯示Dialog的一般流程,構造AlertDialog.Builder,然後設置各種屬性,最後調用AlertDialog.Builder.create方法獲取AlertDialog對象,並且create方法中會執行,構造AlertDialog,設置dialog各種屬性的操作。最後我們調用Dialog.show方法展示窗口,初始化Dialog的布局文件,Window對象等,然後執行mWindowManager.addView方法,開始執行繪制View的操作,並最終將Dialog顯示出來;

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved