天天看點

Android源碼解析:AlertDialog和WindowManager源碼解析

(一)AlertDialog的實作模式

AlertDialog的實作模式使用了Builder模式,通過Builder對象來組裝Dialog的各個部分。AlertController用于存儲Builder成員變量中的各個參數。

AlertDialog可以設定title,message,button等參數,這些參數存儲在AlertController.AlertParams的成員變量P中,AlertController.AlertParams中包含了AlertDialog視圖中對應的成員變量。Builder類在調用create函數時會建立AlertDialog,并将P中的儲存的參數應用到AlertDialog的mAlert對象中。這個是通過P.apply函數來實作的。

在調用完create函數後,我們擷取了AlertDialog對象,就可以使用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);
        } else {
            // Fill the DecorView in on any configuration changes that
            // may have occured while it was removed from the WindowManager.
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        onStart();
        mDecor = mWindow.getDecorView();
       //省略代碼
        mWindowManager.addView(mDecor, l);
        mShowing = true;

        sendShowMessage();
    }
           

show函數執行了三個操作:

1.如果AlertDialog沒有被create,就通過dispatchOnCreate調用了AlertDialog的onCreate方法

2.調用AlertDialog的onStart方法

3.将DecorView添加到WindowManager中

我們看一下onCreate方法的實作。

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

然後我們看一下installContent()方法。

public void installContent() {
        final int contentView = selectContentView();
        mDialog.setContentView(contentView);
        setupView();
    }
           

這方法就實作了兩步,(1)設定AlertDialog的内容布局(2)初始化AlertDialog布局中的各個部分。setupView中使用了大量的findViewById來尋找相關的元件,代碼比較龐雜,對此我們就不一一看了。

(二)WindowManager

AlertDialog的視圖最後是将view傳遞給WindowManger來顯示的。事實上所有的要顯示到螢幕上的内容(包括Activity)都是通過WindowManager來進行操作的。

WindowManager是ContextImpl中注冊的衆多服務之一。各種系統服務會注冊到ContextImpl的一個map容器中,然後通過該服務的關鍵字來擷取。

通過getSystemService方法擷取到WindowManger對象後。我們該如何将Window對象和WindowManager建立聯系呢。在Dialog的構造函數中,我們可以看到這麼一行代碼。

w.setWindowManager(mWindowManager, null, null);
           

我們看一下setWindowManager方法的具體實作。

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        //省略代碼
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
           
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
           

WindowManagerImpl是WindowManagerGlobal的包裝。我們在上一層,通過調用WindowManager的addView方法請求系統将該View顯示到螢幕上,實際上這一切的實作都是通過WindowManagerGlobal的addView方法來實作的。

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //代碼省略
        
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }
           

這裡建構了ViewRootImpl,将布局參數設定給View,存儲ViewRootImpl,View,LayoutParam到清單中。然後通過ViewRootImpl的setView将View顯示到視窗上。

ViewRootImpl實作了native層和Java層View系統通信的橋梁。它會檢測系統中View的消息,并通過調用方法來繪制整棵視圖樹。我們看一下ViewRootImpl的建構方法。

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        //省略代碼
    }
           
public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
           
public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    sWindowManagerService = getWindowManagerService();
                    ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }
           

從上文代碼中可以看到,Android的架構層和native的wms是通過Binder機制進行通信的。架構層通過了Binder機制和Native建立了聯系,并通過了openSession與WMS建立通行會話。

WMS管理的是View的Z軸。

在與WMS建立完聯系後,我們會調用ViewRootImp的setView方法。該方法會向WMS發送顯示Dialog或者Activity中的DecorView的請求,具體代碼如下:

setView方法很複雜,但是主要邏輯是兩步:

(1)requestLayout

 (2)向WMS發送顯示目前Window的請求

requestLayout方法會向handler中發送一個DO_TRAVERSAL消息,這個消息會觸發整個視圖樹的繪制操作,簡而言之就是周遊整棵樹的各個View,執行了Measure,Layout,Draw方法。

總結:

Android源碼解析:AlertDialog和WindowManager源碼解析

架構層和native層通過binder來進行通信

繼續閱讀