TaskAffinity & LaunchMode

TaskAffinity & LaunchMode

  在说TaskAffinity和LaunchMode之前,先说一下Task吧。
既然要说Task,我们先猜想一下,Android的设计者为什么会推出Task这个概念。

  假想一下现在一个用户在使用一个app,而这个app需要调用浏览器。如果用户在浏览器界面点击back键后回到桌面,那么用户的体验当然不会好。因为人们一般都会希望,从哪里来,点击back键后就回到哪里去。也就是用户希望在浏览器点击back键后,还是回到调用浏览器的app中。

  面对这样的需求,推出Task的概念也就是很自然的事了。Task就相当于Activity的集合,用户如果在做某件事时需要调用几个不同app的Activity,那这几个Activity都应该是在一个task中,这个task在Android中是用back stack来管理的,遵循“first in last out”的规则。

  既然我们知道了需要将应用的Activity用task来管理,那将Activity用task管理的依据是什么呢?这就要说到<activity>android:taskAffinity属性了。android:taskAffinity就是将Activity归入task的依据,默认情况下,这个属性的值和<application>android:taskAffinity相同,而<application>android:taskAffinity默认就是包名

  android:taskAffinity通常和LaunchMode配合使用。Android中,activity有四种LaunchMode:standard, singleTop, singleTask, singleInstance

  standard和singleTop没什么可说的,接下来就说说singleTask和singleInstance吧~

  google的官方文档对singleTask有个这样的说明:

The system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.

  也就是说,如果要开启的activity没有被实例化,系统会为新开一个Task来装这个要开启的activity。但我们在实际开发中往往发现事实不是这样。下面我们就要用一个demo验证一下。

  demo的流程比较简单,就是用MainActivity开启SecondAct,而SecondAct的launchMode是singleTask,然后再两个activity中打印出taskId。

<activity 
    android:name="com.lzf.tasktest.SecondAct"
    android:launchMode="singleTask"/>
Log.i(TAG , "MainActivity taskId = " + getTaskId());

这里写图片描述

  可以看到,MainActivity和SecondAct在同一个Task。事实证明:launchMode为singleTask的Activity,启动后不一定就在一个新的Task中。这是为什么呢?原来,如果一个Activity的launchMode是singleTask,FrameWork只是会将这个Activity标记为“可以新开一个Task”,至于是否新开一个Task,还得看android:taskAffinity。下面我们验证一下,别的什么都不改,只是将SecondAct的android:taskAffinity设为com.lzf.tasktest1,看看运行结果吧~

这里写图片描述

  可以看到,这次SecondAct是在一个新的Task中。有了现象看,我们就该分析一下为什么两次的结果会不同了。两次编码上的区别只有SecondAct的android:taskAffinity。第一次没有设置,那么这个属性的值就默认为应用的包名(com.lzf.tasktest),因此MainActivity和SecondAct的android:taskAffinity就一样,运行在同一个Task中。第二次将SecondAct设置为com.lzf.tasktest1了,他们就运行在不同Task中了。所以说singleTask在FrameWork眼中只是一个标志位,它告诉FrameWork可以给这个Activity开一个Task。至于开不开,FrameWork还需要看看android:taskAffinity,才能决定是否给这个Activity新开一个Task。(当然,之前还要判断一下Task和Activity实例是否存在)

  接下来再说说singleInstance。singleInstance相比于singleTask,做得更绝一点。只要Activity的launchMode是singleInstance,FrameWork就会为Activity新开一个Task(在实例化之前同样也有个判断是否存在的过程)。所以如果将上面的demo改一下,将SecondAct设为singleInstance,那么不管怎样,系统都会为SecondAct新开一个Task(判断是否有实例的情况忽略不讲)。那么问题来了,如果我在SecondAct再启动一个ThirdAct呢?如果ThirdAct的launchMode为standard,taskAffinity不设置,ThirdAct会在哪个Task中呢?

这里写图片描述

  可以看到,ThirdAct会和MainActivity在同一个Task,而不是和SecondAct在同一个task。这就有点奇怪了,因为如果A启动B,B默认是和A在同一个Task,但我们看到的现象是“A启动B,B启动C,但C和A在同一个Task”。所以我们大胆猜想,虽然我们没有给C设置launchMode,但由于B是singleTask,因此C的launchMode默认为singleTask。我们来验证一下。我们将ThirdAct的android:taskAffinity设置为com.lzf.tasktest2,然后看看运行效果。

这里写图片描述

  ThirdAct在另外一个Task中了!~不过孤证不成立,我们还是进源码找找证据吧,

public class ActivityStack {

    final int startActivityUncheckedLocked(ActivityRecord r,
        ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
        int grantedMode, boolean onlyIfNeeded, boolean doResume) {
        final Intent intent = r.intent;
        final int callingUid = r.launchedFromUid;

        int launchFlags = intent.getFlags();

        // We'll invoke onUserLeaving before onPause only if the launching
        // activity did not explicitly state that this is an automated launch.
        mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;

        // 省略

        ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
            != 0 ? r : null;

        // If the onlyIfNeeded flag is set, then we can do this if the activity
        // being launched is the same as the one making the call...  or, as
        // a special case, if we do not know the caller then we count the
        // current top activity as the caller.
        if (onlyIfNeeded) {
            // 省略
        }

        if (sourceRecord == null) {
            // 省略
        } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
            // 省略
        } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
            // 省略
        }

        if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
            // 省略
        }

        boolean addingToTask = false;
        if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
            (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                // If bring to front is requested, and no result is requested, and
                // we can find a task that was started with this same
                // component, then instead of launching bring that one to the front.
                if (r.resultTo == null) {
                    // See if there is a task to bring to the front.  If this is
                    // a SINGLE_INSTANCE activity, there can be one and only one
                    // instance of it in the history, and it is always in its own
                    // unique task, so we do a special search.
                    ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
                        ? findTaskLocked(intent, r.info)
                        : findActivityLocked(intent, r.info);
                    if (taskTop != null) {
                        // 省略
                    }
                }
        }

        // 省略

        if (r.packageName != null) {
            // If the activity being launched is the same as the one currently
            // at the top, then we need to check if it should only be launched
            // once.
            ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
            if (top != null && r.resultTo == null) {
                if (top.realActivity.equals(r.realActivity)) {
                    // 省略
                }
            }

        } else {
            // 省略
        }

        boolean newTask = false;

        // Should this be considered a new task?
        if (r.resultTo == null && !addingToTask
            && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
                // todo: should do better management of integers.
                mService.mCurTask++;
                if (mService.mCurTask <= 0) {
                    mService.mCurTask = 1;
                }
                r.task = new TaskRecord(mService.mCurTask, r.info, intent,
                    (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
                // 省略
                newTask = true;
                if (mMainStack) {
                    mService.addRecentTaskLocked(r.task);
                }

        } else if (sourceRecord != null) {
            // 省略
        } else {
            // 省略
        }


        startActivityLocked(r, newTask, doResume);
        return START_SUCCESS;
    }

}

  代码比较多,不过可以看到,如果由A启动B,A的launchMode为singleInstance或singleTask,那对B的处理就和B的launchMode为singleTask一样。

if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
            (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE)

  总结一下,Task存在的意义主要是为了用户体验,让用户在跨应用做某件事时更有连贯性。将Activity分入Task的依据是android:taskAffinity,但将Activity分入哪个Task的依据不仅仅是android:taskAffinity,还与Activity的launchMode紧密相关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值