×

关于Android应用回到桌面会重复打开闪屏页

96
sunshine8
2016.07.17 00:17* 字数 549

现在存在有的时候发现回到桌面会重复打开闪屏页,我研究了一下,有如下结果。


重现方式:

用android的installer安装打开闪屏页,按Home键回到首页,然后点击launcher的图标会再打开一个闪屏页,根据这篇博客是因为再打开时候Intent多了一个Flag,Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
那为什么会多这个flag,而又为什么多了这个flag,会重复多打开页面,这就是我这篇文章要讲的。
解决方案还是上面讲的

if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)> 0) {
    /**为了防止重复启动多个闪屏页面**/
    finish();
    return;
}

桌面launcher的打开与Installer打开的不同

先说结论,installer打开多了一个intent.setPackage(packageName)
代码在Launcher3,抽出来就是

public static void startAppByLauncher(Context context, String packageName) {
     android.content.pm.PackageInfo pi = null;

    try {

        pi = context.getPackageManager().getPackageInfo(packageName, 0);

        Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);

        resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) {

            resolveIntent.setPackage(pi.packageName);

        }

        List<ResolveInfo> apps = context.getPackageManager().queryIntentActivities(resolveIntent, 0);

        ResolveInfo ri = apps.iterator().next();

        if (ri != null) {

            String packageName1 = ri.activityInfo.packageName;

            String className = ri.activityInfo.name;

            Intent intent = new Intent(Intent.ACTION_MAIN);

            intent.addCategory(Intent.CATEGORY_LAUNCHER);

            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |

                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

            ComponentName cn = new ComponentName(packageName1, className);

            intent.setComponent(cn);

            context.startActivity(intent);

        }

    } catch (Exception e) {

        e.printStackTrace();

        Toast.makeText(context.getApplicationContext(), "启动失败",

                Toast.LENGTH_LONG).show();

    }
}

而installer的打开在com.android.packageinstaller.installappprogress

public static void startAppByInstallApp(Context context, String packageName) {

    try {
        Intent intent= context.getPackageManager().getLaunchIntentForPackage(
                packageName);
        context.startActivity(intent);
    } catch (Exception e) {

    }
}

而getLaunchIntentForPackage的实际代码在ApplicationPackageManager

   @Override
    public Intent getLaunchIntentForPackage(String packageName) {
        // First see if the package has an INFO activity; the existence of
        // such an activity is implied to be the desired front-door for the
        // overall package (such as if it has multiple launcher entries).
        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
        intentToResolve.addCategory(Intent.CATEGORY_INFO);
        intentToResolve.setPackage(packageName);
        List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
        // Otherwise, try to find a main launcher activity.
        if (ris == null || ris.size() <= 0) {
            // reuse the intent instance
            intentToResolve.removeCategory(Intent.CATEGORY_INFO);
            intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
            intentToResolve.setPackage(packageName);
            ris = queryIntentActivities(intentToResolve, 0);
        }
        if (ris == null || ris.size() <= 0) {
            return null;
        }
        Intent intent = new Intent(intentToResolve);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName(ris.get(0).activityInfo.packageName,
                ris.get(0).activityInfo.name);
        return intent;
    }

测试了一下,最后发现两者的不同在于installappprogress多了一个intent.setPackage(packageName)。
那为什么多了一个intent.setPackage(packageName)会再此打开时导致多了Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT


FLAG_ACTIVITY_BROUGHT_TO_FRONT是如何产生的

这是在 com.android.server.am.ActivityStackSupervisor

  final ActivityStack lastStack = getLastStack();
                    ActivityRecord curTop = lastStack == null?
                            null : lastStack.topRunningNonDelayedActivityLocked(notTop);
                    if (curTop != null && (curTop.task != intentActivity.task ||
                            curTop.task != lastStack.topTask())) {
                        r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
                        if (sourceRecord == null || (sourceStack.topActivity() != null &&
                                sourceStack.topActivity().task == sourceRecord.task)) {
                            // We really do want to push this one into the
                            // user's face, right now.
                            movedHome = true;
                            if ((launchFlags &
                                    (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
                                    == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
                                // Caller wants to appear on home activity.
                                intentActivity.task.mOnTopOfHome = true;
                            }
                            targetStack.moveTaskToFrontLocked(intentActivity.task, r, options);
                            options = null;
                        }
                    }

关键在于 curTop.task != lastStack.topTask()
这个地方我估摸着是因为packageName会影响到task


FLAG_ACTIVITY_BROUGHT_TO_FRONT是如何起作用

这个我还没找到实际代码,只能看一下官方解释

If, when starting the activity, there is already a task running that starts with this activity, then instead of starting a new instance the current task is brought to the front. The existing instance will receive a call to Activity.onNewIntent() with the new Intent that is being started, and with the Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set. This is a superset of the singleTop mode, where if there is already an instance of the activity being started at the top of the stack, it will receive the Intent as described there (without the FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set). See the Tasks and Back Stack document for more details about tasks.

这个FLAG其实是这个意思,比方说用A打开B,此时在A的Intent中加上这个FLAG,再在B中再启动C,D,如果这个时候在D中再启动B,这个时候最后的栈的情况是 A,C,D,B.

这篇文章有两个地方没解释到,希望大家补充

  1. 为什么packageName为影响到curTop.task != lastStack.topTask()
  2. FLAG_ACTIVITY_BROUGHT_TO_FRONT是如何起作用的
Android最佳实践
Web note ad 1