Activity 启动模式与任务栈 (Task) 全面图文深入记录(下)

2,850 阅读9分钟

转载请注明出处(谢谢):
blog.csdn.net/javazejian/…

  通过上一篇文件的分析,我们对Activity的启动模式有了比较清晰的了解后,本篇我们将继续对Activity启动模式的相关参数和任务栈分析,接下来我们就继续上一篇的问题,如何通过taskAffinity属性在同一个应用中创建多个任务栈进行探究。

任务栈之taskAffinity属性

TaskAffinity特点如下:

  • TaskAffinity 参数标识着Activity所需要的任务栈的名称,默认情况下,一个应用中所有Activity所需要的任务栈名称都为该应用的包名。
  • TaskAffinity 属性一般跟singleTask模式或者跟allowTaskReparenting属性结合使用,在其他情况下没有实际意义。
  • TaskAffinity属性的值不能与当前应用包名相同,否则其值跟作废没两样。

TaskAffinity和singleTask启动模式结合使用

  当TaskAffinity和singleTask启动模式结合使用时,当前Activity的任务栈名称将与TaskAffinity属性指定的值相同,下面我们通过代码来验证,我们同过MainActivity来启动ActivityA,其中MainActivity启动模式为默认模式,ActivityA启动模式为singleTask,而TaskAffinity属性值为android:taskAffinity="com.zejian.singleTask.affinity" MainActivity和ActivityA代码如下:

package comzejian.myapplication;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn= (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(MainActivity.this,ActivityA.class);
                startActivity(i);
            }
        });
    }
}

ActivityA.class 代码如下:

package comzejian.myapplication;

import android.app.Activity;
import android.os.Bundle;

/**
 * Created by zejian
 * Time 16/7/26.
 * Description:
 */
public class ActivityA extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
    }
}

清单文件代码如下:




    
        
            
                

                
            
        

        

    

  现在我们启动MainActivity,后再启动ActivityA,然后我们通过 adb shell dumpsys activity activities 命令查看此时栈的情况:

  我们可以清楚地看到两个任务栈,其中一个是id=249,栈名称为com.zejian.singleTask.affinity的任务栈,该栈包含了ActivityA,另外一个则是id=248,栈名为默认包名的任务栈,包含了MainActivity。到此我们也明白了,我们确实可以通过singleTask与android:taskAffinity属性相结合的方式来指定我们Activity所需要的栈名称,使相应的Activity存在于不同的栈中,图解如下:

当TaskAffinity和allowTaskReparenting结合使用

  首先我们来聊聊allowTaskReparenting属性,它的主要作用是activity的迁移,即从一个task迁移到另一个task,这个迁移跟activity的taskAffinity有关。当allowTaskReparenting的值为“true”时,则表示Activity能从启动的Task移动到有着affinity的Task(当这个Task进入到前台时),当allowTaskReparenting的值为“false”,表示它必须呆在启动时呆在的那个Task里。如果这个特性没有被设定,元素(当然也可以作用在每次activity元素上)上的allowTaskReparenting属性的值会应用到Activity上。默认值为“false”。这样说可能还比较难理解,我们举个例子,比如现在有两个应用A和B,A启动了B的一个ActivityC,然后按Home键回到桌面,再单击B应用时,如果此时,allowTaskReparenting的值为“true”,那么这个时候并不会启动B的主Activity,而是直接显示已被应用A启动的ActivityC,我们也可以认为ActivityC从A的任务栈转移到了B的任务栈中。这就好比我们在路边收养了一只与主人走失了的猫,养着养着突然有一天,主人找上门来了,这只猫也就被带回去了。我们通过图解来更好地理解这种情景:

  我们通过代码层面来验证一下,我们创建两个应用分别为ActivityTask(简称A应用)和ActivityTask2(简称B应用),其中A包含ActivityA,B包含ActivityC,我们通过ActivityA启动B应用中的ActivityC,再回到桌面,启动B应用,此时我们观察A应用和B应用各自栈的变化(因为A,B为不同的应用所以taskAfinity属性值肯定不同,所以这里我们就没必要指定了)。
ActivityA及其清单文件代码如下:

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by zejian
 * Time 16/7/23.
 * Description:
 */
public class ActivityA extends Activity {

    private Button btnC;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);

        btnC= (Button) findViewById(R.id.mainC);
        btnC.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_MAIN);
                intent.addCategory(Intent.CATEGORY_LAUNCHER);
                ComponentName cn = new ComponentName("com.cmcm.activitytask2", "com.cmcm.activitytask2.ActivityC");
                intent.setComponent(cn);
                startActivity(intent);
            }
        });
    }
}

     
           
           
     

ActivityA及其清单文件代码如下:

package com.cmcm.activitytask;

package com.cmcm.activitytask;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by zejian
 * Time 16/7/23.
 * Description:
 */
public class ActivityC extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_c);
    }
}

  我们通过AcitivityA启动B应用的ActivityC后,内存中栈的如下:

  我们可以看到ActivityA和ActivityC同在一个栈中,接着我们回到桌面启动B应用,此时内存中的任务栈如下:

  我们发现ActivityC从A应用的任务栈直接移动到B应用的任务栈,这也就符合我们前面所说的现象了。而当我们修改allowTaskReparenting为false时,再运行,然后重复上面的操作,查看内存中任务栈的变化:
A应用启动B应用的ActivityC时

  回到桌面再启动B应用时

  对比发现,如果allowTaskReparenting值为false时,ActivityC并不会直接从A应用的任务栈迁移到B应用的任务栈,而是B应用直接重新创建了ActivityC的实例。到此我们对于allowTaskReparenting和taskAffinity属性的了解就已经相当深入了,不过有点需要说明的是allowTaskReparenting仅限于singleTop和standard模式,这是因为一个activity的affinity属性由它的taskAffinity属性定义(代表栈名),而一个task的affinity由它的root activity定义。所以,一个task的root activity总是拥有和它所在task相同的affinity。由于以singleTask和singleInstance启动的activity只能是一个task的root activity,因此allowTaskReparenting仅限于以standard 和singleTop启动的activity,大家可以自行测试一下,这里我们就不测试了哈,下面我们再来说说它们可能应用用场景。

TaskAffinity与allowTaskReparenting和singleTask结合时可能发生的应用场景

  • TaskAffinity与singleTask应用场景

  假如现在有这么一个需求,我们的客户端app正处于后台运行,此时我们因为某些需要,让微信调用自己客户端app的某个页面,用户完成相关操作后,我们不做任何处理,按下回退或者当前Activity.finish(),页面都会停留在自己的客户端(此时我们的app回退栈不为空),这显然不符合逻辑的,用户体验也是相当出问题的。我们要求是,回退必须回到微信客户端,而且要保证不杀死自己的app.这时候我们的处理方案就是,设置当前被调起Activity的属性为:

LaunchMode=""SingleTask" taskAffinity="com.tencent.mm"

其中com.tencent.mm是借助于工具找到的微信包名,就是把自己的Activity放到微信默认的Task栈里面,这样回退时就会遵循“Task只要有Activity一定从本Task剩余Activity回退”的原则,不会回到自己的客户端;而且也不会影响自己客户端本来的Activity和Task逻辑。

  • TaskAffinity与allowTaskReparenting应用场景

  一个e-mail应用消息包含一个网页链接,点击这个链接将出发一个activity来显示这个页面,虽然这个activity是浏览器应用定义的,但是activity由于e-mail应用程序加载的,所以在这个时候该activity也属于e-mail这个task。如果e-mail应用切换到后台,浏览器在下次打开时由于allowTaskReparenting值为true,此时浏览器就会显示该activity而不显示浏览器主界面,同时actvity也将从e-mail的任务栈迁移到浏览器的任务栈,下次打开e-买了时并不会再显示该activity
  到此TaskAffinity就全部介绍完了,最后我们再来了解几个跟任务栈相关的属性参数;

清空任务栈

  Android系统除了给我提供了TaskAffinity来指定任务栈名称外,还给我提供了清空任务栈的方法,在一般情况下我们只需要在标签中指明相应的属性值即可。

  • android:clearTaskOnLaunch

  这个属性用来标记是否从task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默认为“false”。这里有点我们必须要注意的,这个属性只对任务栈内的root Activity起作用,任务栈内其他的Activity都会被忽略。如果android:clearTaskOnLaunch属性为“true”,每次我们重新进入这个应用时,我们只会看到根Activity,任务栈中的其他Activity都会被清除出栈。
  比如一个应用的Activity A,B,C,其中clearTaskOnLaunch设置为true,C为默认值,我们依次启动A,B,C,点击HOME,再在桌面点击图标。启动的是A,而B,C将都被移除当前任务栈。也就是说,当Activity的属性clearTaskOnLaunch为true时将被优先启动,其余的Activity(B、C)都被移除任务栈并销毁,除非前面A已经finish销毁,后面的已注册clearTaskOnLaunch为true的activity(B)才会生效。
  特别地,如果我们的应用中引用到了其他应用的Activity,这些Activity设置了android:allowTaskReparenting属性为“true”,则它们会被重新宿主到有共同affinity的task中。

  • android:finishOnTaskLaunch

  finishOnTaskLaunch属性与clearTaskOnLaunch 有些类似,它们的区别是finishOnTaskLaunch是作用在自己身上(把自己移除任务栈,不影响别的Activity),而clearTaskOnLaunch则是作用在别人身上(把别的Activity移除任务栈),如果我们把Activity的android:finishOnTaskLaunch属性值设置为true时,离开这个Activity所依赖的任务栈后,当我们重新返回时,该Activity将会被finish掉,而且其他Activity不会受到影响。

  • android:alwaysRetainTaskState

  alwaysRetainTaskState实际上是给了当前Activity所在的任务栈一个“免死金牌”,如果当前Activity的android:alwaysRetainTaskState设置为true时,那么该Activity所在的任务栈将不会受到任何清理命令的影响,一直保持当前任务栈的状态。

好了,到此本篇也就完结,相信通过两篇的记录我们对Activity的启动模式和任务栈都有相对清晰的了解了哈。