【Android原生问题分析】夸克、抖音划动无响应问题【Android14】

在这里插入图片描述

1 问题描述

偶现问题,用户打开夸克、抖音后,在界面上划动无响应,但是没有ANR。回到Launcher后再次打开夸克/抖音,发现App的界面发生了变化,但是仍然是划不动的。

2 log初分析

复现问题附近的log为:

在这里插入图片描述

用户打开夸克的时间点为“00:27:25”左右,然后是持续的划动无响应,最后在“00:27:36”最后退出了夸克。

现在基本上已经没有冻屏的操作了,从log中也没有看到可能的痕迹,并且根据用户的描述,在夸克界面上划动无响应,切换到别的界面后再切回夸克,看到夸克的界面是有更新的,说明应该是在绘制或者合成的地方卡住了。

由于默认的log有限,无法定位到具体的问题,因此需要添加log。

3 第一次添加log

由于不确定是哪个阶段出了问题,因此先针对Input流程和measure、layout以及draw流程添加了log,后面测试再次复现问题后,查看log发现问题出在draw流程(其实从问题描述上也能看出来,从App切换到Launcher再切换回App,界面发生了变化,说明输入事件应该是被App正确接收到了)。

第一帧正常:

在这里插入图片描述

从第二帧开始异常:

在这里插入图片描述

异常的点在ViewRootImpl.performDraw:

在这里插入图片描述

这里就是问题的直接原因了,明明是亮屏状态,但是ViewRootImpl.mAttachInfo.mDisplayState保存的值却是Display.STATE_OFF,导致代码误判断此时处于灭屏状态,因此不会继续走绘制流程。

至于为什么第一帧有没问题,而从第二帧开始就出问题了,因为第一帧的时候,ViewRootImpl.mReportNextDraw为true,所以没有提前return,可以继续走draw流程。而从第二帧开始,ViewRootImpl.mReportNextDraw就变成了false,导致代码提前返回。

搜索代码,AttachInfo.mDisplayState赋值的地方只有四处:

1、View.dispatchMovedToDisplay

2、ViewRootImpl.onMovedToDisplay

3、ViewRootImpl.setView

4、ViewRootImpl.mDisplayListener.onDisplayChanged

前两处是跟移动到新的Display相关的,可以排除,第3处是窗口首次添加,也可以排除,那么就只有第4处是Display状态改变后,更新AttachInfo.mDisplayState的地方。

另外根据本地调试也的确如此,当发生亮灭屏时,触发的是ViewRootImpl.mDisplayListener.onDisplayChanged:

DisplayManagerGlobal.DisplayManagerCallback.onDisplayEvent

-> DisplayManagerGlobal.handleDisplayEvent

-> DisplayManagerGlobal.DisplayListenerDelegate.sendDisplayEvent

在这里插入图片描述

最终的调用起点在系统进程的DisplayManagerService。

由于之前对DisplayManagerService了解比较少,所以还不清楚问题出在哪儿,这次加的log不太够,需要继续在DisplayManagerService处加log,然后让测试帮忙去复现问题。

但是同时别的同事也合入了几个google的patch,导致后续问题复现不到了,这个问题一度没有了下文,当时觉得还挺可惜,花了挺长的时间去分析,结果后面复现不到了。

4 第二次添加log

虽然之前的问题不了了之了,但是后面别的项目又继续复现了该问题,希望又被点燃了…先在DisplayManagerService中添加一些log,然后继续让测试同事帮忙复现,最终成功复现到了问题。

以下是当时的log分析:

4.1 11-12 22:25:14 - 第一次启动抖音

在这里插入图片描述

这个是后续发生问题的抖音界面第一次启动的时间,此时是正常的。

中间又多次启动抖音以及亮灭屏,但是都没有问题,所以我们跳过这些阶段。

直接看发生问题前的那次启动抖音的情况。

4.2 11-13 10:33:39 - 再次启动抖音

在这里插入图片描述

ViewRootImpl保存的DisplayState已经是2了,即Display.STATE_ON,这次绘制没有问题。

4.3 11-13 10:53:42 - 灭屏

在这里插入图片描述

此次DisplayManagerService向SplashActivity对应进程“17885”进行了通知。

最终抖音对应进程“17885”也打印了相应的onDisplayChanged的log。

4.4 11-13 10:55:07 - 亮屏

在这里插入图片描述

DisplayManagerService没有通知抖音对应进程“17885”,有以下log打印:

11-13 10:55:07.252351  1450  1575 I DisplayManagerService: DisplayManagerService.deliverDisplayEvent: isUidCached=10224
11-13 10:55:07.252761  1450  1575 I DisplayManagerService: DisplayManagerService.deliverDisplayEvent: isUidCached=10224
11-13 10:55:07.253925  1450  1575 D DisplayManagerService: Ignore redundant display event 0/2 to 10224/16998
11-13 10:55:07.253967  1450  1575 I DisplayManagerService: DisplayManagerService.deliverDisplayEvent: isUidCached=10224
11-13 10:55:07.253978  1450  1575 D DisplayManagerService: Ignore redundant display event 0/2 to 10224/16998

4.5 11-13 10:55:16 - 灭屏

在这里插入图片描述

DisplayManagerService没有通知抖音对应进程“17885”。

4.6 11-13 13:00:00 - 出现问题前的最后一次亮屏

在这里插入图片描述

DisplayManagerService还是没有通知抖音对应进程“17885”。

4.7 11-13 13:24:18 - 再次启动抖音,有问题

在这里插入图片描述

原因则是“11-13 10:53:42”那次灭屏DisplayManagerService通知到了抖音对应进程“17885”,随后不管是亮屏还是灭屏都没有再通知抖音进程,导致此次启动抖音SplashActivity后,其ViewRootImpl处保存的DisplayState还是1,即Display.STATE_OFF,灭屏状态,所以不会走绘制流程。

接下来需要看下具体的代码,为什么没有通知到抖音进程,并且打印了这些log:

11-13 10:55:07.252351  1450  1575 I DisplayManagerService: DisplayManagerService.deliverDisplayEvent: isUidCached=10224
11-13 10:55:07.252761  1450  1575 I DisplayManagerService: DisplayManagerService.deliverDisplayEvent: isUidCached=10224
11-13 10:55:07.253925  1450  1575 D DisplayManagerService: Ignore redundant display event 0/2 to 10224/16998
11-13 10:55:07.253967  1450  1575 I DisplayManagerService: DisplayManagerService.deliverDisplayEvent: isUidCached=10224
11-13 10:55:07.253978  1450  1575 D DisplayManagerService: Ignore redundant display event 0/2 to 10224/16998

5 代码分析

5.1 App侧注册display状态的监听

ViewRootImpl有一个DisplayListener的成员变量mDisplayListener,该成员变量用来接收DisplayManagerService关于Display状态改变的通知:

在这里插入图片描述

当onDisplayChanged回调触发,会更新mAttachInfo.mDisplayState的值。

该成员变量通过DisplayManagerGlobal.registerDisplayListener进行注册,最终是调用了IDisplayManager.registerCallbackWithEventMask,跨进程在DisplayManagerService进行了注册,如下图:

在这里插入图片描述

DisplayManagerService内部类BinderService是IDisplayManager的本地实现,然后BinderService.registerCallbackWithEventMask又调用了DisplayManagerService.registerCallbackInternal:

在这里插入图片描述

最终,DisplayManagerService为每一个通过IDisplayManager.registerCallbackWithEventMask监听Display状态的客户端,都创建了一个CallbackRecord对象,用来代表一个客户端记录,该CallbackRecord被保存在DisplayManagerService的成员变量mCallbacks中。

至于CallbackRecord,其内部保存了客户端的pid和uid,因此可以唯一对应一个客户端。

5.2 DisplayManagerService通知客户端Display状态改变

直接放结论,在CallbackRecord.notifyDisplayEventAsync中:

在这里插入图片描述

CallbackRecord中的IDisplayManagerCallback类型的成员变量mCallback保存的是客户端进程传过来的IDisplayManagerCallback代理,因此调用IDisplayManagerCallback.onDisplayEvent最终可以调用到客户端进程的DisplayManagerGlobal:

在这里插入图片描述

最终通知到ViewRootImpl。

CallbackRecord.notifyDisplayEventAsync方法调用的地方有两处,先看第一处,在DisplayManagerService.deliverDisplayEvent:

在这里插入图片描述

这里会将DisplayManagerService.mCallbacks的数据拷贝到DisplayManagerService.mTempCallbacks,然后调用DisplayManagerService.isUidCached方法来判断需要通知的进程是否处于cached mode:

在这里插入图片描述

如果不是,那么说明此时该客户端进程的优先级比较高,需要直接调用CallbackRecord.notifyDisplayEventAsync来通知客户端Display状态改变的消息。

如果客户端进程处于cached mode,那么说明该进程处于一个优先级较低的状态,因此不会立即调用CallbackRecord.notifyDisplayEventAsync,而是创建一个PendingCallback对象,然后保存到DisplayManagerService的mPendingCallbackSelfLocked成员变量中,注意这里mPendingCallbackSelfLocked是根据uid去区分的,并非是pid。

等到客户端进程的进程优先级提高后,再去从DisplayManagerService.mPendingCallbackSelfLocked中取出相应的CallbackRecord,然后调用CallbackRecord.notifyDisplayEventAsync方法通知客户端进程,这也就是第二处调用CallbackRecord.notifyDisplayEventAsync的地方,在PendingCallback.sendPendingDisplayEvent:

在这里插入图片描述

而PendingCallback.sendPendingDisplayEvent方法则由UidImportanceListener.onUidImportance方法调用。

这里的逻辑很好理解,当进程的优先级改变的时候,会回调UidImportanceListener.onUidImportance,然后UidImportanceListener.onUidImportance中继续判断该进程的优先级,如果优先级高于IMPORTANCE_CACHED,那么会继续根据该进程的uid从DisplayManagerService.mPendingCallbackSelfLocked中找其对应的PendingCallback,如果能找到,那么调用PendingCallback.sendPendingDisplayEvent,最终在PendingCallback.sendPendingDisplayEvent中调用CallbackRecord.notifyDisplayEventAsync来通知客户端Display状态的改变。

5.3 省流版

概括一点说,就是并不是每次Display状态发生变化的时候,DisplayMangerService就会去通知抖音进程,而是会调用DisplayManagerService.isUidCached方法来看这个App进程的优先级是否足够高:

1)、如果优先级高于IMPORTANCE_CACHED,说明此时优先级还是挺高的,那么DisplayManagerService会立即通知抖音进程。

2)、如果优先级等于或者低于IMPORTANCE_CACHED,说明优先级不够高,那么DisplayManagerService不会立即通知抖音进程,而是为其创建一个记录,等到抖音进程的优先级改变并且高于IMPORTANCE_CACHED的时候,根据这个记录找到抖音进程,然后通知它。

6 原因分析

1)、首先是这次:”11-13 10:33:39 - 再次启动抖音“

这是离问题发生时间点最近的那次启动抖音。

2)、然后接着:”11-13 10:53:42 - 灭屏“

看到这次灭屏的时候,可能是由于距离启动抖音的时间不长,因此这时抖音进程的优先级还比较高,所以这次灭屏的时候,通知到了抖音进程17885。

3)、接着是”11-13 10:55:07 - 亮屏“

这里我们之前的分析有误,看了代码后才了解,不是说DisplayManagerService没有通知抖音进程17885,而是抖音进程的优先级比较低,所以没有立即通知抖音进程,而是为其创建了PendingCallback,等待抖音进程优先级提高后,再去通知抖音进程,如这里的log:

在这里插入图片描述

但是这里的逻辑有问题。

如log中显示的,这里抖音App的uid为10224,有三个进程,16998,17885,18213,我们关注的抖音的”SplashActivity”所在进程是17885.

然后再看代码:

在这里插入图片描述

首先对于抖音进程16998,由于这是遍历到的抖音的第一个进程,因此DisplayManagerService.mPendingCallbackSelfLocked中是没有抖音uid的信息的,所以这里会基于进程16998对应的CallbackRecord创建一个PendingCallback对象,然后保存到DisplayManagerService.mPendingCallbackSelfLocked中。

然后遍历到抖音进程17885的时候,由于它和抖音进程16998的uid是一样的,都是10224,那么这里就不会为进程17885再去创建一个PendingCallback对象, 而是直接复用上一步创建的那个PendingCallback,如这里的log打印:

”Ignore redundant display event…“

那么这里就是问题的根因所在了,抖音有三个进程,这里只为抖音进程16998对应的CallbackRecord创建了一个PendingCallback,到后续抖音进程优先级提高的时候,那么也只会调用该PendingCallback中的CallbackRecord的notifyDisplayEventAsync方法,那么这意味着只有进程16998会得到Display状态改变的通知,进程17885和进程18213都无法得到通知,再看后面抖音进程优先级提升的情况:

在这里插入图片描述

看到果然只有进程16998得到通知了,我们关注的进程17885没有被通知到,那么进程17885的ViewRootImpl保存的mAttachInfo.mDisplayState将一直是灭屏的状态。

不太省流的省流版

目前的代码,在一个App对应一个uid,以及一个pid的情况下,是够用的,但是实际上一个App对应了一个uid,但是可能会有多个进程,即一个uid会对应多个pid。

再回看我们的代码总结:如果App优先级等于或者低于IMPORTANCE_CACHED,说明优先级不够高,那么DisplayManagerService不会立即通知抖音进程,而是为其创建一个记录,等到抖音进程的优先级改变并且高于IMPORTANCE_CACHED的时候,根据这个记录找到抖音进程,然后通知它。

这里创建的记录,是根据uid去创建的,那么即使抖音有多个进程,也只会创建一个记录,根据遍历顺序来看则是pid最小的那个。

再看问题场景:

1)、启动完抖音后灭屏,此时抖音的优先级比较高,因此DisplayMangerService会立即通知抖音进程,这里抖音的三个进程都能通知到,因此进程17885的ViewRootImpl处保存的Display状态就是灭屏状态。

2)、过了一阵子再次亮屏,由于抖音的优先级降低了,所以DisplayManagerService没有立即通知抖音进程,而是创建为抖音创建了一个记录,等到抖音优先级提高的时候,再去通知抖音进程。此时抖音有三个进程,16998,17885,18213,这里只为pid最小的16998创建了一项纪录。

3)、后续抖音优先级提高的时候,DisplayMangerService只通知了进程16998,由于没有为剩下的两个进程创建记录,因此这两个进程就通知不到了,所以我们关注的进程17885出了问题。

从以上分析可以知道,我们需要为每一个pid都创建一个记录,而非每一个uid。

7 问题解决

最终看到已经有google patch解决这个问题了:

Diff - f2daa46634f6a1e5e329041f07a27dbc894d71b2^! - platform/frameworks/base - Git at Google

Correct the deferred sending of display events.When multiple processes sharing the same uid become non-cached, the
current deferred transmission logic does not consider all of them,
leading to only the first process in the callback list to receive the
pending display events. Moreover, this first process may inadvertently
receive unintended display events.Bug: 325692442
Test: manual, see bug descriptionChange-Id: Ife98a8d843a3000294fbc9929bb78a755534b5dc
Signed-off-by: Hyeongseop Shim <hyeongseop.shim@samsung.corp-partner.google.com>diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ba21a32..434985e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java@@ -464,10 +464,11 @@// May be used outside of the lock but only on the handler thread.private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();-    // Pending callback records indexed by calling process uid.
+    // Pending callback records indexed by calling process uid and pid.// Must be used outside of the lock mSyncRoot and should be selflocked.@GuardedBy("mPendingCallbackSelfLocked")
-    public final SparseArray<PendingCallback> mPendingCallbackSelfLocked = new SparseArray<>();
+    public final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =
+            new SparseArray<>();// Temporary viewports, used when sending new viewport information to the// input system.  May be used outside of the lock but only on the handler thread.
@@ -1011,8 +1012,8 @@}// Do we care about this uid?
-                PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
-                if (pendingCallback == null) {
+                SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(uid);
+                if (pendingCallbacks == null) {return;}@@ -1020,7 +1021,12 @@if (DEBUG) {Slog.d(TAG, "Uid " + uid + " becomes " + importance);}
-                pendingCallback.sendPendingDisplayEvent();
+                for (int i = 0; i < pendingCallbacks.size(); i++) {
+                    PendingCallback pendingCallback = pendingCallbacks.valueAt(i);
+                    if (pendingCallback != null) {
+                        pendingCallback.sendPendingDisplayEvent();
+                    }
+                }mPendingCallbackSelfLocked.delete(uid);}}
@@ -3193,16 +3199,23 @@for (int i = 0; i < mTempCallbacks.size(); i++) {CallbackRecord callbackRecord = mTempCallbacks.get(i);final int uid = callbackRecord.mUid;
+            final int pid = callbackRecord.mPid;if (isUidCached(uid)) {// For cached apps, save the pending event until it becomes non-cachedsynchronized (mPendingCallbackSelfLocked) {
-                    PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+                    SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(
+                            uid);if (extraLogging(callbackRecord.mPackageName)) {
-                        Slog.i(TAG,
-                                "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+                        Slog.i(TAG, "Uid is cached: " + uid
+                                + ", pendingCallbacks: " + pendingCallbacks);}
+                    if (pendingCallbacks == null) {
+                        pendingCallbacks = new SparseArray<>();
+                        mPendingCallbackSelfLocked.put(uid, pendingCallbacks);
+                    }
+                    PendingCallback pendingCallback = pendingCallbacks.get(pid);if (pendingCallback == null) {
-                        mPendingCallbackSelfLocked.put(uid,
+                        pendingCallbacks.put(pid,new PendingCallback(callbackRecord, displayId, event));} else {pendingCallback.addDisplayEvent(displayId, event);

看注释,说的就是多个进程共享同一个uid的情况。

再看修改,将原来的:

public final SparseArray<PendingCallback> mPendingCallbackSelfLocked = new SparseArray<>();

改为了:

    public final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =new SparseArray<>();

很明显,之前是一个uid对应一个PendingCallback对象,现在是一个uid对应一组PendingCallback对象,即一个uid对应多个进程的情况,这样同一个uid下多个进程都能得到通知了,代码比较简单,不再赘述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/61131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 K-means 算法进行豆瓣读书数据的文本聚类分析

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Django5 2024全栈开发指南(二):Django项目配置详解

目录 一、基本配置信息二、资源文件配置2.1 资源路由——STATIC_URL2.2 资源集合——STATICFILES_DIRS2.3 资源部署——STATIC_ROOT2.2.4 媒体资源——MEDIA 三、模板配置四、数据库配置4.1 mysqlclient连接MySQL4.2 pymysql连接MySQL4.3 多个数据库的连接方式4.4 使用配置文件…

数据结构-二叉搜索树(Java语言)

目录 1.概念 2.查找search 3.插入insert ​编辑4.删除remove&#xff08;难点&#xff09; 5.性能分析 1.概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树 : 1.若它的左子树不为空&#xff0c;则左子树上所有节点的值都…

学习笔记:黑马程序员JavaWeb开发教程(2024.11.18)

9.8 Mybatis-基础操作-查询&#xff08;条件查询&#xff09; 需要模糊查询&#xff0c;根据要求&#xff0c;我们需要在关键词前后都加上%&#xff0c;但是我们不能使用‘%#{内容}%’的形式&#xff0c;因为#{内容}最终会变成?&#xff0c;而?不能放在‘’之中&#xff…

数据分析-48-时间序列变点检测之在线实时数据的CPD

文章目录 1 时间序列结构1.1 变化点的定义1.2 结构变化的类型1.2.1 水平变化1.2.2 方差变化1.3 变点检测1.3.1 离线数据检测方法1.3.2 实时数据检测方法2 模拟数据2.1 模拟恒定方差数据2.2 模拟变化方差数据3 实时数据CPD3.1 SDAR学习算法3.2 Changefinder模块3.3 恒定方差CPD3…

学习大数据DAY61 宽表加工

目录 模型设计 加工宽表 任务调度&#xff1a; 大表 - 把很多数据整合起来 方便后续的明细查询和指标计算 模型设计 设计 建模 设计: excel 文档去编写 建模: 使用建模工具 PowerDesigner Navicat 在线画图工具... 把表结构给绘 制出来 共享\项目课工具\pd 加工宽表 数…

C#.Net筑基-模式匹配汇总

01、模式匹配概述 从C#7开始支持的 模式匹配 语法&#xff08;糖&#xff0c;挺甜&#xff09;&#xff0c;可非常灵活的对数据进行条件匹配和提取&#xff0c;经过多个版本的完善&#xff0c;已经非常强大了。 C# 支持多种模式&#xff0c;包括声明、类型、常量、关系、属性…

Python蓝桥杯刷题1

1.确定字符串是否包含唯一字符 题解&#xff1a;调用count函数计算每一个字符出现的次数&#xff0c;如果不等于1就输出no&#xff0c;并且结束循环&#xff0c;如果等于1就一直循环直到计算到最后一个字符&#xff0c;若最后一个字符也满足条件&#xff0c;则输出yes import…

Unity类银河战士恶魔城学习总结(P127 Stat ToolTip属性提示)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了把鼠标放到属性上面就会显示属性的作用 UI_StatToolTip.cs 这段代码实现了一个UI提示框&#xff08;ToolTip&#xff09;功能…

计算机编程中的事件驱动编程模型及其在构建响应式用户界面中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机编程中的事件驱动编程模型及其在构建响应式用户界面中的应用 计算机编程中的事件驱动编程模型及其在构建响应式用户界面中…

ROS第九梯:ROS+VSCode+Python+C++自定义消息发布和订阅

首先,Python版本的ROS项目和C++版本的ROS项目前期创建功能包的步骤基本一致,具体可参考第二章。 费一步:新建msg文件 在功能包(data_input)目录下创建一个msg文件夹,并在msg文件夹下创建一个名为Box的msg文件,具体如下图所示: 该msg文件为一个用于描述3D Box的文件,…

selenium元素定位---元素点击交互异常解决方法

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、异常原因 在编写ui自动化时&#xff0c;执行报错元素无法点击&#xff1a;ElementClickInterceptedException 具体报错&#xff1a;selenium.common.exc…

Front Panel Window Bounds 与 Front Panel Window Bounds 的区别与应用

在LabVIEW中&#xff0c;Front Panel Window Bounds 和 Front Panel WindowBounds 是两个不同的属性节点&#xff0c;用于描述前面板窗口的位置和大小。它们的区别主要体现在它们表示的是窗口的不同部分&#xff0c;具体如下&#xff1a; 1 Window Bounds&#xff1a;调整整个…

H.265流媒体播放器EasyPlayer.js播放器出现加载视频等待画面时长过长的原因排查

在数字媒体时代&#xff0c;用户体验是衡量播放器性能的关键指标之一。EasyPlayer.js网页web无插件播放器作为一款流行的Web视频播放器&#xff0c;其加载速度和响应时间直接影响着用户的观看体验。 1、问题描述 加载视频等待画面时长过长。 2、可能的原因&#xff1a; 检查下…

计算机网络-MSTP基础实验一(单域多实例)

前面我们已经大致了解了MSTP的基本概念和工作原理&#xff0c;但是我自己也觉得MSTP的理论很复杂不结合实验是很难搞懂的&#xff0c;今天来做一个配套的小实验以及一些配置命令。 一、网络拓扑 单域多实例拓扑 基本需求&#xff1a;SW1为VLAN10的网关&#xff0c;SW2为VLAN20的…

大数据-227 离线数仓 - Flume 自定义拦截器(续接上节) 采集启动日志和事件日志

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…

【langchain4j】AIservices能够实现更加灵活的chain

文章目录 AI service介绍如何工作的AiServices提供的能力支持的返回形式 简单的例子&#xff1a;接收用户消息&#xff0c;并按规定返回接收单个变量接收更多动态变量 advanced RAGChaining multiple AI Services&#xff1a;多个AiSerives合并到一起相关教程&#xff1a;[Lang…

【UGUI】背包的交互01(道具信息跟随鼠标+道具信息面板显示)

详细程序逻辑过程 初始化物品栏&#xff1a; 在 Awake 方法中&#xff0c;通过标签找到提示框和信息面板。 循环生成10个背包格子&#xff0c;并为每个格子设置图标和名称。 为每个格子添加 UInterMaager232 脚本&#xff0c;以便处理交互事件。 关闭提示框和信息面板&#…

同步互斥相关习题10道 附详解

PV操作 2016 某系统允许最多10个进程同时读文件F&#xff0c;当同时读文件F的进程不满10个时&#xff0c;欲读该文件的其他文件可立即读&#xff0c;当已有10个进程在读文件F时读&#xff0c;其他欲读文件F的进程必须等待&#xff0c;直至有进程读完后退出方可去读 在实现管…

Postman之数据提取

Postman之数据提取 1. 提取请求头\request中的数据2. 提取响应消息\response中的数据3. 通过正在表达式提取4. 提取cookies数据 本文主要讲解利用pm对象对数据进行提取操作&#xff0c;虽然postman工具的页面上也提供了一部分的例子&#xff0c;但是实际使用时不是很全面&#…