zoukankan      html  css  js  c++  java
  • 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进行辨析。

  • 相关阅读:
    解决virtualbox与mac文件拖拽问题
    SNMP收集
    scapy的安装
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    subprocess.call(cmd, shell=True)
    能够把意见说的让人接受是个技能
    (转)Jenkins2.0 Pipeline 插件执行持续集成发布流程
    ansible 与 Jinja2的结合 -- 安装zabbix
    运维自动化平台-背后的设计计划和架构
    命令行获取zabbix最新的issues
  • 原文地址:https://www.cnblogs.com/webor2006/p/3979623.html
Copyright © 2011-2022 走看看