小心 getLaunchIntentForPackage() 方法

栏目: Android · 发布时间: 5年前

内容简介:用PackageInstaller安装应用,在安装完成界面里点击打开,应用闪屏页打开后,按Home键回到桌面,点击桌面里的应用图标。问题点:再打开一个闪屏页。应用中启动别的应用,以上问题场景使用的是

用PackageInstaller安装应用,在安装完成界面里点击打开,应用闪屏页打开后,按Home键回到桌面,点击桌面里的应用图标。

问题点:再打开一个闪屏页。

问题原因

应用中启动别的应用,以上问题场景使用的是 PackageManager#getLaunchIntentForPackage() 这个API,它的实现是:

// frameworks\base\core\java\android\app\ApplicationPackageManager.java
@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;
}
复制代码

正常桌面启动某个应用的实现如下:

// Launcher3\src\com\android\launcher3\AppInfo.java
public static Intent makeLaunchIntent(Context context, LauncherActivityInfoCompat info,
        UserHandleCompat user) {
    long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
    return new Intent(Intent.ACTION_MAIN)
        .addCategory(Intent.CATEGORY_LAUNCHER)
        .setComponent(info.getComponentName())
        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
        .putExtra(EXTRA_PROFILE, serialNumber);
}
复制代码

对比以上两种启动另一个应用的代码实现,可以发现: PackageManager#getLaunchIntentForPackage() 这个API 多了 intentToResolve.setPackage(packageName);

起始应用A直接使用该方法返回的 Intent 对象去启动目标应用B,该intent会被AMS增加一个flag:FLAG_ACTIVITY_BROUGHT_TO_FRONT,代码如下:

// frameworks\base\services\core\java\com\android\server\am\ActivityStarter.java
/**
 * Figure out which task and activity to bring to front when we have found an existing matching
 * activity record in history. May also clear the task if needed.
 * @param intentActivity Existing matching activity.
 * @return {@link ActivityRecord} brought to front.
 */
private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
    mTargetStack = intentActivity.getStack();
    mTargetStack.mLastPausedActivity = null;
    // If the target task is not in the front, then we need to bring it to the front...
    // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
    // the same behavior as if a new instance was being started, which means not bringing it
    // to the front if the caller is not itself in the front.
    final ActivityStack focusStack = mSupervisor.getFocusedStack();
    ActivityRecord curTop = (focusStack == null)
            ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);

    final TaskRecord topTask = curTop != null ? curTop.getTask() : null;
    if (topTask != null
            && (topTask != intentActivity.getTask() || topTask != focusStack.topTask())
            && !mAvoidMoveToFront) {
        mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); // <- 这里
        if (mSourceRecord == null || (mSourceStack.topActivity() != null &&
                mSourceStack.topActivity().getTask() == mSourceRecord.getTask())) {
            // We really do want to push this one into the user's face, right now.
            if (mLaunchTaskBehind && mSourceRecord != null) {
                intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
            }
            mMovedOtherTask = true;
// ...
}
复制代码

如果该B应用启动后置后台,那么会根据B应用的主界面的lauchMode创建或复用任务栈里的对象,会有意想不到的结果。比如:如果B应用的主界面launchMode是standard,那么会有第二个主界面被创建在任务栈里。

更详细原因分析请参考文章: 关于Android应用回到桌面会重复打开闪屏页

解决方案

第一种

在起始应用A里发起跳转时:

Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
intent.setPackage(null); // 加上这句代码
context.startActivity(intent);
复制代码

第二种

在目标应用B的主界面 onCreate 里,添加:

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

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创造突破性产品

创造突破性产品

Jonathan Cagan、Craig M.Vogel / 机械工业出版社 / 2004-1 / 35.00元

在《创造突破性产品:从产品策略到项目定案的创新》中作者总结多年的研究成果,指明了与产品创新相关的一系列因素,并提供了一套全新的开发突破性产品的理论与方法,该书旨在帮助企业,技术和设计人员: 获得对用户的需求和市场新的趋势的准确洞察力; 认识可以创造新市场的产品机会缺口; 指导产品模糊前期的构造; 正确地运用定性和定量的研究方法; ......一起来看看 《创造突破性产品》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具