Android源码分析之理解Window和WindowManager

2022-06-22 11:40:16 浏览数 (1)

Window和WindowManager概述

Window是一个抽象类,它的具体实现是PhoneWindow,创建一个Window通过WindowManager 就可以完成。WindowManager是外界访问Window的入口,它的具体实现在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC的过程。Android中所有的视图都是通过Window来呈现的,无论是Activity,Dialog还是Toast,Window实际上是View的直接管理者。单击时间由Window传递给DecorView,然后DecorView再传递给我们的View,就连Activity设置视图的方法setContentView底层也是通过Window来完成的。

WindowManager继承于ViewManager:

代码语言:javascript复制
public interface WindowManager extends ViewManager

ViewManager中有3个抽象的方法:

代码语言:javascript复制
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

从这里我们可以得出,WindowManager操作Window的过程实际是在操作Window中的View。

Window的内部机制

Window的添加过程 从Window的源码中可以看出,他本身是一个接口,所以它的逻辑肯定都是由实现类去做的,而这个类就是WindowManagerImpl

代码语言:javascript复制
public class WindowManagerImpl implements WindowManager {

    private final DisplayMetrics mMetrics;
    private final Display mDisplay;

    public WindowManagerImpl(DisplayMetrics metrics) {
        mMetrics = metrics;

        DisplayInfo info = new DisplayInfo();
        info.logicalHeight = mMetrics.heightPixels;
        info.logicalWidth = mMetrics.widthPixels;
        mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info,
                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
    }

    @Override
    public Display getDefaultDisplay() {
        return mDisplay;
    }


    @Override
    public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
        // pass
    }

    @Override
    public void removeView(View arg0) {
        // pass
    }

    @Override
    public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) {
        // pass
    }


    @Override
    public void removeViewImmediate(View arg0) {
        // pass
    }
}

从WindowManagerImpl的源码中,我们会发现它并未实现3大方法,而是交给WindowManagerGlobal来实现,WindowManagerGlobal以工厂的型式对外提供自己的实例

代码语言:javascript复制
public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
}

WindowManagerImpl的工作模式是典型的桥接模式,所有操作全部委托WindowManagerGlobal来实现 分析WindowManagerGlobal的源码,发现它的addView方法主要做了以下几步: 1.检查参数是否合法,如果是子Window那么还需要调整一些布局参数

代码语言:javascript复制
1.parentWindow.adjustLayoutParamsForSubWindow(wparams);

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }

2.创建ViewRootImpl并将View添加到列表中 WindowManagerGlobal内部有如下几个列表比较重要,从名字也可以猜到,他们分别用于存储Window对应的View,ViewRootImpl,WindowManager.LayoutParams

代码语言:javascript复制
private View[] mViews;
    private ViewRootImpl[] mRoots;
private WindowManager.LayoutParams[] mParams;

addView方法中会有如下的一段,进行添加的操作

代码语言:javascript复制
index--;

            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;

3.通过ViewRootImpl来更新界面并完成Window的添加过程,这个步骤是由ViewRootImpl的setView方法完成的,从下面这段代码可以看出来这个结论

代码语言:javascript复制
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;
        }

View的绘制是通过ViewRootImpl来完成的,这里也不例外,在setView内部,会通过requestLayout()方法来完成异步刷新的请求

代码语言:javascript复制
// Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();

requestLayout()方法内部的逻辑:

代码语言:javascript复制
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

scheduleTraversals()是View 的绘制入口

代码语言:javascript复制
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            scheduleConsumeBatchedInput();
        }
}

setView方法内最后有这么一段代码,说明了Window的添加过程最终是由WindowSession来完成的,mWindowSession的类型是IWindowSession,说明这是一个Binder对象,真正的实现类是Session(这个你又知道?),也就是说Window的添加过程是一次IPC调用

代码语言:javascript复制
try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

查看Session内的addToDisplay方法

代码语言:javascript复制
  @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);
}

发现最后是通过mService来添加Window的,而这个mService就是WindowManagerService,所以最后是通过WindowManagerService来完成的,WindowManagerService内部会为每一个应用保留一个Session

Window的删除过程 WindowManagerImpl的工作模式是典型的桥接模式,所有操作全部委托WindowManagerGlobal来实现,所以还是看WindowManagerGlobal的源码中removeView方法

代码语言:javascript复制
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 = removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

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

里面的逻辑,就是通过findViewLocked找到待移除的View的索引,里面的就是一个数组遍历的逻辑

代码语言:javascript复制
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 = removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

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

然后通过removeViewLocked方法,将View进行移除

代码语言:javascript复制
private View removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots[index];
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews[index].getWindowToken());
            }
        }
        root.die(immediate);

        final int count = mViews.length;

        // remove it from the list
        View[] tmpViews = new View[count-1];
        removeItem(tmpViews, mViews, index);
        mViews = tmpViews;

        ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1];
        removeItem(tmpRoots, mRoots, index);
        mRoots = tmpRoots;

        WindowManager.LayoutParams[] tmpParams
                = new WindowManager.LayoutParams[count-1];
        removeItem(tmpParams, mParams, index);
        mParams = tmpParams;

        if (view != null) {
            view.assignParent(null);
            // func doesn't allow null...  does it matter if we clear them?
            //view.setLayoutParams(null);
        }
        return view;
    }

removeViewLocked方法中,其实是通过ViewRootImpl的die方法来进行删除的,WindowManager中提供了removeViewImmediate和removeView(该方法继承自ViewManager)两种接口方法,分别表示同步删除和异步删除removeViewImmediate一般都不使用这个方法来删除Window

ViewRootImpl的die方法代码:

代码语言:javascript复制
public void 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();
        } else {
            if (!mIsDrawing) {
                destroyHardwareRenderer();
            } else {
                Log.e(TAG, "Attempting to destroy the window while drawing!n"  
                        "  window="   this   ", title="   mWindowAttributes.getTitle());
            }
            mHandler.sendEmptyMessage(MSG_DIE);
        }
}

die方法中要一份为2,如果是同步操作,直接调用doDie()方法,如果是异步操作,只是使用mHandler.sendEmptyMessage(MSG_DIE),这个时候View并未完成删除的工作

代码语言:javascript复制
  case MSG_DIE:
                doDie();
                break;

接下来是调用了doDie()方法

代码语言:javascript复制
void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in "   this   " of "   mSurface);
        synchronized (this) {
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                invalidateDisplayLists();
                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;
        }
}

doDie()方法中会调用dispatchDetachedFromWindow()方法,在这个方法内实现View的移除工作,主要做了四件事:垃圾回收,通过Session的remove方法删除Window,调用View的dispatchDetachedFromWindow方法同时会回调View的onDetachedFromWindow以及onDetachedFromWindowInternal,调用WindowManagerGlobal的doRemoveView刷新数据。

Window的更新过程

Window的更新过程是通过WindowManagerGlobal的updateViewLayout来实现的

代码语言:javascript复制
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots[index];
            mParams[index] = wparams;
            root.setLayoutParams(wparams, false);
        }
}

内部逻辑是:首先更新view的LayoutParams,替换掉老的LayoutParams,然后是更新ViewRootImpl的LayoutParams,这里是通过root.setLayoutParams(wparams, false);方法实现

代码语言:javascript复制
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            int oldSoftInputMode = mWindowAttributes.softInputMode;
            // Keep track of the actual window flags supplied by the client.
            mClientWindowLayoutFlags = attrs.flags;
            // preserve compatible window flag if exists.
            int compatibleWindowFlag =
                mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
            // transfer over system UI visibility values as they carry current state.
            attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
            attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
            mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
            if (mWindowAttributes.packageName == null) {
                mWindowAttributes.packageName = mBasePackageName;
            }
            mWindowAttributes.flags |= compatibleWindowFlag;

            applyKeepScreenOnFlag(mWindowAttributes);

            if (newView) {
                mSoftInputMode = attrs.softInputMode;
                requestLayout();
            }
            // Don't lose the mode we last auto-computed.
            if ((attrs.softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                    == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
                        & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                        | (oldSoftInputMode
                                & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
            }
            mWindowAttributesChanged = true;
            scheduleTraversals();
        }
    }

里面最后调用scheduleTraversals()

Window的创建过程

Activity的Window创建过程 在Activity的attach方法里,系统会创建Activity所属的Window,并为其设置回调,下面是attach方法中部分代码

代码语言:javascript复制
mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

Activity所属的Window的具体实现是PhoneWindow,Activity视图是通过setContentView方法附属在Window上面的

代码语言:javascript复制
public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
}

这里是通过getWindow()来处理,getWindow()返回的是Window对象

代码语言:javascript复制
public Window getWindow() {
        return mWindow;
}

Window的具体实现是PhoneWindow,所以只需要查看PhoneWindow的setContentView方法,该方法主要做了以下内容: 1)如果没有DecorView,就通过installDecor()方法创建DecorView,installDecor()部分代码

代码语言:javascript复制
 private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }

了解到DecorView是通过generateDecor()方法产生的,这时候的DecorView只是一个空白的FrameLayout

代码语言:javascript复制
protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
}

所以要查询DecorView在哪里进行初始化的,在PhoneWindow的generateLayout方法中发现DecorView的足迹,然后在generateLayout方法中发现如下一段代码

代码语言:javascript复制
mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

这里mLayoutInflater生成的布局就被添加到DecorView中了

2)将View 添加到DecorView的mContentParent中 回到PhoneWindow的setContentView方法中的mContentParent.addView(view, params);可以验证我们的推断

3)回到Activity的onContentChanged方法通知Activity视图已经发生了改变

代码语言:javascript复制
final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }

这里是调用了Callback的onContentChanged,由于Callback就是Activity本身,所以就是Activity的onContentChanged方法,但里面是个空实现,所以应该是在子类中进行完成

代码语言:javascript复制
 public void onContentChanged() {
    }

经过上面的3步,DecorView初始化完毕,Activity的布局文件也被添加到DecorView的mContentParent中,但DecorView还没有被WindowManager添加到Window中,在ActivityThread的handleResumeActivity方法中,会调用Activity的onResume方法,然后调用Activity的makeVisible方法,在这个方法中DecorView才真正完成了添加和显示

代码语言:javascript复制
void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

Dialog的Window创建过程 1)创建Window Dialog的构造函数中的部分内容如下:

代码语言:javascript复制
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);

2)初始化DecorView并将Dialog的视图添加到DecorView中 这里同样调用的是Window的setContentView

代码语言:javascript复制
  public void setContentView(View view) {
        mWindow.setContentView(view);
}

3)将DecorView添加到Window中并显示 在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中

代码语言:javascript复制
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 {
        }
}

Dialog的Window创建过程和Activity的Window创建差不多,但是在Dialog被关闭时,它会通过WindowManager移除DecorView,这个逻辑在Dialog的dismissDialog()方法中

代码语言:javascript复制
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();
        }
    }

注意:普通的Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,那么会报错

报错是没有应用的token导致的,而应用的token一般只有Activity才有,但是,系统Window比较特殊,它可以不需要token

Toast的Window创建过程 1.Toast是基于Window来实现的,系统中继承了handler做定时处理 2.内部实现IPC过程,第一类访NotificationManagerService(NMS) 3.通过回调TN接口,实现Toast的显示隐藏 4.Toast属于Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法 来指定一个自定义View

代码语言:javascript复制
/**
     * Show the view for the specified duration.
     */
    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
        }
    }

    /**
     * Close the view if it's showing, or don't show it if it isn't showing yet.
     * You do not normally have to call this.  Normally view will disappear on its own
     * after the appropriate duration.
     */
    public void cancel() {
        mTN.hide();

        try {
            getService().cancelToast(mContext.getPackageName(), mTN);
        } catch (RemoteException e) {
            // Empty
        }
}

无论是显示还是隐藏都是通过NotificationManagerService来实现的,查看它的源码中的enqueueToast方法,其中有如下一段

代码语言:javascript复制
else {
                    // Limit the number of toasts that any given package except the android
                    // package can enqueue.  Prevents DOS attacks and deals with leaks.
                    if (!isSystemToast) {
                        int count = 0;
                        final int N = mToastQueue.size();
                        for (int i=0; i<N; i  ) {
                             final ToastRecord r = mToastQueue.get(i);
                             if (r.pkg.equals(pkg)) {
                                 count  ;
                                 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                     Slog.e(TAG, "Package has already posted "   count
                                              " toasts. Not showing more. Package="   pkg);
                                     return;
                                 }
                             }
                        }
                    }

当ToastRecord被添加到mToastQueue中后,NMS会通过showNextToastLocked()方法来显示当前的Toast

代码语言:javascript复制
private 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();
                scheduleTimeoutLocked(record, false);
                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);
                }
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
}

callback实际上就是Toast中的TN对象的远程Binder, 最终会运行在发起Toast请求的应用的Binder线程池中

Toast显示之后,NMS会通过scheduleTimeoutLocked发送延迟消息,时长取决于Toast的时长

代码语言:javascript复制
  private void scheduleTimeoutLocked(ToastRecord r)
        {
            mHandler.removeCallbacksAndMessages(r);
            Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
            //默认3.5s和2s
            long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
            //发送延迟消息
            mHandler.sendMessageDelayed(m, delay);
        }

同样,Toast显示之后消失,也是通过callback来完成的

代码语言:javascript复制
void cancelToastLocked(int index) {
                ToastRecord record = mToastQueue.get(index);
                try {
                    //同样使用回调隐藏
                    record.callback.hide();
                } catch (RemoteException e) {
                    //打印
                }
                //移除已处理过的弹框
                mToastQueue.remove(index);
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) {
                   //如果还有未完成的弹框,继续调用
                    showNextToastLocked();
                }
        }

Toast的显示和影响过程实际上是通过TN这个类实现的,两个主要方法show和hide,对应显示和隐藏, 这两个方法被NMS跨进程调用,因此它们运行在Binder线程池中,为了将执行环境切换到Toast的请求线程,在内部使用了handler

代码语言:javascript复制
rivate static class TN extends ITransientNotification.Stub {
            final Runnable mShow = new Runnable() {
                @Override
                public void run() {
                    //处理显示的逻辑
                    handleShow();
                }
            };

            final Runnable mHide = new Runnable() {
                @Override
                public void run() {
                    //处理隐藏的逻辑
                    handleHide();
                    mNextView = null;
                }
            };

            //构建windManger对象,设置相应params参数
            TN() {
                final WindowManager.LayoutParams params = mParams;
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.format = PixelFormat.TRANSLUCENT;
                params.windowAnimations = com.android.internal.R.style.Animation_Toast;
                params.type = WindowManager.LayoutParams.TYPE_TOAST;
                params.setTitle("Toast");
                params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            }

            @Override
            public void show() {
                if (localLOGV) Log.v(TAG, "SHOW: "   this);
                //通过handler传递消息
                mHandler.post(mShow);
            }

            @Override
            public void hide() {
                if (localLOGV) Log.v(TAG, "HIDE: "   this);
                mHandler.post(mHide);
            }
        }

通过handleShow和handleHide才能真正显示和隐藏Toast的地方

代码语言:javascript复制
  mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        //添加
        mWM.addView(mView, mParams);
        //删除
        if (mView.getParent() != null) {
               if (localLOGV) Log.v(TAG, "REMOVE! "   mView   " in "   this);
               mWM.removeView(mView);
        }

两个重要的AIDL文件

代码语言:javascript复制
//TN实现的接口
oneway interface ITransientNotification {
    void show();
    void hide();
}
代码语言:javascript复制
interface INotificationManager
{
    /** @deprecated use {@link #enqueueNotificationWithTag} instead */
    void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived);
    /** @deprecated use {@link #cancelNotificationWithTag} instead */
    void cancelNotification(String pkg, int id);
    void cancelAllNotifications(String pkg);
    //管理Toast显示和隐藏
    void enqueueToast(String pkg, ITransientNotification callback, int duration);
    void cancelToast(String pkg, ITransientNotification callback);

    void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
    void cancelNotificationWithTag(String pkg, String tag, int id);
}

参考书籍: 《Android开发艺术探索》

0 人点赞