Dialog原始碼學習

weixin_34290000發表於2016-08-09

恍然大悟,Java 萬物皆是物件的真諦,當然Android也不列外,其實我們在寫程式的時候也是在給寫每一個物件; 所以我們在Android Studio中所看到的Java原始碼也是一個個物件的封裝體;

一時如蒙雷擊,我們看原始碼也是如此;

  1. 帶著問題進入
  2. 如果是我寫我該如何寫?
  3. 每一個方法是如何呼叫?

即然如此我們便能無需太多的教學文件,就能駕馭Android中的基本用法。還能學到一些設計模式和一些寫程式碼的技巧;

這是我第一次看試著把自己看的東西寫成部落格。寫得不好請觀者見諒;

Dialog繼承結構

634008-7f4c67fb31783ed6.png
UML建模圖

建立
建立 - 構造方法 - 重點

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

        // 構建顯示時所用的Window;
        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);
    }

解說一下重要的程式碼

 // 1.獲取Window管理器
 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 //2.建立一個 Windows 來供 Dialog 掛載佈局
 final Window w = new PhoneWindow(mContext);
// 3.設定Window和 視窗管理器關聯
w.setWindowManager(mWindowManager, null, null);
// 4.
mListenersHandler = new ListenersHandler(this);

只要簡單的3步就完成了Dialog和佈局所顯示的關聯。
WindowManager是用來幹什麼的呢?
其實和我們的Activity一樣,我們需要寫XML佈局,然後通過LayoutInflater轉化為View設定給Acvitity是一樣的。我們都需要一個顯示的視窗;在Android裡面就叫window;

簡單來說Window是抽象類,具體實現是PhoneWindow,通過WindowManager就可以建立Window。WindowManager是外界訪問Window的入口,但是Window的具體實現是在WindowManagerService中,WindowManager和WindowManagerService的互動是一個IPC過程。所有的檢視例如Activity、Dialog、Toast都是附加在Window上的。

Window w = new PhoneWindow(mContext);
具體關係如下圖:

634008-8c733b75778f3918.jpg
Window圖

需要深入瞭解Window可以看老羅文章:
Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析

顯示

在寫碼的時候發現一個問題,我自己繼承寫了一個dialog

 MyDialog dialog = new MyDialog(context, R.style.default_dialog_style);

為什麼不會呼叫生命週期中的OnCreate()方法?一直以為在構建的時候就應該建立。後來才發現自己理解錯誤,要呼叫show()方法才會顯示。如下

 public void show() {

        // 1.判斷是否顯示
   
     ··· 部分程式碼
    
        mCanceled = false;  
        // 2.呼叫OnCreate(); 
        if (!mCreated) {
            dispatchOnCreate(null);
        }
        // 3.啟動
        onStart();
       // 4. 獲取到Window跟佈局
        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);
        }

        ··· 部分程式碼 -- 設定佈局引數
        try {
         // 5. 新增 佈局到 佈局管理器 中 
            mWindowManager.addView(mDecor, l);
            mShowing = true;
            // 呼叫 show()示監聽回撥
            sendShowMessage();
        } finally {
        }
    }

可以看到第2條中如果如果 mCreated=false才會回撥dispatchOnCreate()繼續深入:

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

// 空實現
 protected void onCreate(Bundle savedInstanceState) {
    }

由上程式碼可以看到onCreate是空實現,我們在自己定義Dialog時複寫onCreate()才會有實際的作用;

接著看OnStart(); 也無太多實際作用。設定顯示mActionBar動畫;

    /**
     * Called when the dialog is starting.
     */
    protected void onStart() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
    }

那麼我們是在哪裡設定佈局的? 如下;佈局是設定在mWindow中的;

    public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

一般我們是複寫在OnCreat()中。或者直接dialog.setContentView(R.layout.layout_view)來設定佈局;
接著把拿到mDecor設定個佈局管理器中。就是原始碼中的第5條。那麼我們寫得佈局就顯示在我們的程式上面了。由於我們直接載入window。因而我們可以直接顯示在所以佈局的頂部;

關閉對話方塊

所有對話方塊都實現了一個介面 DialogInterface

public interface DialogInterface{

... 省略 ...

    public void cancel();
    public void dismiss();
    
... 省略 ...

}

Dialog中還有一個方法hide();所以讓一個對話方塊消失有三種方法;我們來看看有什麼不同:

hide() ---- 只是讓佈局看不到,但是沒有關閉它;並沒有移除螢幕

    public void hide() {
        if (mDecor != null) {
            mDecor.setVisibility(View.GONE);
        }
    }

dismiss

 @Override
    public void dismiss() {
        // 判斷是否在同一執行緒 
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            //不在同一執行緒。傳送關閉的mHandler來關閉對話方塊
            mHandler.post(mDismissAction);
        }
    }

// 銷燬對話方塊
 void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            return;
        }

        // 1. 移除 WindowManger 中 Decor
        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            //2. 銷燬必要元件, 
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            //3. 回撥 onStop方法
            onStop();
            mShowing = false;

            // 4. 傳送 銷燬 通知的監聽 最後介紹:
            sendDismissMessage();
        }
    }

注意:官方文件上同時提到了一點注意事項:如果你要進行一些清理工作的話,不要在重寫dismiss函式,而應該在onStop函式中進行這些清理工作

cancel 和 dismiss 區別

這樣看下來。除了hide()和其他兩個方法有點區別,其他兩個好像就沒有太大區別了:先說結論吧:
Hide()只是隱藏對話方塊;dismiss()就是關閉並結束對話方塊的方法;
cancel()如果沒有設定setOnCancelListener()才會和dismiss()有所不同;
Dialog內部有這樣一段程式碼


// 設定開啟關閉監聽
    private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(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;
            }
        }
    }

如上程式碼所理解,如果你在使用Dialog設定了相應的回撥監聽,會回撥相應的監聽方法;
我們在來看看cancel方法:

    public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mCancelMessage).sendToTarget();
        }
        //回撥dismis方法; 
        dismiss();
    }

如果你設定了回撥監聽

  public void setOnCancelListener(final OnCancelListener listener)

上面可以得出的結論cancel()包含dismiss()cancel()會傳送關閉的訊息通知ListenersHandler去回撥相應的監聽;

Dialog的原始碼大概就是如此了,其實仔細回想下來還是非常簡單的,就是對原始碼的包裝,window的載入而已。難的是載入和顯示的過程。這些我就不去深入研究了。
下一篇準備些Dialog用到的設計模式;裡面包含messagehandle的用法,所以還是有許多可以學習的。

推薦:

一個不錯Dialog開源專案

相關文章