美文网首页手机管理
android home键流程解析

android home键流程解析

作者: 一航jason | 来源:发表于2017-09-15 14:21 被阅读0次

    上一篇文章中我们介绍了android系统的截屏事件,由于截屏事件是一种系统全局处理事件,所以事件的处理逻辑不是在App中执行,而是在PhoneWindowManager中执行。而本文我们现在主要讲解android系统中HOME按键的事件处理,和截屏事件类似,这里的HOME按键也是系统级别的按键事件监听,所以其处理事件的逻辑也应该和截屏事件处理流程类似,从上一篇文章的分析过冲中我们不难发现,系统级别的按键处理逻辑其实都是在PhoneWindowManager中,所以HOME按键的处理逻辑也是在PhoneWindowManager的dispatchUnhandledKey方法中执行,那么我们就从dispatchUnhandleKey方法开始分析HOME按键的处理流程。

    好吧我们看一下PhoneWindowManager的dispatchUnhandleKey方法的实现:

    @Override
        public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
            ...
            KeyEvent fallbackEvent = null;
            if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                final KeyCharacterMap kcm = event.getKeyCharacterMap();
                final int keyCode = event.getKeyCode();
                final int metaState = event.getMetaState();
                final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0;
    
                // Check for fallback actions specified by the key character map.
                final FallbackAction fallbackAction;
                if (initialDown) {
                    fallbackAction = kcm.getFallbackAction(keyCode, metaState);
                } else {
                    fallbackAction = mFallbackActions.get(keyCode);
                }
    
                if (fallbackAction != null) {
                    if (DEBUG_INPUT) {
                        Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
                                + " metaState=" + Integer.toHexString(fallbackAction.metaState));
                    }
    
                    final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
                    fallbackEvent = KeyEvent.obtain(
                            event.getDownTime(), event.getEventTime(),
                            event.getAction(), fallbackAction.keyCode,
                            event.getRepeatCount(), fallbackAction.metaState,
                            event.getDeviceId(), event.getScanCode(),
                            flags, event.getSource(), null);
    
                    if (!interceptFallback(win, fallbackEvent, policyFlags)) {
                        fallbackEvent.recycle();
                        fallbackEvent = null;
                    }
    
                    if (initialDown) {
                        mFallbackActions.put(keyCode, fallbackAction);
                    } else if (event.getAction() == KeyEvent.ACTION_UP) {
                        mFallbackActions.remove(keyCode);
                        fallbackAction.recycle();
                    }
                }
            }
    
            if (DEBUG_INPUT) {
                if (fallbackEvent == null) {
                    Slog.d(TAG, "No fallback.");
                } else {
                    Slog.d(TAG, "Performing fallback: " + fallbackEvent);
                }
            }
            return fallbackEvent;
        }
    

    通过查看源码,我们重点看一下dispatchUnhandledKey方法中调用的interceptFallback方法,关于HOME按键的处理逻辑也是在这个方法体中的,所以继续看一下interceptFallback方法的实现:

    private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
            int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
            if ((actions & ACTION_PASS_TO_USER) != 0) {
                long delayMillis = interceptKeyBeforeDispatching(
                        win, fallbackEvent, policyFlags);
                if (delayMillis == 0) {
                    return true;
                }
            }
            return false;
        }
    

    通过分析源码我们知道关于HOME按键的处理逻辑主要是在interceptKeyBeforeDispatching方法的实现的,既然这样,我们看一下interceptKeyBeforeDispatching方法的实现:

    @Override
        public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
            ...
            // First we always handle the home key here, so applications
            // can never break it, although if keyguard is on, we do let
            // it handle it, because that gives us the correct 5 second
            // timeout.
            if (keyCode == KeyEvent.KEYCODE_HOME) {
    
                // If we have released the home key, and didn't do anything else
                // while it was pressed, then it is time to go home!
                if (!down) {
                    cancelPreloadRecentApps();
    
                    mHomePressed = false;
                    if (mHomeConsumed) {
                        mHomeConsumed = false;
                        return -1;
                    }
    
                    if (canceled) {
                        Log.i(TAG, "Ignoring HOME; event canceled.");
                        return -1;
                    }
    
                    // If an incoming call is ringing, HOME is totally disabled.
                    // (The user is already on the InCallUI at this point,
                    // and his ONLY options are to answer or reject the call.)
                    TelecomManager telecomManager = getTelecommService();
                    if (telecomManager != null && telecomManager.isRinging()) {
                        Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
                        return -1;
                    }
    
                    // Delay handling home if a double-tap is possible.
                    if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
                        mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
                        mHomeDoubleTapPending = true;
                        mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                                ViewConfiguration.getDoubleTapTimeout());
                        return -1;
                    }
    
                    handleShortPressOnHome();
                    return -1;
                }
    
                // If a system window has focus, then it doesn't make sense
                // right now to interact with applications.
                WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
                if (attrs != null) {
                    final int type = attrs.type;
                    if (type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
                            || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
                            || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                        // the "app" is keyguard, so give it the key
                        return 0;
                    }
                    final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
                    for (int i=0; i<typeCount; i++) {
                        if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
                            // don't do anything, but also don't pass it to the app
                            return -1;
                        }
                    }
                }
    
                // Remember that home is pressed and handle special actions.
                if (repeatCount == 0) {
                    mHomePressed = true;
                    if (mHomeDoubleTapPending) {
                        mHomeDoubleTapPending = false;
                        mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
                        handleDoubleTapOnHome();
                    } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
                            || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
                        preloadRecentApps();
                    }
                } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                    if (!keyguardOn) {
                        handleLongPressOnHome(event.getDeviceId());
                    }
                }
                return -1;
            }
    
            // Let the application handle the key.
            return 0;
        }
    

    这里我们主要看一下对android系统HOME按键的处理逻辑,通过分析源码我们知道HOME按键进入launcher界面的主要逻辑是在handleShortPressOnHome();方法中执行的,所以我们继续看一下handleShortPressOnHome方法的实现。

    private void handleShortPressOnHome() {
            // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
            getHdmiControl().turnOnTv();
    
            // If there's a dream running then use home to escape the dream
            // but don't actually go home.
            if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
                mDreamManagerInternal.stopDream(false /*immediate*/);
                return;
            }
    
            // Go home!
            launchHomeFromHotKey();
        }
    

    可以看到在handleShortPressOnHome方法中调用了launchHomeFromHotKey方法,该方法的注释用于go home,所以继续看一下该方法的实现:

    void launchHomeFromHotKey() {
            launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
        }
    

    可以看到在launchHomeFromHotKey方法中我们又调用了launchHomeFromHotkey的重构方法,这样我们看一下这个重构方法的实现。

    void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
            if (respectKeyguard) {
                if (isKeyguardShowingAndNotOccluded()) {
                    // don't launch home if keyguard showing
                    return;
                }
    
                if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
                    // when in keyguard restricted mode, must first verify unlock
                    // before launching home
                    mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
                        @Override
                        public void onKeyguardExitResult(boolean success) {
                            if (success) {
                                try {
                                    ActivityManagerNative.getDefault().stopAppSwitches();
                                } catch (RemoteException e) {
                                }
                                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                                startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
                            }
                        }
                    });
                    return;
                }
            }
    
            // no keyguard stuff to worry about, just launch home!
            try {
                ActivityManagerNative.getDefault().stopAppSwitches();
            } catch (RemoteException e) {
            }
            if (mRecentsVisible) {
                // Hide Recents and notify it to launch Home
                if (awakenFromDreams) {
                    awakenDreams();
                }
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                hideRecentApps(false, true);
            } else {
                // Otherwise, just launch Home
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
            }
        }
    

    可以发现在方法中我们首先调用了ActivityManagerNative.getDefault().stopAppSwitches();该方法主要用于暂停后台的打开Activity的操作,避免打扰用户的操作。比如这时候我们在后台打开一个新的App,那么这时候由于要回到home页面,所以需要先延时打开。方法执行这个方法之后然后执行了sendCloseSystemWindows方法,该方法主要实现了对当前系统App页面的关闭操作,下面我们先看一下ActivityManagerNative.getDefault().stopAppSwitches();方法的实现,这里的ActivityManagerNative.getDefault我们在前面已经多次说过了其是一个Binder对象,是应用进程Binder客户端用于与ActivityManagerService之间通讯,所以这里最终调用的是ActivityManagerService的stopAppsSwitches方法,这样我们就继续看一下ActivityManagerService的stopAppsSwitches方法的实现。

    @Override
        public void stopAppSwitches() {
            if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException("Requires permission "
                        + android.Manifest.permission.STOP_APP_SWITCHES);
            }
    
            synchronized(this) {
                mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
                        + APP_SWITCH_DELAY_TIME;
                mDidAppSwitch = false;
                mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
                Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
                mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
            }
        }
    

    可以发现这里主要是发送了一个异步消息,并且msg.what为DO_PENDING_ACTIVITY_LAUNCHES_MSG,即跳转Activity,然后我们继续我们看一下mHandler的handleMessage方法当msg.what为DO_PENDING_ACTIVITY_LAUNCHES_MSG时的操作。而且我们可以发现这里的异步消息是一个延时的异步消息,延时的时间为APP_SWITCH_DELAY_TIME,我们可以看一下该变量的定义:

    // Amount of time after a call to stopAppSwitches() during which we will
        // prevent further untrusted switches from happening.
        static final long APP_SWITCH_DELAY_TIME = 5*1000;
    

    然后我们可以看一下mHander的handleMessage方法的具体实现:

    case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
                    synchronized (ActivityManagerService.this) {
                        mStackSupervisor.doPendingActivityLaunchesLocked(true);
                    }
                } break;
    

    可以发现这里直接调用了mStackSupervisor.doPendingActivityLaunchesLocked方法,好吧,继续看一下doPendingActivityLaunchesLocked方法的实现。

    final void doPendingActivityLaunchesLocked(boolean doResume) {
            while (!mPendingActivityLaunches.isEmpty()) {
                PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
                startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
                        doResume && mPendingActivityLaunches.isEmpty(), null, null);
            }
        }
    

    可以发现这里就是调用了startActivity的操作了,这里就是开始启动Activity了,所以当我们按下HOME按键的时候,后台的startActivity都会延时5秒钟执行...

    然后回到我们的launchHomeFromHotKey方法,看一下launchHomeFromHotKey方法的实现。

    void sendCloseSystemWindows(String reason) {
            PhoneWindow.sendCloseSystemWindows(mContext, reason);
        }
    

    可以发现这里调用了PhoneWindow的静态方法sendCloseSystemWindow,继续看一下该方法的实现逻辑。

    public static void sendCloseSystemWindows(Context context, String reason) {
            if (ActivityManagerNative.isSystemReady()) {
                try {
                    ActivityManagerNative.getDefault().closeSystemDialogs(reason);
                } catch (RemoteException e) {
                }
            }
        }
    

    看到这里,很明显了又是调用了Binder的进程间通讯,最终ActivityManagerService的closeSystemDialogs方法会被执行,所以我们继续看一下ActivityManagerService的closeSystemDialogs方法的实现。

    @Override
        public void closeSystemDialogs(String reason) {
            enforceNotIsolatedCaller("closeSystemDialogs");
    
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            try {
                synchronized (this) {
                    // Only allow this from foreground processes, so that background
                    // applications can't abuse it to prevent system UI from being shown.
                    if (uid >= Process.FIRST_APPLICATION_UID) {
                        ProcessRecord proc;
                        synchronized (mPidsSelfLocked) {
                            proc = mPidsSelfLocked.get(pid);
                        }
                        if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                            Slog.w(TAG, "Ignoring closeSystemDialogs " + reason
                                    + " from background process " + proc);
                            return;
                        }
                    }
                    closeSystemDialogsLocked(reason);
                }
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    

    可以发现其实在方法体中将关闭窗口的逻辑下发到了closeSystemDialogsLocked中,所以我们继续看一下closeSystemDialogsLocked方法的实现。

    void closeSystemDialogsLocked(String reason) {
            Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            if (reason != null) {
                intent.putExtra("reason", reason);
            }
            mWindowManager.closeSystemDialogs(reason);
    
            mStackSupervisor.closeSystemDialogsLocked();
    
            broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                    AppOpsManager.OP_NONE, null, false, false,
                    -1, Process.SYSTEM_UID, UserHandle.USER_ALL);
        }
    

    可以发现在方法体中首先调用了mWindowManager.closeSystemDialogs方法,该方法就是关闭当前页面中存在的系统窗口,比如输入法,壁纸等:

    @Override
        public void closeSystemDialogs(String reason) {
            synchronized(mWindowMap) {
                final int numDisplays = mDisplayContents.size();
                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
                    final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
                    final int numWindows = windows.size();
                    for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
                        final WindowState w = windows.get(winNdx);
                        if (w.mHasSurface) {
                            try {
                                w.mClient.closeSystemDialogs(reason);
                            } catch (RemoteException e) {
                            }
                        }
                    }
                }
            }
        }
    

    讲过这样一层操作之后,我们就关闭了当前中存在的系统窗口。然后还是回到我们的launchHomeFromHotKey方法,我们发现在方法体的最后我们调用了startDockOrHome方法,这个方法就是实际的跳转HOME页面的方法了,我们可以具体看一下该方法的实现。

    void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
            if (awakenFromDreams) {
                awakenDreams();
            }
    
            Intent dock = createHomeDockIntent();
            if (dock != null) {
                try {
                    if (fromHomeKey) {
                        dock.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
                    }
                    startActivityAsUser(dock, UserHandle.CURRENT);
                    return;
                } catch (ActivityNotFoundException e) {
                }
            }
    
            Intent intent;
    
            if (fromHomeKey) {
                intent = new Intent(mHomeIntent);
                intent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
            } else {
                intent = mHomeIntent;
            }
    
            startActivityAsUser(intent, UserHandle.CURRENT);
        }
    

    可以发现我们在方法体中调用了createHomeDockIntent,这个方法的作用就是创建到达HOME页面的Intent对象,然后我们调用了startActivityAsUser方法,这样经过一系列的调用之后就调起了home页面的Activity,所以这时候系统就返回到了HOME页面。

    总结:

    home键的处理事件是在PhoneWindowManager类中处理,监听到操作后首先关闭系统window窗体,然后调用系统launcher桌面程序,这样就会到了手机桌面了。

    相关文章

      网友评论

        本文标题:android home键流程解析

        本文链接:https://www.haomeiwen.com/subject/kcissxtx.html