背景:
前些天在学员在学员群里有聊到一个需求,那就是把手机桌面点击应用图标后,不是进行全屏显示,而是都进行自由窗口显示。这个其实有点类似我们windows电脑打开app,每个app都是一个非全屏的窗口,而且可以同时显示很多app的窗口。而不是一直被一个app的全屏窗口覆盖。如下图所示:
这个需求如何实现呢?那么接下来就来详细实现一下。
实现过程:
先看看B站UP主“千里马学框架”直播笔记:
实现Launcher端启动App都变成自由窗口模式非全屏模式
核心思路:
这个windowmode需要从FullScreen —> Freeform ,需要在startActivity时候携带相关相关参数,把windowmode和launchBounds进行设置。
步骤如下:
1、寻找到相关的桌面点击App的startActivity的相关方法,考虑对这个方法进行修改参数
2、对startActivity时候携带的ActivityOptions参数进行添加相关windowmode,launcherbounds的设置
launcher启动App的方法堆栈
假设你是一个桌面小白,根本不知道桌面的具体业务代码,请问你有什么方法可以快速定位出桌面是在哪个代码中startActivity吗?
方案1:
硬看代码,这里就需要自己先了解launcher的app图标代码,去寻找app的图标点击onClick方法,一步步寻找
缺点:现在的launcher代码已经变得很复杂,封装很多,基本上直接看代码很难找到对应onClick方法,因为层次很深,而且onClick方法可能还是lamada表达式等,大大增加了寻找难度。
方案2:
打印堆栈方法或者调试,首先有了马哥framework课程的基础后,大家都知道Activity的启动最后都会调用的Instrumentation的execStartActivity方法中
所以完全可以这里加入Instrumentation加入相关的堆栈或者调试寻找到onClick和startActivity方法
getService:156, ActivityTaskManager (android.app)
execStartActivity:1873, Instrumentation (android.app)
startActivityForResult:5589, Activity (android.app)
startActivityForResult:1834, Launcher (com.android.launcher3)
startActivityForResult:845, QuickstepLauncher (com.android.launcher3.uioverrides)
startActivity:6041, Activity (android.app)
startActivitySafely:379, ActivityContext (com.android.launcher3.views)
startActivitySafely:2196, Launcher (com.android.launcher3)
startActivitySafely:350, QuickstepLauncher (com.android.launcher3.uioverrides)
startAppShortcutOrInfoActivity:351, ItemClickHandler (com.android.launcher3.touch)
onClickAppShortcut:309, ItemClickHandler (com.android.launcher3.touch)
onClick:94, ItemClickHandler (com.android.launcher3.touch)
$r8$lambda$c3IcSovkrXGdCZtXy0f_A5Sz5VA:-1, ItemClickHandler (com.android.launcher3.touch)
onClick:-1, ItemClickHandler$$ExternalSyntheticLambda6 (com.android.launcher3.touch)
onItemClicked:388, QuickstepLauncher (com.android.launcher3.uioverrides)
onClick:-1, QuickstepLauncher$$ExternalSyntheticLambda11 (com.android.launcher3.uioverrides)
performClick:7659, View (android.view)
performClickInternal:7636, View (android.view)
-$$Nest$mperformClickInternal:-1, View (android.view)
run:30156, View$PerformClick (android.view)
handleCallback:958, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:205, Looper (android.os)
loop:294, Looper (android.os)
main:8177, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:552, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:971, ZygoteInit (com.android.internal.os)
看一下堆栈是不是很清晰整个startActivity的过程,清楚知道在哪里启动的onClick且一步步到对应的startActivity,这里寻找最贴近context的startActivity,那就是
startActivitySafely:379, ActivityContext (com.android.launcher3.views)
好到了这里就算寻找到添加的地方
添加相关参数并修复bug:
添加相关的修改部分
default RunnableList startActivitySafely(
View v, Intent intent, @Nullable ItemInfo item) {
//省略
ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
: makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON
? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);
UserHandle user = item == null ? null : item.user;
Bundle optsBundle = options.toBundle();
//省略
if (isShortcut) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item);
} else if (user == null || user.equals(Process.myUserHandle())) {
//开始在原来启动options基础添加相关的WindowingMode和LaunchBounds
options.options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
final Rect r = new Rect(0, 0, 1440, (int)(2960 * 0.63f));
options.options.setLaunchBounds(r);
//添加结束后需要重新转换成toBundle
optsBundle = options.toBundle();
// Could be launching some bookkeeping activity
context.startActivity(intent, optsBundle);
}
上面修改已经有详细注释了,看看添加后的效果:
看到确实是可以启动Freeform模式的App,不过好像窗口的坐标不是我们设置的,一直显示在左上角,而且状态栏还遮盖了,导致无法拖拉自由窗口,那么到底啥原因导致该问题呢?
这里其实以前学习马哥分屏自由窗口专题时候就有发现类似问题,马哥也带大家手把手解决了,现在依然用老方法打堆栈定位啥原因:
追查到了其实是因为远程动画也有对坐标进行再次设置成0,所以一直左上角,那么是否可以考虑去除桌面启动app远程动画呢?具体桌面去除呢?具体如下:
//最重要不在复用原启动ActivityOptions
ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
final Rect r = new Rect(0, 0, 1440, (int)(2960 * 0.63f));
activityOptions.setLaunchBounds(r);
// Could be launching some bookkeeping activity
context.startActivity(intent, activityOptions.toBundle());//最重要不在复用原启动ActivityOptions
上面最重要就是不在复用原桌面启动ActivityOptions,自己全新定义ActivityOptions,只有setLaunchWindowingMode和setLaunchBounds,然后startActivity就只带这个ActivityOptions。
最后的完美效果如下:
具体详情试看方式:
投屏专题部分:
https://mp.weixin.qq.com/s/IGm6VHMiAOPejC_H3N_SNg
更多framework详细代码和资料参考如下链接
hal+perfetto+surfaceflinger
https://mp.weixin.qq.com/s/LbVLnu1udqExHVKxd74ILg
其他课程七件套专题:
点击这里
https://mp.weixin.qq.com/s/Qv8zjgQ0CkalKmvi8tMGaw
视频试看:
https://www.bilibili.com/video/BV1wc41117L4/
更多framework假威风耗:androidframework007