首页 > 其他分享 >理解Window和WIndowManager

理解Window和WIndowManager

时间:2024-09-11 16:52:38浏览次数:3  
标签:LayoutParams WIndowManager WindowManager Window 理解 Activity view View

Window表示一个窗口的概念,在日常开发中直接接触Window的机会并不多,但是在某些特殊时候我们需要在桌面上显示一个类似悬浮窗的东西,那么这种效果就需要用到Window来实现。Window是一个抽象类,它的具体实现是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和Window-ManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际是View的直接管理者。从第4章中所讲述的View的事件分发机制也可以知道,单击事件由Window传递给DecorView,然后再由DecorView传递给我们的View,就连Activity的设置视图的方法setContentView在底层也是通过Window来完成的。

1.Window和WindowManager

首先了解如何使用WindowManger添加一个Window,

mFloatingButton = new Button(this); 
mFloatingButton.setText("button"); 
mLayoutParams = new WindowManager.LayoutParams( 
                    LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT,0,0, 
                    PixelFormat.TRANSPARENT); 
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 
                    | LayoutParams.FLAG_NOT_FOCUSABLE 
                    | LayoutParams. FLAG_SHOW_WHEN_LOCKED 
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; 
mLayoutParams.x = 100; 
mLayoutParams.y = 300; 
mWindowManager.addView(mFloatingButton,mLayoutParams);

首先创建了一个Button实例,然后创建一个mLyoutParams,这个类是管理窗口布局的一个类,用于描述窗口的各个属性,然后设置他的参数,将其添加到WindowManager

上述代码可以将一个Button添加到屏幕坐标为(100,300)的位置上。

WindowManager. LayoutParams中的flags和type这两个参数比较重要

Flags参数表示Window的属性,它有很多选项,通过这些选项可以控制Window的显示特性,这里主要介绍几个比较常用的选项

FLAG_NOT_FOCUSABLE

表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。

FLAG_NOT_TOUCH_MODAL

在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件。

FLAG_SHOW_WHEN_LOCKED

开启此模式可以让Window显示在锁屏的界面上。

Type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子Window不能单独存在,它需要附属在特定的父Window之中, 比如常见的一些Dialog就是一个子Window。系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏这些都是系统Window。

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面

在三类Window中,应用Window的层级范围是1~99(值为100到1000,实际上这个范围并没有定义,可能是Android系统预留给未来扩展使用的),子Window的层级范围是1000~1999,系统Window的层级范围是2000~2999,这些层级范围对应着WindowManager.LayoutParams的type参数。如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。很显然系统Window的层级是最大的,而且系统层级有很多值,一般我们可以选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果采用TYPE_SYSTEM_ERROR,只需要为type参数指定这个层级即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;同时声明权限:。

WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了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); 
}

对开发者来说,WindowManager常用的就只有这三个功能而已,但是这三个功能已经足够我们使用了。它可以创建一个Window并向其添加View,还可以更新Window中的View,另外如果想要删除一个 Window,那么只需要删除它里面的View即可。由此来看,WindowManager操作Window的过程更像是在操作Window中的View。我们时常见到那种可以拖动的Window效果,其实是很好实现的,只需要根据手指的位置来设定LayoutParams中的x和y的值即可改变Window的位置。首先给View设置onTouchListener:mFloatingButton.setOnTouchListener(this)。然后在onTouch方法中不断更新View的位置即可:

2.Window的内部机制

Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View的形式存在。这点从 WindowManager的定义也可以看出,它提供的三个接口方法addView、updateViewLayout以及removeView都是针对View的,这说明View才是Window存在的实体。在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。为了分析Window的内部机制,这里从Window的添加、删除以及更新说起。

2.1Window的添加过程

Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl类。在WindowManagerImpl中Window的三大操作的实现如下:

@Override 
public void addView(View view,ViewGroup.LayoutParams params) { 
    mGlobal.addView(view,params,mDisplay,mParentWindow); 
} 
@Override 
public void updateViewLayout(View view,ViewGroup.LayoutParams params) { 
    mGlobal.updateViewLayout(view,params); 
} 
@Override 
public void removeView(View view) { 
    mGlobal.removeView(view,false); 
}

WindowManagerImpl没有直接实现,而是将这些操作丢给了WindowManagerGlobal,他以工厂的形式向外提供自己的实例,WindowManager-Global的addView方法主要分为如下几步。

  1. 检查参数是否合法,如果是子Window还需要调整布局参数
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); 
}
  1. 创建ViewRootImpl并将其View添加到列表中
    1. 在WindowManagerGlobal内部有如下几个列表比较重要:
private final ArrayList<View> mViews = new ArrayList<View>(); 
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList   
    <ViewRootImpl>(); 
    private final ArrayList<WindowManager.LayoutParams> mParams = 
        new ArrayList<WindowManager.LayoutParams>(); 
private final ArraySet<View> mDyingViews = new ArraySet<View>();

在上面的声明中,mViews存储的是所有Window所对应的View,mRoots存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是删除操作还未完成的Window对象。

然后是添加Window的一系列对象添加到列表中

root = new ViewRootImpl(view.getContext(),display); 
view.setLayoutParams(wparams); 
mViews.add(view); 
mRoots.add(root); 
mParams.add(wparams);
  1. 通过ViewRootImpl更新界面并完成Window的添加过程

这个步骤由ViewRootImpl的setView方法来完成,在setView内部会通过requestLayout来完成异步刷新请求。在下面的代码中,scheduleTraversals实际是View绘制的入口:

public void requestLayout() { 
    if (!mHandlingLayoutInLayoutRequest) { 
        checkThread(); 
        mLayoutRequested = true; 
        scheduleTraversals(); 
    } 
}

前面是更新了界面,现在是要完成Window的添加

ViewRootImpl在初始化的时候会通过getWindowSession方法从WindowManagerGlobal中获取IWindowSession的实例。WindowManagerGlobal是一个管理应用所有窗口的全局类,它持有到WindowManagerService的连接以及IWindowSession的引用。 当ViewRootImpl需要处理与窗口相关的操作时,比如添加或移除窗口,它会通过这个IWindowSession与系统的WindowManagerService进行通信,完成所需的操作。

接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中,mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次

IPC调用。

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

在Session内部会通过WindowManagerService来实现Window的添加,代码如下所示。

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

如此一来,Window的添加请求就交给WindowManagerService去处理了,在Window-ManagerService内部会为每一个应用保留一个单独的Session

2.2Window的删除过程

Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的。下面是WindowManagerGlobal的removeView的实现:

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

removeView的逻辑很清晰,首先通过findViewLocked来查找待删除的View的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked来做进一步的删除,如下所示

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

removeViewLocked是通过ViewRootImpl来完成删除操作的。在WindowManager中提供了两种删除接口removeView和removeViewImmediate,它们分别表示异步删除和同步删除,其中removeViewImmediate使用起来需要特别注意,一般来说不需要使用此方法来删除Window以免发生意外的错误。这里主要说异步删除的情况,具体的删除操作由ViewRoot-Impl的die方法来完成。在异步删除的情况下,die方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最后会将其添加到mDyingViews中,mDyingViews表示待删除的View列表。ViewRootImpl的die方法如下所示。

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

在die方法内部只是做了简单的判断,如果是异步删除,那么就发送一个MSG_DIE的消息,ViewRootImpl中的Handler会处理此消息并调用doDie方法,如果是同步删除(立即删除),那么就不发消息直 接调用doDie方法,这就是这两种删除方式的区别。在doDie内部会调用dispatchDetachedFromWindow方法,真正删除View的逻辑在dispatchDetachedFromWindow方法的内部实现。dispatchDetachedFromWindow方法主要做四件事:

(1)垃圾回收相关的工作,比如清除数据和消息、移除回调。

(2)通过Session的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。

(3)调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetached-FromWindow()以及onDetachedFromWindowInternal()。对于onDetachedFromWindow()大家一定不陌生,当View从Window

中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。

(4)调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。

2.3Womdow的更新过程

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.Layout-Params)params; 
    view.setLayoutParams(wparams); 
    synchronized (mLock) { 
        int index = findViewLocked(view,true); 
        ViewRootImpl root = mRoots.get(index); 
        mParams.remove(index); 
        mParams.add(index,wparams); 
        root.setLayoutParams(wparams,false); 
    } 
}

updateViewLayout方法做的事情就比较简单了,首先它需要更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams

方法来实现的。在ViewRootImpl中会通过scheduleTraversals方法来对View重新布局,包括测量、布局、重绘这三个过程。除了View本身的重绘以外,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的,它同样是3.一个IPC过程。

3.Window的创建过程

View无法单独存在,他需要依托Window这个抽象概念上,有视图的地方就会有Window,在安卓中常见的可以提供视图的地方有Activity,Dialog,Toast,(前面View的分发机制有提到过Activity将事件分发给了Window然后才分发给力ViewRoot)每一个这些视图都对应一个Window,现在分析这些视图元素的创建过程

3.1Activity的Window创建过程

分析Activity中Window的创建过程就必须了解他的Activity的启动过程,详细启动过程在第九章,这里先大概了解即可,Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量

java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); 
activity = mInstrumentation.newActivity(cl,component.getClassName(),r.intent); 
... 
if (activity != null) { 
    Context appContext = createBaseContextForActivity(r,activity); 
    CharSequence title = r.activityInfo.loadLabel(appContext.getPackage-Manager()); 
    Configuration config = new Configuration(mCompatConfiguration); 
    if (DEBUG_CONFIGURATION) Slog.v(TAG,"Launching activity " 
        + r.activityInfo.name + " with config " + config); 
    activity.attach(appContext,this,getInstrumentation(),r.token, 
        r.ident,app,r.intent,r.activityInfo,title,r.parent, 
        r.embeddedID,r.lastNonConfigurationInstances,config, 
        r.voiceInteractor); 
    ... 
}

在Activity的attach中,系统会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建是通过PolicyManager的makeNewWindow方法实现的。由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中的方法很多,但是有几个却是我们都非常熟悉的,比如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent,等等,代码如下所示。

//创建一个Window
mWindow = PolicyManager.makeNewWindow(this); 
//设置回调方法
mWindow.setCallback(this); 
mWindow.setOnWindowDismissedCallback(this); 
//每个窗口对象都关联有一个布局填充器(LayoutInflater),此方法设置了一个私有工厂,用于在填充布局时创建视图。当系统创建新的视图时,这个工厂会被调用来产生这些视图实例。
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); 
}

从上面分析,Activity的Window是通过PolicyManager的一个工厂方法来创建的,但是从PolicyManager的类名可以看出,它不是一个普通的类,它是一个策略类。PolicyManager中实现的几个工厂方法全部在策略接口IPolicy中声明了,IPolicy的定义如下:

public interface IPolicy { 
    public Window makeNewWindow(Context context); 
    public LayoutInflater makeNewLayoutInflater(Context context); 
    public WindowManagerPolicy makeNewWindowManager(); 
    public FallbackEventHandler makeNewFallbackEventHandler(Context  context); 
}

在这个接口中定义了window的创建方法,然后这个方法的真正实现是在,PolicyManager,他的实现方法如下

public Window makeNewWindow(Context context) { 
    return new PhoneWindow(context); 
}

它根据这个上下文对象创建了一个PhoneWindow对象,到了这里他的创建完成了,

下面是Activity视图是如何附着在Window上的,在这里Activity的视图由setContentView方法

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

这个操作Activity是交给了Window处理,而Window的实现是PhoneWindow,所以下面看PhoneWindow的setContentView的方法,这个方法主要分为下面几步

  1. 如果没有DecorView就创建它

DecorView在第四章有提到过,他是Activity中的顶级Veiw,他的内部包含标题栏和内容栏,DecorView的创建过程由installDecor方法完成,在方法内部会通过generateDecor方法来创建DecorView

protected DecorView generateDecor() { 
    return new DecorView(getContext(),-1); 
}

为了初始化DecorView的结构,PhoneWindow还需要通过generateLayout方法来加载具体的布局文件到DecorView中,具体的布局文件和系统版本以及主题有关

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

这里的ID_ANDROID_CONTENT的定义如下,这个id所对应的ViewGroup就是mContentParent:

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content
  1. 将View添加到DecorView的mContentParent中

这个过程就比较简单了,由于在步骤1中已经创建并初始化了DecorView,因此这一步直接将Activity的视图添加到DecorView的mContentParent中即可:mLayoutInflater.inflate(layoutResID,mContentParent)。到

此为止,Activity的布局文件已经添加到DecorView里面了,由此可以理解Activity的setContentView这个方法的来历了。不知道读者是否曾经怀疑过:为什么不叫setView呢?它明明是给Activity设置视图的啊!

从这里来看,它的确不适合叫setView,因为Activity的布局文件只是被添加到DecorView的mContentParent中,因此叫setContentView更加准确。

  1. 回调Activity的onContentChanged方法通知Activity视图已经发生改变

这个过程就更简单了,由于Activity实现了Window的Callback接口,这里表示Activity的布局文件已经被添加到DecorView的mContentParent中了,于是需要通知Activity,使其可以做相应的处理。Activity的 onContentChanged方法是个空实现,我们可以在子Activity中处理这个回调。这个过程的代码如下所示。

final Callback cb = getCallback(); 
if (cb != null && !isDestroyed()) { 
    cb.onContentChanged(); 
}

到了上面,DecorView已经被创建并且初始化,布局文件也成功添加到了DecorVeiw的mContentParent,到这里DecorView还没有被添加到WindowManger添加到window。

这里需要正确理解Window的概念,Window更多表示的是一种抽象的功能集合,虽然说早在Activity的attach方法中Window就已经被创建了,但是这个时候由于DecorView并没有被 WindowManager识别,所以这个时候的Window无法提供具体功能,因为它还无法接收外界的输入信息。

在ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着会调用Activity 的makeVisible(),正是在makeVisible方法中,DecorView真正地完成了添加和显示这两个过程,到这里Activity的视图才能被用户看到,如下所示

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

3.2Dialog的Window的创建过程

他和之前的Activity创建Window差不多,

1.创建Window

Dialog中的Window的创建同样是通过PolicyManager的makeNewWindow方法,创建后对象是PhoneWindow

Dialog(Context context,int theme,boolean createContextThemeWrapper) { 
    ... 
    mWindowManager = (WindowManager)context.getSystemService(Context. WINDOW_SERVICE); 
    Window w = PolicyManager.makeNewWindow(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中

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

3.将DecorView添加到Window中并显示

在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中,如下所示

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

从上面三个步骤可以发现,Dialog的Window创建和Activity的Window创建过程很类似,二者几乎没有什么区别。当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)。

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

另外,系统Window比较特殊,它可以不需要token,因此在上面的例子中,只需要指定对话框的Window为系统类型就可以正常弹出对话框。在本章一开始讲到,WindowManager.LayoutParams中的type表示Window的类型,而系统Window的层级范围是2000~2999,这些层级范围就对应着type参数。系统Window的层级有很多值,对于本例来说,可以选用TYPE_SYSTEM_OVERLAY来指定对话框的Window类型为系统Window,如下所示。

dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR)

然后别忘了在AndroidManifest文件中声明权限从而可以使用系统Window,如下所示。

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

3.3 Toast的Window创建过程

首先Toast也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以系统采用了Handler。在Toast的内部有两类IPC过程

第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。为了便于描述,下面将NotificationManagerService简称为NMS。

Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法来指定一个自定义View,不管如何,它们都对应Toast的一个View类型的内部成员mNextView。Toast提供了show和cancel分别用于显示和隐藏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 
    } 
} 
public void cancel() { 
    mTN.hide(); 
    try { 
        getService().cancelToast(mContext.getPackageName(),mTN); 
    } catch (RemoteException e) { 
    // Empty 
    } 
}

在这里不管是show还是cancel,俩个方法的核心操作都是通过NMS实现的,在这里的TN是一个Binder类,在Toast在使用NMS进行IPC过程的时候,NMS会跨进程回调TN的方法,这个过程中,TN是运行在Binder的进程中的,所以需要使用Hander来切换进程到当前进程中,

在显示过程中,首先判断了是否有显示内容,然后创建了NMS对象,enqueueToast首先将Toast请求封装为ToastRecord对象并将其添加到一个名为 mToastQueue的队列中。mToastQueue其实是一个ArrayList。对于非系统应用来说,mToastQueue中最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial of Service)。

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

在这里的代码我们可以看到他是有对队列中是消息数量做一个判断处理的

前面将将一个消息封装后添加到了mToastQueue中,然后NMS会通过showToastLocked方法来显示当前的Toast

Toast的显示其实是通过ToastRecord的callback方法实现的,callback方法是Toast中的TN对象调用的远程Binder,

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

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

这里的 scheduleTimeoutLocked(record); 发送一个延时消息,就是用来安排这个自动取消任务的。

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,将传入的r和包装,然后确定时长之后发送延时消息,在指定时间后调用清除Toast

然后是Toast的隐藏,他和显示类似,也是通过callback完成的,也是一次IPC过程

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

Toast的显示和隐藏是通过TN这个类实现的,他有俩个方法分别是show和hide,这个俩个方法都是被NMS跨进程调用的所以它运行在Binder线程池中,为了进行线程池的转换,它调用的Handler,

@Override 
public void show() { 
    if (localLOGV) Log.v(TAG,"SHOW: " + this); 
    mHandler.post(mShow); 
} 

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

这里的mShow和mHide是两个Runnable,它们内部分别调用了handleShow和handleHide方法。由此可见,handleShow和handleHide才是真正完成显示和隐藏Toast的地方。TN的handleShow中会将Toast

的视图添加到Window中,如下所示

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 
mWM.addView(mView,mParams)

而NT的handleHide中会将Toast的视图从Window中移除,如下所示。

if (mView.getParent() != null) { 
    if (localLOGV) Log.v(TAG,"REMOVE! " + mView + " in " + this); 
    mWM.removeView(mView); 
}

标签:LayoutParams,WIndowManager,WindowManager,Window,理解,Activity,view,View
From: https://blog.csdn.net/m0_73986294/article/details/142109891

相关文章

  • 冒泡排序理解
    1.1思路冒泡排序是一种简单的排序方法。基本思路是通过两两比较相邻的元素并交换它们的位置,从而使整个序列按照顺序排列。该算法一趟排序后,最大值总是会移到数组的最后面,那么接下来就不用再考虑这个最大值。一直重复这样的操作,最终就可以得到排序完成的数组。这种算法是稳......
  • Windows系统这操作真绝了,功能-1,用户效率下降80%
    作为Windows十几年的老用户来说,控制面板这个界面,小白是熟悉得不能再熟悉的了!这个面板出现之后,没有配文字或者是看不懂的语言,小白也能知道自己想要的功能在哪。Windows控制面板即将退出历史舞台?这个陪伴着我们走过39年岁月的功能也将迎来了退休?没错,微软这一次的Windows更新......
  • 如何使用zabbix内置 key 配置windows服务监控
    原作者:乐维社区原出处:乐维社区原文章链接:https://forum.lwops.cn/article/618windows的服务管理工具中有许多不同类型的服务,包括系统、应用程序、驱动程序、自定义服务等。在监控这些windows服务的时候,我们可以直接使用内置的函数key去进行监控。 Zabbix的内置key(键值)系统是......
  • 深入理解 Redis 的文件事件处理器
    概述Redis的文件事件处理器是基于Reactor模式实现的,内部采用IO多路复用程序来同时监听多个套接字,当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,此时文件事件处理器就会调用套接字之前关联好的事......
  • 一文理解协程----还不明白请来砍我
    说在前头:本文话糙理不糙,用大白话说明协程的核心思想,协程,指的是单个线程里执行多个并发任务,一个协程对应一个任务,重点来了,!!!协程是用户空间的概念,也就是说不管你一个线程里有多少个协程,在操作系统看来,你就是一个单线程,只要你有一处代码阻塞了,那么os就会挂起整个线程,所以说这多......
  • Windows Server 2022 rdp
    继续水一篇:2022废弃了xddm转而使用wddm,rdp的渲染有比较大的变化。高版本的unreal又需要2022支持,被迫走上魔改windows以提升2022rdp环境下抓屏帧数的道路。测试代码来自https://github.com/robmikh/Win32CaptureSample,只手动添加了输出fps逻辑。patchwindows后能在[60,90]......
  • Parallels Desktop 20 发布下载,macOS Sequoia 和 Windows 11 24H2 支持准备就绪
    ParallelsDesktopforMac20.0.0(build55653)-在Mac上运行WindowsmacOSSequoia和Windows1124H2支持准备就绪请访问原文链接:https://sysin.org/blog/parallels-desktop/,查看最新版。原创作品,转载请保出处。作者主页:sysin.org在Mac上运行Windows全新登场Pa......
  • 深入理解DocumentFragment -文档片段
    什么是文档片段?(MDN解释:)DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的Document使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。作用是什么与document相比,最大的区别是DocumentFragment不是真实DOM树的一部分,它的变化不会触......
  • 如何在Windows上搭建并运行DolphinScheduler前后端开发环境
    作者:海豚调度研究随笔编辑整理:曾辉前言ApacheDolphinScheduler是一个优秀的分布式调度系统,广泛应用于大数据处理和自动化任务管理中。本文详细介绍了如何在Windows环境下搭建ApacheDolphinScheduler的前后端开发环境。包括从源码的下载、环境配置、数据库初始化、依赖安装......
  • 09 Windows批处理之标签和无序执行
    在最基本的层面上,标签是一种标识符,它用尽可能少的文字简明地定义了一种产品或一个对象。如果我们没有标签,商业就会停滞不前;杂货店里会摆满一架又一架神秘的罐头产品。晚餐吃什么?它可能是豆类或南瓜派混合物;我们要打开才能知道。如果没有标签,批处理就不会陷入如此混乱的境地,但......