认识Android中Window(三) 之 Window的创建过程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lyz_zyx/article/details/82916801

前面文章有提到,Android中所有的View呈现都是通过Window做到的,像悬浮窗、Activity、Dialog、Toast都是通过Window来呈现的。因为View不能单独存在,它必须附着在Window这个抽象的概念上面,所以有View的地方就有Window。

Activity 的 Window 创建过程

我们在文章《Android应用程序启动详解(一)》中有分析过Activity从startActivity()方法后的启动过程有介绍过ActivityThread.performLaunchActivity()方法。在这个方法内部做了3件大事,第1是通过反射机制创建了 Activity 的实例对象,第3是onCreate的回调,而第2是调用Acitivty.attach 方法为Activity初始化关联的一系列属性。Window 创建就在这里发生的,系统会创建 Activity 所属的 Window 对象并为其设置回调接口,请看源码:

Activity.java

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

        attachBaseContext(context);



        mFragments.attachActivity(this, mContainer, null);



        // 关键代码

        mWindow = PolicyManager.makeNewWindow(this);

        mWindow.setCallback(this);

        mWindow.setOnWindowDismissedCallback(this);

        mWindow.getLayoutInflater().setPrivateFactory(this);

        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {

            mWindow.setSoftInputMode(info.softInputMode);

        }

        ……

        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;

    }

可以看一以,Window对象的创建是通过PolicyManager.makeNewWindow()方法来实现。在创建完成后,设置了回调setCallback并传入this,所以当Window接到外界的状态改变时就会回调Activity方法。我们重点关注一下PolicyManager.makeNewWindow(),从名字可以看出PolicyManager是一个策略类,它实现了几个工厂方法全部的策略接口在IPolicy中声明,真正实现的类是Policy,请看它们的代码:

IPolicy.java

public interface IPolicy {

    Window makeNewWindow(Context var1);

    LayoutInflater makeNewLayoutInflater(Context var1);

    WindowManagerPolicy makeNewWindowManager();

    FallbackEventHandler makeNewFallbackEventHandler(Context var1);

}

Policy.java

public class Policy implements IPolicy {

    ⋯⋯

    public Window makeNewWindow(Context context) {

        return new PhoneWindow(context);

    }

    ⋯⋯

}

这里就能说明,Window的具体且唯一实现的确是PhoneWindow。Window创建完成后就要来看看Activity 的View是如何附属到 Window 上的,而 Activity 的View由 setContentView() 提供,所以从 setContentView 入手,它的源码如下:

PhoneWindow.java

    public void setContentView(int layoutResID) {

        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window

        // decor, when theme attributes and the like are crystalized. Do not check the feature

        // before this happens.

        if (mContentParent == null) {

            // 关键代码1,如果没有 DecorView 就创建一个

            installDecor();

        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            mContentParent.removeAllViews();

        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,

                    getContext());

            transitionTo(newScene);

        } else {

            // 关键代码2:将 View 添加到 DecorView 的 mContentParent 中

            mLayoutInflater.inflate(layoutResID, mContentParent);

        }

        // 关键代码3:回调 Activity 的 onContentChanged 方法通知 Activity View已经发生改变

        final Callback cb = getCallback();

        if (cb != null && !isDestroyed()) {

            cb.onContentChanged();

        }

    }

方法中,重点做了3件事情,关键代码1中,判断mContentParent就否存在,mContentParent就是DecorView的内容部分。(DecorView即顶级View,关于DecorView的绍参考《View的工作原理(一)之 View的三大过程 认识MeasureSpec。如果不存在就创建DecorView;关键代码2中,直接将 Activity 的View添加到 DecorView 的 mContentParent,由此可以理解 Activity 的 setContentView 这个方法名的来历,为什么不叫 setView 呢?因为 Activity 的布局文件只是被添加到 DecorView 的 mContentParent 中,因此叫 setContentView;关键代码3中,就是处理Activity 的onContentChanged回调,因为从前面的setCallback()知道传入的是Activity,所以这里的getCallback()返回的就是Activity。

到目前为止,DecorView 已经被创建并初始化完毕,Activity 的布局文件也已经成功添加到了 DecorView 的 mContentParent 中,但是DecorView 还没有被 WindowManager 正式添加到 Window 中。在 ActivityThread 的 handleResumeActivity 方法中,首先会调用Acitivy 的 onResume 方法,接着会调用 Acitivy 的 makeVisible() 方法,正是在 makeVisible 方法中,DecorView 才真正的完成了显示过程,到这里 Activity 的视图才能被用户看到,如下:

Activity.java

    void makeVisible() {

        if (!mWindowAdded) {

            ViewManager wm = getWindowManager();

            wm.addView(mDecor, getWindow().getAttributes());

            mWindowAdded = true;

        }

        mDecor.setVisibility(View.VISIBLE);

    }

 

Dialog 的 Window 创建过程

Dialog 的 Window 的创建过程与 Activity 非常相似,请看Dialog类的构造函数:

Dialog.java   

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

        // 关键代码

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

    }

注释的关键代码中的new PhoneWindow再明显不过了。接着将Dialog的View添加到DecorView中,也是setContentView()方法:

    public void setContentView(@NonNull View view) {

        mWindow.setContentView(view);

    }

mWindow就是PhoneWindow,所以跟Activity是一样的,最后再看看是如何将DecorView添加到Window中去显示,那就是show()方法了:

    public void show() {

        ⋯⋯

        // 关键代码

        mWindowManager.addView(mDecor, l);

        mShowing = true;

        sendShowMessage();

    }

有显示就有关闭,所以在dismissDialog()方法中会将DecorView移除,请看代码:

    void dismissDialog() {

        ⋯⋯

        try {

            mWindowManager.removeViewImmediate(mDecor);

        } finally {

            ⋯⋯

        }

    }

我们知道,普通的Dialog有一个特殊之处,就是必须采用Activity的Context,如果是采用Application的Context的话就会报错。这是因为没有应用 token导致的,而应用 token 一般只有 Activity 拥有,另外,系统 Window 比较特殊,可以不需要 token。WindowManager.LayoutParams中的type表示Window的类型,而系统Window就是层级范围是2000~2999范围的,例如:TYPE_SYSTEM_ERROR,使用如下:

dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR); ,另外别忘记要在AndroidManifest中声明android.permission.SYSTEM_ALERT_WINDOW权限。

 

token

token是用来表示窗口的一个令牌,只有符合条件的token才能被WindowManagerService通过添加到应用上。还记得上面Activity.attach 方法为Activity初始化关联的一列属性有这样一行代码吗:

Activity.java

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

        ⋯⋯

        mToken = token;

        ⋯⋯

        mWindow.setWindowManager(

                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

                mToken, mComponent.flattenToString(),

                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

        ⋯⋯

    }

可以看到token传递到mWindow.setWindowManager中去,再来看看下面做了什么事情:

Window.java

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,

            boolean hardwareAccelerated) {

        mAppToken = appToken;

        mAppName = appName;

        mHardwareAccelerated = hardwareAccelerated

                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);

        if (wm == null) {

            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);

        }

        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

    }

在setWindowManager中,appToken赋值到Window上,同时在当前Window上创建了WindowManager。

在一系列的传递中和跨进程通信中,token会到达WindowManagerServer中去,请看addWindow()方法:

WindowManagerServer.java

public int addWindow(Session session, IWindow client, int seq,

            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,

            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,

            InputChannel outInputChannel) {

        ⋯⋯

            if (token == null) {

                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {

                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (rootType == TYPE_INPUT_METHOD) {

                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (rootType == TYPE_VOICE_INTERACTION) {

                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (rootType == TYPE_WALLPAPER) {

                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (rootType == TYPE_DREAM) {

                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (rootType == TYPE_QS_DIALOG) {

                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {

                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "

                            + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

                if (type == TYPE_TOAST) {

                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.

                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,

                            parentWindow)) {

                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "

                                + attrs.token + ".  Aborting.");

                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                    }

                }

                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();

                token = new WindowToken(this, binder, type, false, displayContent,

                        session.mCanAddInternalSystemWindow);

            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {

                atoken = token.asAppWindowToken();

                if (atoken == null) {

                    Slog.w(TAG_WM, "Attempted to add window with non-application token "

                          + token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;

                } else if (atoken.removed) {

                    Slog.w(TAG_WM, "Attempted to add window with exiting application token "

                          + token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_APP_EXITING;

                }

            } else if (rootType == TYPE_INPUT_METHOD) {

                if (token.windowType != TYPE_INPUT_METHOD) {

                    Slog.w(TAG_WM, "Attempted to add input method window with bad token "

                            + attrs.token + ".  Aborting.");

                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (rootType == TYPE_VOICE_INTERACTION) {

                if (token.windowType != TYPE_VOICE_INTERACTION) {

                    Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "

                            + attrs.token + ".  Aborting.");

                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (rootType == TYPE_WALLPAPER) {

                if (token.windowType != TYPE_WALLPAPER) {

                    Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "

                            + attrs.token + ".  Aborting.");

                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (rootType == TYPE_DREAM) {

                if (token.windowType != TYPE_DREAM) {

                    Slog.w(TAG_WM, "Attempted to add Dream window with bad token "

                            + attrs.token + ".  Aborting.");

                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {

                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {

                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "

                            + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (type == TYPE_TOAST) {

                // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.

                addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,

                        callingUid, parentWindow);

                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {

                    Slog.w(TAG_WM, "Attempted to add a toast window with bad token "

                            + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (type == TYPE_QS_DIALOG) {

                if (token.windowType != TYPE_QS_DIALOG) {

                    Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "

                            + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;

                }

            } else if (token.asAppWindowToken() != null) {

                Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType);

                // It is not valid to use an app token with other system types; we will

                // instead make a new token for it (as if null had been passed in for the token).

                attrs.token = null;

                token = new WindowToken(this, client.asBinder(), type, false, displayContent,

                        session.mCanAddInternalSystemWindow);

            }

            final WindowState win = new WindowState(this, session, client, token, parentWindow,

                    appOp[0], seq, attrs, viewVisibility, session.mUid,

                    session.mCanAddInternalSystemWindow);

            if (win.mDeathRecipient == null) {

                // Client has apparently died, so there is no reason to

                // continue.

                Slog.w(TAG_WM, "Adding window client " + client.asBinder()

                        + " that is dead, aborting.");

                return WindowManagerGlobal.ADD_APP_EXITING;

            }

            if (win.getDisplayContent() == null) {

                Slog.w(TAG_WM, "Adding window to Display that has been removed.");

                return WindowManagerGlobal.ADD_INVALID_DISPLAY;

            }

            mPolicy.adjustWindowParamsLw(win.mAttrs);

            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

            res = mPolicy.prepareAddWindowLw(win, attrs);

            if (res != WindowManagerGlobal.ADD_OKAY) {

                return res;

            }

            ⋯⋯

        return res;

    }

可以看到方法内根据token做了很多的判断,return对应不同的值。都没问题则开始添加Window。而在ViewRootImpl的setView()方法则有判断对应的返回值来报错:

ViewRootImpl.java   

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

        synchronized (this) {

            if (mView == null) {

                ⋯⋯

                if (res < WindowManagerGlobal.ADD_OKAY) {

                    mAttachInfo.mRootView = null;

                    mAdded = false;

                    mFallbackEventHandler.setView(null);

                    unscheduleTraversals();

                    setAccessibilityFocus(null, null);

                    switch (res) {

                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:

                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- token " + attrs.token

                                    + " is not valid; is your activity running?");

                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- token " + attrs.token

                                    + " is not for an application");

                        case WindowManagerGlobal.ADD_APP_EXITING:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- app for token " + attrs.token

                                    + " is exiting");

                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- window " + mWindow

                                    + " has already been added");

                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:

                            // Silently ignore -- we would have just removed it

                            // right away, anyway.

                            return;

                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:

                            throw new WindowManager.BadTokenException("Unable to add window "

                                    + mWindow + " -- another window of type "

                                    + mWindowAttributes.type + " already exists");

                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:

                            throw new WindowManager.BadTokenException("Unable to add window "

                                    + mWindow + " -- permission denied for window type "

                                    + mWindowAttributes.type);

                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:

                            throw new WindowManager.InvalidDisplayException("Unable to add window "

                                    + mWindow + " -- the specified display can not be found");

                        case WindowManagerGlobal.ADD_INVALID_TYPE:

                            throw new WindowManager.InvalidDisplayException("Unable to add window "

                                    + mWindow + " -- the specified window type "

                                    + mWindowAttributes.type + " is not valid");

                    }

                    throw new RuntimeException(

                            "Unable to add window -- unknown error code " + res);

                }

                ⋯⋯

            }

        }

    }

Toast 的 Window 创建过程

Toast也是基本Window来实现的,它属于系统Window,其内部通过IPC访问系统服务NotificationManagerService来完成。Toast内部的视图可以是系统默认样式也可以通过 setView() 方法自定义 View,不管如何,它们都对应 Toast 的内部成员mNextView,Toast 提供 show() 和 cancal() 用于显示和取消显示Toast,它内部是跟NotificationManagerService的IPC 过程,代码如下:

Toast.java

public class Toast {

    public void setView(View view) {

        mNextView = view;

    }

    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

        }

    }

    public void cancel() {

        mTN.cancel();

    }

TN 是一个 Binder 类,当 NotificationManagerService 处理 Toast 的显示或隐藏请求时会跨进程回调 TN 中的方法。由于 TN 运行在 Binder 线程池中,所以需要通过 Handler 将其切换到发送 Toast 请求所在的线程:

private static class TN extends ITransientNotification.Stub {

        public void cancel() {

            if (localLOGV) Log.v(TAG, "CANCEL: " + this);

            mHandler.obtainMessage(CANCEL).sendToTarget();

        }

        // 关键代码1:后面介绍

        @Override

        public void show(IBinder windowToken) {

            if (localLOGV) Log.v(TAG, "SHOW: " + this);

            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();

        }

           // 关键代码2:后面介绍

        @Override

        public void hide() {

            if (localLOGV) Log.v(TAG, "HIDE: " + this);

            mHandler.obtainMessage(HIDE).sendToTarget();

        }

       ⋯⋯

            mHandler = new Handler(looper, null) {

                @Override

                public void handleMessage(Message msg) {

                    switch (msg.what) {

                        case SHOW: {

                            IBinder token = (IBinder) msg.obj;

                            handleShow(token);

                            break;

                        }

                        case HIDE: {

                            handleHide();

                            // Don't do this in handleHide() because it is also invoked by

                            // handleShow()

                            mNextView = null;

                            break;

                        }

                        case CANCEL: {

                            handleHide();

                            // Don't do this in handleHide() because it is also invoked by

                            // handleShow()

                            mNextView = null;

                            try {

                                // 关键代码3

                                getService().cancelToast(mPackageName, TN.this);

                            } catch (RemoteException e) {

                            }

                            break;

                        }

                    }

                }

            };

        }

}

来看看上面show()方法中调用了NotificationManagerService的 enqueueToast 方法:

NotificationManagerService.java

private final IBinder mService = new INotificationManager.Stub() {

        @Override

        public void enqueueToast(String pkg, ITransientNotification callback, int duration)   {

            ⋯⋯

            synchronized (mToastQueue) {

                int callingPid = Binder.getCallingPid();

                long callingId = Binder.clearCallingIdentity();

                try {

                    ToastRecord record;

                    int index = indexOfToastLocked(pkg, callback);

                    if (index >= 0) {

                        record = mToastQueue.get(index);

                        record.update(duration);

                    } else {

                        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;

                                     }

                                 }

                            }

                        }

                        Binder token = new Binder();

                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);

                        record = new ToastRecord(callingPid, pkg, callback, duration, token);

                        // 关键代码1

                        mToastQueue.add(record);

                        index = mToastQueue.size() - 1;

                        keepProcessAliveIfNeededLocked(callingPid);

                    }

                    if (index == 0) {

                        // 关键代码2

                        showNextToastLocked();

                    }

                } finally {

                    Binder.restoreCallingIdentity(callingId);

                }

            }

        }

⋯⋯

}

上面方法内部将 Toast 请求封装为 ToastRecord 对象并将其添加到一个名为 mToastQueue 的队列中,对于非系统应用来说,mToastQueue 中最多同时存在 50 个 ToastRecord,用于防止 DOS (Denial of Service  拒绝服务)。当 ToastRecord 添加到 mToastQueue 中后,NotificationManagerService 就会通过showNextToastLocked 方法来顺序显示 Toast,但是 Toast 真正的显示并不是在 NotificationManagerService 中完成的,而是由ToastRecord 的 callback 来完成的,继续看下showNextToastLocked()方法源码:  

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

                ⋯⋯

            }

        }

    }

代码中的callback实际上就是Toast中的TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成,最终被调用的TN中的方法会运行在发起Toast请求的应用的Binder线程池中。

Toast 显示以后,NotificationManagerService 还调用了 sheduleTimeoutLocked() 方法发送一个延时消息,具体的延时时长取决于 Toast 的显示时长:   

private void scheduleTimeoutLocked(ToastRecord r)  {

        mHandler.removeCallbacksAndMessages(r);

        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);

        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;

        mHandler.sendMessageDelayed(m, delay);

    }

MESSAGE_TIMEOUT消息会调用到handleTimeout()方法:

    private void handleTimeout(ToastRecord record)  {

        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);

        synchronized (mToastQueue) {

            int index = indexOfToastLocked(record.pkg, record.callback);

            if (index >= 0) {

                // 关键代码

                cancelToastLocked(index);

            }

        }

    }

handleTimeout()会通过 cancelToastLocked()方法来隐藏 Toast 并将它从 mToastQueue 中移除,这时如果 mToastQueue 中还有其他 Toast,那么 NotificationManagerService 就继续显示其他 Toast。Toast 的隐藏也是通过 ToastRecord 的 callback 来完成的,同样也是一次 IPC 过程。实际上,Toast 发起的取消显示cancelToast()方法内部也是调用了cancelToastLocked()方法:

    void cancelToastLocked(int index) {

        ToastRecord record = mToastQueue.get(index);

        try {

            // 关键代码

            record.callback.hide();

        } catch (RemoteException e) {

            ⋯⋯

        }

        ToastRecord lastToast = mToastQueue.remove(index);

        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);

        keepProcessAliveIfNeededLocked(record.pid);

        if (mToastQueue.size() > 0) {

            showNextToastLocked();

        }

    }

以上显示和隐藏提到的关键代码中:record.callback.show(record.token);和record.callback.hide();最终是通过 Toast 的 TN 类来实现的,TN 类的两个方法 show() 和 hide(),这两个方法的源码我们在上面介绍Handler时已经贴过,它们的实现就是往Handler发送 SHOW 和 HIDE 消息,然后执行handleShow()和handleHide()方法:   

private static class TN extends ITransientNotification.Stub {

       public void handleShow(IBinder windowToken) {

            ⋯⋯

            if (mView != mNextView) {

                ⋯⋯

                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

                ⋯⋯

                if (mView.getParent() != null) {                   

                    mWM.removeView(mView);

                }

                ⋯⋯

                try {

                  // 关键代码

                    mWM.addView(mView, mParams);

                    trySendAccessibilityEvent();

                } catch (WindowManager.BadTokenException e) {

                    /* ignore */

                }

            }

        }

       public void handleHide() {

            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);

            if (mView != null) {

                if (mView.getParent() != null) {

                   // 关键代码

                    mWM.removeViewImmediate(mView);

                }

                mView = null;

            }

        }

    }

 

完...

 

 

——本文部分内容参考自《Android开发艺术探索》

猜你喜欢

转载自blog.csdn.net/lyz_zyx/article/details/82916801
今日推荐