按home键后stopAppSwitches延时5S

1、简介

本文是在APP界面点击Home键后,马上在后台启动Activity会延时5秒而引起的源码分析文章。

2、现象描述

在APP界面,点击Home键后会打开一个悬浮窗,以表示APP在后台运行。点击悬浮窗上的按键会返回APP。就是这么一个简单的过程,却无意中发现了这个BUG。当我点击Home键后,回到主桌面,然后点击悬浮窗上的按钮返回APP,而APP不会马上返回,需要延时一段时间才会启动,开始我以为是APP初始化比较耗时引起的,但这个怀疑马上就被打消了,因为我点击Back键后,回到主桌面,再点击悬浮窗上的按钮返回APP,非常迅速的就启动了。同时,我在主桌面上点击APP,也是非常迅速的就启动了。Why?这里就存在两个问题了:

  1. 点击Home键,再点击悬浮窗上的按钮启动会延时。而点击Back键不会。
  2. 点击Home键,再点击主桌面的APP也不会延时启动。

3、源码分析(Android Q)

启动Activity,我们都知道是调用startActivity,startActivity在framework中的调用流程,真正有用的就是下面startActivityLocked这个函数了。

、、frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
  final int startActivityLocked() {
        ...

        final ActivityStack stack = getFocusedStack();
        //下面的两个判断条件就是真正影响Activity是否延时启动的关键
        //这里的uid比较就是对应了我们上面的问题2. 点击Home键,再点击主桌面的APP也不会延时启动。
        //在主桌面点击APP,它的uid和callingUid是相等的所以不会进入下面的判断,会退出if条件,执行后面的方法。也就不会加入延时列表直接启动了。
        if (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
            //如果条件都满足Activity启动将放到延时启动列表中
            //checkAppSwitchAllowedLocked在ActivityManagerService中,代码在下面
            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
                PendingActivityLaunch pal = new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
                mService.mPendingActivityLaunches.add(pal);
                setDismissKeyguard(false);
                ActivityOptions.abort(options);
                return ActivityManager.START_SWITCHES_CANCELED;
            }
        }

        ...
        err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options);
        ...
    }

//frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

    // 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;

    //这里有两个判断条件,如果都不满足,就会返回false,返回false就会加入到延时列表中了
    boolean checkAppSwitchAllowedLocked(int callingPid, int callingUid,
            String name) {
        //mAppSwitchesAllowedTime的时间小于当前的系统时间不会延时
        //mAppSwitchesAllowedTime的赋值在stopAppSwitches函数中
        if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
            return true;
        }
        //权限检测,如果APP有STOP_APP_SWITCHES权限不会延时
        final int perm = checkComponentPermission(
                android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
                callingUid, -1, true);
                if (perm == PackageManager.PERMISSION_GRANTED) {
            return true;
        }

        Slog.w(TAG, name + " request from " + callingUid + " stopped");
        return false;
    }

    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) {
            //调用stopAppSwitches函数后,mAppSwitchesAllowedTime会在当前系统的时间上加上5秒
            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);
            //延时5秒后发送DO_PENDING_ACTIVITY_LAUNCHES_MSG消息,它的处理在doPendingActivityLaunchesLocked函数中。
            mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
        }
    }

    final void doPendingActivityLaunchesLocked(boolean doResume) {
        final int N = mPendingActivityLaunches.size();
        if (N <= 0) {
            return;
        }
        //将延时列表中的Activity一个个启动
        for (int i=0; i<N; i++) {
            PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
            mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags,
                    doResume && i == (N-1), null);
        }
        mPendingActivityLaunches.clear();
    }

上面是ActivityManagerService延时处理Activity的一个机制。下面来分析下Home按键的响应处理,代码如下:

//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
    void launchHomeFromHotKey(int displayId, final boolean awakenFromDreams,
            final boolean respectKeyguard) {
        if (respectKeyguard) {
            if (isKeyguardShowingAndNotOccluded()) {
                // don't launch home if keyguard showing
                return;
            }

            if (!mKeyguardOccluded && 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) {
                            startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
                        }
                    }
                });
                return;
            }
        }

        // no keyguard stuff to worry about, just launch home!
        if (mRecentsVisible) {
            try {
             //调用ActivityManagerService中的stopAppSwitches()函数
                ActivityManager.getService().stopAppSwitches();
            } catch (RemoteException e) {}

            // Hide Recents and notify it to launch Home
            if (awakenFromDreams) {
                awakenDreams();
            }
            hideRecentApps(false, true);
        } else {
            // Otherwise, just launch Home
            startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
        }
    }

上面已经把大部分核心代码列出来了,并做了部分中文注释,感兴趣的可以看看。接下来我们把上面这个逻辑理顺一下。点击Home键,会调用ActivityManagerNative.getDefault().stopAppSwitches()->stopAppSwitches()(ActivityManagerService.java),这个函数会将mAppSwitchesAllowedTime设置成当前系统时间加上5秒后的时间,然后5秒后发送处理启动延时队列中的Activity的消息。所以,在点击Home键后,在后台Service或者BroadcasetReceiver中启动Activity时,在checkAppSwitchAllowedLocked()(ActivityManagerService.java)时,如果mAppSwitchesAllowedTime的时间大于当前的系统时间且没有STOP_APP_SWITCHES权限,Activity会加入到延时列表中。等Handler的5秒延时到达后,在启动Activity。
当然checkAppSwitchAllowedLocked前还有一个uid判断if (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid),这样也就解释了我们的第二个问题,所以开头的问题一和问题二都得到了解答。

4、解决方案

通过上面的源码分析,我们知道如果想要我们的应用在Home界面快速响应,就是给APP加上android.Manifest.permission.STOP_APP_SWITCHES,当然这个权限是有限制的,普通APP是不能使用的,所以我们得在AndroidManifest.xml和Android.mk加上相应的内容。

AndroidManifest.xml

<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
Android.mk

LOCAL_CERTIFICATE := platform
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值