frameworks 之 Focus window
frameworks 之 Focus window
- 1 查看当前焦点窗口
- 1.1 dump window
- 1.2 dump input
- 1.3 input Events 日志
- 1.4 dump SurfaceFlinger
- 1.5 查看关键的日志
- 2 更新当前焦点应用
- 3 更新当前焦点窗口
- 3.1 焦点丢失
- 3.2 获得窗口焦点
- 4 窗口信息到InputDispatcher
- 4.1 焦点应用通知InputDispatcher
- 4.2 焦点窗口通知InputDispatcher
- 4.2.1 更新窗口信息 setInputWindowInfo
- 4.2.1.1 WMS通知到SurfaceFlinger
- 4.2.1.2 SurfaceFlinger 到 InputDIspatcher
- 4.2.2 请求窗口焦点
- 4.2.2.1 WMS通知到SurfaceFlinger
- 4.2.2.2 SurfaceFlinger 到 InputDIspatcher
- 5 打印 Focus entering" 或 Focus leaving
- 3 堆栈
- 3.1 setFocusApp
讲解 焦点窗口如何显示以及如何查看当前焦点窗口
相关知识点
- SurfaceFlinger是Android系统中负责合成各个层(Layer)并将其显示到屏幕上的服务。应用通过SurfaceComposerClient与SurfaceFlinger通信,Transaction则是用来收集一批图层状态变更,然后一次性提交,这样可以提高效率,减少多次IPC调用的开销
涉及到的类如下
- frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
- frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
- frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
- frameworks/base/services/core/java/com/android/server/wm/WindowState.java
- frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
- frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
- frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
- frameworks/base/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
- frameworks/base/core/java/android/view/SurfaceControl.java
- frameworks/native/libs/gui/SurfaceComposerClient.cpp
- frameworks/base/core/jni/android_view_SurfaceControl.cpp
- rameworks/base/core/jni/android_hardware_input_InputWindowHandle.cpp
- frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
- frameworks/native/services/surfaceflinger/WindowInfosListenerInvoker.cpp
- frameworks/native/services/inputflinger/dispatcher/FocusResolver.cpp
- frameworks/native/services/inputflinger/InputManager.cpp
相关流程图,引用 该博客文章 《Android焦点之FocusWindow切换》 和 《Android焦点之SurfaceFlinger传递给InputFinger》
请求窗口焦点
设置窗口信息
1 查看当前焦点窗口
1.1 dump window
adb shell dumpsys window
执行后部分内容如下
- WINDOW MANAGER LAST ANR 打印出上次 ANR的原因,也会包含ANR时候的窗口信息,如 mCurrentFocus,mFocusedApp
- mCurrentFocus 表示当前焦点窗口
- mFocusedApp 表示当前焦点应用
WINDOW MANAGER LAST ANR (dumpsys window lastanr)ANR time: 2025年2月21日 上午6:36:09Application at fault: ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivityReason: Application does not have a focused windowWindows added in display #0 since null focus: [Window{87d5194 u0 com.example.mysystemdialog/com.example.mysystemdialog.MainActivity}]Windows removed in display #0 since null focus: [Window{26b1193 u0 Splash Screen com.example.mysystemdialog}]....
WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)mLastOrientationSource=WindowedMagnification:0:31@59915038deepestLastOrientationSource=ActivityRecord{d4b3e0 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t14}overrideConfig={1.0 310mcc260mnc [zh_CN_#Hans,en_US] ldltr sw411dp w411dp h773dp 560dpi nrml long port finger qwerty/v/v dpad/v winConfig={ mBounds=Rect(0, 0 - 1440, 2960) mAppBounds=Rect(0, 0 - 1440, 2792) mMaxBounds=Rect(0, 0 - 1440, 2960) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.30 fontWeightAdjustment=0}Display: mDisplayId=0 rootTasks=4init=1440x2960 560dpi cur=1440x2960 app=1440x2792 rng=1440x1356-2792x2708deferred=false mLayoutNeeded=false mTouchExcludeRegion=SkRegion((0,0,1440,2960))mLayoutSeq=636mCurrentFocus=Window{ea70127 u0 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher}mFocusedApp=ActivityRecord{d4b3e0 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t14}
如果只想查看 ANR 也可以直接执行如下命令查看.
WINDOW MANAGER LAST ANR (dumpsys window lastanr)ANR time: 2025年2月21日 上午6:36:09Application at fault: ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivityReason: Application does not have a focused windowWindows added in display #0 since null focus: [Window{87d5194 u0 com.example.mysystemdialog/com.example.mysystemdialog.MainActivity}]Windows removed in display #0 since null focus: [Window{26b1193 u0 Splash Screen com.example.mysystemdialog}]
...
Last ANR continued
WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)mLastOrientationSource=WindowedMagnification:0:31@59915038deepestLastOrientationSource=ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivity t19}overrideConfig={1.0 310mcc260mnc [zh_CN_#Hans,en_US] ldltr sw411dp w411dp h773dp 560dpi nrml long port finger qwerty/v/v dpad/v winConfig={ mBounds=Rect(0, 0 - 1440, 2960) mAppBounds=Rect(0, 0 - 1440, 2792) mMaxBounds=Rect(0, 0 - 1440, 2960) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.30 fontWeightAdjustment=0}Display: mDisplayId=0 rootTasks=5init=1440x2960 560dpi cur=1440x2960 app=1440x2792 rng=1440x1356-2792x2708deferred=false mLayoutNeeded=false mTouchExcludeRegion=SkRegion((0,0,1440,2960))
1.2 dump input
执行如下命令
adb shell dumpsys input
打印内容如下
- FocusedApplications 表示当前焦点的应用
- FocusedWindows 表示当前的焦点窗口
- Input Dispatcher State at time of last ANR 开始记录上一次ANR的状态
Input Dispatcher State:DispatchEnabled: trueDispatchFrozen: falseInputFilterEnabled: falseFocusedDisplayId: 0FocusedApplications:displayId=0, name='ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivity t19}', dispatchingTimeout=5000msFocusedWindows:displayId=0, name='577c5c1 Application Not Responding: com.example.mysystemdialog'FocusRequests:displayId=0, name='577c5c1 Application Not Responding: com.example.mysystemdialog' result='OK'Input Dispatcher State at time of last ANR:ANR:Time: 2025-02-21 06:36:09Reason: ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivity t19} does not have a focused windowWindow: ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivity t19}
...FocusedApplications:displayId=0, name='ActivityRecord{7f16991 u0 com.example.mysystemdialog/.MainActivity t19}', dispatchingTimeout=5000msFocusedWindows: <none>
1.3 input Events 日志
也可以过滤日志查看当前的焦点
adb shell logcat -b events | grep input_focus
执行后可以看到如下日志
- Focus leaving 表示输入正在取消
- Focus request 表示输入请求
- Focus entering 表示输入焦点在这个window
02-21 06:36:02.570 6677 6764 I input_focus: [Focus leaving ea70127 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher (server),reason=NO_WINDOW]
02-21 06:36:10.304 6677 6700 I input_focus: [Focus request 577c5c1 Application Not Responding: com.example.mysystemdialog,reason=UpdateInputWindows]
02-21 06:36:10.371 6677 6764 I input_focus: [Focus entering 577c5c1 Application Not Responding: com.example.mysystemdialog (server),reason=Window became focusable. Previous reason: NOT_VISIBLE]
1.4 dump SurfaceFlinger
执行如下命令可以查看,显示如下,HWC是决定显示的图层,后面打 * 号 表示当前的焦点
1.5 查看关键的日志
打开对应的日志命令
adb shell wm logging enable-text WM_DEBUG_SCREEN_ON WM_DEBUG_APP_TRANSITIONS WM_DEBUG_FOCUS
过滤日志关键词
adb shell logcat | grep -IE "Changing focus from|findFocusedWindow: No focusable|setFocusedApp|setAppVisibility|Relayout "
2 更新当前焦点应用
当窗口的弹出,并不会改变当前焦点应用。只有改变应用切换才会触发,各种情况触发堆栈如3.1 打印。所以存在如焦点窗口是systemUi, 焦点应用是应用等情况。触发变化大部分都是从 ActivityTaskManagerService 的 setResumedActivityUncheckLocked 当更改ActivityReord 状态为resume状态的时候。
void setResumedActivityUncheckLocked(ActivityRecord r, String reason) {....mLastResumedActivity = r;// 通过设置可见的时候,就会执行displayContent的setFocusedApp方法去更新当前焦点的mFocusedApp变量// 拉出状态栏等操作并不会改变该焦点应用final boolean changed = r.mDisplayContent.setFocusedApp(r);if (changed) {mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,true /*updateInputWindows*/);}
会调用 displayContent 的 setFocusedApp 设置当前焦点的APP,如果有改变 则又会调 WindowManagerService 的 updateFocusedWindowLocked 进行更新窗口焦点
boolean setFocusedApp(ActivityRecord newFocus) {...final Task oldTask = mFocusedApp != null ? mFocusedApp.getTask() : null;final Task newTask = newFocus != null ? newFocus.getTask() : null;mFocusedApp = newFocus;if (oldTask != newTask) {if (oldTask != null) oldTask.onAppFocusChanged(false);if (newTask != null) newTask.onAppFocusChanged(true);}...}
接着又调用
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);return changed;}
3 更新当前焦点窗口
当启动一个activity的时候 会经历2个阶段,焦点为null ,到焦点存在。
当 新Activity 启动的时候 进入 resume 和 上一个Activity就会进入 onPause 状态
3.1 焦点丢失
当进入桌面onPause的时候,会通过 setVisibility 设置桌面应用不可见
- 将对应打开中和关闭中的app移除,后续在根据可见状态添加
- 调用 setVisibleRequested 设置对应的变量
void setVisibility(boolean visible, boolean deferHidingClient) {...ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,"setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",appToken, visible, appTransition, isVisible(), mVisibleRequested,Debug.getCallers(6));final DisplayContent displayContent = getDisplayContent();// 先将要打开的app和关闭的app移除,后面在根据visibledisplayContent.mOpeningApps.remove(this);displayContent.mClosingApps.remove(this);waitingToShow = false;// 当前设置是否可见的时候,会将mVisibleRequested赋值,用于判断canReceiveKeys是否可触摸setVisibleRequested(visible);...}
setVisibleRequested 方法主要将 mVisibleRequested 设置为false ,后续判断是否可见接收事件会用到该变量
private void setVisibleRequested(boolean visible) {if (visible == mVisibleRequested) {return;}// 设置可见的请求mVisibleRequested = visible;setInsetsFrozen(!visible);if (app != null) {mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);}logAppCompatState();}
对应的日志打印包括堆栈如下
02-23 01:19:35.788 561 9003 V WindowManager: setAppVisibility(Token{5253e43 ActivityRecord{3fcedf9 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t25}}, visible=false): mNextAppTransitionRequests=[TRANSIT_OPEN, TRANSIT_OPEN], mNextAppTransitionFlags= visible=true mVisibleRequested=true Callers=com.android.server.wm.ActivityRecord.setVisibility:4784 com.android.server.wm.ActivityRecord.makeInvisible:5488 com.android.server.wm.EnsureActivitiesVisibleHelper.setActivityVisibilityState:224 com.android.server.wm.EnsureActivitiesVisibleHelper.process:140 com.android.server.wm.TaskFragment.updateActivityVisibilities:979 com.android.server.wm.Task.lambda$ensureActivitiesVisible$15:4866
在进入onPause的时候,又让对应的应用变为onResume状态,此时启动的应用也会调用对应的 setVisibility,将变量 mVisibleRequested 设置为true ,注意此时启动的会启动的windowState是闪屏页,目标的Activity还没创建AddView。
在设置 状态onActivityStateChanged的时候,如果是onResume 即会进入setResumedActivityUncheckLocked开始寻找对应的 窗口,看是否改变
void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,String reason) {warnForNonLeafTaskFragment("onActivityStateChanged");if (record == mResumedActivity && state != RESUMED) {setResumedActivity(null, reason + " - onActivityStateChanged");}if (state == RESUMED) {if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason);}setResumedActivity(record, reason + " - onActivityStateChanged");if (record == mRootWindowContainer.getTopResumedActivity()) {mAtmService.setResumedActivityUncheckLocked(record, reason);}mTaskSupervisor.mRecentTasks.add(record.getTask());}}
setResumedActivityUncheckLocked 方法会调用 displayContent的 setFocusedApp 方法设置焦点
void setResumedActivityUncheckLocked(ActivityRecord r, String reason) {...mLastResumedActivity = r;// 通过设置可见的时候,就会执行displayContent的setFocusedApp方法去更新当前焦点的mFocusedApp变量// 拉出状态栏等操作并不会改变该焦点应用final boolean changed = r.mDisplayContent.setFocusedApp(r);if (changed) {mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,true /*updateInputWindows*/);}...}
接着又调用 RootWindowContainer的updateFocusedWindowLocked
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);return changed;}
又会遍历各自的displayContent 的 updateFocusedWindowLocked 更新焦点
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {mTopFocusedAppByProcess.clear();boolean changed = false;int topFocusedDisplayId = INVALID_DISPLAY;for (int i = mChildren.size() - 1; i >= 0; --i) {final DisplayContent dc = mChildren.get(i);// 更新对应窗口焦点changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);}}
updateFocusedWindowLocked
- 会先通过 findFocusedWindowIfNeeded 遍历获取对应的窗口
- 判断是否和当前一样,是的话返回false,否则将新的窗口赋值给 mCurrentFocus
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,int topFocusedDisplayId) {// 查找新焦点,如果界面setVisibility为false则没焦点WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);// 如果是一样的则返回if (mCurrentFocus == newFocus) {return false;}...// 赋值新的焦点给mCurrentFocus变量ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));final WindowState oldFocus = mCurrentFocus;mCurrentFocus = newFocus;}
findFocusedWindowIfNeeded 方法通过 findFocusedWindow 方法(判断实现为 mFindFocusedWindow)遍历获取,并将符合条件的保存到 mTmpWindow, 并返回。
WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)? findFocusedWindow() : null;}WindowState findFocusedWindow() {mTmpWindow = null;// 开始从上到下遍历查找,查找到保存到 mTmpWindow 变量forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);if (mTmpWindow == null) {ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",getDisplayId());return null;}return mTmpWindow;}
mFindFocusedWindow 主要通过 WindowState的canReceiveKeys 方法
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {// 当前聚焦的应用final ActivityRecord focusedApp = mFocusedApp;ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",w, w.mAttrs.flags, w.canReceiveKeys(),w.canReceiveKeysReason(false /* fromUserTouch */));// 判断当前是否能接收事件,主要依靠mVisibleRequested属性// 而该属性在 setVisisable设置,表示是否可见if (!w.canReceiveKeys()) {return false;}}
canReceiveKeys 方法判断如下,通过isVisibleRequestedOrAdding,可见和是否没带 FLAG_NOT_FOCUSABLE等变量判断
public String canReceiveKeysReason(boolean fromUserTouch) {return "fromTouch= " + fromUserTouch+ " isVisibleRequestedOrAdding=" + isVisibleRequestedOrAdding()+ " mViewVisibility=" + mViewVisibility+ " mRemoveOnExit=" + mRemoveOnExit+ " flags=" + mAttrs.flags+ " appWindowsAreFocusable="+ (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))+ " canReceiveTouchInput=" + canReceiveTouchInput()+ " displayIsOnTop=" + getDisplayContent().isOnTop()+ " displayIsTrusted=" + getDisplayContent().isTrusted();}public boolean canReceiveKeys(boolean fromUserTouch) {final boolean canReceiveKeys = isVisibleRequestedOrAdding()&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))&& canReceiveTouchInput();if (!canReceiveKeys) {return false;}// Do not allow untrusted virtual display to receive keys unless user intentionally// touches the display.return fromUserTouch || getDisplayContent().isOnTop()|| getDisplayContent().isTrusted();}
而 isVisibleRequestedOrAdding 方法 主要就是依赖刚才的变量 mVisibleRequested
boolean isVisibleRequestedOrAdding() {final ActivityRecord atoken = mActivityRecord;return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))&& isVisibleByPolicy() && !isParentWindowHidden()&& (atoken == null || atoken.mVisibleRequested)&& !mAnimatingExit && !mDestroying;}
所以 因为桌面设置了不可见 和新启动的windowState是 Splash Screen 虽然可见,但是携带了 FLAG_NOT_FOCUSABLE ,所以遍历下来,此时没有可聚焦的窗口,就会返回null.。这样 当前的 mCurrentFocus 变为了 null
3.2 获得窗口焦点
- 当对应的Activity启动后,进入realStartActivityLocked方法 时候,就会触发 setVisibility,将变量 mVisibleRequested 设置为true
具体日志如下
02-23 01:19:35.951 561 9003 V WindowManager: setAppVisibility(Token{279e3b3 ActivityRecord{b8ad70 u0 com.android.gallery3d/.app.GalleryActivity t34}}, visible=true): mNextAppTransitionRequests=[], mNextAppTransitionFlags= visible=true mVisibleRequested=true Callers=com.android.server.wm.ActivityRecord.setVisibility:4784 com.android.server.wm.ActivityRecord.completeResumeLocked:5695 com.android.server.wm.Task.minimalResumeActivityLocked:4784 com.android.server.wm.ActivityTaskSupervisor.realStartActivityLocked:938 com.android.server.wm.RootWindowContainer.startActivityForAttachedApplicationIfNeeded:2017 com.android.server.wm.RootWindowContainer.$r8$lambda$auelgeOhCvbItmS_07q5VFEb1ac:0
- 当窗口焦点丢失后,启动的Activity启动后,就会通过 addview 触发3部曲中的 relayout
WMS的 relayoutWindow 方法当检测到窗口从不可见变为可见,可见性有变化的时候或者flag有变化,focusMayChange 即会变为 true。所以就会调用 updateFocusedWindowLocked
public int relayoutWindow(...){// 如果可见不一样 或者flagFLAG_NOT_FOCUSABL有变化boolean focusMayChange = win.mViewVisibility != viewVisibility|| ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)|| (!win.mRelayoutCalled);// 如果窗口改变则执行该方法去更新对应屏幕的焦点窗口if (focusMayChange) {if (updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/)) {imMayMove = false;}}
}
updateFocusedWindowLocked 上面已讲解,此时 目标应用 已经为可见可触摸 ,所以可以查询到 ,所以焦点会变更为 桌面焦点。这样焦点就恢复了
4 窗口信息到InputDispatcher
当WMS保存了对应的焦点应用和窗口后,也要同步到 Input端。DisplayContent 负责传输的类是InputMonitor 。
4.1 焦点应用通知InputDispatcher
从上面流程可以看出 displayContent的时候会调用 InputMonitor对应的 setFocusedAppLw 方法开始通知
boolean setFocusedApp(ActivityRecord newFocus) {///ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "setFocusedApp %s displayId=%d Callers=%s",newFocus, getDisplayId(), Debug.getCallers(4));final Task oldTask = mFocusedApp != null ? mFocusedApp.getTask() : null;final Task newTask = newFocus != null ? newFocus.getTask() : null;mFocusedApp = newFocus;if (oldTask != newTask) {if (oldTask != null) oldTask.onAppFocusChanged(false);if (newTask != null) newTask.onAppFocusChanged(true);}// 将新的焦点通过mInputMonitor传递给inputgetInputMonitor().setFocusedAppLw(newFocus);
}
而 setFocusedAppLw 直接调用 InputManagerService 的 setFocusedApplication 方法。
getInputApplicationHandle 在 ActivityRecord 构造了 InputApplicationHandle 对象,用于跟native交互。里面的token 为 ActivityRecord 的appToken,属于ibinder对象
void setFocusedAppLw(ActivityRecord newApp) {// Focused app has changed.// 构建 InputApplicationHandle对象,将ActivityRecord的AIDL对象appToken传递进去mService.mInputManager.setFocusedApplication(mDisplayId,newApp != null ? newApp.getInputApplicationHandle(true /* update */) : null);}
InputManagerService 又会调用 native方法 nativeSetFocusedApplication
public void setFocusedApplication(int displayId, InputApplicationHandle application) {nativeSetFocusedApplication(mPtr, displayId, application);}
查看 com_android_server_input_InputManagerService.cpp 文件的 nativeSetFocusedApplication 方法,又会调用 NativeInputManager 的 setFocusedApplication
static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,jlong ptr, jint displayId, jobject applicationHandleObj) {NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);im->setFocusedApplication(env, displayId, applicationHandleObj);
}
NativeInputManager 又会继续调用 inputDIspatcher的 setFocusedApplication
void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,jobject applicationHandleObj) {if (!applicationHandleObj) {return;}std::shared_ptr<InputApplicationHandle> applicationHandle =android_view_InputApplicationHandle_getHandle(env, applicationHandleObj);applicationHandle->updateInfo();// 调用 inputDispater的setFocusedApplication方法mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle);
}
InputDispatcher 又会调用 setFocusedApplicationLocked
void InputDispatcher::setFocusedApplication(int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {if (DEBUG_FOCUS) {ALOGD("setFocusedApplication displayId=%" PRId32 " %s", displayId,inputApplicationHandle ? inputApplicationHandle->getName().c_str() : "<nullptr>");}{ // acquire lockstd::scoped_lock _l(mLock);// 设置当前对应的ApplicationsetFocusedApplicationLocked(displayId, inputApplicationHandle);} // release lock// Wake up poll loop since it may need to make new input dispatching choices.mLooper->wake();
}
该方法会从 mFocusedApplicationHandlesByDisplay 根据displayId 获取之前聚焦的应用,如果相等不处理,不相等的话则替换放到 mFocusedApplicationHandlesByDisplay, 为空的话则移除
void InputDispatcher::setFocusedApplicationLocked(int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {// 从mFocusedApplicationHandlesByDisplay获取当前的已聚焦的应用std::shared_ptr<InputApplicationHandle> oldFocusedApplicationHandle =getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);// 判断是否相等if (sharedPointersEqual(oldFocusedApplicationHandle, inputApplicationHandle)) {return; // This application is already focused. No need to wake up or change anything.}// Set the new application handle.// r如果为空则移除,否则就根据displayId 保存inputApplicationHandleif (inputApplicationHandle != nullptr) {mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;} else {mFocusedApplicationHandlesByDisplay.erase(displayId);}// No matter what the old focused application was, stop waiting on it because it is// no longer focused.resetNoFocusedWindowTimeoutLocked();
}
这样就将当前聚焦的应用传递到InputDispatcher
4.2 焦点窗口通知InputDispatcher
相对于聚焦应用的通知,窗口通知比较复杂,需要先从WMS通知到 SurfaceFlinger,SurfaceFlinger 在通知到 InputDispatcher。
当焦点有改变的时候如上面流程就会调用到 DisplayContent 的 updateFocusedWindowLocked
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,int topFocusedDisplayId) {// 查找新焦点,如果界面setVisibility为false则没焦点WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);// 如果是一样的则返回if (mCurrentFocus == newFocus) {return false;}...// 赋值新的焦点给mCurrentFocus变量ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));final WindowState oldFocus = mCurrentFocus;mCurrentFocus = newFocus;...if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {// If we defer assigning layers, then the caller is responsible for doing this part.// 当焦点改变,通过 inputMonitor通知 setInputFocusLwgetInputMonitor().setInputFocusLw(newFocus, updateInputWindows);}}
setInputFocusLw 会调用对应的 updateInputWindowsLw 方法 ,该方法又会通过 scheduleUpdateInputWindows 启动 mUpdateInputWindows 任务
void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s display=%d",newWindow, mDisplayId);...if (updateInputWindows) {updateInputWindowsLw(false /*force*/);}}void updateInputWindowsLw(boolean force) {if (!force && !mUpdateInputWindowsNeeded) {return;}// 启动mUpdateInputWindows任务scheduleUpdateInputWindows();}private void scheduleUpdateInputWindows() {if (mDisplayRemoved) {return;}if (!mUpdateInputWindowsPending) {mUpdateInputWindowsPending = true;mHandler.post(mUpdateInputWindows);}}
UpdateInputWindows 主要执行了 UpdateInputForAllWindowsConsumer的 updateInputWindows 方法
private class UpdateInputWindows implements Runnable {@Overridepublic void run() {synchronized (mService.mGlobalLock) {...// Add all windows on the default display.mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);}}}
UpdateInputForAllWindowsConsumer 主要工作
- 调用 disolayContent的forAllWindows,遍历所有windowState,去更新对应的信息
- 调用 updateInputFocusRequest 去申请输入焦点,要通过requestFocus 申请了,才会在InputDispatcher 有数据,这样才会在后续的焦点更新那从request数据中获取到,不然一直会是null,没输入事件
private final class UpdateInputForAllWindowsConsumer implements Consumer<WindowState> {...private void updateInputWindows(boolean inDrag) {...// 遍历所有窗口信息,因为传入的为this,遍历的时候会调用的accept方法mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);// 更新所有焦点,只要通过requestFocus 申请了,才会在InputDispatcher 有数据,// 这样才会在后续的焦点更新那从request数据中获取到,不然一直会是null,没输入事件updateInputFocusRequest(mRecentsAnimationInputConsumer);if (!mUpdateInputWindowsImmediately) {// 合并对应的事务mDisplayContent.getPendingTransaction().merge(mInputTransaction);mDisplayContent.scheduleAnimation();}Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}@Overridepublic void accept(WindowState w) {// InputWindowHandleWrapper 包含displayId和InputApplicationHandlefinal InputWindowHandleWrapper inputWindowHandle = w.mInputWindowHandle;...if (w.mInputChannelToken == null || w.mRemoved|| (!w.canReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {// 如果不可输入但是拥有 Surface,调用populateOverlayInputInfo重置下信息,如token重置// 在 openInputChannel的时候会赋值if (w.mWinAnimator.hasSurface()) {// Make sure the input info can't receive input event. It may be omitted from// occlusion detection depending on the type or if it's a trusted overlay.populateOverlayInputInfo(inputWindowHandle, w);setInputWindowInfoIfNeeded(mInputTransaction,w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);return;}// Skip this window because it cannot possibly receive input.return;}...// 有surfaceif (w.mWinAnimator.hasSurface()) {// 构造对应的inputWindowHandle信息,用于native传输populateInputWindowHandle(inputWindowHandle, w);// 调用 InputTransaction事务将InputWindowHandleWrapper放进去setInputWindowInfoIfNeeded(mInputTransaction,w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);}}}
因为 forAllWindows 传入的是 this,所以会触发 accept 方法。该方法主要
- 如果不可输入但是拥有 Surface,调用populateOverlayInputInfo重置下信息,如token重置为null(在 openInputChannel的时候会赋值)
- 最后有Surface的时候 调用 populateInputWindowHandle ,构造对应的inputWindowHandle信息,用于native传输
4.2.1 更新窗口信息 setInputWindowInfo
4.2.1.1 WMS通知到SurfaceFlinger
通过 populateInputWindowHandle 设置好之后,就会调用 setInputWindowInfoIfNeeded,因为设置过所以 isChanged 返回true。所以会调用 applyChangesToSurface 方法提交到事务
static void setInputWindowInfoIfNeeded(SurfaceControl.Transaction t, SurfaceControl sc,InputWindowHandleWrapper inputWindowHandle) {if (DEBUG_INPUT) {Slog.d(TAG_WM, "Update InputWindowHandle: " + inputWindowHandle);}// 只要有设置就会为trueif (inputWindowHandle.isChanged()) {inputWindowHandle.applyChangesToSurface(t, sc);}}
里面会调用事务的 setInputWIndowInfo
void applyChangesToSurface(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc) {// 调用Transaction的setInputWindowInfo将setInputWindowInfo保存进入t.setInputWindowInfo(sc, mHandle);mChanged = false;}
调用 nativeSetInputWindowInfo 将 对应 InputWindowHandle 信息传递给 SurfaceControl
public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {checkPreconditions(sc);nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);return this;}
nativeSetInputWindowInfo 内容如下
- 通过 android_view_InputWindowHandle_getHandle 构建出 NativeInputWindowHandle 对象
- 调用 updateInfo 方法,将 java的inputWindowHandler属性拿出,拷贝到 NativeInputWindowHandle变量
- 调用 SurfaceComposerClient的setInputWindowInfo 更新窗口信息到数组
// transactionObj参数是Transaction的指针,而nativeObject是SurfaceControl的指针
static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,jlong nativeObject, jobject inputWindow) {auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);// 创建 NativeInputWindowHandle 对象sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(env, inputWindow);// 将 java的inputWindowHandler属性拿出,拷贝到 NativeInputWindowHandle变量handle->updateInfo();// 获取SurfaceControlauto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);// 调用 SurfaceComposerClient的setInputWindowInfo,会保存到 mComposerStates 数组,后续apply事务的时候就会从该数据遍历transaction->setInputWindowInfo(ctrl, *handle->getInfo());
}
android_view_InputWindowHandle_getHandle 方法。会根据ptr属性获取,如果不为空则直接获取,否则在创建,并设置到对应ptr属性
sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env, jobject inputWindowHandleObj) {if (!inputWindowHandleObj) {return NULL;}AutoMutex _l(gHandleMutex);jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);NativeInputWindowHandle* handle;// 获取对应的指针如果不为空则直接获取,否则在创建,并设置到对应ptr属性。if (ptr) {handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);} else {jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);handle = new NativeInputWindowHandle(objWeak);handle->incStrong((void*)android_view_InputWindowHandle_getHandle);env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,reinterpret_cast<jlong>(handle));}return handle;
}
updateInfo 进行深拷贝
bool NativeInputWindowHandle::updateInfo() {JNIEnv* env = AndroidRuntime::getJNIEnv();jobject obj = env->NewLocalRef(mObjWeak);...// windowState的tokenjobject tokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.token);if (tokenObj) {mInfo.token = ibinderForJavaObject(env, tokenObj);env->DeleteLocalRef(tokenObj);} else {mInfo.token.clear();}...// 获取对应的 AIDL token(IWindow AIDL接口,用于wms和应用交互通知,如viewRootImpl的W)// 通过 ibinderForJavaObject 转换放到 windowTokenjobject windowTokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.windowToken);if (windowTokenObj) {mInfo.windowToken = ibinderForJavaObject(env, windowTokenObj);env->DeleteLocalRef(windowTokenObj);} else {mInfo.windowToken.clear();}env->DeleteLocalRef(obj);return true;
}
setInputWindowInfo 方法,
- 通过 getLayerState 创建对应 layer_state_t,该方法很重要会将对应的信息保存到 mComposerStates
- 将windowInfo放到WindowInfoHandle,并赋值给 layer_state_t
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setInputWindowInfo(const sp<SurfaceControl>& sc, const WindowInfo& info) {// 获取对应的 layer_state_tlayer_state_t* s = getLayerState(sc);if (!s) {mStatus = BAD_INDEX;return *this;}// 将windowInfo放到WindowInfoHandle,并赋值给 layer_state_ts->windowInfoHandle = new WindowInfoHandle(info);// 记录变化的s->what |= layer_state_t::eInputInfoChanged;return *this;
}
getLayerState 会获取
- SurfaceControl 对应的 binder对象。从mComposerStates数组判断是否存在
- 不包含则创建,将对应信息放进去layer_state_t 并根据handler为key,放进mComposerStates
- 返回 layer_state_t
layer_state_t* SurfaceComposerClient::Transaction::getLayerState(const sp<SurfaceControl>& sc) {// 获取SurfaceControl的binderauto handle = sc->getLayerStateHandle();// 判断 mComposerStates 是否包含,不包含则创建,并根据handler为key,放进mComposerStates// 后续事务提交的时候,会从mComposerStates该数组遍历if (mComposerStates.count(handle) == 0) {// we don't have it, add an initialized layer_state to our listComposerState s;// 放进 layer_state_t 中s.state.surface = handle;s.state.layerId = sc->getLayerId();mComposerStates[handle] = s;}// 返回 layer_state_treturn &(mComposerStates[handle].state);
}
4.2.1.2 SurfaceFlinger 到 InputDIspatcher
事务在 提交的时候,就会调用 apply方法。又会调用过 nativeApplyTransaction 方法。
public void apply(boolean sync) {applyResizedSurfaces();notifyReparentedSurfaces();nativeApplyTransaction(mNativeObject, sync);}
native 又会调用 SurfaceComposerClient 的 apply 方法。
static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);// 调用 SurfaceComposerClient 的applytransaction->apply(sync);
}
apply方法
- 获取 SurfaceFlinger的Binder代理sf
- 开始遍历 mComposerStates, 添加到 composerStates
- 跨进程调用SurfaceFlinger的setTransactionState把composerStates作为参数
status_t SurfaceComposerClient::Transaction::apply(bool synchronous) {if (mStatus != NO_ERROR) {return mStatus;}// 获取SurfaceFlinger的Binder代理sf,单例sp<ISurfaceComposer> sf(ComposerService::getComposerService());...// 开始遍历 mComposerStates, 添加到 composerStatesfor (auto const& kv : mComposerStates){composerStates.add(kv.second);}...// 如果已经有则直接用,否则创建TransactionCompletedListenersp<IBinder> applyToken = mApplyToken? mApplyToken: IInterface::asBinder(TransactionCompletedListener::getIInstance());// 跨进程调用SurfaceFlinger的setTransactionState把composerStates作为参数sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,{} /*uncacheBuffer - only set in doUncacheBufferTransaction*/,hasListenerCallbacks, listenerCallbacks, mId);}
setTransactionState 会创建 TransactionState 对象,并调用 queueTransaction 方法,将事务(Transaction)加入队列。
status_t SurfaceFlinger::setTransactionState(const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {...// 将参数放到 TransactionState 对象中。TransactionState state{frameTimelineInfo, states,displays, flags,applyToken, inputWindowCommands,desiredPresentTime, isAutoTimestamp,uncacheBuffer, postTime,permissions, hasListenerCallbacks,listenerCallbacks, originPid,originUid, transactionId};...// 把TransactionState放入队列,且会启动对应的scheduleCommitqueueTransaction(state);// Check the pending state to make sure the transaction is synchronous.if (state.transactionCommittedSignal) {waitForSynchronousTransaction(*state.transactionCommittedSignal);}return NO_ERROR;
}
在每次SurfaceFlinger主线程进行commit操作的时候,都会调用一次updateInputFlinger方法去更新一遍需要派发给InputDispatcher的窗口信息。
updateInputFlinger 方法会调用 notifyWindowInfos 方法开始通知 给inputDispatcher
void SurfaceFlinger::updateInputFlinger() {ATRACE_CALL();if (!mInputFlinger) {return;}if (mVisibleRegionsDirty || mInputInfoChanged) {mInputInfoChanged = false;// 调用通知给inputDispatchernotifyWindowInfos();} ...for (const auto& focusRequest : mInputWindowCommands.focusRequests) {mInputFlinger->setFocusedWindow(focusRequest);}
;
}
notifyWindowInfos
- 通过 traverseInReverseZOrder 开始遍历,判断是否需要,通过layer里面的 hasInputInfo 方法,hasInputInfo通过inputInfo的token是否为空判断,并将放到 windowInfos 数组尾部
- 通过 mWindowInfosListenerInvoker 开始触发通知,给 InputDispatcher
void SurfaceFlinger::notifyWindowInfos() {std::vector<WindowInfo> windowInfos;// 开始遍历,过滤需要的windowInfosmDrawingState.traverseInReverseZOrder([&](Layer* layer) {// 判断是否需要,通过layer里面的 hasInputInfo 方法,hasInputInfo通过inputInfo的token是否为空判断if (!layer->needsInputInfo()) return;sp<DisplayDevice> display = enablePerWindowInputRotation()? ON_MAIN_THREAD(getDisplayWithInputByLayer(layer)): nullptr;// When calculating the screen bounds we ignore the transparent region since it may// result in an unwanted offset.// 根据layer填满outWindowInfos信息windowInfos.push_back(layer->fillInputInfo(display));});// 开始触发通知,给 InputDispatchermWindowInfosListenerInvoker->windowInfosChanged(windowInfos,mInputWindowCommands.syncInputWindows);
}
WindowInfosListenerInvoker 的 windowInfosChanged方法又会遍历listner,里面其实就是 InputDispather
void WindowInfosListenerInvoker::windowInfosChanged(const std::vector<WindowInfo>& windowInfos,bool shouldSync) {...// 这里listener其实就是InputDispatherfor (const auto& listener : windowInfosListeners) {listener->onWindowInfosChanged(windowInfos,shouldSync ? mWindowInfosReportedListener : nullptr);}
}
查看 InputDispather 的 onWindowInfosChanged 方法
void InputDispatcher::onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos) {// The listener sends the windows as a flattened array. Separate the windows by display for// more convenient parsing.std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;for (const auto& info : windowInfos) {handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());handlesPerDisplay[info.displayId].push_back(new WindowInfoHandle(info));}setInputWindows(handlesPerDisplay);
}
setInputWindows 主要更新 内容
- 调用 updateWindowHandlesForDisplayLocked,更新mWindowHandlesByDisplay对应该屏幕下的数据
- 调用 FocusResolver 的 setInputWindows 开始设置焦点窗口
void InputDispatcher::setInputWindowsLocked(const std::vector<sp<WindowInfoHandle>>& windowInfoHandles, int32_t displayId) {if (DEBUG_FOCUS) {...ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str());}...// 比较更旧的windowInfo,更新mWindowHandlesByDisplay对应该屏幕下的数据updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);// 判断是否改变窗口焦点std::optional<FocusResolver::FocusChanges> changes =mFocusResolver.setInputWindows(displayId, windowHandles);if (changes) {onFocusChangedLocked(*changes);}...
}
FocusResolver 的 setInputWindows 方法
- 通过getFocusedWindowToken(通过mFocusedWindowTokenByDisplay数组) 获取还没更新前的焦点窗口
- 调用 isTokenFocusable,判断下当前窗口是否可以聚焦,如果还聚焦的话,则继续授予其焦点。返回null,表示不需要改变。
- 通过 getFocusRequest(通过 mFocusRequestByDisplay 数组)方法获取请求的焦点窗口,需要调用 (requestFocus),如果没请求过的话,则表示没焦点。
- 如果请求过,则再次通过 isTokenFocusable ,判断该窗口是否符合,如何的话则调用 updateFocusedWindow 方法进行更新,如果不符合的话,则传入null,将当前屏幕设置为没焦点。
std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows(int32_t displayId, const std::vector<sp<WindowInfoHandle>>& windows) {std::string removeFocusReason;// Check if the currently focused window is still focusable.// 通过 mFocusedWindowTokenByDisplay(mFocusedWindowTokenByDisplay数组) 检查下当前还没更新的焦点是否还在,这时候还没更新,所以获取的是旧的const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);if (currentFocus) {Focusability result = isTokenFocusable(currentFocus, windows);// 返回ok,表示当前窗口仍可以聚焦,那么我们将继续授予其焦点if (result == Focusability::OK) {return std::nullopt;}removeFocusReason = NamedEnum::string(result);}// We don't have a focused window or the currently focused window is no longer focusable. Check// to see if we can grant focus to the window that previously requested focus.// 请求的requestFocus((mFocusRequestByDisplay数组))是否存在,如果没请求,那 updateFocusedWindow 只会传入nullconst std::optional<FocusRequest> request = getFocusRequest(displayId);if (request) {sp<IBinder> requestedFocus = request->token;const Focusability result = isTokenFocusable(requestedFocus, windows);const Focusability previousResult = mLastFocusResultByDisplay[displayId];mLastFocusResultByDisplay[displayId] = result;// 如果可以 则调用 updateFocusedWindow 更新当前聚焦焦点if (result == Focusability::OK) {return updateFocusedWindow(displayId,"Window became focusable. Previous reason: " +NamedEnum::string(previousResult),requestedFocus, request->windowName);}}// Focused window is no longer focusable and we don't have a suitable focus request to grant.// Remove focus if needed.// 因为没请求,传入为空的对象更新为空的,让其失去焦点return updateFocusedWindow(displayId, removeFocusReason, nullptr);
}
isTokenFocusable 判断是否符合条件 ,遍历表示是否可见,是否可聚焦。
FocusResolver::Focusability FocusResolver::isTokenFocusable(const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows) {bool allWindowsAreFocusable = true;bool visibleWindowFound = false;bool windowFound = false;for (const sp<WindowInfoHandle>& window : windows) {// 遍历到相等才执行判断if (window->getToken() != token) {continue;}windowFound = true;// 至少有一个窗口可见if (window->getInfo()->visible) {// Check if at least a single window is visible.visibleWindowFound = true;}// 检查所有窗口是否可聚焦if (!window->getInfo()->focusable) {// Check if all windows with the window token are focusable.allWindowsAreFocusable = false;break;}}if (!windowFound) {return Focusability::NO_WINDOW;}if (!allWindowsAreFocusable) {return Focusability::NOT_FOCUSABLE;}if (!visibleWindowFound) {return Focusability::NOT_VISIBLE;}return Focusability::OK;
}
updateFocusedWindow 方法
- 会获取当前的窗口和要更新的窗口是否一致,是的话返回为空,表示不需要改变。
- 在判断 newFocus 是否为空,不为空的则更新到 mFocusedWindowTokenByDisplay 数组,否则调用 errase 方法移除。丢失焦点。
std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(int32_t displayId, const std::string& reason, const sp<IBinder>& newFocus,const std::string& tokenName) {// 获取之前的焦点窗口,如果一样则返回空。sp<IBinder> oldFocus = getFocusedWindowToken(displayId);if (newFocus == oldFocus) {return std::nullopt;}// 如果传入参数不为空,则更新到 mFocusedWindowTokenByDisplay 数据// 为空则调用 errase,移除。if (newFocus) {mFocusedWindowTokenByDisplay[displayId] = {tokenName, newFocus};} else {mFocusedWindowTokenByDisplay.errase(displayId);}return {{oldFocus, newFocus, displayId, reason}};
}
这样就完成了到 InputDispatcher的聚焦窗口更新。
4.2.2 请求窗口焦点
更新聚焦窗口,需要request数组有对应的请求,之前的 InputMonitor 遍历后更新窗口信息,又会调用 updateInputFocusRequest 方法。又会调用 requestFocus 方法
4.2.2.1 WMS通知到SurfaceFlinger
private void updateInputFocusRequest(InputConsumerImpl recentsAnimationInputConsumer) {...// mInputChannelToken是否为空可以判断是否聚焦输入final IBinder focusToken = focus != null ? focus.mInputChannelToken : null;if (focusToken == null) {mInputFocus = null;return;}...// 请求输入焦点requestFocus(focusToken, focus.getName());}
requestFocus 会打印请求的日志和 调用事务 Transaction 的 setFocusedWindow 方法。
private void requestFocus(IBinder focusToken, String windowName) {if (focusToken == mInputFocus) {return;}mInputFocus = focusToken;// 将对应焦点token设置到事务mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId);// 请求日志EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName,"reason=UpdateInputWindows");ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName);}
而 setFocusedWindow 又会调用 nativeSetFocusedWindow 方法。
public Transaction setFocusedWindow(@NonNull IBinder token, String windowName,int displayId) {nativeSetFocusedWindow(mNativeObject, token, windowName,null /* focusedToken */, null /* focusedWindowName */, displayId);return this;}
nativeSetFocusedWindow 会创建对应的 FocusRequest 对象,并调用 setFocusedWindow 方法。
static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj,jobject toTokenObj, jstring windowNameJstr,jobject focusedTokenObj, jstring focusedWindowNameJstr,jint displayId) {auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);if (toTokenObj == NULL) return;// 转换对应的binder对象sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));sp<IBinder> focusedToken;if (focusedTokenObj != NULL) {focusedToken = ibinderForJavaObject(env, focusedTokenObj);}// 创建 FocusRequest对象FocusRequest request;request.token = toToken;if (windowNameJstr != NULL) {ScopedUtfChars windowName(env, windowNameJstr);request.windowName = windowName.c_str();}request.focusedToken = focusedToken;if (focusedWindowNameJstr != NULL) {ScopedUtfChars focusedWindowName(env, focusedWindowNameJstr);request.focusedWindowName = focusedWindowName.c_str();}request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);request.displayId = displayId;// 调用 SurfaceComposerClient的setFocusedWindowtransaction->setFocusedWindow(request);
}
setFocusedWindow 方法又会将对应的request 放到 mInputWindowCommands的focusRequests 数组
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFocusedWindow(const FocusRequest& request) {// 添加 mInputWindowCommands 到 focusRequestsmInputWindowCommands.focusRequests.push_back(request);return *this;
}
跟上面一样 apply的时候 调用 SurfaceFlinger的 setTransactionState 时候 将 mInputWindowCommands 带过去给
status_t SurfaceComposerClient::Transaction::apply(bool synchronous) {...// 如果已经有则直接用,否则创建TransactionCompletedListenersp<IBinder> applyToken = mApplyToken? mApplyToken: IInterface::asBinder(TransactionCompletedListener::getIInstance());// 跨进程调用SurfaceFlinger的setTransactionState把composerStates作为参数// apply的时候 也会将 mInputWindowCommands 带过去sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,{} /*uncacheBuffer - only set in doUncacheBufferTransaction*/,hasListenerCallbacks, listenerCallbacks, mId);
}
4.2.2.2 SurfaceFlinger 到 InputDIspatcher
同样 inputWindowCommands 也会放在 TransactionState 中,并调用 queueTransaction 放进队列,最终也会调用到 updateInputFlinger,会遍历对应的数组,然后调用 InputFlinger 的setFocusedWindow方法,也即是 InputManager 的 setFocusedWindow
// 更新对应的
void SurfaceFlinger::updateInputFlinger() {ATRACE_CALL();if (!mInputFlinger) {return;}if (mVisibleRegionsDirty || mInputInfoChanged) {mInputInfoChanged = false;// 调用通知给inputDispatchernotifyWindowInfos();} ...// 开始遍历每一个焦点请求,调用 mInputFlingerfor (const auto& focusRequest : mInputWindowCommands.focusRequests) {mInputFlinger->setFocusedWindow(focusRequest);}mInputWindowCommands.clear();
}
class InputManager : public InputManagerInterface, public BnInputFlinger {
protected:~InputManager() override;
inputManger 又会调用 mDispatcher 的setFocusedWindow
binder::Status InputManager::setFocusedWindow(const FocusRequest& request) {mDispatcher->setFocusedWindow(request);return binder::Status::ok();
}
InputDispatcher里面 又会调用 FocusResolver的setFocusedWindow
void InputDispatcher::setFocusedWindow(const FocusRequest& request) {{ // acquire lockstd::scoped_lock _l(mLock);// 又会调用 FocusResolver的setFocusedWindowstd::optional<FocusResolver::FocusChanges> changes =mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));if (changes) {onFocusChangedLocked(*changes);}} // release lock// Wake up poll loop since it may need to make new input dispatching choices.mLooper->wake();
}
FocusResolver 的 setFocusedWindow 方法
- 获取当前的窗口焦点**,如果和当前的一样,则直接返回空,表示无变化**
- 如果请求聚焦已经是聚焦的的情况下进入,如果 跟当前的不一致,则用当前的即可,如果当前request是可用的话,则调用 updateFocusedWindow 更新,否则也返回空,抛弃该请求
- 如果是可用的窗口,将对应的信息保存到 mFocusRequestByDisplay 中,在 setInputWindows 中可用
- 再次判断是否可用,不可用则调用 updateFocusedWindow,否则传入null 丢失窗口,因为保存在 mFocusRequestByDisplay 中,等待下一次窗口状态改变请求。
std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(const FocusRequest& request, const std::vector<sp<WindowInfoHandle>>& windows) {const int32_t displayId = request.displayId;// 获取当前的窗口焦点const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);if (currentFocus == request.token) {ALOGD_IF(DEBUG_FOCUS,"setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",request.windowName.c_str(), displayId);return std::nullopt;}// Handle conditional focus requests, i.e. requests that have a focused token. These requests// are not persistent. If the window is no longer focusable, we expect focus to go back to the// previously focused window.// 如果请求聚焦已经是聚焦的的情况下进入,(正常请求的时候,并还没聚焦)if (request.focusedToken) {// 跟当前的不一致,则用当前的即可if (currentFocus != request.focusedToken) {ALOGW("setFocusedWindow %s on display %" PRId32" ignored, reason: focusedToken %s is not focused",request.windowName.c_str(), displayId, request.focusedWindowName.c_str());return std::nullopt;}// 是否可聚焦,是的话更新窗口信息Focusability result = isTokenFocusable(request.token, windows);if (result == Focusability::OK) {return updateFocusedWindow(displayId, "setFocusedWindow with focus check",request.token, request.windowName);}// 已经不符合可聚焦了,无需设置 返回空指针ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",request.windowName.c_str(), displayId, NamedEnum::string(result).c_str());return std::nullopt;}// 如果是可用的窗口,将对应的信息保存到 mFocusRequestByDisplay 中,在 setInputWindows 中可用Focusability result = isTokenFocusable(request.token, windows);// Update focus request. The focus resolver will always try to handle this request if there is// no focused window on the display.mFocusRequestByDisplay[displayId] = request;mLastFocusResultByDisplay[displayId] = result;// 如果结果是ok,更新对应的焦点窗口if (result == Focusability::OK) {return updateFocusedWindow(displayId, "setFocusedWindow", request.token,request.windowName);}// The requested window is not currently focusable. Wait for the window to become focusable// but remove focus from the current window so that input events can go into a pending queue// and be sent to the window when it becomes focused.// 当前request不是符合的窗口 传入null 设置当前为没焦点窗口,后续 窗口改变,通过 setInputWindows 可用return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result),nullptr);
}
这样就完成了 焦点的请求流程
5 打印 Focus entering" 或 Focus leaving
从上面流程 调用 setFocusedWindow 或者 setInputWindowsLocked 有改变后就会调用 onFocusChangedLocked 方法。
// 判断是否改变窗口焦点std::optional<FocusResolver::FocusChanges> changes =mFocusResolver.setInputWindows(displayId, windowHandles);if (changes) {onFocusChangedLocked(*changes);}
// 又会调用 FocusResolver的setFocusedWindowstd::optional<FocusResolver::FocusChanges> changes =mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));if (changes) {onFocusChangedLocked(*changes);}
onFocusChangedLocked 会根据传进来的 oldFocus 和 newFocus,分别调用 enqueueFocusEventLocked 方法,发送 focus的event事件
void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes) {// 如果有改变,分别调用 旧焦点和新焦点,传入 enqueueFocusEventLocked 方法// 旧的if (changes.oldFocus) {std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus);if (focusedInputChannel) {CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,"focus left window");synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason);}}// 新的if (changes.newFocus) {enqueueFocusEventLocked(changes.newFocus, true /*hasFocus*/, changes.reason);}...
}
构造对应event type为 EventEntry::Type::FOCUS 的事件,插入到 mInboundQueue 即 IQ队列
void InputDispatcher::enqueueFocusEventLocked(const sp<IBinder>& windowToken, bool hasFocus,const std::string& reason) {if (mPendingEvent != nullptr) {// Move the pending event to the front of the queue. This will give the chance// for the pending event to get dispatched to the newly focused windowmInboundQueue.push_front(mPendingEvent);mPendingEvent = nullptr;}std::unique_ptr<FocusEntry> focusEntry =std::make_unique<FocusEntry>(mIdGenerator.nextId(), now(), windowToken, hasFocus,reason);// This event should go to the front of the queue, but behind all other focus events// Find the last focus event, and insert right after itstd::deque<std::shared_ptr<EventEntry>>::reverse_iterator it =std::find_if(mInboundQueue.rbegin(), mInboundQueue.rend(),[](const std::shared_ptr<EventEntry>& event) {return event->type == EventEntry::Type::FOCUS;});// Maintain the order of focus events. Insert the entry after all other focus events.mInboundQueue.insert(it.base(), std::move(focusEntry));
}
下一次循环唤醒的时候,就会执行 dispatchOnce 方法。和之前一样队列有东西 就会调用 dispatchOnceInnerLocked。该方法会从 IQ队列获取,判断到类型为 EventEntry::Type::FOCUS 就会调用 dispatchFocusLocked 方法。
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {switch (mPendingEvent->type) {case EventEntry::Type::FOCUS: {std::shared_ptr<FocusEntry> typedEntry =std::static_pointer_cast<FocusEntry>(mPendingEvent);dispatchFocusLocked(currentTime, typedEntry);done = true;dropReason = DropReason::NOT_DROPPED; // focus events are never droppedbreak;}}
}
dispatchFocusLocked 就会打印对应的窗口离开和进入日志。
void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry) {std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);if (channel == nullptr) {return; // Window has gone away}InputTarget target;target.inputChannel = channel;target.flags = InputTarget::FLAG_DISPATCH_AS_IS;entry->dispatchInProgress = true;// 打印对应的信息std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +channel->getName();std::string reason = std::string("reason=").append(entry->reason);android_log_event_list(LOGTAG_INPUT_FOCUS) << message << reason << LOG_ID_EVENTS;dispatchEventLocked(currentTime, entry, {target});
}
3 堆栈
3.1 setFocusApp
启动App堆栈
setFocusedApp:3655, DisplayContent (com.android.server.wm)
setResumedActivityUncheckLocked:4539, ActivityTaskManagerService (com.android.server.wm)
onActivityStateChanged:579, TaskFragment (com.android.server.wm)
setState:5163, ActivityRecord (com.android.server.wm)
minimalResumeActivityLocked:4783, Task (com.android.server.wm)
realStartActivityLocked:938, ActivityTaskSupervisor (com.android.server.wm)
startSpecificActivity:1005, ActivityTaskSupervisor (com.android.server.wm)
resumeTopActivity:1348, TaskFragment (com.android.server.wm)
resumeTopActivityInnerLocked:5037, Task (com.android.server.wm)
resumeTopActivityUncheckedLocked:4971, Task (com.android.server.wm)
resumeFocusedTasksTopActivities:2398, RootWindowContainer (com.android.server.wm)
resumeFocusedTasksTopActivities:2383, RootWindowContainer (com.android.server.wm)
completePause:1608, TaskFragment (com.android.server.wm)
activityPaused:5734, ActivityRecord (com.android.server.wm)
activityPaused:176, ActivityClientController (com.android.server.wm)
onTransact:548, IActivityClientController$Stub (android.app)
onTransact:121, ActivityClientController (com.android.server.wm)
execTransactInternal:1179, Binder (android.os)
execTransact:1143, Binder (android.os)
按home建,回退的时候
setFocusedApp:3655, DisplayContent (com.android.server.wm)
setResumedActivityUncheckLocked:4539, ActivityTaskManagerService (com.android.server.wm)
moveFocusableActivityToTop:3001, ActivityRecord (com.android.server.wm)
moveTaskToFront:5581, Task (com.android.server.wm)
setTargetRootTaskIfNeeded:2733, ActivityStarter (com.android.server.wm)
recycleTask:2062, ActivityStarter (com.android.server.wm)
startActivityInner:1762, ActivityStarter (com.android.server.wm)
startActivityUnchecked:1594, ActivityStarter (com.android.server.wm)
executeRequest:1195, ActivityStarter (com.android.server.wm)
execute:674, ActivityStarter (com.android.server.wm)
startHomeActivity:179, ActivityStartController (com.android.server.wm)
startHomeOnTaskDisplayArea:1604, RootWindowContainer (com.android.server.wm)
lambda$startHomeOnDisplay$12$RootWindowContainer:1545, RootWindowContainer (com.android.server.wm)
apply:-1, RootWindowContainer$$ExternalSyntheticLambda10 (com.android.server.wm)
reduceOnAllTaskDisplayAreas:554, TaskDisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:2091, WindowContainer (com.android.server.wm)
startHomeOnDisplay:1544, RootWindowContainer (com.android.server.wm)
startHomeOnDisplay:5839, ActivityTaskManagerService$LocalService (com.android.server.wm)
startDockOrHome:5106, PhoneWindowManager (com.android.server.policy)
startDockOrHome:5111, PhoneWindowManager (com.android.server.policy)
launchHomeFromHotKey:3254, PhoneWindowManager (com.android.server.policy)
launchHomeFromHotKey:3211, PhoneWindowManager (com.android.server.policy)
handleShortPressOnHome:1351, PhoneWindowManager (com.android.server.policy)
access$2200:240, PhoneWindowManager (com.android.server.policy)
lambda$handleHomeButton$0$PhoneWindowManager$DisplayHomeButtonHandler:1501, PhoneWindowManager$DisplayHomeButtonHandler (com.android.server.policy)
run:-1, PhoneWindowManager$DisplayHomeButtonHandler$$ExternalSyntheticLambda0 (com.android.server.policy)
handleCallback:938, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
run:67, HandlerThread (android.os)
run:44, ServiceThread (com.android.server)
run:45, UiThread (com.android.server)
setFocusedApp:3655, DisplayContent (com.android.server.wm)
setResumedActivityUncheckLocked:4539, ActivityTaskManagerService (com.android.server.wm)
onActivityStateChanged:579, TaskFragment (com.android.server.wm)
setState:5163, ActivityRecord (com.android.server.wm)
resumeTopActivity:1227, TaskFragment (com.android.server.wm)
resumeTopActivityInnerLocked:5037, Task (com.android.server.wm)
resumeTopActivityUncheckedLocked:4971, Task (com.android.server.wm)
resumeTopActivityUncheckedLocked:4985, Task (com.android.server.wm)
resumeFocusedTasksTopActivities:2398, RootWindowContainer (com.android.server.wm)
resumeTargetRootTaskIfNeeded:2788, ActivityStarter (com.android.server.wm)
recycleTask:2113, ActivityStarter (com.android.server.wm)
startActivityInner:1762, ActivityStarter (com.android.server.wm)
startActivityUnchecked:1594, ActivityStarter (com.android.server.wm)
executeRequest:1195, ActivityStarter (com.android.server.wm)
execute:674, ActivityStarter (com.android.server.wm)
startHomeActivity:179, ActivityStartController (com.android.server.wm)
startHomeOnTaskDisplayArea:1604, RootWindowContainer (com.android.server.wm)
lambda$startHomeOnDisplay$12$RootWindowContainer:1545, RootWindowContainer (com.android.server.wm)
apply:-1, RootWindowContainer$$ExternalSyntheticLambda10 (com.android.server.wm)
reduceOnAllTaskDisplayAreas:554, TaskDisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:392, DisplayArea (com.android.server.wm)
reduceOnAllTaskDisplayAreas:2091, WindowContainer (com.android.server.wm)
startHomeOnDisplay:1544, RootWindowContainer (com.android.server.wm)
startHomeOnDisplay:5839, ActivityTaskManagerService$LocalService (com.android.server.wm)
startDockOrHome:5106, PhoneWindowManager (com.android.server.policy)
startDockOrHome:5111, PhoneWindowManager (com.android.server.policy)
launchHomeFromHotKey:3254, PhoneWindowManager (com.android.server.policy)
launchHomeFromHotKey:3211, PhoneWindowManager (com.android.server.policy)
handleShortPressOnHome:1351, PhoneWindowManager (com.android.server.policy)
access$2200:240, PhoneWindowManager (com.android.server.policy)
lambda$handleHomeButton$0$PhoneWindowManager$DisplayHomeButtonHandler:1501, PhoneWindowManager$DisplayHomeButtonHandler (com.android.server.policy)
run:-1, PhoneWindowManager$DisplayHomeButtonHandler$$ExternalSyntheticLambda0 (com.android.server.policy)
handleCallback:938, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
run:67, HandlerThread (android.os)
run:44, ServiceThread (com.android.server)
run:45, UiThread (com.android.server)
后退键
setFocusedApp:3655, DisplayContent (com.android.server.wm)
setResumedActivityUncheckLocked:4539, ActivityTaskManagerService (com.android.server.wm)
moveFocusableActivityToTop:3001, ActivityRecord (com.android.server.wm)
moveHomeActivityToTop:1880, TaskDisplayArea (com.android.server.wm)
adjustFocusToNextFocusableTask:2484, Task (com.android.server.wm)
finishIfPossible:3144, ActivityRecord (com.android.server.wm)
finishActivity:451, ActivityClientController (com.android.server.wm)
onTransact:688, IActivityClientController$Stub (android.app)
onTransact:121, ActivityClientController (com.android.server.wm)
execTransactInternal:1184, Binder (android.os)
execTransact:1143, Binder (android.os)
相关文章:
frameworks 之 Focus window
frameworks 之 Focus window 1 查看当前焦点窗口1.1 dump window1.2 dump input1.3 input Events 日志1.4 dump SurfaceFlinger1.5 查看关键的日志 2 更新当前焦点应用3 更新当前焦点窗口3.1 焦点丢失3.2 获得窗口焦点 4 窗口信息到InputDispatcher4.1 焦点应用通知InputDispat…...
危害被低估的Netgear认证前漏洞CVE-2019-20760分析
概述 Netgear R9000设备2019年爆出认证绕过漏洞CVE-2019-20760,题目之所以说这个漏洞远被低估,主要以下两个原因: 实际漏洞危害较大,公开信息仅显示该漏洞为一个认证绕过漏洞,没有具体漏洞信息或者POC,但…...
微软量子芯片引领人工智能革命,开启计算新纪元
摘要:微软近日发布了具有里程碑意义的量子芯片,这一突破性技术不仅为量子计算带来了新的可能性,更为人工智能领域带来了前所未有的发展机遇。本文将探讨微软量子芯片如何与人工智能相结合,以及它对未来计算世界的深远影响。 一、…...
PHP2(WEB)
##解题思路 打开页面什么线索都没有,目录扫描只是扫出来一个index.php,而源代码没有东西,且/robots.txt是不允许访问的 于是一番查询后发现,有个index.phps的文件路径,里头写着一段php的逻辑,对url的id参数…...
Linux 下 VIM 编辑器学习记录:从基础到进阶(中)
在 Linux 系统的学习与实践过程中,对文件内容的查看是一项极为基础且高频的操作。熟练掌握各类内容查看命令,不仅能提升我们在 Linux 环境下的工作效率,对于学习 Java 全栈开发的同学来说,在处理项目相关的配置文件、日志文件时也…...
使用Python进行PDF隐私信息检测
在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要。本文将介绍如何使用Python及其相关库来检测PDF文件中的隐私信息,如姓名、身份证号、手机号和邮箱等。 C:\pythoncode\new\checkp…...
【Python爬虫(45)】Python爬虫新境界:分布式与大数据框架的融合之旅
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
网络安全科普系统开发与设计(springboot论文源码调试讲解)
第4章 总体设计 4.1系统目标 本系统旨在达到科普知识信息展示、案例分析信息查询、试题信息添加、评论、答题管理、信息分类等为一体,为用户和管理员提供服务。使管理员更加轻松的进行工作,使用户更加快速的解决自己的问题。本系统的设计界面丰富&#…...
前后端对接
前端与后端的对接主要通过 接口 进行数据交互,具体流程和方式如下: 1. 明确需求与接口定义 前后端协商:确定需要哪些接口、接口的功能、请求参数和返回格式。接口文档:使用工具(如 Swagger、Postman、Apifoxÿ…...
深入浅出 SQLSugar:快速掌握高效 .NET ORM 框架
SQLSugar 是一个高效、易用的 .NET ORM 框架,支持多种数据库(如 SQL Server、MySQL、PostgreSQL 等)。它提供了丰富的功能,包括 CRUD 操作、事务管理、动态表名、多表联查等,开发者可以通过简单的链式操作实现复杂的数…...
计算机专业知识【深入理解子网中的特殊地址:为何 192.168.0.1 和 192.168.0.255 不能随意分配】
在计算机网络的世界里,IP 地址是设备进行通信的关键标识。对于常见的子网,如 192.168.0.0/24,我们可能会疑惑为何某些地址不能分配给主机使用。接下来,我们就以 192.168.0.0/24 为例,详细解释为何 192.168.0.1 和 192.…...
网络安全与措施
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 # 网络安全问题概述 1) 数据安全 访问(授权访问);存储(容灾、备份或异地备份等) 2) 应用程序 不能…...
复刻Dummy机械臂保姆教程
一直想复刻稚晖君的Dummy机械臂,24年11月底终于开始行动,上网学习、材料准备、组装调试前后耗时1个多月,终于在春节前顺利完工,正好用它给大家拜个年。很多同学在咨询细节,这里就整理一份保姆式教程,其中学…...
|网络安全|网络安全学习方法
1、先网络后安全 很多初学者还没搞定网络看懂网络拓扑,就急着研究防火墙或VPN,其实这样就不清楚整个网络架构是如何安全演进的。正确的流程是:先通过网络协议和拓扑设计的学习,能独立搭建一个企业网/校园网,再引入局域…...
内外网隔离文件传输解决方案|系统与钉钉集成+等保合规,安全提升70%
一、背景与痛点 在内外网隔离的企业网络环境中,员工与外部协作伙伴(如钉钉用户)的文件传输面临以下挑战: 1. **安全性风险**:内外网直连可能导致病毒传播、数据泄露。 2. **操作繁琐**:传统方式需频繁切…...
EasyRTC:轻量化SDK赋能嵌入式设备,开启智能硬件音视频通讯新篇章
在智能硬件与物联网飞速发展的今天,嵌入式设备的音视频通讯能力正变得愈发重要。然而,受限于硬件资源,尤其是Flash存储空间的不足,传统音视频通讯方案往往难以在嵌入式设备上实现高效集成。EasyRTC凭借其轻量级SDK和先进的技术架构…...
如何使用3D高斯分布进行环境建模
使用3D高斯分布来实现建模,主要是通过高斯分布的概率特性来描述空间中每个点的几何位置和不确定性。具体来说,3D高斯分布被用来表示点云数据中的每一个点或体素(voxel)的空间分布和不确定性,而不是单纯地存储每个点的坐…...
Java 大视界 -- 总结与展望:Java 大数据领域的新征程与无限可能(96)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
从零开始:VirtualBox安装Ubuntu 24.04.1 LTS
博客系列:Ubuntu虚拟机搭建与Python开发环境配置全攻略 第一篇:从零开始:VirtualBox安装Ubuntu 24.04.1 LTS(当前)第二篇:解决VirtualBox卡顿问题:配置优化和常见错误排查第三篇:轻…...
使用大语言模型(Deepseek)构建一个基于 SQL 数据的问答系统
GitHub代码仓库 架构 从高层次来看,这些系统的步骤如下: 将问题转换为SQL查询:模型将用户输入转换为SQL查询。 执行SQL查询:执行查询。 回答问题:模型根据查询结果响应用户输入。 样本数据 下载样本数据…...
记录此刻:历时两月,初步实现基于FPGA的NVMe SSD固态硬盘存储控制器设计!
背景 为满足实验室横向项目需求,在2024年12月中下旬导师提出基于FPGA的NVMe SSD控制器研发项目。项目核心目标为:通过PCIe 3.0 x4接口实现单盘3000MB/s的持续读取速率。 实现过程 调研 花了半个月的时间查阅了一些使用FPGA实现NVME SSD控制器的论文、…...
【mysql】核心参数,SHOW VARIABLES
核心参数分类解析 SET GLOBAL slow_query_log = ON; 修改参数设置 一、核心参数分类解析 1. 连接和线程配置 max_connections (200):允许的最大并发连接数,超过会拒绝新连接。 wait_timeout (28800秒):非交互式连接的空闲超时时间(默认8小时)。 interactive_timeout (…...
centOS 7.9 安装JDK MYSQL
jdk: Java Archive Downloads - Java SE 17.0.12 and earlier CentOS安装JDK17教程(完整版) - 秦胜飞 - 博客园 sudo yum update wget https://download.oracle.com/java/17/archive/jdk-17.0.3.1_linux-x64_bin.rpm yum install ./jdk-17.0.3.1_linux…...
【OS安装与使用】part5-ubuntu22.04基于conda安装pytorch+tensorflow
文章目录 一、待解决问题1.1 问题描述1.2 解决方法 二、方法详述2.1 必要说明2.2 应用步骤2.2.1 明确pytorch安装依赖2.2.2 conda创建虚拟环境2.2.3 安装pytorch2.2.4 验证pytorch安装2.2.5 安装Tensorflow2.2.6 验证Tensorflow安装 三、疑问四、总结 一、待解决问题 1.1 问题…...
SVM 支持向量机
Owner: 潘达斯奈基 #数据科学/机器学习/SVM 一 支持向量机简介 支持向量机(Support Vector Machine, SVM)是一个二元分类算法,是对感知器算法模型的一种拓展,现在的SVM算法支持线性分类和非线性分类应用,…...
第1章大型互联网公司的基础架构——1.9 LSM Tree
**LSM Tree(Log-Structured Merge Tree)是一种对高并发写数据非常友好的键值存储模型,同时兼顾了查询效率。**LSMTree是我们下面将要介绍的NoSQL数据库所依赖的核心数据结构,例如BigTable.、HBase、 Cassandra、TiDB 等。 1.9.1 …...
053 性能压测 单机锁 setnx
文章目录 性能压测-压力测试索引thymeleafnginx减少数据库查询(代码有bug)缓存 安全单机锁(防止缓存击穿)setnx pom.xml 性能压测-压力测试 1 响应时间(Response Time: RT):响应时间指用户从客…...
眼见不一定为实之MySQL中的不可见字符
目录 前言 一、问题的由来 1、需求背景 2、数据表结构 二、定位问题 1、初步的问题 2、编码是否有问题 3、依然回到字符本身 三、深入字符本身 1、回归本质 2、数据库解决之道 3、代码层解决 四、总结 前言 在开始今天的博客内容之前,正在看博客的您先…...
【Java 面试 八股文】JVM 虚拟机篇
JVM 虚拟机篇 1. JVM组成1.1 JVM由那些部分组成,运行流程是什么?1.2 什么是程序计数器?1.3 你能给我详细的介绍Java堆吗?1.4 Java 虚拟机栈1.4.1 Java Virtual machine Stacks (java 虚拟机栈)1.4.2 栈和堆的区别1.4.3 垃圾回收是否涉及栈内…...
达梦数据库学习笔记@1
目录 达梦数据库学习笔记一、表空间管理(一)默认表空间(二)相关数据字典(三)表空间操作(四)临时表空间管理 二、重做日志管理(一)系统视图(二&…...
条款23:宁以non-member、non-friend替换member函数
1.使用场景举例 网络浏览器类 当然这一功能也可以由一个non-menber函数(更好的封装,本条款的核心立意)提供: 面向对象守则要求数据应该尽可能的被封装,然而与直观相反地,member函数clearEverything带来的封…...
代码审计初探
学会了基础的代码审计后,就该提高一下了,学一下一些框架的php代码审计 先从一些小众的、已知存在漏洞的cms入手 phpems php的一款开源考试系统 源码下载 https://down.chinaz.com/soft/34597.htm 环境部署 windows审计,把相关文件放到phps…...
2025前端框架最新组件解析与实战技巧:Vue与React的革新之路
作者:飞天大河豚 引言 2025年的前端开发领域,Vue与React依然是开发者最青睐的框架。随着Vue 3的全面普及和React 18的持续优化,两大框架在组件化开发、性能优化、工程化支持等方面均有显著突破。本文将从最新组件特性、使用场景和编码技巧三…...
Eclipse自动排版快捷键“按了没有用”的解决办法
快捷键按了没有用,通常是因为该快捷键方式被其他软件占用了,即别的软件也设置了这个快捷键,导致你按了之后电脑不知道该响应哪个软件。 解决办法:1.将当前软件的这个快捷键改了;2.找到占用的那个软件,把那…...
小型字符级语言模型的改进方向和策略
小型字符级语言模型的改进方向和策略 一、回顾小型字符级语言模型的处理流程 前文我们已经从零开始构建了一个小型字符级语言模型,那么如何改进和完善我们的模型呢?有哪些改进的方向?我们先回顾一下模型的流程: 图1 小型字符级语言模型的处理流程 (1)核心模块交互过程:…...
请简述一下Prefab(预制体)的本质是什么?
在 Unity 中,Prefab(预制体)是一种非常重要的资产类型。 Prefab 本质上是一个可重复使用(开发者可以在场景中多次实例化同一个预制体)的游戏对象模板(预制体就像一个模板,对预制体本身的修改会…...
【开源项目】分布式文本多语言翻译存储平台
分布式文本多语言翻译存储平台 地址: Gitee:https://gitee.com/dreamPointer/zza-translation/blob/master/README.md 一、提供服务 分布式文本翻译服务,长文本翻译支持流式回调(todo)分布式文本多语言翻译结果存储服…...
使用GPU训练模型
1.说明 本地训练模型可以用CPU和GPU,但是GPU的性能比CPU要好得多,所以如果有独立显卡的,尽量还是用GPU来训练模型。 使用GPU需要安装Cuda和Cudnn 2.安装Cuda 安装cuda之前,首先看一下显卡支持的cuda版本,在命…...
DPVS-3: 双臂负载均衡测试
测试拓扑 双臂模式, 使用两个网卡,一个对外,一个对内。 Client host是物理机, RS host都是虚拟机。 LB host是物理机,两个CX5网卡分别在两个子网。 配置文件 用dpvs.conf.sample作为双臂配置文件,其中…...
Spring Security+JWT+Redis实现项目级前后端分离认证授权
1. 整体概述 权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制到资源,用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。 1.1 认证概述 认证是确认用户身份的过程,确保用户是谁。 1.1.1 …...
马斯克宣布Grok语音模式正式上线:早期测试版本 可能有一些问题
快科技2月23日消息,据报道,马斯克旗下xAI团队近期动作频频,继2月18日直播发布Grok最新版本Grok3后,马斯克又在社交平台X上宣布,Grok语音模式早期测试版现已在Grok应用程序上线,并对其表现给予了高度评价。 …...
P9631 [ICPC 2020 Nanjing R] Just Another Game of Stones Solution
Description 给定序列 a ( a 1 , a 2 , ⋯ , a n ) a(a_1,a_2,\cdots,a_n) a(a1,a2,⋯,an),有 m m m 个操作分两种: chmax ( l , r , k ) \operatorname{chmax}(l,r,k) chmax(l,r,k):对每个 i ∈ [ l , r ] i \in [l,r] i∈[l,…...
请求go构建缓存,go clean -cache
go clean -cache go 构建时会产生很多缓存, 一般是目录:/Users/xxx/Library/Caches/go-build 此目录README: This directory holds cached build artifacts from the Go build system. Run "go clean -cache" if the directory …...
安全面试4
文章目录 给的源码是ThinkPHP框架的话,审计起来和没有使用框架的有什么不同,从流程上或者从关注的点上有什么不同框架代码审计的流程无框架代码审计的流程 反序列的时候,unserialize()反序列一个字符串的时候,对象会有一些魔术方法…...
HTML之JavaScript DOM操作元素(1)
HTML之JavaScript DOM操作元素(1) 3.对元素进行操作1.操作元素的属性 元素名.属性名 ""2.操作元素的样式 元素.style.样式名 "" 样式名 "-" 要进行驼峰转换3.操作元素的文本 元素名.innerText 只识别文本元素名…...
SpringBoot+Vue+微信小程序的猫咖小程序平台(程序+论文+讲解+安装+调试+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 在当下这个高速发展的时代,网络科技正以令人惊叹的速度不断迭代更新。从 5G …...
【十一】Golang 指针
💢欢迎来到张胤尘的开源技术站 💥开源如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 指针指针定义指针初始化& 操作符new 函数初始…...
“conda”不是内部或外部命令,也不是可运行的程序或批处理文件
有的时候,我们发现在cmd黑框中输入conda时,cmd会显示“conda”不是内部或外部命令,也不是可运行的程序或批处理文件,那这时候该怎么解决呢? Step01:我们找到Anconda的安装目录。然后找到里面的bin文件夹&am…...
通过LM Studio本地私有化部署DeepSeek-R1模型,无网络也能用
打开LM Studio官网https://lmstudio.ai/ 选择适合自己的操作系统,下载LM Studio安装包 本地电脑安装成功后运行LM Studio,顶部文本框输入deepseek,点击搜索模型 在搜索结果中选择7B参数模型, 如上图右侧提示“No result found”说…...
GPU和FPGA的区别
GPU(Graphics Processing Unit,图形处理器)和 FPGA(Field-Programmable Gate Array,现场可编程门阵列)不是同一种硬件。 我的理解是,虽然都可以用于并行计算,但是GPU是纯计算的硬件…...