尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Android自由窗口生命周期与置顶实现解析

Android自由窗口生命周期与置顶实现解析 1. Android自由窗口生命周期解析作为一名长期从事Android系统开发的工程师我最近在分析Android U自由窗口浮窗功能时发现其生命周期管理与传统全屏窗口存在显著差异。本文将深入探讨这些差异背后的技术原理并分享我在实际开发中的调试经验。1.1 生命周期差异现象我们先来看两组实际运行时的生命周期日志对比从桌面直接启动应用的生命周期流程1181 I wm_on_top_resumed_lost_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,topStateChangedWhenResumed] 1181 I wm_on_paused_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,performPause,0] 2765 I wm_on_create_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,performCreate,129] 2765 I wm_on_start_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,handleStartActivity,1] 2765 I wm_on_resume_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,RESUME_ACTIVITY,17] 2765 I wm_on_top_resumed_gained_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,topStateChangedWhenResumed] 1181 I wm_on_stop_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,STOP_ACTIVITY_ITEM,6]从多任务启动自由窗口的生命周期流程// 进入多任务 1181 I wm_on_restart_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,performRestart,0] 1181 I wm_on_start_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,handleStartActivity,10] 1181 I wm_on_resume_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,RESUME_ACTIVITY,11] 1181 I wm_on_top_resumed_gained_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,topStateChangedWhenResumed] // 启动自由窗口 2073 I wm_on_paused_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,performPause,1] 1181 I wm_on_top_resumed_lost_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,topStateChangedWhenResumed] 2073 I wm_on_stop_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,handleRelaunchActivity,0] 2073 I wm_on_destroy_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,performDestroy,0] 2073 I wm_on_create_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,performCreate,34] 2073 I wm_on_start_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,handleStartActivity,1] 2073 I wm_on_resume_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,RESUME_ACTIVITY,28] 2073 I wm_on_top_resumed_gained_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,topStateChangedWhenResumed]关键差异点在于启动自由窗口时底层ActivityLauncher3没有执行pause流程。这引发了我们的思考为什么自由窗口启动不会导致桌面Activity暂停1.2 生命周期差异原理分析要理解这个现象我们需要深入Activity启动流程中的pause机制。核心逻辑位于TaskFragment.java的resumeTopActivity方法final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options, boolean deferPause) { ...... boolean pausing !deferPause taskDisplayArea.pauseBackTasks(next);这里deferPause的值通过Task.resumeTopActivityUncheckedLocked方法传递通常为false。因此!deferPause为true关键判断在于taskDisplayArea.pauseBackTasks(next)。pauseBackTasks方法会遍历TaskDisplayArea下的所有leafTask节点最底端Task节点判断是否需要暂停当前处于resumed状态的Activityboolean pauseBackTasks(ActivityRecord resuming) { final int[] someActivityPaused {0}; forAllLeafTasks(leafTask - { leafTask.forAllLeafTaskFragments((taskFrag) - { final ActivityRecord resumedActivity taskFrag.getResumedActivity(); if (resumedActivity ! null !taskFrag.canBeResumed(resuming)) { if (taskFrag.startPausing(false /* uiSleeping*/, resuming, pauseBackTasks)) { someActivityPaused[0]; } } }, true /* traverseTopToBottom */); }, true /* traverseTopToBottom */); return someActivityPaused[0] 0; }决定是否暂停的关键在于!taskFrag.canBeResumed(resuming)的判断。canBeResumed方法定义如下boolean canBeResumed(Nullable ActivityRecord starting) { return isTopActivityFocusable() getVisibility(starting) TASK_FRAGMENT_VISIBILITY_VISIBLE; }其中getVisibility(starting)方法是核心。它会检查当前TaskFragment的可见性状态TaskFragmentVisibility int getVisibility(ActivityRecord starting) { ...... for (int i parent.getChildCount() - 1; i 0; --i) { final WindowContainer other parent.getChildAt(i); ...... final int otherWindowingMode other.getWindowingMode(); if (otherWindowingMode WINDOWING_MODE_FULLSCREEN) { if (isTranslucent(other, starting)) { gotTranslucentFullscreen true; continue; } return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } else if (otherWindowingMode WINDOWING_MODE_MULTI_WINDOW other.matchParentBounds()) { if (isTranslucent(other, starting)) { gotTranslucentFullscreen true; continue; } return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } ...... } ...... return gotTranslucentFullscreen ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT : TASK_FRAGMENT_VISIBILITY_VISIBLE; }关键点在于自由窗口的WindowingMode既不是WINDOWING_MODE_FULLSCREEN也不是WINDOWING_MODE_MULTI_WINDOW因此getVisibility()返回TASK_FRAGMENT_VISIBILITY_VISIBLE使得canBeResumed()返回true最终!taskFrag.canBeResumed(resuming)为false不会触发pause流程。实际开发中的经验在调试窗口模式相关问题时可以通过adb shell dumpsys window windows查看各窗口的WindowingMode属性这是定位问题的关键信息。2. 自由窗口置顶实现方案自由窗口的另一个重要特性是能够保持在最顶层显示即使启动其他应用也不被覆盖。下面分析实现这一特性的技术方案。2.1 Launcher侧的设置在Launcher3代码中启动自由窗口时会设置相应的ActivityOptionsprivate ActivityOptions makeLaunchOptions(Activity activity) { ActivityOptions activityOptions ActivityOptions.makeBasic(); activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); // 设置窗口尺寸和位置 final View decorView activity.getWindow().getDecorView(); final WindowInsets insets decorView.getRootWindowInsets(); final Rect r new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2); r.offsetTo(insets.getSystemWindowInsetLeft() 50, insets.getSystemWindowInsetTop() 50); activityOptions.setLaunchBounds(r); // 关键设置保持在顶部 activityOptions.setTaskAlwaysOnTop(true); return activityOptions; }然而实际测试发现仅这样设置并不能保证窗口始终置顶。我们需要确认system_server端是否正确处理了这个参数。2.2 system_server端的处理关键逻辑在TaskDisplayArea.getOrCreateRootTask方法中Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop, Nullable Task candidateTask, Nullable Task sourceTask, Nullable ActivityOptions options, int launchFlags) { ...... if (windowingMode ! WINDOWING_MODE_UNDEFINED candidateTask.isRootTask() candidateTask.getWindowingMode() ! windowingMode) { candidateTask.mTransitionController.collect(candidateTask); candidateTask.setWindowingMode(windowingMode); // 注意这里没有处理AlwaysOnTop设置 } return candidateTask.getRootTask(); }从代码可见系统虽然设置了窗口模式但没有处理AlwaysOnTop属性导致Launcher的设置未生效。2.3 解决方案经过分析我找到了两种可行的修改方案方案一在设置窗口模式时同步设置AlwaysOnTopif (windowingMode ! WINDOWING_MODE_UNDEFINED candidateTask.isRootTask() candidateTask.getWindowingMode() ! windowingMode) { candidateTask.mTransitionController.collect(candidateTask); candidateTask.setWindowingMode(windowingMode); // 添加AlwaysOnTop设置 candidateTask.setTaskAlwaysOnTop(true); }这种方案简单直接但耦合性较高不够灵活。方案二在任务创建后统一处理int startActivityFromRecents(int callingPid, int callingUid, int taskId, SafeActivityOptions options) { ...... synchronized (mService.mGlobalLock) { ...... try { task mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP); // 添加自由窗口的AlwaysOnTop处理 if(task.inFreeformWindowingMode()){ task.setAlwaysOnTop(activityOptions.getTaskAlwaysOnTop()); } ......方案二更为推荐因为处理位置集中便于维护只在自由窗口模式下生效不影响其他窗口模式保留了ActivityOptions的原始设置意图开发经验分享在修改窗口管理相关代码时务必考虑各种窗口模式全屏、分屏、画中画、自由窗口等的兼容性避免引入模式间的冲突。3. 自由窗口开发中的常见问题在实际开发中我遇到了几个典型问题及解决方案3.1 窗口尺寸异常现象自由窗口启动后尺寸不符合预期可能过大或过小。排查步骤检查ActivityOptions.setLaunchBounds()设置的Rect是否正确确认DecorView的尺寸是否已正确初始化检查WindowInsets是否获取正确解决方案// 更稳健的尺寸计算方式 final View decorView activity.getWindow().getDecorView(); decorView.post(() - { final Rect bounds new Rect(0, 0, decorView.getWidth() * 2 / 3, // 默认占屏幕2/3宽度 decorView.getHeight() * 2 / 3); // 默认占屏幕2/3高度 bounds.offsetTo( insets.getSystemWindowInsetLeft() 50, insets.getSystemWindowInsetTop() 50); activityOptions.setLaunchBounds(bounds); });3.2 生命周期回调顺序异常现象自由窗口切换时生命周期回调顺序与预期不符。解决方案在AndroidManifest.xml中为Activity配置正确的resizeableActivity属性处理onConfigurationChanged时考虑自由窗口的特殊情况重写onMultiWindowModeChanged方法处理模式切换Override public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig); if (isInMultiWindowMode) { // 自由窗口模式下的特殊处理 adjustUIForFreeform(); } else { // 全屏模式恢复 restoreFullscreenUI(); } }3.3 输入焦点问题现象自由窗口与底层窗口间的输入焦点切换不正常。排查工具adb shell dumpsys window windows adb shell dumpsys input解决方案确保窗口设置了正确的FLAG_NOT_FOCUSABLE或FLAG_ALT_FOCUSABLE_IM标志在onWindowFocusChanged中正确处理焦点变化考虑使用WindowManager.LayoutParams.token管理窗口层级4. 自由窗口的最佳实践基于实际项目经验我总结出以下自由窗口开发的最佳实践尺寸适应性提供多种预设尺寸选项记住用户最后一次使用的窗口尺寸实现最小/最大尺寸限制位置管理实现窗口拖拽功能提供吸附到边缘的辅助功能避免窗口重叠关键系统UI生命周期处理正确处理配置变更保存和恢复窗口状态处理与其他窗口模式的互操作性能优化减少自由窗口中的过度绘制优化资源加载策略考虑使用单独的进程承载自由窗口用户体验提供明显的窗口控制按钮实现平滑的过渡动画确保与手势导航的兼容性自由窗口功能为Android应用提供了更灵活的用户体验但同时也带来了额外的复杂度。通过深入理解其工作原理和遵循最佳实践开发者可以创建出既功能强大又稳定可靠的自由窗口应用。
返回列表