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

android源碼解析(二十)--)Dialog取消繪制流程

編輯:關於Android編程

上幾篇文章中我們分析了Dialog的加載繪制流程,也分析了Acvityi的加載繪制流程,說白了Android系統中窗口的展示都是通過Window對象控制,通過ViewRootImpl對象執行繪制操作來完成的,那麼窗口的取消繪制流程是怎麼樣的呢?這篇文章就以Dialog為例說明Window窗口是如何取消繪制的。

有的同學可能會問前幾篇文章介紹Activity的加載繪制流程的時候為何沒有講Activity的窗口取消流程,這裡說明一下。那是因為當時說明的重點是Activity的加載與繪制流程,而取消繪制流程由於混雜在Activity的生命周期管理,可能不太明顯,所以這裡將Window窗口的取消繪制流程放在Dialog中,其實他們的取消繪制流程都是相似的,看完Dialog的取消繪制流程之後,再看一下Activity的取消繪制流程就很簡單了。

還記得我們上一篇文章關於Dialog的例子麼?我們通過AlertDialog.Builder創建了一個AlertDialog,並通過Activity中的按鈕點擊事件來顯示這個AlertDialog,而在AlertDialog中定義了一個“知道了”按鈕,點擊這個按鈕就會觸發alertDialog.cancel方法,通過執行這個方法,我們的alertDialog就不在顯示了,很明顯的,cancel方法執行過程中就執行了取消繪制的邏輯,這裡我們先看一下我們的例子核心代碼:

title.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this.getApplication());
                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.cannel();
                    }
                });
                alertDialog = builder.create();
                alertDialog.show();
            }
        });

這裡的title就是我們自己的Activity中的一個TextView,通過注冊這個TextView的點擊事件,來顯示一個AlertDialog,通過注冊AlertDialog中按鈕的點擊事件,執行alertDialog的cancel方法。

好吧,看一下Dialog的cannel方法的具體實現:

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

可以看到方法體中,若當前Dialog沒有取消,並且設置了取消message,則調用Message.obtain(mCancel).sendToTarget(),前面已經分析過這裡的sendToTarget方法會回調我們注冊的異步消息處理邏輯:

public void setOnCancelListener(final OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }

可以看到如果我們在初始化AlertDialog.Builder時,設置了setOnCancelListener,那麼我們就會執行mListenersHandler的異步消息處理,好吧,這裡看一下mListenersHandler的定義:

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

好吧,這裡調用的是設置的OnCancelListener的onCancel方法,也就是說我們調用dialog.cancel方法時首先會判斷dialog是否調用了setOnCancelListener若設置了,則先調用OnCancelListener的onCancel方法,然後再次執行dismiss方法,若我們沒有為Dialog.Builder設置OnCancelListener那麼cancel方法和dismiss方法是等效的。

這樣,我們來看一下dismiss方法的實現邏輯:

public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            mHandler.post(mDismissAction);
        }
    }

可以看到,這裡首先判斷當前線程的Looper是否是主線程的Looper(由於mHandler是在主線程中創建的,所以mHandler.getLooper返回的是主線程中創建的Looper對象),若是的話,則直接執行dismissDialog()方法,否則的話,通過mHandler發送異步消息至主線程中,簡單來說就是判斷當前線程是否是主線程,若是主線程則執行dismissDialog方法否則發送異步消息,我們看一下mHandler對異步消息的處理機制,由於這裡的mDismissAction是一個Runnable對象,所以這裡直接看一下mDismissAction的定義:

private final Runnable mDismissAction = new Runnable() {
        public void run() {
            dismissDialog();
        }
    };

好吧,這裡的異步消息最終也是調用的dismissDialog方法。。。。

所以無論我們執行的cancel方法還是dismiss方法,無論我們方法是在主線程執行還是子線程中執行,最終調用的都是dismissDialog方法,那麼就看一下dismissDialog是怎麼個執行邏輯。

void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }

好吧,看樣子代碼還不是特別多,方法體中,首先判斷當前的mDector是否為空,或者當前Dialog是否在顯示,若為空或者沒有在顯示,則直接return掉,也就是說當前我們的dialog已經不再顯示了,則我們不需要往下在執行。

然後我們調用了mWindow.isDestroyed()方法,判斷Window對象是否已經被銷毀,若已經被銷毀,則直接return,並打印錯誤日志。

然後我們調用了mWindowManager.removeViewImmediate(mDector),這裡的mDector是我們Dialog窗口的根布局,看這個方法的名字應該就是Dialog去除根布局的操作了,可以看一下這個方法的具體實現。前幾篇文章中我們已經分析過了這裡的mWindowManager其實是WindowManagerImpl的實例,所以這裡的removeViewImmediate方法應該是WindowManagerImpl中的方法,我們看一下它的具體實現:

@Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

可以發現,這裡它調用了mGlobal.removeView方法,而這裡的mGlobal是WindowManagerGlobal的實例,所以我們再看一下WIndowManagerGlobal中removeView的實現邏輯:

public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

可以發現,這裡在獲取了保存的mDector組件之後,又調用了removeViewLocked方法,我們在看一下這個方法的具體實現邏輯:

private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

看到了麼,我們獲取了mDector組件的ViewRootImpl,然後調用了其的die方法,通過這個方法實現Window組件的銷毀流程。

boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

可以看到在方法體中有調用了doDie方法,看名字應該就是真正執行window銷毀工作的方法了,我們在看一下doDie方法的具體實現:

void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

可以看到方法體中,首先調用了checkThread方法,介紹Activity的繪制流程的時候有過介紹,判斷當前執行代碼的線程,若不是主線程,則拋出異常:

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

我們順著doDie的方法往下看,又調用了dispatchDetachedFromWindow()方法,這個方法主要是銷毀Window中的各中成員變量,臨時變量等

void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }

     mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }

可以看到我們在方法中調用了mView.dispatchDetachedFromWindow方法,這個方法的作用就是將mView從Window中detach出來,我們可以看一下這個方法的具體實現:

void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
            }
        }

        onDetachedFromWindow();
        onDetachedFromWindowInternal();

        InputMethodManager imm = InputMethodManager.peekInstance();
        if (imm != null) {
            imm.onViewDetachedFromWindow(this);
        }

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }

        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
        }

        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }
    }

其中onDetachedFromWindow方法是一個空的回調方法,這裡我們重點看一下onDetachedFromWindowInternal方法:

protected void onDetachedFromWindowInternal() {
        mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;

        removeUnsetPressCallback();
        removeLongPressCallback();
        removePerformClickCallback();
        removeSendViewScrolledAccessibilityEventCallback();
        stopNestedScroll();

        // Anything that started animating right before detach should already
        // be in its final state when re-attached.
        jumpDrawablesToCurrentState();

        destroyDrawingCache();

        cleanupDraw();
        mCurrentAnimation = null;
    }

onDetachedFromWindowInternal方法的方法體也不是特別長,都是一些調用函數,這裡看一下destropDrawingCache方法,這個方法主要是銷毀View的緩存Drawing,我們來看一下具體實現:

public void destroyDrawingCache() {
        if (mDrawingCache != null) {
            mDrawingCache.recycle();
            mDrawingCache = null;
        }
        if (mUnscaledDrawingCache != null) {
            mUnscaledDrawingCache.recycle();
            mUnscaledDrawingCache = null;
        }
    }

這裡的mDrawingCache其實就是一個Bitmap類型的成員變量,而這裡調用的recycler和置空操作其實就是把View中執行draw方法之後緩存的bitmap清空。

這裡需要說明的是,我們View組件的最終顯示落實是通過draw方法實現繪制的,而我們的draw方法的參數是一個Canvas,這是一個畫布的對象,通過draw方法就是操作這個對象並顯示出來,而Canvas對象之所以能夠實現顯示的效果是因為其內部保存著一個Bitmap對象,通過操作Canvas對象實質上是操作Canvas對象內部的Bitmap對象,而View組件的顯示也就是通過這裡的Bitmap來實現的。

而我們上文中置空了bitmap對象就相當於把View組件的顯示效果置空了,就是相當於我們取消了View的draw方法的執行效果,繼續回到我們的dispatchDetachedFromWindow方法,在執行了mView.dispatchDetachedFromWindow()方法之後,又調用了mView = null;方法,這裡設置mView為空,這樣我們有取消了View的meature和layouot的執行效果。

這樣經過一系列的操作之後我們的Dialog的取消繪制流程就結束了,現在我們來看一下Activity的取消繪制流程。還記得我們“Activity的銷毀流程”麼?
當我們調用activity的finish方法的時候回調用ActivityThread的handleDestroyActivity方法,我們來看一下這個方法的實現:

private void handleDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance) {
        ...
        wm.removeViewImmediate(v);
        ...            
    }

可以看到這裡調用了這裡調用了wm.removeViewImmediate方法,這個方法不就是我們剛剛分析Dialog銷毀繪制流程的起始方法麼?以後的邏輯都是詳細的,這樣我們就實現了Activity的取消繪制流程。

總結:

窗口的取消繪制流程是相似的,包括Activity和Dialog等;

通過調用WindowManager.removeViewImmediate方法,開始執行Window窗口的取消繪制流程;

Window窗口的取消繪制流程,通過清空bitma撤銷draw的執行效果,通過置空View撤銷meature和layout的執行效果;

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