當心 getLaunchIntentForPackage() 方法

  • 應用中響應 android.intent.action.MAINandroid.intent.category.LAUNCHER 在本文中稱爲主界面。
  • 本文基於 Android O

問題現象

用PackageInstaller安裝應用,在安裝完成界面裏點擊打開,應用閃屏頁打開後,按Home鍵回到桌面,點擊桌面裏的應用圖標。java

問題點:再打開一個閃屏頁。android

問題緣由

應用中啓動別的應用,以上問題場景使用的是 PackageManager#getLaunchIntentForPackage() 這個API,它的實現是:app

// 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;
}
複製代碼

正常桌面啓動某個應用的實現以下:ide

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

起始應用A直接使用該方法返回的 Intent 對象去啓動目標應用B,該intent會被AMS增長一個flag:FLAG_ACTIVITY_BROUGHT_TO_FRONT,代碼以下:spa

// 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,那麼會有第二個主界面被建立在任務棧裏。code

更詳細緣由分析請參考文章:關於Android應用回到桌面會重複打開閃屏頁server

解決方案

第一種

在起始應用A裏發起跳轉時:對象

Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
intent.setPackage(null); // 加上這句代碼
context.startActivity(intent);
複製代碼

第二種

在目標應用B的主界面onCreate裏,添加:ip

super.onCreate(savedInstanceState);
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)> 0) {
    /**爲了防止重複啓動多個閃屏頁面**/
    finish();
    return;
}
複製代碼
相關文章
相關標籤/搜索