Activity之launchMode理解

对于Activity中的四个lauchMode【standard(默认)、singleTop、singleTask、singleInstance】的介绍网上已经有大把的文章了,但是在实际应用开发时,对于这几个的区别一直搞混,在有些实际场景中需要通过设置不同模式来解决的比较模糊,所以有必要记录一下自己对它们的理解,做下备忘,当然是结合网上的资料,而不重复造轮子了,当拿来主义,另外工作中碰到与这里相关的场景也会不断添加,达到融会贯通,话不多说,进入正题。

对于这些模块的介绍,可以参考:http://blog.csdn.net/yyingwei/article/details/8295969,对于第一种默认的模式这里就不记录了,比较容易理解,下面主要是记录一下剩下三种模式:

singleTop:

对于它的直观解释,可以从人家博文中看下,下面先贴出来原话:

从其文字描述来看,该模式还是比较好理解的,下面用实验来证明上面的理论,为了方便,这里用三个界面来阐述既可:ActvityA代表A、ActvityB代表B、ActvityC代表C,其实验步骤如下:

实验一:

用代码进行实验,代码结构如下:

ActivityA.java:

public class ActivityA extends Activity implements OnClickListener {

    private Button button;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityA onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_a);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, ActivityB.class);
        startActivity(intent);
    }
}

ActivityB.java:

public class ActivityB extends Activity implements OnClickListener {
    private Button button;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityB onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_b);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, ActivityC.class);
        startActivity(intent);
    }
}

 

ActivityC.java:

public class ActivityC extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityC onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_c);
    }
}

其布局为:

activity_a.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ActivityA" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="go ActivityB" />

</RelativeLayout>

 

activity_b.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ActivityB" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="go ActivityC" />

</RelativeLayout>

 

activity_c.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ActivityC" />

</RelativeLayout>

其AndroidManifest.xml配置如下,都是采用默认配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.launchmodeltest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.launchmodeltest.ActivityA"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.example.launchmodeltest.ActivityB" />
        <activity android:name="com.example.launchmodeltest.ActivityC" />
    </application>

</manifest>

运行如下:

输出日志如下:

此时进行第二步,从ActivityC中再次跳转到ActivityC,在AcitivtyC的lauchMode为默认情况下,很容易理解,会再重新创建一个ActivityC的实例,但是如果将其设置为singleTop呢?上面结果也已经说明了,下面来验证下:

 

添加一个跳转的按钮:

ActivityC.java:

public class ActivityC extends Activity implements OnClickListener {
    private Button button;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityC onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_c);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, ActivityC.class);
        startActivity(intent);
    }
}

activity_c.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ActivityC" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="go ActivityC" />

</RelativeLayout>

运行如下:

从中可以发现,我按了多次跳转ActivityC,都没有创建它的新实例,因为此时它已经在栈顶了,这时按back键返回三次既可退出,这就是singleTop的作用:如果要打开的Activity配了此种启动模式,如果它已经处于栈顶,则不会重新创建实例了。

但是,我们知道对于Activity,有一个onNewIntent()方法,在实际工作中,当设置singleTask模式时,如果要跳转的Activity已经打开过了,再跳转时就会走这个方法,再回到我们现在的这个例子,虽说点击了跳转不要再创建一个新的ActivityC,那它的onNewIntent()方法会走么?下面来论证下:

可见,最终会走onNewIntent()方法,下面来处理第二种情况。

实验二:

先看下博客原文描述:

咱们来实验下,先将ActivityB的lauchMode设置为singleTop:

 ActivityC.java:

public class ActivityC extends Activity implements OnClickListener {
    private Button button;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityC onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_c);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d("cexo", "ActivityC onNewIntent()");
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, ActivityB.class);
        startActivity(intent);
    }
}

见证奇迹的时刻到了:

运行日志如下:

所以,对于这个模式所带来的效果就是:如果要跳转的Activity在栈顶,再次打开则不会重新创建实例,只会走该Activity的onNewIntent()方法;如果不在栈顶,则会重新创建一个实例,注意:之前的实例还是保留。

singleTask:

这里就不费口舌了,直接上别人的原话:

下面就来用实验来证实下,首先将ActivityB中launchMode改为singleTask,并且将其它Activity的launchMode还原,如下:

这时先按这样的顺序启动:ActivityA---->ActivityB----->ActivityC,然后再从ActivityC跳转到ActivityB,查看下栈的情况:

从结果可以看出,当从ActivityC跳转到ActivityB时,这时按两下back键则就退回了桌面,那就说明ActivityB为栈顶了,也就是ActivityC被销毁了,关于这个销毁状态就不打印,可以从运行结果中看出来。

当从ActivityC跳转到ActivityB时,这时也会走ActivityB的onNewIntent()方法,这里就不演示了。

另外,作者对该lauchMode进行了进一步的阐述,总结得挺好的,平常对于SingleTask都想不到这种细节,针对它我自己再来理解下:

1.singleTask 并不一定处于栈底

2.singleTask 并不一定会启动新的task

对于这两点的解释,先引用原作者的:

结合自己的实验来理解,从ActivityA启动ActivityB,其Task还是同一个,也就是说明"singleTask 并不一定会启动新的task",从输出日志可以看出来: 

 

另外ActivityB很显然没有在栈底,所以也说明了"singleTask 并不一定处于栈底"

3.singleTask 并一定会是栈底的根元素

对于这个情况,原作者的解释如下:

关于这个,可以做一个实验来证明下,实验流程用图来说明下:

首先将ActivityB的声明为显示intent,以便另外工程TestApk来调用,如下:

然后按这个顺利启动:ActivityA-------->ActivityB--------->ActivityC:

接着新建一个工程,名叫"TestApk":

MainActivity.java:

public class MainActivity extends Activity implements OnClickListener {

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "MainActivity onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        // 调用另外一个工程的ActivityB
        startActivity(new Intent("com.example.launchmodeltest.ActivityB"));
    }

}

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="go ActivityB" />

</RelativeLayout>

下面分两个场景来试验下:

1,LaunchModelTest工程没有启动,然后去调用ActivityB,看这时ActivityB是否会启动新的Task呢?

从中可以发现,实时是新启了一个Task,而且按back退出时,按一下ActivityB就从LaunchModelTest退出来了,这说明ActivityB就位于栈底了,论证了作者所说明的。

2、LaunchModelTest工程先启动,并且按照ActivityA---->ActivityB---->ActivityC的顺序启动了,然后TestApk工程去调用ActivityB,看这时ActivityB是否会启动新的Task呢?

从中可以看出,这时走了ActivityB的onNewIntent方法了,因为栈中已经有ActivityB实例了,则直接用,而且ActivityB还在原来的Task中,并未新启动,下面来看下退栈的顺序:

也就是先退当前栈,由于ActivityB界面最后显示,所以先退它所在的栈,所以按了两个back才退出LaunchModelTest,然后再退其它栈,也就是TestApk所在的栈,记住一点:先退当前栈,然后再退其它栈

关于这个模式,最后再来记下作者总结的,很关键:

singleInstance:

先上别人的解释,然后再一一去用实验验证:

上面已经将文字划分了多个段,对应不同的情况,下面一一来验证:

①:Task栈1的情况为:A B C。C通过Intent跳转到D,而D的Launch mode为singleInstance,则将会新建一个Task栈2。此时Task栈1的情况还是为:A B C。Task栈2的情况为:D。此时屏幕界面显示D的内容:

首先先将所有的Activity的LaunchMode都还原则默认的:

 新建一个ActivityD,并将其LauchMode设置为singleInstance,如下:

ActivityD.java:

public class ActivityD extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityD onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_d);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d("cexo", "ActivityD onNewIntent()");
    }


}

activity_d.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ActivityD" />

</RelativeLayout>

并修改ActivityC.java,点击按钮跳转到ActivityD.java:

ActivityC.java:

public class ActivityC extends Activity implements OnClickListener {
    private Button button;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("cexo", "ActivityC onCreate()----taskId:" + getTaskId());
        setContentView(R.layout.activity_c);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d("cexo", "ActivityC onNewIntent()");
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, ActivityD.class);
        startActivity(intent);
    }
}

按照上面的流程,这时看下最终效果:

可以看到,跳转到ActivityD时,新建了一个栈,正好如文字描述一样。

②:如果这时D又通过Intent跳转到D,则Task栈2中也不会新建一个D的实例,所以两个栈的情况也不会变化

添加一个Button用来跳转到ActivityD自身,修改如下:

这时再看下结果:

可以看到,当打开了ActivityD之后,再跳到它时,是不会再创建实例的,栈中会保证唯一,跟singleTask模式差不多【关于singleTask和singleInstance的区别,之后会讨论】,但是onNewIntent()方法会触发,也跟描述的一致。

③:如果D跳转到C,则栈1的情况变成了:A B C C,因为C的Launch mode为standard,此时如果再按返回键,则栈1变成:A B C。也就是说现在界面还显示C的内容,不是D

将ActivityD中的跳转改为跳到ActivityC,如下:

这时看下结果:

从退栈的过程来看,先退当前栈,然后再退其它栈,跟singleTask也基本类似,另外从ActivityD跳转到ActivityC时,新创建了一个,所以返回了两次才退到了ActivityB。

另外,从作者关于这个mode的额外描述中,提到了一个特珠情况:"现在有一个问题就是这时这种情况下如果用户点击了Home键,则再也回不到D的即时界面了。",下面也来实验下:

看到,这时按home键之后,一直按back键则退到ActivityA之后,整个程序就退出了,确实ActivityD就永远回不去了,关于解决方案作者也说了,比较容易理解,这里就不多赘述了,但是让我有一个新的发现,这也许就是singleInstance与singleTask之间最大的区别点吧,基于上面这个过程【重要!】,咱们再来走一遍流程,仔细看着,会有新大路发现:

发现新大路了没?就是消失不见的ActivityD所在的栈并未消失,当再次调它时则会切回来,也就是说这个栈是与APP无关的,不同的APP最终都可以共用这个栈,如果这时再返回,再走一遍流程应该就会创建ActivityD了,因为按back时,已经将ActivityD主动退出栈了,是否是这样了,下面继续:

按back键:

再走一遍:

果真如此,这就是关于lauchmode的不同点,之后会会对最难以区分的SingleTask和SingleInstance进行辨析。

 

posted on 2014-09-18 17:01  cexo  阅读(328)  评论(0编辑  收藏  举报

导航