Window的创建

news/2024/11/7 12:45:20/

Window的创建

上一篇说到了Window和WindowManager的关系并且讲述了WindowManager如何添加Window与Window内部的三个方法的实现

这篇主要讲几个常见的Window的创建比如Activity,Dialog和Toast

其中Activity属于应用Window

Dialog属于子Window

Toast属于系统Window

z-ordered越来越大,它的优先级就越来越大

Activity的Window的创建

Activity的启动最终是由ActivityThreadperformLaunchActivity,这个方法在内部会通过内加载器创建一个Activity的实例变量。

然后调用attach,将一系列上下文环境变量关联起来,确保 Activity 可以正常运行并与应用程序的其他组件进行通信。

我们可以看看attach关联了哪些

我们看看它的源码

通过ctrl+n

 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.activityConfigCallback,r.assistToken, r.shareableActivityToken);

并且在attach的过程中还会创建Activity所属的Window

记得我们在View的工作原理的时候说到过:

Activity创建完成后,会把DecorView添加到Window中,并创建相应的ViewRootImpl,再把ViewRootImpl与DecorView关联起来

而Activity的Window的创建就是在performLaunchActivity中创建Activity后调用attach来创建的

1.Window的创建

问题:2个Window的不同

attach

《艺术开发探索》中说在attach中通过PolicyManagermakeNewWindow获得window,但我找了半天没找到那段源码

只在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, IBinder assistToken,IBinder shareableActivityToken) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(mWindowControllerCallback);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();mMainThread = aThread;mInstrumentation = instr;mToken = token;mAssistToken = assistToken;mShareableActivityToken = shareableActivityToken;mIdent = ident;mApplication = application;mIntent = intent;mReferrer = referrer;mComponent = intent.getComponent();mActivityInfo = info;mTitle = title;mParent = parent;mEmbeddedID = id;mLastNonConfigurationInstances = lastNonConfigurationInstances;if (voiceInteractor != null) {if (lastNonConfigurationInstances != null) {mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;} else {mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,Looper.myLooper());}}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);mWindow.setPreferMinimalPostProcessing((info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);getAutofillClientController().onActivityAttached(application);setContentCaptureOptions(application.getContentCaptureOptions());}

即它没有用**PolicyManager.makeNewWindow(this)**来创建Window的

而是

mWindow = new PhoneWindow(this, window, activityConfigCallback);

其中这里面的window对象来自

抽象类

public abstract class Window

chatGPT的回答是:

实际的 Window 创建是在 PhoneWindow 中进行的,而不是在 Activityattach() 方法中直接调用 PolicyManager.makeNewWindow(this)

final void attach(...) {...mWindow = new PhoneWindow(this, window, activityConfigCallback);...
}private void performLaunchActivity(...) {...// 创建 Activity 的实例Activity activity = instantiateActivity(cl, component);...// 创建 PhoneWindow 对象Window window = policyManager.makeNewWindow(activity);...// 调用 attach() 方法activity.attach(..., window, ...);...
}

总结起来,mWindow = new PhoneWindow(this, window, activityConfigCallback); 创建了一个具体的 PhoneWindow 对象,用于管理 Activity 的窗口。而 Window window = policyManager.makeNewWindow(activity); 创建了一个基本的 Window 对象,作为系统策略管理器与 Activity 的窗口之间的接口。

第一个Window是具体的窗口实现,用于管理 Activity 的窗口。

第二个Window是系统级别的Window对象,并不是直接与 Activity 关联的窗口对象。

并且给出的最终回复,在 Activity 的创建过程中,主要是创建了一个具体的 PhoneWindow 对象,用于表示和管理 Activity 的窗口。系统级别的 Window 对象由策略管理器创建,用于处理窗口的管理和操作。


我们创建完成PhoneWindow后,Activity实现了windowcallBack接口,可以把Activity自己设置为window的观察者。然后我们就开始初始化WindowManager,记得之前我们说过的Window没办法直接访问,我们只能通过访问WindowManager来访问Window

mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);     

我们继续看看setWindowManager的源码

2.WindowManager的初始化过程

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken;mAppName = appName;mHardwareAccelerated = hardwareAccelerated;if (wm == null) {//获取到应用服务的WindowManagerwm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

mAppToken,mAppName, mHardwareAccelerated这三个就是给它们赋了个值m主要还是下面的

先判断传进去的WindowManager为不为空,为空的话进行初始化

,这个初始化与上一篇博客我进行完WindowManager的布局后的初始化一样都是

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

然后将**((WindowManagerImpl)wm).createLocalWindowManager(this)赋给mWindowManager**

话说mWindowManager是什么,我们点击它,会发现它是一个全局变量

private WindowManager mWindowManager;

而((WindowManagerImpl)wm).createLocalWindowManager(this)是在创建一个本地的窗口管理器对象。

本地窗口管理器是一个与设备本地窗口系统交互的对象,它提供了与底层窗口系统通信的功能。通过创建本地窗口管理器,可以在应用程序中实现对窗口的创建、显示、布局、交互等操作。


PhoneWindow类型的mWindow已经通过

mWindow = new PhoneWindow();

创建好了

刚才在setWindowManager中的内部也成功创建好了WindowManager

便可以成功绑定WindowWindowManager

又因为我们之前说过WindowManager主要是由WindowManagerImpl实现的,

所以刚才那句话也就相当于WindowWindowManagerImpl进行成功的绑定

我们就可以通过调用WindowManagerImpl的那三个方法来实现Windowadd,remove,update

3.流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVB1PIU0-1685279766886)(../../assets/流程图-导出 (3)]-1685262524947-1.png)

4.把Activity的布局文件设置给PhoneWindow

上面提到调用Activityattach方法之后,会回调ActivityonCreate方法,在其中会调用setContentView来设置布局,如下:

public void setContentView(View view, ViewGroup.LayoutParams params) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();
}

Activity将setContentView具体实现交给了Window处理,这里的getWindow返回我们上面创建的PhoneWindow对象。我们继续看下去:

// 注意他有多个重载的方法,要选择参数对应的方法
public void setContentView(int layoutResID) {// 创建DecorViewif (mContentParent == null) {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 {// **这里根据布局id加载布局,把Activity的布局加载到DecorView的**mContentParent**中**mLayoutInflater.inflate(layoutResID, mContentParent);}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {// 回调activity的方法,**通知Activity视图已经发生改变**cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

首先我们先看第一个if

虽然不知道它在干什么但是我们看到了

  installDecor();

就可以明白它创建了一个DecorView

首先判断 mContentParent 是否为 null。mContentParent 是 Window 的内容视图的父级容器,如果为 null,表示 DecorView 还未创建,因此需要调用 installDecor() 来创建并安装 DecorView。

如果 mContentParent 不为 null,则进一步判断是否存在 FEATURE_CONTENT_TRANSITIONS 特性。如果不存在该特性,表示不需要进行内容转场动画,那么可以通过 mContentParent.removeAllViews() 清空已有的内容视图。

之后第二个if就是判断是否存在 FEATURE_CONTENT_TRANSITIONS 特性,如果开启了该特性,则会使用场景切换的方式来加载布局

没开启的话会直接使用 mLayoutInflater.inflate(layoutResID, mContentParent) 方法将指定布局文件加载到 DecorView 的 mContentParent 容器中,完成 Activity 的布局加载。

之后就是 回调Activity的callBack方法

4.1简单流程

总之把Activity的布局文件加载到PhoneWindow就以下几个流程

1.判断DecorView是否创建,没有创建就创建

2.将Activity的布局加载到DecorView的mContentParent

3.进行Activity的回调

所以我们就可以知道了Activity的加载布局文件为什么是setContentView了,因为Activity的加载布局文件最后加载到了Window的DecorWindow的**ContentView**中


5.总结前三步

现在回顾以下

我们刚才做的

1是创建了Window,(Window是在ActivityThread中调用performLaunchActivity中创建的Activity调用attach中创建的)

2.如何创建WindowManager,并且进行了绑定(在Window调用setWindowManager中创建的WindowManager)

3.如何将Activity的布局文件设置给PhoneWindow(1.判断DecorView是否创建

​ 2.将布局文件传给DecorView的ContentParent

​ 3.进行Activity的回调,给Activity说DecorView已经创建好了)

现在就差最后一步了,那就是把DecorView作为window添加到屏幕上。

肯定想的是那很简单啊,WindowManager已经和Window绑定了,那么我们直接WindowManager.add不就好了

而且在Activity的创建的时候我们说过:

Activity创建完成后,会把DecorView添加到Window中,并创建相应的ViewRootImpl,再把ViewRootImpl与DecorView关联起来

但是

但是这个时候由于DecorView并没有被WindowManager识别,所以这个时候的Window无法提供具体功能,因为它还无法接收外界的输入信息

6.将DecorView添加到Window中

还是在ActivityThread中

我们调用

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {// 调用Activity的onResume方法final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);...// 让decorView显示到屏幕上if (r.activity.mVisibleFromClient) {r.activity.makeVisible();
}

首先第一步是performResumeActivity进行了onResume方法的回调

第二步是**makeVisible()**让decorView显示到屏幕上

我们点击makeVisible()看里面的源码

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

会发现它在这个地方调用了addView,并且将mDecor设置为可见

7.Activity创建的流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9W5bacP-1685279766887)(../../assets/流程图-导出 (4)]-1685265053644-3.png)

Dialog的Window创建

只要你把Activity的Window的创建好了,Dialog的Window创建就很容易了

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {...// 获取windowManagermWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//mWindowManager其实是Activity的WindowManager,这里的context一般是activity// 构造PhoneWindowfinal Window w = new PhoneWindow(mContext);mWindow = w;// 初始化PhoneWindoww.setCallback(this);w.setOnWindowDismissedCallback(this);w.setOnWindowSwipeDismissedCallback(() -> {if (mCancelable) {cancel();}});w.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);
}public Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}// 获取的是activity的windowManagerif (WINDOW_SERVICE.equals(name)) {return mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);
}

1.创建Window

这步和Activity的Window创建一样,就没必要讲了

2.初始化DecorView并将Dialog视图添加到DecorView中

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

3.将DecorView添加到Window并显示

public void show() {...// 回调onStart方法,获取前面初始化好的decorviewonStart();mDecor = mWindow.getDecorView();...WindowManager.LayoutParams l = mWindow.getAttributes();...// 利用windowManager来添加window    mWindowManager.addView(mDecor, l);
//这里的mWindowManager是Activity的WindowManager...mShowing = true;sendShowMessage();
}

Dialog的Window创建和Activity的Window创建过程有很多类似的地方,二者几乎没有区别

Dialog在被关闭的时候,会通过WindowManager来移除DecorView:

mWindowManager.removeViewImmediate(mDecor);

removeViewImmediate是同步方法

removeView是异步方法

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

是没有应用token导致的,而应用token一般只有Activity拥有,所以只需要用Activity作为Context来显示对话框。

系统Window比较特殊,它可以不要token

我们只需要这么改就可以运行了

dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM.ERROR);

还有

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

Toast的Window创建

Toast比Dialog稍微难一点,虽然Toast也是基于Window来实现的,但是Toast有定时取消这一功能。

所以系统采用了Handler

Toast内部有2类IPC过程:

1.Toast访问NotificationManagerService

2.NotificationManagerService回调Toast里的TN接口

Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认的样式另一种是通过setView方法来指定一个自定义View,视图都对应Toast的一个View类型的内部成员mNextView。Toast提供了show和cancel分别用于显示和隐藏Toast,show和cancel的内部是IPC过程,

Toast的show()

首先看Toast的显示过程,它调用了NMS中的enqueueToast方法,如下所示。

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

NMSenqueueToast方法的第一个参数表示当前应用的包名,第二个参数tn表示远程回调,第三个参数表示Toast的时长。

enqueueToast会先将Toast请求封装成为ToastRecord对象并将它添加到一个名为mToastQueue的ArrayList集合中

对于非系统应用来说,mToastQueue最多同时存在50个ToastRecord,这样做是为了防止DOS,如果不这样做,使用大量循环弹出Toast会导致其他应用没机会弹出Toast,那么对于其他应用的Toast请求,系统的行为就是拒绝服务

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

正常情况下,一个app的ToastRecord不会到达上限,当ToastRecord被添加到mToastQueue后,NMS会通过showNextToastLocked方法显示当前的Toast

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 dieint index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}keepProcessAliveLocked(record.pid);if (mToastQueue.size() > 0) {record = mToastQueue.get(0);} else {record = null;}}}}

需要注意的是Toast的显示是由ToastRecord的callback完成的,这个callback是Toast中的TN对象的远程Binder

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

Toast显示以后,NMS还会通过scheduleTimeoutLocked方法发送一个延迟消息,消息的延迟取决于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);
}

LONG_DELAY为3.5s,而 SHORT_DELAY为2.5s

我最开始以为这个就决定着

Toast.makeText(MainActivity.this,"nihao",Toast.LENGTH_LONG).show()

的这个的持续时间,后来搜了一下发现:

实际上,delay 的值是用于处理 Toast 消息的显示时间间隔,而不是 Toast 消息的总显示时间。在 Android 中,LONG_DELAYSHORT_DELAY 是表示延迟时间的常量,具体的值可能会根据系统的设置而有所不同。

Toast的show()总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R23kHBJI-1685279766888)(../../assets/流程图-导出 (5)].png)

我对show方法的理解就是Toast把它封装到ToastRecord并把它放入ToastQueue集合中

然后NMS会进行回调ToastRecord中的callback即Toast中的TN方法

Toast的hide()

try {record.callback.hide();
} catch (RemoteException e) {Slog.w(TAG, "Object died trying to hide notification " + record.callback+ " in package " + record.pkg);// don't worry about this, we're about to remove it from// the list anyway
}

Toast的显示和隐藏的本质

可以看出来Toast的显示和隐藏过程实际上是通过Toast的TN这个类实现的,它有一个**show()方法和一个hide()**方法

分别对应Toast的显示和隐藏

由于这两个方法是被NMS跨进程调用的,因此它们都在Binder线程池中,为了将执行环境切换到Toast所在的线程,内部使用Handler

/*** schedule handleShow into the right thread*/
@Override
public void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);
}/*** schedule handleHide into the right thread*/
@Override
public void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);
}

可以发现mShow和mHide是两个Runnable,内部分别调用了handleShow和handleHide,所以它们两个才是真正完成显示和隐藏的

TN的handleShow会将Toast视图加载到Window中

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

而TN的handleHide会将Toast视图从Window中移除


http://www.ppmy.cn/news/99968.html

相关文章

开闭原则正确姿势, 使用AOP优雅的记录日志, 非常的哇塞

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 JDK8 新特性 Stream API 进阶 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f649;。 …

【Accessors注解】记录使用 lombook 注解姿势不对导致无法使用 BeanCopier 复制属性的问题

目录 背景定位问题分析原因为什么 BeanUtils.copyProperties() 可以为什么 BeanCopier 不可以 总结 背景 前几天看同事写的代码&#xff0c;发现不同分层对象之间的转换用的 spring 自带的 BeanUtils.copyProperties()&#xff0c;并且复制的还是对象集合。一时技痒&#xff0…

机器学习笔记 - 基于MATLAB的简单车牌识别系统参考代码

1、简述 车牌识别 (NPR) 是一种计算机视觉和模式识别技术,用于提取和解释车辆车牌上的字符。这里的重点是使用 MATLAB 实现一个简单的 NPR 系统,MATLAB 是一种用于科学计算和图像处理的强大编程语言和环境。目标是开发一个自动化系统,该系统可以检测图像中的车牌,从车牌中…

多元回归预测 | Matlab白鲸算法(BWO)优化BP神经网络回归预测,BWO-BP回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab白鲸算法(BWO)优化BP神经网络回归预测,BWO-BP回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %--------------…

表的增删改查

目录 表的增删改查create(创建)单行数据 全列插入多行数据 指定列插入插入否则更新替换 retrieve(读取)SELECT 列全列查询指定列查询查询字段为表达式为查询结果指定别名结果去重 WHERE 条件英语不及格的同学及英语成绩 ( < 60 )&#xff08;<&#xff09;语文成绩在 […

2.进程和线程

程序、进程、线程 概述 程序是静态的代码集合进程是程序在执行过程中的实例&#xff0c;是操作系统分配资源的基本单位线程是进程内的执行单位&#xff0c;用于实现并发执行和共享资源 程序&#xff08;Program&#xff09; 程序是指一组指令的集合&#xff0c;它是静态的、…

555定时器的基本原理和应用案例

前言 555定时器常用于脉冲波形的产生和整形电路中&#xff0c;之前在查找555定时器的原理图和基本管脚信息时&#xff0c;网上的内容大多含糊不清&#xff0c;没有讲的很详细&#xff0c;要么只是单一的管脚图&#xff0c;要么就是简单的文字解释&#xff0c;并且大多数缺乏基…

车载软件架构 —— 闲聊几句AUTOSAR OS(二)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 在最艰难的时候,自己就别去幻想太远的将来,只要鼓励自己过好今天就行了! 这世间有太多的猝不及防,有些东西根本不配占有自己的情绪,人生就是一场体验,…