小心stopAppSwitches这个坑

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/QQxiaoqiang1573/article/details/77015379

简介

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

案例还原

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

来让我们带着这两个问题开始源码分析之路。

源码分析(4.4.3)

启动Activity,我们都知道是调用startActivity,那么我们就从这个函数开始入手。

frameworks/base/core/java/android/app/ContextImpl.java

startActivity()->mMainThread.getInstrumentation().execStartActivity();

frameworks/base/core/java/android/app/Instrumentation.java

execStartActivity()->ActivityManagerNative.getDefault().startActivity();

frameworks/base/core/java/android/app/ActivityManagerNative.java

startActivity()(ActivityManagerService.java)

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

startActivity()->startActivityAsUser()->mStackSupervisor.startActivityMayWait()

frameworks/base/services/java/com/android/server/am/ActivityStackSupervisor.java

startActivityMayWait()->startActivityLocked()

上面是startActivity在framework中的调用流程,真正有用的就是下面startActivityLocked这个函数了。

    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/java/com/android/server/am/ActivityManagerService.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/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

    void launchHomeFromHotKey() {
        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowingAndNotHidden()) {
            // don't launch home if keyguard showing
        } else if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
            // when in keyguard restricted mode, must first verify unlock
            // before launching home
            mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
                public void onKeyguardExitResult(boolean success) {
                    if (success) {
                        try {
                            //调用ActivityManagerService中的stopAppSwitches()函数
                            ActivityManagerNative.getDefault().stopAppSwitches();
                        } catch (RemoteException e) {
                        }
                        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                        startDockOrHome();
                    }
                }
            });
        } else {
            // no keyguard stuff to worry about, just launch home!
            try {
                //调用ActivityManagerService中的stopAppSwitches()函数
                ActivityManagerNative.getDefault().stopAppSwitches();
            } catch (RemoteException e) {
            }
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
            startDockOrHome();
        }
    }

上面已经把大部分核心代码列出来了,并做了部分中文注释,感兴趣的可以看看。接下来我们把上面这个逻辑理顺一下。点击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),这样也就解释了我们的第二个问题,所以开头的问题一和问题二都得到了解答。

解决方案

通过上面的源码分析,我们知道如果想要我们的应用在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

总结

源码在手,天下我有。

参考文章

关于在 Service 或 BroadcastReceiver 中 startActivity 的问题

展开阅读全文

没有更多推荐了,返回首页