Android輔助功能---全局手勢放大

 

 

在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) {

 

           //1mEventHandler爲空

180        if (mEventHandler == null) {

181            super.onInputEvent(event, policyFlags);

182            return;

183        }

184        //2EventStreamState爲空

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屬性的做法是值得效仿的,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章