前篇常规启动传送门:
SystemUI之QuickStep探索(常规启动篇)
通过上划手势启动最近任务的方式:
SystemUI部分
上划手势的起始点是在导航栏上,所以先从NavigationBarView上看起。
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (shouldDeadZoneConsumeTouchEvents(event)) {
return true;
}
switch (event.getActionMasked()) {
...
}
// 交给NavigationBarGestureHelper处理
return mGestureHelper.onInterceptTouchEvent(event);
}
可以看到随后把event交给mGestureHelper处理,mGestureHelper是一个NavigationBarGestureHelper类的对象,onInterceptTouchEvent处理流程:
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mIsInScreenPinning = mNavigationBarView.inScreenPinning();
mNotificationsVisibleOnDown = !mStatusBar.isPresenterFullyCollapsed();
}
if (!canHandleGestures()) {
return false;
}
// 交给QuickStepController处理
boolean result = mQuickStepController.onInterceptTouchEvent(event);
if (mDockWindowEnabled) {
result |= interceptDockWindowEvent(event);
}
return result;
}
然后又传到了QuickStepController中:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return handleTouchEvent(event);
}
private boolean handleTouchEvent(MotionEvent event) {
// 如果没有绑定的service,或者quickstep配置关闭了,则跳过不处理
if (mOverviewEventSender.getProxy() == null || (!mNavigationBarView.isQuickScrubEnabled()
&& !mNavigationBarView.isQuickStepSwipeUpEnabled())) {
return false;
}
mNavigationBarView.requestUnbufferedDispatch(event);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
int x = (int) event.getX();
int y = (int) event.getY();
// End any existing quickscrub animations before starting the new transition
if (mTrackAnimator != null) {
mTrackAnimator.end();
mTrackAnimator = null;
}
// 以下判断是点击还是长按
mCurrentNavigationBarView = mNavigationBarView.getCurrentView();
mHitTarget = mNavigationBarView.getButtonAtPosition(x, y);
if (mHitTarget != null) {
// Pre-emptively delay the touch feedback for the button that we just touched
mHitTarget.setDelayTouchFeedback(true);
}
mTouchDownX = x;
mTouchDownY = y;
mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
mQuickStepStarted = false;
mAllowGestureDetection = true;
break;
}
case MotionEvent.ACTION_MOVE: {
if (mQuickStepStarted || !mAllowGestureDetection){
break;
}
int x = (int) event.getX();
int y = (int) event.getY();
int xDiff = Math.abs(x - mTouchDownX);
int yDiff = Math.abs(y - mTouchDownY);
boolean exceededScrubTouchSlop, exceededSwipeUpTouchSlop;
int pos, touchDown, offset, trackSize;
// 判断竖屏和横屏状态时的触摸偏移量是否足够触发quickstep
if (mIsVertical) {
exceededScrubTouchSlop =
yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff;
exceededSwipeUpTouchSlop =
xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff;
pos = y;
touchDown = mTouchDownY;
offset = pos - mTrackRect.top;
trackSize = mTrackRect.height();
} else {
exceededScrubTouchSlop =
xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff;
exceededSwipeUpTouchSlop =
yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff;
pos = x;
touchDown = mTouchDownX;
offset = pos - mTrackRect.left;
trackSize = mTrackRect.width();
}
// 启动quickstep
// Decide to start quickstep if dragging away from the navigation bar, otherwise in
// the parallel direction, decide to start quickscrub. Only one may run.
if (!mQuickScrubActive && exceededSwipeUpTouchSlop) {
if (mNavigationBarView.isQuickStepSwipeUpEnabled()) {
startQuickStep(event);
}
break;
}
// Do not handle quick scrub if disabled
if (!mNavigationBarView.isQuickScrubEnabled()) {
break;
}
if (!mDragPositive) {
offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width();
}
final boolean allowDrag = !mDragPositive
? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown;
float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
// 启动quickscrub
if (allowDrag) {
// Passing the drag slop then touch slop will start quick step
if (!mQuickScrubActive && exceededScrubTouchSlop) {
startQuickScrub();
}
}
// 左右滑动切换任务
if (mQuickScrubActive && (mDragPositive && offset >= 0
|| !mDragPositive && offset <= 0)) {
try {
mOverviewEventSender.getProxy().onQuickScrubProgress(scrubFraction);
if (DEBUG_OVERVIEW_PROXY) {
Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to send progress of quick scrub.", e);
}
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// 结束quickscrub,回到应用
endQuickScrub(true /* animate */);
break;
}
// Proxy motion events to launcher if not handled by quick scrub
// Proxy motion events up/cancel that would be sent after long press on any nav button
if (!mQuickScrubActive && (mAllowGestureDetection || action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP)) {
// 上划event事件传递给launcher的quickstep处理
proxyMotionEvents(event);
}
return mQuickScrubActive || mQuickStepStarted;
}
在handleTouchEvent()里,首先会判断quickstep是否可用,在常规启动的文章中,已经大致讲过了SystemUI的OverviewProxyService与Launcher的TouchInteractionService之间的关系,这边就不再赘述了。其他的enable属性在framework力都有对应的config属性可以配置。
随后对event的action进行判断处理:
- DOWN:记录起始位置的x和y,记录后续是点击还是长按,初始化一些flag
- MOVE:首先根据move的坐标判断滑动的偏移量是否足够触发quickstep,如果满足条件的话则调用启动quickstep的函数,否则继续判断是否足够触发quickscrub(quickscrub是指在导航栏上左右划动快速切换最近任务卡片),满足条件则启动quickscrub,并计算划动量具体切换到哪张卡片
- UP&CANCEL:如果当前是quickscrub,啧结束quickscrub流程;如果当前是quickstep,则将event通过proxyMotionEvents()函数传递给Launcher的service处理
private void startQuickStep(MotionEvent event) {
mQuickStepStarted = true;
event.transform(mTransformGlobalMatrix);
try {
mOverviewEventSender.getProxy().onQuickStep(event);
if (DEBUG_OVERVIEW_PROXY) {
Log.d(TAG_OPS, "Quick Step Start");
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to send quick step started.", e);
} finally {
event.transform(mTransformLocalMatrix);
}
mOverviewEventSender.notifyQuickStepStarted();
mHandler.removeCallbacksAndMessages(null);
...
}
private boolean proxyMotionEvents(MotionEvent event) {
final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
event.transform(mTransformGlobalMatrix);
try {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget());
}
overviewProxy.onMotionEvent(event);
if (DEBUG_OVERVIEW_PROXY) {
Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
}
return true;
}
...
return false;
}
可以看到这几个函数最后都要通过OverviewProxyService的SystemUIProxy调用Launcher端的TouchInteractionService来处理。
Launcher部分
TouchInteractionService
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@Override
public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException {
TraceHelper.beginSection("SysUiBinder");
setupTouchConsumer(downHitTarget);
TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
}
@Override
public void onMotionEvent(MotionEvent ev) {
mEventQueue.queue(ev);
String name = sMotionEventNames.get(ev.getActionMasked());
if (name != null){
TraceHelper.partitionSection("SysUiBinder", name);
}
}
@Override
public void onBind(ISystemUiProxy iSystemUiProxy) {
mISystemUiProxy = iSystemUiProxy;
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
}
@Override
public void onQuickScrubStart() {
mEventQueue.onQuickScrubStart();
TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
}
@Override
public void onQuickScrubProgress(float progress) {
mEventQueue.onQuickScrubProgress(progress);
}
@Override
public void onQuickScrubEnd() {
mEventQueue.onQuickScrubEnd();
TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
}
@Override
public void onOverviewToggle() {
mOverviewCommandHelper.onOverviewToggle();
}
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
if (triggeredFromAltTab) {
setupTouchConsumer(HIT_TARGET_NONE);
mEventQueue.onOverviewShownFromAltTab();
} else {
mOverviewCommandHelper.onOverviewShown();
}
}
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab initiates quick scrub. Ending it here.
mEventQueue.onQuickScrubEnd();
}
}
@Override
public void onQuickStep(MotionEvent motionEvent) {
mEventQueue.onQuickStep(motionEvent);
TraceHelper.endSection("SysUiBinder", "onQuickStep");
}
@Override
public void onTip(int actionType, int viewType) {
mOverviewCommandHelper.onTip(actionType, viewType);
}
};
以上是所有在Launcher端实现的SystemUIProxy接口对应的实现函数,其中onOverviewToggle是上一章中讲过的常规启动方式调用的地方,而另外几个这次涉及到的函数,无一例外都和mEventQueue有关。mEventQueue是MotionEventQueue类的对象,类似SystemUI的CommandQueue,在这个类里每个被调用的函数都有一个定义好的action(queue() 比较特殊,他是处理event事件的函数,所以直接用event里的action),随后用Choreographer处理启动quickstep相关的动画。
关于Android系统的编舞者Choreographer类,可参考下面的文章了解,本文中不再细讲。
Android8.1 Choreographer机制与源码分析
MotionEventQueue
private void runFor(Choreographer caller) {
synchronized (mExecutionLock) {
EventArray array = swapAndGetCurrentArray(caller);
int size = array.size();
for (int i = 0; i < size; i++) {
MotionEvent event = array.get(i);
// 自定义的action类型
if (event.getActionMasked() == ACTION_VIRTUAL) {
switch (event.getAction()) {
case ACTION_QUICK_SCRUB_START:
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
case ACTION_QUICK_SCRUB_PROGRESS:
mConsumer.onQuickScrubProgress(event.getX());
break;
case ACTION_QUICK_SCRUB_END:
mConsumer.onQuickScrubEnd();
break;
case ACTION_RESET:
// 对应Service的onPreMotionEvent,初始化Consumer
mConsumer.reset();
break;
case ACTION_DEFER_INIT:
mConsumer.deferInit();
break;
case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
mConsumer.onShowOverviewFromAltTab();
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
case ACTION_QUICK_STEP:
// 对应Service的onQuickStep
mConsumer.onQuickStep(event);
break;
case ACTION_COMMAND:
mConsumer.onCommand(event.getSource());
break;
default:
Log.e(TAG, "Invalid virtual event: " + event.getAction());
}
// MotionEvent类型的action
} else {
// 对应Service的onMotionEvent
mConsumer.accept(event);
}
event.recycle();
}
array.clear();
array.lastEventAction = ACTION_CANCEL;
}
}
mConsumer为OtherActivityTouchConsumer
【注意】:这个函数里面的event,无论是新实例化的含有自定义action的,还是通过SystemUI参数传过来的,在处理完毕后都必须调用recycle(),新实例化的自不用说,就算是SystemUI里onInterceptTouchEvent的参数,在跨进程通讯的Service里,参数都必须是实现Parcelable,且会被重新序列化,也相当于一个新的对象了,因此原先onInterceptTouchEvent周期里自动recycle()并不适用于现在的对象,所以需要手动去recycle()回收。
最后再看下onQuickStep的流程吧
@Override
public void onQuickStep(MotionEvent ev) {
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once we pass the actual
// touch slop
// 启动最近任务以及对应的缩放渐隐动画
startTouchTrackingForWindowAnimation(ev.getEventTime());
mPassedInitialSlop = true;
mStartDisplacement = getDisplacement(ev);
}
notifyGestureStarted();
}
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
// Create the shared handler
RecentsAnimationState animationState = new RecentsAnimationState();
final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper);
...
// 启动launcher的recent activity
Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
mHomeIntent,
new AssistDataReceiver() {
@Override
public void onHandleAssistData(Bundle bundle) {
mRecentsModel.preloadAssistData(mRunningTask.id, bundle);
}
}, animationState, null, null);
if (Looper.myLooper() != Looper.getMainLooper()) {
startActivity.run();
try {
drawWaitLock.await(LAUNCHER_DRAW_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (Exception e) {
// We have waited long enough for launcher to draw
}
} else {
// We should almost always get touch-town on background thread. This is an edge case
// when the background Choreographer has not yet initialized.
BackgroundExecutor.get().submit(startActivity);
}
}
可以看到这边就是最后启动recent activity的地方了,其中mHomeIntent里的componentName为OverviewCommandHelper里设置的,具体可查看上一章常规启动篇最后的initOverviewTargets.
总结
以上就是Quickstep的手势启动流程了,因为启动过程是伴随着动画的,所以其中涉及到的动画相关的代码非常多,这些本文中暂时都没有细讲,Quickstep需要研究的知识点太多了,后续有待更深入的分析。