分屏SplitScreen干货技能:如何监听分屏Task进入退出及上下分屏位置
***//***//***/aidl接口主要有两个接口:onStagePositionChanged--代表分屏的stage位置有变化的时候回调,正常都是main side stage 值一般为0,1onTaskStageChanged --代表分屏task的有变化时候回调,比如退出分屏,进入分屏和2个参数stage,visible。stage代表当前taskId处于main还是side stag
背景:
原文参考:分屏SplitScreen干货技能:如何监听分屏Task进入退出及上下分屏位置之ISplitScreenListener
在系统中开发一些需求时候,偶尔需要识别当前系统是不是处于分屏模式,上下分屏分别对应是什么Task等需求。
例如:Activity进入分屏模式,进入后有退出分屏模式,回到Activity全屏
经常会有Activity进入分屏模式,进入后有退出分屏模式,回到Activity全屏
需求也是需要监听当前Activity是否还处于分屏还是全屏,分屏和全屏时候分别哪些Activity,分屏的side和main对应的Activity是谁?
这个时候大家第一时间会想到Activity本身不是有相关的方法么?可以通过configration等获取windowmode是否为mutilwindow模式就可以。
frameworks/base/core/java/android/app/Activity.java
/**
* Returns true if the activity is currently in multi-window mode.
* @see android.R.attr#resizeableActivity
*
* @return True if the activity is in multi-window mode.
*/
public boolean isInMultiWindowMode() {
return mIsInMultiWindowMode;
}
但是这个一般只能简单识别自己当前Activity是否处于 multi-window mode,如果分屏的两个Task都不是自己Activity,那就无法使用,而且这个也无法知道自己是否退出属于一个主动获取行为。
分屏通知接口介绍
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
package com.android.wm.shell.splitscreen;
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
*/
oneway interface ISplitScreenListener {
/**
* Called when the stage position changes.
*/
void onStagePositionChanged(int stage, int position);
/**
* Called when a task changes stages.
*/
void onTaskStageChanged(int taskId, int stage, boolean visible);
}
aidl接口主要有两个接口:
onStagePositionChanged --代表分屏的stage位置有变化的时候回调,正常都是main side stage 值一般为0,1
onTaskStageChanged --代表分屏task的有变化时候回调,比如退出分屏,进入分屏
和2个参数stage,visible。
stage代表当前taskId处于main还是side stage的位置,值一般为0,1上下分屏,如果-1就是退出分屏。
visible就是代表当前taskId是否显示
实战使用
第一步:实现ISplitScreenListener.Stub
packages/apps/Launcher3/quickstep/src/com/android/quickstep/TopTaskTracker.java
/**
* This class tracked the top-most task and some 'approximate' task history to allow faster
* system state estimation during touch interaction
*/
public class TopTaskTracker extends ISplitScreenListener.Stub
implements TaskStackChangeListener, SafeCloseable {
@Override
public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
if (DEBUG) {
Log.i(TAG, "onStagePositionChanged: stage=" + stage + ", position=" + position);
}
if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
mMainStagePosition.stagePosition = position;
} else {
mSideStagePosition.stagePosition = position;
}
}
@Override
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (DEBUG) {
Log.i(TAG, "onTaskStageChanged: taskId=" + taskId
+ ", stage=" + stage + ", visible=" + visible);
}
//省略
}
第二步:注册注销监听registerSplitScreenListener
//注册split相关回调
SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
//注销split相关回调
SystemUiProxy.INSTANCE.get(mContext).unregisterSplitScreenListener(this);
分屏变化源码分析
先分析一下注册方法registerSplitScreenListener:
SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
这里调用到SystemUiProxy的registerSplitScreenListener方法
packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java
//
// Splitscreen
//
public void registerSplitScreenListener(ISplitScreenListener listener) {
if (mSplitScreen != null) {
try {
//这里主要是调用mSplitScreen这个分屏代理进行register
mSplitScreen.registerSplitScreenListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
}
mSplitScreenListener = listener;
}
接下来看看mSplitScreen桌面获取的:
packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java
/**
* Sets proxy state, including death linkage, various listeners, and other configuration objects
*/
@MainThread
public void setProxy(ISystemUiProxy proxy, IPip pip, IBubbles bubbles, ISplitScreen splitScreen,
IOneHanded oneHanded, IShellTransitions shellTransitions,
IStartingWindow startingWindow, IRecentTasks recentTasks,
ISysuiUnlockAnimationController sysuiUnlockAnimationController,
IBackAnimation backAnimation, IDesktopMode desktopMode,
IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) {
//省略
mSplitScreen = splitScreen;
//省略
}
可以看到是通过SystemUiProxy类的setProxy进行的调用,那么setProxy又是如何被调用的呢?
packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
public void onInitialize(Bundle bundle) {
//省略不服
//获取分屏的ISplitScreen
ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
KEY_EXTRA_SHELL_SPLIT_SCREEN));
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
//调用setProxy
SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
unfoldTransition, dragAndDrop);
}));
sIsInitialized = true;
}
那么这里的 onInitialize肯定是一个跨进程方法,属于systemui进程回调的,那么ISplitScreen对象其实在systemui进程进行放入的,具体代码如下:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
void onInit() {
//省略
//这里调用createExternalInterface创建binder对象与KEY_EXTRA_SHELL_SPLIT_SCREEN进行map匹配
mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
this::createExternalInterface, this);
}
private ExternalInterfaceBinder createExternalInterface() {
//直接new ISplitScreenImpl
return new ISplitScreenImpl(this);
}
这里的看看ISplitScreenImpl
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
/**
* The interface for calls from outside the host process.
*/
@BinderThread
private static class ISplitScreenImpl extends ISplitScreen.Stub
implements ExternalInterfaceBinder {
可以看到这里ISplitScreenImpl就是ISplitScreen.Stub这个实体binder对象。
再看看构造方法:
public ISplitScreenImpl(SplitScreenController controller) {
mController = controller;
//构造出mListener,注意这里构造会直接把mSplitScreenListener注册到SplitScreenController中去
mListener = new SingleInstanceRemoteListener<>(controller,
c -> c.registerSplitScreenListener(mSplitScreenListener),
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
}
//这里是mSplitScreenListener方法最后是会转发onTaskStageChanged事件跨进程通知客户端
private final SplitScreen.SplitScreenListener mSplitScreenListener =
new SplitScreen.SplitScreenListener() {
@Override
public void onStagePositionChanged(int stage, int position) {
//回调会直接调用mListener,也就是远程调用
mListener.call(l -> l.onStagePositionChanged(stage, position));
}
@Override
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
}
};
看看注册mSplitScreenListener到SplitScreenController中:
public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
mStageCoordinator.registerSplitScreenListener(listener);
}
可以看到ISplitScreenImpl构造的mSplitScreenListener直接被注册mStageCoordinator中。
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
if (mListeners.contains(listener)) return;
mListeners.add(listener);
sendStatusToListener(listener);
}
这里总结一下,弯弯绕绕比较多:
1、分屏的binder对象构造,其实也就是ISplitScreenImpl对象,在SplitScreenController的onInit方法中进行
2、ISplitScreenImpl在构造时候就已经把他的成员变量mSplitScreenListener注册到了StageCoordinator中
3、等待后续客户端调用ISplitScreenImpl的register注册远程的代理ISplitScreenListener过来时候,会与赋值SplitScreenImpl的mListener
4、StageCoordinator有相关的变化时候,先调用上面的mSplitScreenListener,再binder调用到远端客户端
再看看对应客户端调用的register方法:
这里看到是调用到ISplitScreenImpl
private static class ISplitScreenImpl extends ISplitScreen.Stub
implements ExternalInterfaceBinder {
@Override
public void registerSplitScreenListener(ISplitScreenListener listener) {
executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
(controller) -> mListener.register(listener));
}
可以看得出最后客户端注册的ISplitScreenListener都被注册到mListener中。
接下来systemui分屏回调部分
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
boolean present, boolean visible) {
int stage;
if (present) {
stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
} else {
// No longer on any stage
stage = STAGE_TYPE_UNDEFINED;
}
if (present) {
updateRecentTasksSplitPair();
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
//进行通知
mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
}
}
这里的onStageChildTaskStatusChanged方法被调用堆栈如下:
onStageChildTaskStatusChanged:1957, StageCoordinator (com.android.wm.shell.splitscreen)
onChildTaskStatusChanged:3699, StageCoordinator$StageListenerImpl (com.android.wm.shell.splitscreen)
onTaskAppeared:200, StageTaskListener (com.android.wm.shell.splitscreen)
updateTaskListenerIfNeeded:659, ShellTaskOrganizer (com.android.wm.shell)
onTaskInfoChanged:546, ShellTaskOrganizer (com.android.wm.shell)
lambda$onTaskInfoChanged$6:310, TaskOrganizer$1 (android.window)
run:0, TaskOrganizer$1$$ExternalSyntheticLambda4 (android.window)
handleCallback:959, Handler (android.os)
dispatchMessage:100, Handler (android.os)
loopOnce:232, Looper (android.os)
loop:317, Looper (android.os)
run:85, HandlerThread (android.os)
实战监听分屏进入退出log打印
下面来展示进入和退出分屏的效果,这块我们就直接使用Launcher代码中TopTaskTracker类来进行实战测试,通过log打印既可以看出Task相关变化
packages/apps/Launcher3/quickstep/src/com/android/quickstep/TopTaskTracker.java
进入分屏时候日志打印如下:
I TopTaskTracker: onTaskMovedToFront: (moved taskInfo to front) taskId=151, baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.messaging cmp=com.android.messaging/.ui.conversationlist.ConversationListActivity }
I TopTaskTracker: onTaskStageChanged: taskId=151, stage=1, visible=false
I TopTaskTracker: onTaskStageChanged: taskId=150, stage=0, visible=false
I TopTaskTracker: onTaskStageChanged: taskId=150, stage=0, visible=false
I TopTaskTracker: onTaskStageChanged: taskId=151, stage=1, visible=false
I TopTaskTracker: onTaskMovedToFront: (moved taskInfo to front) taskId=150, baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.dialer cmp=com.android.dialer/.main.impl.MainActivity }
I TopTaskTracker: onTaskStageChanged: taskId=150, stage=0, visible=true
I TopTaskTracker: onTaskStageChanged: taskId=151, stage=1, visible=true
退出分屏时候打印如下:
I TopTaskTracker: onTaskStageChanged: taskId=151, stage=1, visible=true
I TopTaskTracker: onTaskStageChanged: taskId=150, stage=-1, visible=true
I TopTaskTracker: onTaskStageChanged: taskId=151, stage=-1, visible=true
I TopTaskTracker: onTaskMovedToFront: (moved taskInfo to front) taskId=151, baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.messaging cmp=com.android.messaging/.ui.conversationlist.ConversationListActivity }
更多framework实战开发干货,请关注下面“千里马学框架”
更多推荐



所有评论(0)