Android辅助功能---全局手势放大

1,554 阅读22分钟
原文链接: zhuanlan.zhihu.com

在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.java

通过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属性的做法是值得效仿的,可以完成一些特殊需求的,可以多思考下这个的点做些挖掘。