hi,在手机屏幕屏占比越来越大的时候,用户对手机的屏幕交互体验也在提升,把原来的导航键3个按钮的交互方式,在android P产生了一个巨大的变化,增加了全面屏上导航手势的方式,后面版本也是在P的基础进行一些细微修改。
[入门课,实战课,跨进程专题]
导航手势给人的用户体验也确实很不错,但是他的整套代码实现也是相当复杂的,今天我们对导航手势的OtherActivityInputConsumer情况来进行分析。
1、首先OtherActivityInputConsumer指的是什么情况呢?
这里我们字面意思就可以知道它大概是指的是目前在其他Activity,要通过手势返回到Launcher,这样一个场景。例如一下几幅图:
这个时候处于联系人界面
通过手势滑动到Launcher
直观剖析涉及几个过程:
1、联系人界面在手指滑动过程中实际还依然是一个Activity的窗口,因为滑动过程中Activity窗口是可以更新变化的,加上可以看events日志:
11-29 03:58:57.046 25180 25180 I am_on_restart_called: [0,com.android.launcher3.lineage.LineageLauncher,handleWindowVisibility]
11-29 03:58:57.084 25180 25180 I am_on_start_called: [0,com.android.launcher3.lineage.LineageLauncher,handleWindowVisibility]
手在滑动过程中这里只看到有桌面Activity onStart,没有onResume,联系人应用也并没有onPasue
11-29 03:59:15.709 2522 6076 I am_focused_stack: [0,0,0,31,RecentsAnimation.onAnimationFinished()]
11-29 03:59:15.712 31776 31776 I am_on_top_resumed_lost_called: [0,com.android.contacts.activities.PeopleActivity,topStateChangedWhenResumed]
11-29 03:59:15.714 2522 6076 I am_pause_activity: [0,106262359,com.android.contacts/.activities.PeopleActivity,userLeaving=false]
11-29 03:59:15.726 2522 6076 I am_set_resumed_activity: [0,com.android.launcher3/.lineage.LineageLauncher,resumeTopActivityInnerLocked]
11-29 03:59:15.730 2522 6076 I am_add_to_stopping: [0,106262359,com.android.contacts/.activities.PeopleActivity,makeInvisible]
11-29 03:59:15.734 2522 6076 I am_resume_activity: [0,239824215,471,com.android.launcher3/.lineage.LineageLauncher]
11-29 03:59:15.743 25180 25180 I am_on_resume_called: [0,com.android.launcher3.lineage.LineageLauncher,RESUME_ACTIVITY]
11-29 03:59:15.771 31776 31776 I am_on_paused_called: [0,com.android.contacts.activities.PeopleActivity,performPause]
这里就滑到多任务界面后松手,这里可以看出这个时候Launcher才真正onResume,联系人应用onPasue
2、从第一部分的分析场景看出,这个时候居然是联系人界面显示着还是Resume同时,桌面Activity的RecentView也同时显示,这种场景其实对于大部分同学来说都是没有见过的。因为平时都是要么显示是Launcher的Activity,要么是联系人Activity的界面,这个一下显示两个Activity界面情况还真的。。没见过
下面来想想,该怎么如果我们要处理这种两个Activity同时显示情况,适合由谁来主导这种协调的显示过程呢?
哈哈,这其实很好想到设计者一定会放到Launcher来负责协调两个Activity的同时显示界面,其实这也就是桌面多了一个文件夹quickstep原因,这部分代码还依赖Systemui部分。
重点介绍OtherActivityInputConsumer情况下的多任务卡片的手势运行步骤:
路径:quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
private void initInputMonitor() {
//省略部分
try {
mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
.monitorGestureInput("swipe-up", mDefaultDisplayId), KEY_EXTRA_INPUT_MONITOR);
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);//这里调用InputMonitor注册了全局触摸事件监听,有触摸事件来了后会触发onInputEvent方法
//省略部分
}
//接受全局触摸的onInputEvent
private void onInputEvent(InputEvent ev) {
//省略部分
if (event.getAction() == ACTION_DOWN) {
mLogId = TOUCH_INTERACTION_LOG.generateAndSetLogId();
sSwipeSharedState.setLogTraceId(mLogId);
if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
boolean useSharedState = mConsumer.useSharedSwipeState();
mConsumer.onConsumerAboutToBeSwitched();
mConsumer = newConsumer(useSharedState, event);//这里在其他Activity滑动时候mConsumer其实就是我们今天主角OtherActivityInputConsumer
TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
mUncheckedConsumer = mConsumer;
} else if (mIsUserUnlocked && mMode == Mode.NO_BUTTON
&& canTriggerAssistantAction(event)) {
//省略部分
}
}
TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
mUncheckedConsumer.onMotionEvent(event);//这里会调用对应Consumer的onMotionEvent方法
}
好的上面已介绍出了OtherActivityInputConsumer的onMotionEvent,因为本身整个手势动作都是触摸引发,所以接下来触摸相关,这里因为细节代码实在太多,很多地方只能省略,梳理出大概过程,大家知道个轮廓,然后顺着轮廓去自己详细分析。
//quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@Override
public void onMotionEvent(MotionEvent ev) {
//省略部分
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
//省略部分
if (!mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
}
//省略部分
break;
}
//省略部分
case ACTION_MOVE: {
//省略部分
if (mInteractionHandler != null) {
if (mPassedWindowMoveSlop) {
// 根据得出的触摸滑动距离,调用对应的updateDisplacement方法来更新滑动界面RecentView和联系人窗口位置
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
}
if (mMode == Mode.NO_BUTTON) {
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask);
mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask);
}
}
break;
}
case ACTION_CANCEL:
case ACTION_UP: {
finishTouchTracking(ev);
break;
}
}
}
这里主要两个方法:
1、startTouchTrackingForWindowAnimation方法
//quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
private void startTouchTrackingForWindowAnimation(
long touchTimeMs, boolean isLikelyToStartNewTask) {
//省略部分
if (listenerSet != null) {
listenerSet.addListener(handler);
mSwipeSharedState.applyActiveRecentsAnimationState(handler);
notifyGestureStarted();
} else {
//构造出RecentsAnimationListenerSet对象
RecentsAnimationListenerSet newListenerSet =
mSwipeSharedState.newRecentsAnimationListenerSet();
newListenerSet.addListener(handler);
Intent intent = handler.getLaunchIntent();
intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
//通过startRecentsActivityAsync来调用把newListenerSet传递到startRecentsActivityAsync
startRecentsActivityAsync(intent, newListenerSet);
}
}
这里主要是构造RecentsAnimationListenerSet对象然后传递到startRecentsActivityAsync
quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
//这里调用是ActivityManagerWrapper的startRecentsActivity方法
UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
.startRecentsActivity(intent, null, listener, null, null));
}
//frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
/**
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
public void startRecentsActivity(Intent intent, final AssistDataReceiver assistDataReceiver,
final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
try {
//省略部分
IRecentsAnimationRunner runner = null;
if (animationHandler != null) {
//这里构造出了一个binder的服务端,他是准备让systemserver调用的
runner = new IRecentsAnimationRunner.Stub() {
@Override
public void onAnimationStart(IRecentsAnimationController controller,
RemoteAnimationTarget[] apps, Rect homeContentInsets,
Rect minimizedHomeBounds) {
final RecentsAnimationControllerCompat controllerCompat =
new RecentsAnimationControllerCompat(controller);
final RemoteAnimationTargetCompat[] appsCompat =
RemoteAnimationTargetCompat.wrap(apps);
//这里会调用到上一个步骤的RecentsAnimationListenerSet
animationHandler.onAnimationStart(controllerCompat, appsCompat,
homeContentInsets, minimizedHomeBounds);
}
@Override
public void onAnimationCanceled(boolean deferredWithScreenshot) {
animationHandler.onAnimationCanceled(
deferredWithScreenshot ? new ThumbnailData() : null);
}
};
}
//这里跨进程调用到了ActivityTaskManagerService到了systemserver端
ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner);
//省略部分
这时候代码就运行到了systemserver的 ActivityTaskManagerService:
base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public void startRecentsActivity(Intent intent, @Deprecated IAssistDataReceiver unused,
@Nullable IRecentsAnimationRunner recentsAnimationRunner) {
//省略部分
try {
synchronized (mGlobalLock) {
final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
final int recentsUid = mRecentTasks.getRecentsComponentUid();
final WindowProcessController caller = getProcessController(callingPid, callingUid);
// Start a new recents animation
//构造出对应的RecentsAnimation动画
final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor,
getActivityStartController(), mWindowManager, intent, recentsComponent,
recentsUid, caller);
if (recentsAnimationRunner == null) {
anim.preloadRecentsActivity();
} else {
//调用RecentsAnimation的startRecentsActivity方法
anim.startRecentsActivity(recentsAnimationRunner);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
这里调用到了RecentsAnimation的startRecentsActivity
base/services/core/java/com/android/server/wm/RecentsAnimation.java
void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
//省略部分
ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
mTargetActivityType);//获取目标栈,这里是Home,就是Launcher
ActivityRecord targetActivity = getTargetActivity(targetStack);
final boolean hasExistingActivity = targetActivity != null;
if (hasExistingActivity) {
final ActivityDisplay display = targetActivity.getDisplay();
mRestoreTargetBehindStack = display.getStackAbove(targetStack);
//省略部分
}
//省略部分
try {
if (hasExistingActivity) {
// Move the recents activity into place for the animation if it is not top most
//把桌面Launcher的栈stack移到联系人这个当前显示栈的后一位,紧跟着
mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
//省略部分
} else {
//省略部分
}
//省略部分
mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION,
"startRecentsActivity");
//初始化多任务动画相关
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, mDefaultDisplay.mDisplayId,
mStackSupervisor.mRecentTasks.getRecentTaskIds());
//这里需要调用保证Launcher是处于Visible的状态,因为之前一直在后台当然是非visible状态
mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
//省略部分
} catch (Exception e) {
Slog.e(TAG, "Failed to start recents activity", e);
throw e;
} finally {
mWindowManager.continueSurfaceLayout();
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
这里主要介绍一下initializeRecentsAnimation这个关键方法:
base/services/core/java/com/android/server/wm/WindowManagerService.java
public void initializeRecentsAnimation(int targetActivityType,
IRecentsAnimationRunner recentsAnimationRunner,
RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
SparseBooleanArray recentTaskIds) {
synchronized (mGlobalLock) {
//这里构造出RecentsAnimationController对象
mRecentsAnimationController = new RecentsAnimationController(this,
recentsAnimationRunner, callbacks, displayId);
mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
//调用了RecentsAnimationController的initialize方法
mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
}
}
这里的调用了RecentsAnimationController的initialize
base/services/core/java/com/android/server/wm/RecentsAnimationController.java
public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds) {
mTargetActivityType = targetActivityType;
mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
// Make leashes for each of the visible/target tasks and add it to the recents animation to
// be started
//获取当前显示的Task
final ArrayList<Task> visibleTasks = mDisplayContent.getVisibleTasks();
//获取目标Stack
final TaskStack targetStack = mDisplayContent.getStack(WINDOWING_MODE_UNDEFINED,
targetActivityType);
if (targetStack != null) {
for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
final Task t = targetStack.getChildAt(i);
if (!visibleTasks.contains(t)) {
visibleTasks.add(t);//把对应Stack的task加入到visibleTasks
}
}
}
final int taskCount = visibleTasks.size();
for (int i = 0; i < taskCount; i++) {
final Task task = visibleTasks.get(i);
final WindowConfiguration config = task.getWindowConfiguration();
if (config.tasksAreFloating()
|| config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
continue;
}
//把对应visibleTasks一个个加入Animation中
addAnimation(task, !recentTaskIds.get(task.mTaskId));
}
//省略部分
//调用
mService.mWindowPlacerLocked.performSurfacePlacement();
// Notify that the animation has started
if (mStatusBar != null) {
mStatusBar.onRecentsAnimationStateChanged(true /* running */);
}
}
这里主要来看看addAnimation和performSurfacePlacement:
base/services/core/java/com/android/server/wm/RecentsAnimationController.java
@VisibleForTesting
AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "addAnimation(" + task.getName() + ")");
//基于Task构造出对应的TaskAnimationAdapter
final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
isRecentTaskInvisible);
//这里直接调用了task的startAnimation
task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */);
task.commitPendingTransaction();
//taskAdapter添加到了mPendingAnimations
mPendingAnimations.add(taskAdapter);
return taskAdapter;
}
这里主要就是TaskAnimationAdapter构造以后,task调用了startAnimation
base/services/core/java/com/android/server/wm/WindowContainer.java
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
if (DEBUG_ANIM) Slog.v(TAG, "Starting animation on " + this + ": " + anim);
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden);
}
然后调用到了SurfaceAnimator的startAnimation,这里才是关键:
base/services/core/java/com/android/server/wm/SurfaceAnimator.java
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
cancelAnimation();
return;
}
//根据AnimationAdapter创建出了对应的mLeash牵引
mLeash = createAnimationLeash(surface, t,
mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), hidden);
mAnimatable.onAnimationLeashCreated(t, mLeash);
if (mAnimationStartDelayed) {
if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
return;
}
mAnimation.startAnimation(mLeash, t, mInnerAnimationFinishedCallback);
}
创建出来了mLeash动画Surface,然后把mLeash设置回了AnimationAdapter
创建牵引过程:
private SurfaceControl createAnimationLeash(SurfaceControl surface, Transaction t, int width,
int height, boolean hidden) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
.setParent(mAnimatable.getAnimationLeashParent())
.setName(surface + " - animation-leash");
final SurfaceControl leash = builder.build();
t.setWindowCrop(leash, width, height);
t.show(leash);
// TODO: change this back to use show instead of alpha when b/138459974 is fixed.
t.setAlpha(leash, hidden ? 0 : 1);
t.reparent(surface, leash);
return leash;
}
那么接下来分析 mService.mWindowPlacerLocked.performSurfacePlacement()方法:
他最后一路调用会调用到base/services/core/java/com/android/server/wm/RootWindowContainer.java:
// "Something has changed! Let's make it correct now."
// TODO: Super crazy long method that should be broken down...
void performSurfacePlacementNoTrace(boolean recoveringMemory) {
//省略非本次重点讨论部分
// Defer starting the recents animation until the wallpaper has drawn
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
if (recentsAnimationController != null) {
recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
}
//省略非本次重点讨论部分
}
//这里会调用到RecentsAnimationController的checkAnimationReady:
void checkAnimationReady(WallpaperController wallpaperController) {
if (mPendingStart) {
final boolean wallpaperReady = !isTargetOverWallpaper()
|| (wallpaperController.getWallpaperTarget() != null
&& wallpaperController.wallpaperTransitionReady());
if (wallpaperReady) {
//调用了RecentsAnimationController的startAnimation
mService.getRecentsAnimationController().startAnimation();
}
}
}
这里来看RecentsAnimationController的startAnimation:
base/services/core/java/com/android/server/wm/RecentsAnimationController.java
void startAnimation() {
//省略非本次重点讨论部分
try {
final ArrayList<RemoteAnimationTarget> appAnimations = new ArrayList<>();
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
//taskAdapter创建出对应的RemoteAnimationTarget
final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationApp();
if (target != null) {
appAnimations.add(target);
} else {
removeAnimation(taskAdapter);
}
}
//省略非本次重点讨论部分
//这里调用了mRunner的onAnimationStart,这个mRunner就是在桌面端的binder服务端对象,这里把appTargets传递给了Launcher端,其实就是mPendingAnimations中的taskAdapter调用createRemoteAnimationApp()创建出来的
mRunner.onAnimationStart(mController, appTargets, contentInsets, minimizedHomeBounds);
if (DEBUG_RECENTS_ANIMATIONS) {
Slog.d(TAG, "startAnimation(): Notify animation start:");
for (int i = 0; i < mPendingAnimations.size(); i++) {
final Task task = mPendingAnimations.get(i).mTask;
Slog.d(TAG, "\t" + task.mTaskId);
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to start recents animation", e);
}
final SparseIntArray reasons = new SparseIntArray();
reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
}
//这里对createRemoteAnimationApp进行解释
RemoteAnimationTarget createRemoteAnimationApp() {
//省略部分
//这里主要mTaskId,mCapturedLeash(Leash的SurfaceControl,这个属于非常重要的,后面Launcher手势动画客户端就是靠它来控制画面缩放移动)
mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
!topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
insets, mTask.getPrefixOrderIndex(), mPosition, mBounds,
mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null);
return mTarget;
}