在Android的辅助功能中,存在一个点击三次屏幕触发屏幕放大功能。
辅助功能中打开
放大后效果
这个功能的使用频率实在是低...但是为什么会想记录一下这个功能的实现原理。第一,在处理性能问题的时候遇到了相关代码;其次其实现的原理还是具有部分启发性质的。主要还是研究启发部分:
1、如何实现手势拦截
2、全局放大的原理(主要在system_server中存在双编舞者协作实现),如下图所示在启动手势放大过程中systrace抓取到下面的现象:
两个编舞者协同工作
一、手势拦截
在设置中打开放大手势的开关,会设置Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED这个属性值,AccessiblityContentObserver中的onChange会处理这个值的变化:
@Override
4456 public void onChange(boolean selfChange, Uri uri) {
4457 synchronized (mLock) {
4458 // Profiles share the accessibility state of the parent. Therefore,
4459 // we are checking for changes only the parent settings.
4460 UserState userState = getCurrentUserStateLocked();
4461
4462 // If the automation service is suppressing, we will update when it dies.
4463 if (userState.isUiAutomationSuppressingOtherServices()) {
4464 return;
4465 }
4466
4467 if (mTouchExplorationEnabledUri.equals(uri)) {
4468 if (readTouchExplorationEnabledSettingLocked(userState)) {
4469 onUserStateChangedLocked(userState);
4470 }
4471 } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
4472 if (readDisplayMagnificationEnabledSettingLocked(userState)) {
4479 onUserStateChangedLocked(userState);
4480 }
4481 }
在onUserStateChangedLocked中会调用updateMagnificationLocked以及scheduleUpdateInputFilter去更新当前系统状态:
updateMagnificationLocked是用于建立wms和辅助功能服务的联系
scheduleUpdateInputFilter是用于在输入层面建立手势拦截,往Input流程中加入inputfilter
1820 private void updateMagnificationLocked(UserState userState) {
1821 if (userState.mUserId != mCurrentUserId) {
1822 return;
1823 }
1824
1825 if (userState.mIsDisplayMagnificationEnabled ||
1826 userHasListeningMagnificationServicesLocked(userState)) {
1827 // Initialize the magnification controller if necessary
1828 getMagnificationController();
//核心在于放大控制器的注册
1829 mMagnificationController.register();
1830 } else if (mMagnificationController != null) {
//当关闭此功能的时候会调用反注册
1831 mMagnificationController.unregister();
1832 }
1833 }
实际上就是通过MagnificationController去注册。
55/**
56 * This class is used to control and query the state of display magnification
57 * from the accessibility manager and related classes. It is responsible for
58 * holding the current state of magnification and animation, and it handles
59 * communication between the accessibility manager and window manager.
60 */
61class MagnificationController
从对这个类的描述可以看出,它是为了控制和查询当前屏幕的放大状态;其次用于辅助服务和WMS之间的通信工作。这些具体的含义还是放到代码中去一一解释。
首先看看他的register函数:
public void register() {
130 synchronized (mLock) {
131 if (!mRegistered) {
//step1、注册广播监听亮灭屏事件
132 mScreenStateObserver.register();
//step2、注册WMS中的回调(与WMS之间通信)
133 mWindowStateObserver.register();
//step3、使能跟动画相关的函数(虽然这个类名字有点奇怪,但还是能猜到是跟动画相关的)
134 mSpecAnimationBridge.setEnabled(true);
135 // Obtain initial state.
136 mWindowStateObserver.getMagnificationRegion(mMagnificationRegion);
137 mMagnificationRegion.getBounds(mMagnificationBounds);
138 mRegistered = true;
139 }
140 }
141 }
step1就略过从step2开始看它是如何跟wms进行交互的。
/**
957 * This class handles the screen magnification when accessibility is enabled.
958 */
959 private static class WindowStateObserver
960 implements WindowManagerInternal.MagnificationCallbacks {
......
975
976 public WindowStateObserver(Context context, MagnificationController controller) {
977 mController = controller;
978 mWindowManager = LocalServices.getService(WindowManagerInternal.class);
979 mHandler = new CallbackHandler(context);
980 }
981
982 public void register() {
987 mWindowManager.setMagnificationCallbacks(this);
990 }
991
WindowStateObserver实现了接口MagnificationCallbacks,这个接口是wms用于通知放大控制器当前wms端有了哪些变化的:
/**
51 * Callbacks for contextual changes that affect the screen magnification
52 * feature.
53 */
54 public interface MagnificationCallbacks {
55
56 /**
57 * Called when the region where magnification operates changes. Note that this isn't the
58 * entire screen. For example, IMEs are not magnified.
*这种情况在放大的情况下点开了输入法,输入法界面是不能够被放大的,但是由于其占用了一定的屏幕空间,就会导致放大的区域变小,wms就会回调注册的该方法
59 *
60 * @param magnificationRegion the current magnification region
61 */
62 public void onMagnificationRegionChanged(Region magnificationRegion);
63
64 /**
65 * Called when an application requests a rectangle on the screen to allow
66 * the client to apply the appropriate pan and scale.
67 *
68 * @param left The rectangle left.
69 * @param top The rectangle top.
70 * @param right The rectangle right.
71 * @param bottom The rectangle bottom.
72 */
73 public void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
74
75 /**
76 * Notifies that the rotation changed.
77 *
78 * @param rotation The current rotation.
79 */
80 public void onRotationChanged(int rotation);
81
82 /**
83 * Notifies that the context of the user changed. For example, an application
84 * was started.
*context发生变化(个人理解为当前Activity发生了切换)
85 */
86 public void onUserContextChanged();
87 }
通过注册WindowStateObserver到WMS,就建立wms和AccessibilityMS的沟通了。
回到前面的step3,使能SpecAnimationBridge,从下面这个类的注释可以看出它有两个功能
/**
727 * Class responsible for animating spec on the main thread and sending spec
728 * updates to the window manager.
729 */
730 private static class SpecAnimationBridge {
1:将放大相关的参数发送给wms
2:在主线程上管理动画:一般而言system_server中只有android.display这条线程有编舞者用来做系统窗口的动画,这里的SpecAnimationBridge就会使用UI线程来创建编舞者,完成放大的动画操作
回到建立手势拦截上,scheduleUpdateInputFilter函数就是完成插入一个inputfilter到input流程中
1383 private void scheduleUpdateInputFilter(UserState userState) {
1384 mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_INPUT_FILTER, userState).sendToTarget();
1385 }
1386
1387 private void updateInputFilter(UserState userState) {
1388 boolean setInputFilter = false;
1389 AccessibilityInputFilter inputFilter = null;
1390 synchronized (mLock) {
1391 int flags = 0;
......
1412 if (flags != 0) {
1413 if (!mHasInputFilter) {
1414 mHasInputFilter = true;
1415 if (mInputFilter == null) {
1416 mInputFilter = new AccessibilityInputFilter(mContext,
1417 AccessibilityManagerService.this);
1418 }
1419 inputFilter = mInputFilter;
1420 setInputFilter = true;
1421 }
1422 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
1423 } else {
......
1430 }
1431 }
1432 if (setInputFilter) {
1433 mWindowManagerService.setInputFilter(inputFilter);
1434 }
1435 }
先抛开一些细节,主要的原理就是创建一个AccessibilityInputFilter(其基类是InputFilter),并根据对应的辅助功能设置其flag,然后通过setInputFilter设置到wms中去。
在android.view包下存在一个InputFilter用于做输入事件的拦截,但是这个API是设定为hide的,APP当然是不能去使用的。 可以进入如下的链接阅读以下这个类的描述
通过inputFilter的注释可以得到有几个要点:
1、当前系统中只能install一个inputfilter
2、inputfilter的作用域在传递给APP之前
3、event流必须是内部一致的,也就是必须是down-up-down-up这样的序列而不能是down-down-up-up这样
4、当有事件达到时会回调public void onInputEvent(InputEvent event, int policyFlags)这个函数进行处理
这里插入介绍一个InputFilterHost类,在InputFilter不处理当前Event的时候通过InputFilterHost的sendInputEvent将输入事件再次注入到native层的InputManagerService中,然后走正常的input流程
/**
2226 * Hosting interface for input filters to call back into the input manager.
2227 */
2228 private final class InputFilterHost extends IInputFilterHost.Stub {
2229 private boolean mDisconnected;
2230
2231 public void disconnectLocked() {
2232 mDisconnected = true;
2233 }
2234
2235 @Override
2236 public void sendInputEvent(InputEvent event, int policyFlags) {
2237 if (event == null) {
2238 throw new IllegalArgumentException("event must not be null");
2239 }
2240
2241 synchronized (mInputFilterLock) {
2242 if (!mDisconnected) {
2243 nativeInjectInputEvent(mPtr, event, Display.DEFAULT_DISPLAY, 0, 0,
2244 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
2245 policyFlags | WindowManagerPolicy.FLAG_FILTERED);
2246 }
2247 }
2248 }
2249 }
到此基本上对InputFilter有个大致的概念了。到此打开手势开关之后,主要就做了两件事:
1、创建MagnificationController跟wms和AccessibilityMS建立沟通
2、往InputManagerService插入InputFilter
那接下来看看AccessibilityInputFilter是具体怎么实作出过滤手势的。基类虽然简单但是这个类的实现还是比较复杂的。
还是从基础的流程开始,因为当有事件进来的话会回调onInputEvent,AccessibilityInputFilter的onInputEvent方法
173 @Override
174 public void onInputEvent(InputEvent event, int policyFlags) {
//1、mEventHandler为空
180 if (mEventHandler == null) {
181 super.onInputEvent(event, policyFlags);
182 return;
183 }
184 //2、EventStreamState为空
185 EventStreamState state = getEventStreamState(event);
186 if (state == null) {
187 super.onInputEvent(event, policyFlags);
188 return;
189 }
190 //3、如果这个event没有标记为传递给用户
191 int eventSource = event.getSource();
192 if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
193 state.reset();
194 mEventHandler.clearEvents(eventSource);
195 super.onInputEvent(event, policyFlags);
196 return;
197 }
198 //4、如果设备的deviceID发生变化
199 if (state.updateDeviceId(event.getDeviceId())) {
200 mEventHandler.clearEvents(eventSource);
201 }
202 //5、如果设备ID无效
203 if (!state.deviceIdValid()) {
204 super.onInputEvent(event, policyFlags);
205 return;
206 }
207
208 if (event instanceof MotionEvent) {
//6、需要添加该filter的时候的flag满足会影响滑动事件
209 if ((mEnabledFeatures & FEATURES_AFFECTING_MOTION_EVENTS) != 0) {
210 MotionEvent motionEvent = (MotionEvent) event;
211 processMotionEvent(state, motionEvent, policyFlags);
212 return;
213 } else {
214 super.onInputEvent(event, policyFlags);
215 }
216 } else if (event instanceof KeyEvent) {
217 ......
219 }
220 }
如上面代码所示,很多不满足条件的情况下,就会通过super.onInputEvent(event, policyFlags)交给inputfilter处理,也就是交给inputfilterhost重新注入到输入的流程中去。
这里有两个比较陌生的东西:mEventHandler和state,这两个先不详细解释,后面再做介绍
最后如果正常的话则会调用processMotionEvent处理
252 private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
253 if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
254 super.onInputEvent(event, policyFlags);
255 return;
256 }
257
258 if (!state.shouldProcessMotionEvent(event)) {
259 return;
260 }
261
262 batchMotionEvent(event, policyFlags);
263 }
然后会call到batchMotionEvent:
277 private void batchMotionEvent(MotionEvent event, int policyFlags) {
278 if (DEBUG) {
279 Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags);
280 }
//step1、如果当前时间队列为空,则以此事件作为队头,然后申请一次编舞者的input处理(这里还是第一次看到编舞者的input回调的实例)
281 if (mEventQueue == null) {
282 mEventQueue = MotionEventHolder.obtain(event, policyFlags);
283 scheduleProcessBatchedEvents();
284 return;
285 }
//step2、看当前的event是否跟队头的event是相同属性的,如果是相同属性则可以批量处理。例如那种手指移动的事件,对于这种手势想检测那种手指移动画出来的几何图形估计就不太可能
//后面会研究下针对几何图形的检测有什么办法
286 if (mEventQueue.event.addBatch(event)) {
287 return;
288 }
//step3、如果上面两种情况都不是,则把当前这次的事件串到事件队列中去
289 MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags);
290 holder.next = mEventQueue;
291 mEventQueue.previous = holder;
292 mEventQueue = holder;
293 }
这里的MotionEventHolder类就是每个Event的容器,一个容器中只放一个event,由静态变量的对象池进行管理,用于节省创建对象的开销 ;mEventQueue是则是这个输入队列的队头
看到上面的step1的时候肯定会有一个疑问,就是为啥只建立一个队头就需要马上去请求处理。例如点击三次触发放大这种情况,那么队头只有一个ACTION_DOWN的时候就会去触发处理了,明明你离攒够6个事件还差的远
下面这个runnable就是post到编舞者上类型为input的回调
private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
94 @Override
95 public void run() {
96 final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
97 if (DEBUG) {
98 Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos);
99 }
//这个函数对队列中的event事件进行处理
100 processBatchedEvents(frameTimeNanos);
101 if (DEBUG) {
102 Slog.i(TAG, "End batch processing.");
103 }
//如果之前的processBatchedEvents对队列中的事件没有完全消化,则我们就继续等待,请求下一次编舞者到来的时候能否处理完
//所以针对之前的只有一个action_down的情况,肯定是处理不掉需要继续等待的
104 if (mEventQueue != null) {
105 scheduleProcessBatchedEvents();
106 }
107 }
108 };
那关键就是processBatchedEvents是依据什么规则来消耗当前的事件队列了
295 private void processBatchedEvents(long frameNanos) {
296 MotionEventHolder current = mEventQueue;
297 if (current == null) {
298 return;
299 }
//因为每次来的新的event都是放在队头,所以每次解析的时候,先要逐渐往后退,让current指向队尾,也就是最早的事件
300 while (current.next != null) {
301 current = current.next;
302 }
303 while (true) {
//跳出死循环的条件1:队列消耗完毕
304 if (current == null) {
305 mEventQueue = null;
306 break;
307 }
//event的事件时间如果晚于当前编舞者执行的事件,则该轮回调不处理
308 if (current.event.getEventTimeNano() >= frameNanos) {
309 // Finished with this choreographer frame. Do the rest on the next one.
310 current.next = null;
311 break;
312 }
//这里感觉是依次将事件灌入到handleMotionEvent函数中,如果灌入的事件序列满足某个模式则会马上触发
//例如三次点击事件的down-up-down-up-down-up检测到了则会触发放大
313 handleMotionEvent(current.event, current.policyFlags);
314 MotionEventHolder prior = current;
315 current = current.previous;
316 prior.recycle();
317 }
318 }
再看handleMotionEvent的处理
private void handleMotionEvent(MotionEvent event, int policyFlags) {
321 if (DEBUG) {
322 Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
323 }
324 // Since we do batch processing it is possible that by the time the
325 // next batch is processed the event handle had been set to null.
326 if (mEventHandler != null) {
327 mPm.userActivity(event.getEventTime(), false);
328 MotionEvent transformedEvent = MotionEvent.obtain(event);
329 mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
330 transformedEvent.recycle();
331 }
332 }
这里又有了之前提到的mEventHandler;这个的类型是EventStreamTransformation,从名字也能看出这个类是将事件流进行转换,系统中有很多这个的实现体; 从下面这个addFirstEventHander来看,EventStreamTransformation也是链式排列通过onMotionEvent对事件链表进行处理
/**
426 * Adds an event handler to the event handler chain. The handler is added at the beginning of
427 * the chain.
428 *
429 * @param handler The handler to be added to the event handlers list.
430 */
431 private void addFirstEventHandler(EventStreamTransformation handler) {
432 if (mEventHandler != null) {
433 handler.setNext(mEventHandler);
434 } else {
435 handler.setNext(this);
436 }
437 mEventHandler = handler;
438 }
我们就只看跟放大手势相关的EventStreamTransformation
class MagnificationGestureHandler implements EventStreamTransformation {
其onMotionEvent的实现如下:
148 @Override
149 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
//如果当前的event不是来自于触摸屏则交由下个EventStreamTransformation处理
150 if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
151 if (mNext != null) {
152 mNext.onMotionEvent(event, rawEvent, policyFlags);
153 }
154 return;
155 }
//mDetectControlGestures这个变量代表是否检测控制手势,如果这个为false则会直接return掉(这。。。不适用为何还要把这个插进去?)
156 if (!mDetectControlGestures) {
157 if (mNext != null) {
158 dispatchTransformedEvent(event, rawEvent, policyFlags);
159 }
160 return;
161 }
//这里先刷新一下检测状态,后面根据状态做处理
162 mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags);
163 switch (mCurrentState) {
164 case STATE_DELEGATING: {
165 handleMotionEventStateDelegating(event, rawEvent, policyFlags);
166 }
167 break;
168 case STATE_DETECTING: {
169 mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
170 }
171 break;
172 case STATE_VIEWPORT_DRAGGING: {
173 mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags);
174 }
175 break;
176 case STATE_MAGNIFIED_INTERACTION: {
177 // mMagnifiedContentInteractionStateHandler handles events only
178 // if this is the current state since it uses ScaleGestureDetecotr
179 // and a GestureDetector which need well formed event stream.
180 }
181 break;
182 default: {
183 throw new IllegalStateException("Unknown state: " + mCurrentState);
184 }
185 }
186 }
上面提到的几个STATE因为没有注释,还没有完全理清其含义,这个留在以后讨论手势的实现里面再进一步确认
mMagnifiedContentInteractionStateHandler的类型为下面这个,看定义也是比较麻烦,就先不管无关细节
353 /**
354 * This class determines if the user is performing a scale or pan gesture.
* 这个类的主要作用就是在已经放大的基础上,处理用户的滑动和缩放操作
355 */
356 private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener
357 implements OnScaleGestureListener, MotionEventHandler {
其onMotionEvent实现如下,当有触摸事件进来的时候:
380 @Override
381 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
382 //step1:先由放大手势检测器处理
mScaleGestureDetector.onTouchEvent(event);
//step2:再由姿势检测器处理滑动操作(因为这个姿势检测器只实现了onScroll操作)
383 mGestureDetector.onTouchEvent(event);
//step3:如果当前的状态非STATE_MAGNIFIED_INTERACTION就直接return
//从这里我们可以猜测出来STATE_MAGNIFIED_INTERACTION对应的就是开启了放大的状态,且没有正在拖动和缩放的过程中
384 if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
385 return;
386 }
387 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
388 clear();
389 mMagnificationController.persistScale();
390 if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
391 transitionToState(STATE_VIEWPORT_DRAGGING);
392 } else {
393 transitionToState(STATE_DETECTING);
394 }
395 }
396 }
step1和step2都是利用Android的API提供的手势工具类处理对缩放手势和滑动手势的处理:
滚动手势,应该对应到的是两指触摸的那种滑动
430 @Override
431 public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX,
432 float distanceY) {
433 if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
434 return true;
435 }
436 if (DEBUG_PANNING) {
437 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
438 + " scrollY: " + distanceY);
439 }
440 mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
441 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
442 return true;
443 }
最终会通知MagnificationController对缩放区域做偏移
类似的缩放操作是通过setScale去对缩放区域进行放大和缩小
413 @Override
414 public boolean onScale(ScaleGestureDetector detector) {
......
446
447 final float pivotX = detector.getFocusX();
448 final float pivotY = detector.getFocusY();
449 mMagnificationController.setScale(scale, pivotX, pivotY, false,
450 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
451 return true;
452 }
MagnificationController的偏移和缩放最终都是通过其设置的动画以及其和wms的一些交互实现的,这个会在第二部分中介绍到
了解了缩放和缩放后的拖动操作的具体实现的位置,那么还有一个三击屏幕开启的手势还没有提到实现的位置
当检测到放大手势时,会通过DetectingStateHandler的onActionTap来触发屏幕放大的操作
private final class DetectingStateHandler implements MotionEventHandler
这个类主要就是用于检测三击屏幕的手势这块就先略过以后讨论,当检测到三击屏幕之后会调用下面的函数
private void onActionTap(MotionEvent up, int policyFlags) {
773 if (DEBUG_DETECTING) {
774 Slog.i(LOG_TAG, "onActionTap()");
775 }
776
777 if (!mMagnificationController.isMagnifying()) {
778 final float targetScale = mMagnificationController.getPersistedScale();
779 final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
780 mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true,
781 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
782 } else {
783 mMagnificationController.reset(true);
784 }
785 }
通过MagnificcationController的setScaleAndCenter去设定缩放的幅度和中心点
二、屏幕放大
紧接上面的MagnificationController.setScaleAndCenter,前三个参数指定了放大的倍数以及放大的中心点
469 public boolean setScaleAndCenter(
470 float scale, float centerX, float centerY, boolean animate, int id) {
471 synchronized (mLock) {
472 if (!mRegistered) {
473 return false;
474 }
475 return setScaleAndCenterLocked(scale, centerX, centerY, animate, id);
476 }
477 }
478
479 private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
480 boolean animate, int id) {
//step1、先更新放大的参数信息(放大倍数和中心点)
481 final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
//step2、通过参数动画桥来更新当前的显示状态
482 mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
483 if (isMagnifying() && (id != INVALID_ID)) {
484 mIdOfLastServiceToMagnify = id;
485 }
486 return changed;
487 }
通过SpecAnimationBridge的updateSentSpec来启动放大的操作
808 public void updateSentSpec(MagnificationSpec spec, boolean animate) {
809 if (Thread.currentThread().getId() == mMainThreadId) {
810 // Already on the main thread, don't bother proxying.
811 updateSentSpecInternal(spec, animate);
812 } else {
813 mHandler.obtainMessage(ACTION_UPDATE_SPEC,
814 animate ? 1 : 0, 0, spec).sendToTarget();
815 }
816 }
无论caller是否是主线程最终会在主线程上调用到下面函数
818 /**
819 * Updates the sent spec.
820 */
821 private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
822 if (mTransformationAnimator.isRunning()) {
823 mTransformationAnimator.cancel();
824 }
825
826 // If the current and sent specs don't match, update the sent spec.
827 synchronized (mLock) {
828 final boolean changed = !mSentMagnificationSpec.equals(spec);
829 if (changed) {
830 if (animate) {
831 animateMagnificationSpecLocked(spec);
832 } else {
833 setMagnificationSpecLocked(spec);
834 }
835 }
836 }
837 }
会先判断当前是否有动画在执行,如果正在执行则取消掉;判断当前更新的Spec跟之前的Spec是否相等,如果发生了改变,然后根据是否需要执行动画选择按照新的Spec运行动画或者仅仅设置一个新的Spec。
当然从前面的代码来看这个动画肯定是需要执行的,所以来看下animateMagnificationSpecLocked函数
839 private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
840 mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
841 mTransformationAnimator.start();
842 }
这个动画就是由TransformationAnimator完成的,其作为一个属性动画定义为
762 final MagnificationSpecProperty property = new MagnificationSpecProperty();
763 final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
764 final long animationDuration = context.getResources().getInteger(
765 R.integer.config_longAnimTime);
766 mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
767 mSentMagnificationSpec);
768 mTransformationAnimator.setDuration(animationDuration);
769 mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
创建属性动画的第二个参数property的定义如下,当动画间隔的时长到的时候会回调其set操作
872 private static class MagnificationSpecProperty
873 extends Property<SpecAnimationBridge, MagnificationSpec> {
874 public MagnificationSpecProperty() {
875 super(MagnificationSpec.class, "spec");
876 }
877
878 @Override
879 public MagnificationSpec get(SpecAnimationBridge object) {
880 synchronized (object.mLock) {
881 return object.mSentMagnificationSpec;
882 }
883 }
884
885 @Override
886 public void set(SpecAnimationBridge object, MagnificationSpec value) {
887 synchronized (object.mLock) {
888 object.setMagnificationSpecLocked(value);
889 }
890 }
891 }
会调用SpecAnimationBridge的setMagnificationSpecLocked操作去更新放大动画
844 private void setMagnificationSpecLocked(MagnificationSpec spec) {
845 if (mEnabled) {
846 if (DEBUG_SET_MAGNIFICATION_SPEC) {
847 Slog.i(LOG_TAG, "Sending: " + spec);
848 }
849 //step1、根据当前动画更新放大Spec
850 mSentMagnificationSpec.setTo(spec);
//step2、然后通过WindowManager去更新Spec以及触发动画
851 mWindowManager.setMagnificationSpec(spec);
852 }
853 }
然后call到AccessibilityController的setMagnificationSpecLocked函数,AccessibilityController这个类文件在/frameworks/base/services/core/java/com/android/server/wm下,说明他应该是属于wms的东西
123 public void setMagnificationSpecLocked(MagnificationSpec spec) {
124 if (mDisplayMagnifier != null) {
125 mDisplayMagnifier.setMagnificationSpecLocked(spec);
126 }
127 if (mWindowsForAccessibilityObserver != null) {
128 mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
129 }
130 }
最核心的在调用DisplayMagnifier的setMagnificationSpecLocked
275 public void setMagnificationSpecLocked(MagnificationSpec spec) {
//step1、更新视口的缩放参数
276 mMagnifedViewport.updateMagnificationSpecLocked(spec);
//step2、计算放大的视口的边框
277 mMagnifedViewport.recomputeBoundsLocked();
//step3、触发WindowManager的窗口切换动画
278 mWindowManagerService.scheduleAnimationLocked();
279 }
在这个时候我们就可以得到这个放大的流程中存在两个编舞者协同工作的结论了 如下图所示:
编舞者之前的协同工作原理
在System Server的主线程中有一个跟放大参数更新相关的属性动画在利用主线程的编舞者更新参数,其次在android.display线程上用于组织系统窗口动画的编舞者负责实际去更新放大界面的对应的Surface
来到WindowAnimator的animateLocked
/** Locked on mService.mWindowMap. */
809 private void animateLocked(long frameTimeNs) {
......
//step1、为每个窗口准备Surface
878 for (int j = 0; j < N; j++) {
879 windows.get(j).mWinAnimator.prepareSurfaceLocked(true);
880 }
......
889
890 for (int i = 0; i < numDisplays; i++) {
891 final int displayId = mDisplayContentsAnimators.keyAt(i);
892
893 testTokenMayBeDrawnLocked(displayId);
894
895 final ScreenRotationAnimation screenRotationAnimation =
896 mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
897 if (screenRotationAnimation != null) {
898 screenRotationAnimation.updateSurfacesInTransaction();
899 }
900
901 orAnimating(mService.getDisplayContentLocked(displayId).animateDimLayers());
902 orAnimating(mService.getDisplayContentLocked(displayId).getDockedDividerController()
903 .animate(mCurrentTime));
904 //TODO (multidisplay): Magnification is supported only for the default display.
//step2、绘制放大后显示的边框
905 if (mService.mAccessibilityController != null
906 && displayId == Display.DEFAULT_DISPLAY) {
907 mService.mAccessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
908 }
909 }
......
990 }
如上代码中截取的step1和step2,在之前的系统窗口动画的流程中并不是很起眼,但是这两个地方对放大这个功能确实核心的步骤;
step1、
652 void prepareSurfaceLocked(final boolean recoveringMemory) {
1653 final WindowState w = mWin;
1654 if (!hasS urface()) {
1655 if (w.mOrientationChanging) {
1656 if (DEBUG_ORIENTATION) {
1657 Slog.v(TAG, "Orientation change skips hidden " + w);
1658 }
1659 w.mOrientationChanging = false;
1660 }
1661 return;
1662 }
1663
......
1674
1675 boolean displayed = false;
1676 //这里就会根据放大参数得到当前的窗口的Frame大小
1677 computeShownFrameLocked();
1678
1679 setSurfaceBoundariesLocked(recoveringMemory);
在computeShownFrameLocked函数中,会先去获取放大参数,然后再对该窗口进行apply;其中applyMagnificationSpec会对当前的窗口Surface的矩阵进行变换
1149 if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
1150 MagnificationSpec spec = mService.mAccessibilityController
1151 .getMagnificationSpecForWindowLocked(mWin);
1152 applyMagnificationSpec(spec, tmpMatrix);
1153 }
setSurfaceBoundariesLocked函数会通过SurfaceControl去设定到SurfaceFlinger中去,代码比较长,就不贴了
step2、就是计算和绘制文章开头放大那张图的橘黄色的边框
主要就是计算当前边框的范围,主要因为有些Window是规定不支持缩放的,例如虚拟导航栏和输入法窗口。这部分主要涉及的是Region的子交并补的操作,值得去看下这些数学相关的计算思路
此外还想说明这个边框是绘制在一个独立的Layer上的,名字叫:Magnification Overlay,可以通过dumpsys SurfaceFlinger查看当前系统中是否存在该layer
其创建是通过ViewPort的构造创建的:
public ViewportWindow(Context context) {
705 SurfaceControl surfaceControl = null;
706 try {
707 mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
708 surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession,
709 SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT,
710 SurfaceControl.HIDDEN);
711 } catch (OutOfResourcesException oore) {
712 /* ignore */
713 }
714 mSurfaceControl = surfaceControl;
715 mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
716 .getLayerStack());
717 mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw(
718 WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
719 * WindowManagerService.TYPE_LAYER_MULTIPLIER);
720 mSurfaceControl.setPosition(0, 0);
721 mSurface.copyFrom(mSurfaceControl);
722
......
736 }
总结:Input的高级进阶应该就是手势检测了,手势检测确实设计起来需要比较高的精细度,需要考虑比较完整,设计状态机,这个还需更深入研究下;其次这种利用双编舞者在UI执行属性动画,在Display线程去改变Surface属性的做法是值得效仿的,