【Android開發藝術探索】理解Window和WindowManager

来源:https://www.cnblogs.com/milovetingting/archive/2020/01/08/12166488.html
-Advertisement-
Play Games

個人博客: "http://www.milovetingting.cn" 理解Window和WindowManager Window和WindowManager WindowManager.LayoutParams 關註flags和type兩個參數: Flags參數表示Window的屬性,可以控制W ...


個人博客:
http://www.milovetingting.cn

理解Window和WindowManager

window_windowmanager_mind.png

Window表示一個視窗的概念,是一個抽象類,具體實現是PhoneWindow,可以通過WindowManager創建一個Window。WindowManager是外界訪問Window的入口,Window具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。

Window和WindowManager

WindowManager.LayoutParams

關註flags和type兩個參數:

Flags參數表示Window的屬性,可以控制Window的顯示特性。

** FLAG_NOT_FOCUSABLE **

表示Window不需要獲取焦點,也不需要接收各種輸入事件,此標記會同時啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的Window。

** FLAG_NOT_TOUCH_MODAL **

系統會將當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域以內的單擊事件自己處理。

** FLAG_SHOW_WHEN_LOCKED **

讓Window顯示在鎖屏的界面上。

Type參數表示Window類型

Window有三種類型:應用Window、子Window、系統Window。應用Window對應一個Activity。子Window不能單獨存在,需要附屬在特定的父Window中,如Dialog就是子Window。系統Window需要聲明許可權才能創建,如Toast和系統狀態欄就是系統Window。

Window是分層的,每個Window都有對應的z-ordered,層級大的覆蓋在層級小的Window上。應用Window的層級範圍是1-99,子Window的層級範圍是1000-1999,系統Window層級範圍是2000-2999。

WindowManager常用的三個方法:添加View、更新View和刪除View。這是從ViewManager實現過來的。

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

Window的內部機制

Window是一個抽象概念,每一個Window都對應一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯繫。

Window的添加過程

Window的添加過程需要通過WindowManager的addView來實現。WindowManager是一個介面,它的實現類是WindowManagerImpl。

public void addView(View view, LayoutParams params) {
        this.applyDefaultToken(params);
        this.mGlobal.addView(view, params, this.mContext.getDisplay(), this.mParentWindow);
    }

WindowManagerImpl並沒有直接實現addView,而是通過內部的WindowManagerGlobal實現的。

public void addView(View view, android.view.ViewGroup.LayoutParams params, Display display, Window parentWindow) {
        if (view == null) {
            //檢查view
            throw new IllegalArgumentException("view must not be null");
        } else if (display == null) {
            //檢查display
            throw new IllegalArgumentException("display must not be null");
        } else if (!(params instanceof LayoutParams)) {
            //檢查params
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        } else {
            LayoutParams wparams = (LayoutParams)params;
            if (parentWindow != null) {
                //調整子視窗的佈局參數
                parentWindow.adjustLayoutParamsForSubWindow(wparams);
            } else {
                Context context = view.getContext();
                if (context != null && (context.getApplicationInfo().flags & 536870912) != 0) {
                    wparams.flags |= 16777216;
                }
            }

            View panelParentView = null;
            int index;
            ViewRootImpl root;
            synchronized(this.mLock) {
                if (this.mSystemPropertyUpdater == null) {
                    this.mSystemPropertyUpdater = new Runnable() {
                        public void run() {
                            synchronized(WindowManagerGlobal.this.mLock) {
                                for(int i = WindowManagerGlobal.this.mRoots.size() - 1; i >= 0; --i) {
                                    ((ViewRootImpl)WindowManagerGlobal.this.mRoots.get(i)).loadSystemProperties();
                                }

                            }
                        }
                    };
                    SystemProperties.addChangeCallback(this.mSystemPropertyUpdater);
                }

                int index = this.findViewLocked(view, false);
                if (index >= 0) {
                    if (!this.mDyingViews.contains(view)) {
                        //不允許重覆添加視窗
                        throw new IllegalStateException("View " + view + " has already been added to the window manager.");
                    }

                    ((ViewRootImpl)this.mRoots.get(index)).doDie();
                }

                if (wparams.type >= 1000 && wparams.type <= 1999) {
                    index = this.mViews.size();

                    for(int i = 0; i < index; ++i) {
                        if (((ViewRootImpl)this.mRoots.get(i)).mWindow.asBinder() == wparams.token) {
                            panelParentView = (View)this.mViews.get(i);
                        }
                    }
                }

                //創建ViewRootImpl
                root = new ViewRootImpl(view.getContext(), display);
                //設置LayoutParams
                view.setLayoutParams(wparams);
                this.mViews.add(view);
                this.mRoots.add(root);
                this.mParams.add(wparams);
            }

            try {
                //ViewRootImpl添加view
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException var15) {
                synchronized(this.mLock) {
                    index = this.findViewLocked(view, false);
                    if (index >= 0) {
                        this.removeViewLocked(index, true);
                    }
                }

                throw var15;
            }
        }
    }

ViewRootImpl調用setView方法

public void setView(View view, LayoutParams attrs, View panelParentView) {
        synchronized(this) {
            if (this.mView == null) {
                //...

                this.mAdded = true;
                //1、調用requestLayout方法
                this.requestLayout();
                
                //...

                int res;
                try {
                    this.mOrigWindowType = this.mWindowAttributes.type;
                    this.mAttachInfo.mRecomputeGlobalAttributes = true;
                    this.collectViewAttributes();
                    //2、通過Session添加Window
                    res = this.mWindowSession.addToDisplay(this.mWindow, this.mSeq, this.mWindowAttributes, this.getHostVisibility(), this.mDisplay.getDisplayId(), this.mAttachInfo.mContentInsets, this.mAttachInfo.mStableInsets, this.mAttachInfo.mOutsets, this.mInputChannel);
                } catch (RemoteException var20) {
                    this.mAdded = false;
                    this.mView = null;
                    this.mAttachInfo.mRootView = null;
                    this.mInputChannel = null;
                    this.mFallbackEventHandler.setView((View)null);
                    this.unscheduleTraversals();
                    this.setAccessibilityFocus((View)null, (AccessibilityNodeInfo)null);
                    throw new RuntimeException("Adding window failed", var20);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }

                }

                //...
                if (res < 0) {
                    //添加Window失敗
                    this.mAttachInfo.mRootView = null;
                    this.mAdded = false;
                    this.mFallbackEventHandler.setView((View)null);
                    this.unscheduleTraversals();
                    this.setAccessibilityFocus((View)null, (AccessibilityNodeInfo)null);
                    switch(res) {
                    case -10:
                        throw new InvalidDisplayException("Unable to add window " + this.mWindow + " -- the specified window type " + this.mWindowAttributes.type + " is not valid");
                    case -9:
                        throw new InvalidDisplayException("Unable to add window " + this.mWindow + " -- the specified display can not be found");
                    case -8:
                        throw new BadTokenException("Unable to add window " + this.mWindow + " -- permission denied for window type " + this.mWindowAttributes.type);
                    case -7:
                        throw new BadTokenException("Unable to add window " + this.mWindow + " -- another window of type " + this.mWindowAttributes.type + " already exists");
                    case -6:
                        return;
                    case -5:
                        throw new BadTokenException("Unable to add window -- window " + this.mWindow + " has already been added");
                    case -4:
                        throw new BadTokenException("Unable to add window -- app for token " + attrs.token + " is exiting");
                    case -3:
                        throw new BadTokenException("Unable to add window -- token " + attrs.token + " is not for an application");
                    case -2:
                    case -1:
                        if (view.getContext().getPackageName().startsWith("com.google.android.gms")) {
                            try {
                                if (AppGlobals.getPackageManager().isFirstBoot()) {
                                    Log.d(this.mTag, "firstboot crash return");
                                    return;
                                }
                            } catch (RemoteException var22) {
                                var22.printStackTrace();
                                return;
                            }
                        }

                        throw new BadTokenException("Unable to add window -- token " + attrs.token + " is not valid; is your activity running?");
                    default:
                        throw new RuntimeException("Unable to add window -- unknown error code " + res);
                    }
                }

                //...

                //將view和ViewRootImpl關聯起來
                view.assignParent(this);
                
                //...
            }

        }
    }

1、requestLayout方法

public void requestLayout() {
        if (!this.mHandlingLayoutInLayoutRequest) {
            //A檢測線程
            this.checkThread();
            this.mLayoutRequested = true;
            //B開始View的繪製
            this.scheduleTraversals();
        }

    }

A:檢測線程,如果不是主線程,則報錯。

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

B:開始View的繪製

void scheduleTraversals() {
        if (!this.mTraversalScheduled) {
            this.mTraversalScheduled = true;
            this.mTraversalBarrier = this.mHandler.getLooper().getQueue().postSyncBarrier();
            //回調mTraversalRunnable
            this.mChoreographer.postCallback(2, this.mTraversalRunnable, (Object)null);
            if (!this.mUnbufferedInputDispatch) {
                this.scheduleConsumeBatchedInput();
            }

            this.notifyRendererOfFramePending();
            this.pokeDrawLockIfNeeded();
        }

    }

回調mTraversalRunnable

final class TraversalRunnable implements Runnable {
        TraversalRunnable() {
        }

        public void run() {
            //調用doTraversal
            ViewRootImpl.this.doTraversal();
        }
    }

調用doTraversal

void doTraversal() {
        if (this.mTraversalScheduled) {
            this.mTraversalScheduled = false;
            this.mHandler.getLooper().getQueue().removeSyncBarrier(this.mTraversalBarrier);
            if (this.mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            //調用performTraversals
            this.performTraversals();
            if (this.mProfile) {
                Debug.stopMethodTracing();
                this.mProfile = false;
            }
        }

    }

調用performTraversals方法,在performTraversals方法內會調用performMeasure()、PerformLayout()、PerformDraw(),並最終會調用view的measure()、layout()、draw()方法

回到ViewRootImpl的setView方法,在2處通過WindowSession調用addToDisplay()方法,WindowSession是一個Binder對象,最終的實現為Session類。Session中的addToDisplay方法:

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

addToDisplay方法通過調用WindowManagerService的addWindow方法。具體的addWindow方法,此處不再分析。

Window的刪除過程

Window的刪除過程和添加過程基本一樣,都是先通過WindowManagerImpl,再通過WindowManagerGlobal來實現的。

public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        } else {
            synchronized(this.mLock) {
                int index = this.findViewLocked(view, true);
                View curView = ((ViewRootImpl)this.mRoots.get(index)).getView();
                this.removeViewLocked(index, immediate);
                if (curView != view) {
                    throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView);
                }
            }
        }
    }

removeView方法中又調用removeViewLocked方法

private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = (ViewRootImpl)this.mRoots.get(index);
        View view = root.getView();
        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(((View)this.mViews.get(index)).getWindowToken());
            }
        }

        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent((ViewParent)null);
            if (deferred) {
                this.mDyingViews.add(view);
            }
        }

    }

在removeViewLocked方法里調用ViewRootImpl的die方法

boolean die(boolean immediate) {
        if (immediate && !this.mIsInTraversal) {
            this.doDie();
            return false;
        } else {
            if (!this.mIsDrawing) {
                this.destroyHardwareRenderer();
            } else {
                Log.e(this.mTag, "Attempting to destroy the window while drawing!\n  window=" + this + ", title=" + this.mWindowAttributes.getTitle());
            }

            this.mHandler.sendEmptyMessage(3);
            return true;
        }
    }

die方法中,如果不是立即移除,則通過Handler發送一個移除消息,如果是立即移除,則調用doDie方法

void doDie() {
    //檢查線程
        this.checkThread();
        synchronized(this) {
            if (this.mRemoved) {
                return;
            }

            this.mRemoved = true;
            if (this.mAdded) {
                this.dispatchDetachedFromWindow();
            }

            if (this.mAdded && !this.mFirst) {
                this.destroyHardwareRenderer();
                if (this.mView != null) {
                    int viewVisibility = this.mView.getVisibility();
                    boolean viewVisibilityChanged = this.mViewVisibility != viewVisibility;
                    if (this.mWindowAttributesChanged || viewVisibilityChanged) {
                        try {
                            if ((this.relayoutWindow(this.mWindowAttributes, viewVisibility, false) & 2) != 0) {
                                this.mWindowSession.finishDrawing(this.mWindow);
                            }
                        } catch (RemoteException var6) {
                        }
                    }

                    this.mSurface.release();
                }
            }

            this.mAdded = false;
        }

        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

真正移除Window是在dispatchDetachedFromWindow方法中實現的

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

        this.mAccessibilityInteractionConnectionManager.ensureNoConnection();
        this.mAccessibilityManager.removeAccessibilityStateChangeListener(this.mAccessibilityInteractionConnectionManager);
        this.mAccessibilityManager.removeHighTextContrastStateChangeListener(this.mHighContrastTextManager);
        this.removeSendWindowContentChangedCallback();
        this.destroyHardwareRenderer();
        this.setAccessibilityFocus((View)null, (AccessibilityNodeInfo)null);
        this.mView.assignParent((ViewParent)null);
        this.mView = null;
        this.mAttachInfo.mRootView = null;
        this.mSurface.release();
        if (this.mInputQueueCallback != null && this.mInputQueue != null) {
            this.mInputQueueCallback.onInputQueueDestroyed(this.mInputQueue);
            this.mInputQueue.dispose();
            this.mInputQueueCallback = null;
            this.mInputQueue = null;
        }

        if (this.mInputEventReceiver != null) {
            this.mInputEventReceiver.dispose();
            this.mInputEventReceiver = null;
        }

        try {
            this.mWindowSession.remove(this.mWindow);
        } catch (RemoteException var2) {
        }

        if (this.mInputChannel != null) {
            this.mInputChannel.dispose();
            this.mInputChannel = null;
        }

        this.mDisplayManager.unregisterDisplayListener(this.mDisplayListener);
        this.unscheduleTraversals();
    }

在dispatchDetachedFromWindow中,最終通過Session來移除Window。

移除Window後,調用WindowManagerGlobal的doRemoveView方法將之前列表中的記錄清除

void doRemoveView(ViewRootImpl root) {
        synchronized(this.mLock) {
            int index = this.mRoots.indexOf(root);
            if (index >= 0) {
                this.mRoots.remove(index);
                this.mParams.remove(index);
                View view = (View)this.mViews.remove(index);
                this.mDyingViews.remove(view);
            }
        }

        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            this.doTrimForeground();
        }

    }

Window的更新過程

Window的更新是通過WindowManagerGlobal的updateViewLayout方法來實現的

public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        } else if (!(params instanceof LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        } else {
            LayoutParams wparams = (LayoutParams)params;
            view.setLayoutParams(wparams);
            synchronized(this.mLock) {
                int index = this.findViewLocked(view, true);
                ViewRootImpl root = (ViewRootImpl)this.mRoots.get(index);
                this.mParams.remove(index);
                this.mParams.add(index, wparams);
                root.setLayoutParams(wparams, false);
            }
        }
    }

Window創建過程

Activity的Window創建過程

先說明Activity的創建過程。Activity是在ActivityThread的performLaunchActivity方法中創建的。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //...

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //創建activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                //創建window
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                //將activity和window關聯起來
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                //...

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

Activity的attach方法

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        //創建window
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        //...

        //設置WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
        enableAutofillCompatibilityIfNeeded();
    }

window創建完成後,在Activity的setContentView方法中,將View附加到Window中的DecorView上。

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

window的唯一實現類為PhoneWindow,因此查看PhoneWindow的setContentView方法

public void setContentView(int layoutResID) {
        if (this.mContentParent == null) {
            //如果沒有DecorView,則創建
            this.installDecor();
        } else if (!this.hasFeature(12)) {
            this.mContentParent.removeAllViews();
        }

        if (this.hasFeature(12)) {
            Scene newScene = Scene.getSceneForLayout(this.mContentParent, layoutResID, this.getContext());
            this.transitionTo(newScene);
        } else {
            this.mLayoutInflater.inflate(layoutResID, this.mContentParent);
        }

        this.mContentParent.requestApplyInsets();
        android.view.Window.Callback cb = this.getCallback();
        if (cb != null && !this.isDestroyed()) {
            cb.onContentChanged();
        }

        this.mContentParentExplicitlySet = true;
    }

View被添加到Window中的DecorView後,Window並沒有馬上被添加。在Activity的onResume方法中,通過調用makeVisible方法才被添加。

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

Dialog的Window創建過程

1、創建Window

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                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.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

2、初始化DecorView並將Dialog的視圖添加到DecorView中

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

3、將DecorView添加到Window

public void show() {
        //...

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

        sendShowMessage();
    }

普通Dialog必須採用Activity的Context。

Toast的Window創建過程

Toast也是基於Window來實現,但由於Toast具有定時取消功能,所以系統採用了Handler。

Toast的顯示

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

在show方法中,通過IPC調用NotificationManager的enqueueToast方法

        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }
            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
                            || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                                ? " due to package suspended by administrator."
                                : " by user request."));
                return;
            }

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index;
                    // All packages aside from the android package can enqueue one toast at a time
                    if (!isSystemToast) {
                        index = indexOfToastPackageLocked(pkg);
                    } else {
                        index = indexOfToastLocked(pkg, callback);
                    }

                    // If the package already has a toast, we update its toast
                    // in the queue, we don't move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                        record.update(callback);
                    } else {
                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                    }
                    keepProcessAliveIfNeededLocked(callingPid);
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

showNextToastLocked方法

    @GuardedBy("mToastQueue")
    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show(record.token);
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Object died trying to show notification " + record.callback
                        + " in package " + record.pkg);
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveIfNeededLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

通過record.callback,即Toast中的TN對象,調用show方法

        @Override
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }

通過Handler發送消息,然後在處理消息的邏輯中調用了handleShow

public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            // If a cancel/hide is pending - no need to show - at this point
            // the window token is already invalid and no need to do any work.
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                mParams.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                // Since the notification manager service cancels the token right
                // after it notifies us to cancel the toast there is an inherent
                // race and we may attempt to add a window after the token has been
                // invalidated. Let us hedge against that.
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }

在handleShow方法中,將View添加到了window。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...