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