Android四大组件之Activity入门篇

Activity在Android APP中负责与用户的交互,是APP界面功能的载体和入口。一个APP中可以存在多个Activity,每个Activity就是一个交互界面。它们可以运行在一个进程中,也可以运行在不同的进程中。那么Android系统是怎么管理这些Activity的呢?Activity是怎么被启动的?它的生命周期又是怎样的呢?

目录

  1. Activity的管理-任务栈
  2. Activity的生命周期
    2-1 正常情况下的生命周期
    2-2 异常情况下的生命周期
    2-3 Activity生命周期流程图
  3. Activity的启动模式
    3-1 standard模式
    3-2 singleTop模式
    3-3 singleTask模式
    3-4 singleInstance模式
    3-5 启动模式与startActivityForResult

一、Activity的管理--任务栈

  每个Activity的实例都是保存在任务栈中,这个任务栈是一个堆栈,满足先进后出的特点,由Android系统创建。因此,它的特性如下:
a、保存Activity实例
b、栈名由属性taskAffinity属性指定,默认情况下就是包名
c、不依附于APP,依附于系统。因此,它可以被多个进程共享
d、因为存在多个任务栈,所以位于栈顶的Activity实例不一定可见,但可见的Activity一定存在栈顶。

  前面两点容易理解,可这第三点怎么来验证呢?我们可以写两个简单的demo,指定的栈名一样,然后两个demo分别打印出任务栈的taskId,如果相同,这说明任务栈是可以被多个进程共享的。
Demo的代码如下:

 

public class BaseActivity extends AppCompatActivity{
  ....
    protected void dumpTaskAffinity(){
        try {
            ActivityInfo info = this.getPackageManager()
                    .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.i(this.getClass().getSimpleName(), "current activity hascode = " + this.hashCode() + " taskId = " + getTaskId() + " taskAffinity = "+info.taskAffinity + " taskHasCode = " + info.hashCode());
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
    }
}

 

//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:taskAffinity="com.carol.practice.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

注意:android:taskAffinity指定的名字必须是xxx.xxx的形式,如果写成android:taskAffinity=“xxx”这样的形式,在安装时会提示错误:

 

taskAffinity取名错误时的安装提示.png

demo1的代码如下:

 

public class Demo1BaseActivity extends AppCompatActivity{
   ...
    protected void dumpTaskAffinity(){
        try {
            ActivityInfo info = this.getPackageManager()
                    .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.i(this.getClass().getSimpleName(), "current activity hascode = " + this.hashCode() + " taskId = " + getTaskId() + " taskAffinity = "+info.taskAffinity + " taskHasCode = " + info.hashCode());
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

public class Demo1MainActivity extends Demo1BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
    }
}

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Demo1MainActivity"
            android:taskAffinity="com.carol.practice.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

可以看下两个demo几乎是一样的,只是Activity的名字不同罢了。这个时候我们分别运行,他们的输出结果如下:

 

demo的输出结果.png

 

demo1的输出结果.png

  对比我们可以看出,两个APP的栈ID都是19,栈名也是一样的。这说明任务栈确实不是APP创建的而是由系统创建的,并且可以被多个APP共享。
  通过对比,我们发现两个APP的Activity实例的hascode值也是一样的,这是不是说明他们用的是同一个实例呢?其实,hascode并不能用来表明实例的地址,两个相同的hascode值不能说明是同一个实例,但可以通过hascode值的不等来判断不是同一个实例。

二、Activity的生命周期

  2-1 正常情况下的生命周期

  一个Acitvity从创建到销毁的过程就是它的一次完整的生命周期。对应的生命周期函数依次是onCreate、onStart、onResume、onPause、onStop、onDestory,在一次生命周期中,Activity表现为四种状态,分别是运行状态、暂停状态、停止状态和销毁状态。那怎么来区分当前Activity当前处在哪个状态呢,这个就跟前面讲的任务栈有关系了。

  2-1-1、运行状态

  Activity实例位于栈顶并可见并能与用户交互,对应的生命周期函数是onResume。执行onStart时,Activity可见,但是还无法和用户进行交互,当执行完onResume函数之后才可以和用户进行交互。这种状态下的Activity优先级最高,异常情况下,系统最不愿意回收这种Activity。

  2-1-2、暂停状态

  Activity实例不在栈顶但是可见,对应的生命周期函数是onPause。一般情况下,我们在做Activity切换时这个状态时很短暂的,因为第二个Activity会马上覆盖掉之前的Activity,是之前的Activity不可见。但如果你要切换到的Activity不是占满屏幕类似对话框(不是Dialog)这样的Activity时就会处一直处在这个状态,直到发生下一次切换。从暂停状态回到运行状态会直接执行onResume函数。这种状态下的Activity优先级较高,异常情况下,如果存在更低优先级的Activity,系统也不会回收它。

  2-1-3、停止状态

  Activity实例不在栈顶且不可见,对应的生命周期函数是onStop。此时要切换到的Activity会完全覆盖掉之前的Activity。这种状态下的Activity系统仍然会保存它,只有在异常情况下,比如内存不够了,它的优先级最低,系统会回收它。

  2-1-4、销毁状态

  Activity实例被从任务栈中移除,对应的生命周期函数是onDestory。处于这种状态下,不管是否出现异常情况,系统都会回收。
  在Activity的生命周期中还有两个函数onSaveInstanceState和onRestoreInstanceState。这个onSaveInstanceState函数,在正常启动Activity时不会被调用。它的调用时机有如下几个:
a、点击home键回到手机界面时。
b、从Activity A跳转到Activity B时,A的onSaveInstanceState方法会被调用。
c、发生异常,比如系统配置发生改变时,当前Activity被杀死时。
  至于onRestoreInstanceState方法只会在发生异常,比如系统配置发生改变时,当前Activity被杀死后重新创建时调用,它的调用时机是在onStart之后,onResume之前。

  2-2 异常情况下的生命周期

  前面所讲是正常情况下Activity的生命周期流动情况,在异常情况下,当前的Activity或是整个进程都会被杀死,这个时候Android系统又提供了另一些处理函数,比如保存当前的数据防止数据丢失。异常情况主要分两种,资源相关的系统配置发生改变和系统内存不足。

  2-2-1 资源相关的系统配置发生改变导致当前的Activity被杀死重建

  首先,系统配置发生改变是一种什么样的情况?最常见的例子就是手机的横屏和竖屏之间的切换,这种情况下,系统配置会自动改变,此时当前的Activity会被杀死并重新创建。注意,要杀死的Activity是当前处于运行状态的Activity(不能说是栈顶的Activity,因为可能存在多个任务栈),处于其他状态下的Activity不会被杀死。被杀死时,系统会调用当前Activity的onPause、onStop、onDestory还有onSaveInstanceState方法。onPause、onStop、onDestory是依次调用的,至于onSaveInstanceState方法的调用可能在onPause之前也可能在onPause之后,但一定在onStop之前。这个怎么验证呢?可以创建两个Activity,第一个Activity启动第二个Activity,然后旋转屏幕。为了说明,杀死的是运行状态的Activity而不是只是存在栈顶的Activity,我们把启动的第二个Activity的启动模式指定为singleTask,任务栈名和第一个Activity的不同。代码如下:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Demo1MainActivity"
            android:taskAffinity="com.carol.practice.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Demo1BaseActivity"/>
        <activity android:name=".Demo1FirstActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task2"/>
    </application>

</manifest>

 

public class Demo1MainActivity extends Demo1BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Demo1MainActivity.this, Demo1FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

Demo1MainActivity的启动模式标准启动模式、任务栈名是"com.carol.practice.task1"

 

public class Demo1FirstActivity extends Demo1BaseActivity {
    private static final String TAG = "Demo1FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        dumpTaskAffinity();
    }
}

Demo1FirstActivity的启动模式singleTask,任务栈名是"com.carol.practice.task2"。和Demo1MainActivity不一样。

 

public class Demo1BaseActivity extends AppCompatActivity{
   private static final String TAG = "Demo1BaseActivity";
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       Log.i(TAG, getClass().getSimpleName() + " onCreate");
   }

   @Override
   protected void onNewIntent(Intent intent) {
       super.onNewIntent(intent);
       Log.i(TAG, getClass().getSimpleName() + " onNewIntent");
   }

   @Override
   protected void onStart() {
       super.onStart();
       Log.i(TAG, getClass().getSimpleName() + " onStart");
   }

   @Override
   protected void onResume() {
       super.onResume();
       Log.i(TAG, getClass().getSimpleName() + " onResume");
   }

   @Override
   protected void onPause() {
       super.onPause();
       Log.i(TAG, getClass().getSimpleName() + " onPause");
   }

   @Override
   protected void onRestart() {
       super.onRestart();
       Log.i(TAG, getClass().getSimpleName() + " onRestart");
   }

   @Override
   protected void onStop() {
       super.onStop();
       Log.i(TAG, getClass().getSimpleName() + " onStop");
   }
   
   @Override
   protected void onSaveInstanceState(Bundle outState) {
       super.onSaveInstanceState(outState);
       Log.i(TAG, getClass().getSimpleName() + " onSaveInstanceState");
   }

   @Override
   protected void onRestoreInstanceState(Bundle savedInstanceState) {
       super.onRestoreInstanceState(savedInstanceState);
       Log.i(TAG, getClass().getSimpleName() + " onRestoreInstanceState");
   }

   @Override
   protected void onDestroy() {
       super.onDestroy();
       Log.i(TAG, getClass().getSimpleName() + " onDestroy");
   }

   protected void dumpTaskAffinity(){
       try {
           ActivityInfo info = this.getPackageManager()
                   .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
           Log.i(TAG, this.getClass().getSimpleName() + " current activity hascode = " + this.hashCode() + " taskId = " + getTaskId() + " taskAffinity = "+info.taskAffinity + " taskHasCode = " + info.hashCode());
       } catch (PackageManager.NameNotFoundException e) {
           e.printStackTrace();
       }
   }
}

运行,从Demo1MainActivity跳转到Demo1FirstActivity,然后旋转。结果如下:

 

系统配置发生改变导致当前Activity被杀死后重现创建.png

从输出结果看,可以得出几点:
a、系统配置更改时要杀死只是当前运行状态的Activity,不是Activity处于栈顶就会被杀死。
b、杀死当前Activity之前onSaveInstanceState方法被调用。
c、当前Activity重新创建之后onRestoreInstanceState方法在onStart之后被调用。
  那如果系统上同时运行两个APP呢?
结果就不贴了,隐藏在后台的进程它的Activity都处在停止状态,所以当发生屏幕旋转时,不会被杀死,只有当前处于运行状态的Activity会被杀死重建。
  系统配置对应一个属性configChange,如果我们不想在屏幕发生旋转时杀死当前Activity可以设置这个属性,如下:

 

android:configChange="orientation|screenSize"

注意:只设置orientation是没有效果的,屏幕旋转仍会杀死重建,需要和screenSize组合在一起使用才行。

  2-2-2 资源内存不足导致低优先级的Activity被杀死

  Activity的优先级我们在前面讲Activity生命周期的四种状态时已经讲过了,优先级最低就是Activity处在停止状态的时候。当系统内存不足的时候,系统就会杀死这些最低优先级的Activity,在《Android开发艺术探索》中说是会杀掉拥有最低优先级Activity的进程,因此,这里不太明白到底只是杀死低优先级的Activity还是整个进程被杀死。不过根据Android官方开发文档的流程图来看,当内存不足时,处在暂停状态和停止状态的Activity所在的进程会被杀死,进程被杀死之前还是会调用onSaveInstanceState,当要返回到这个Activity时会重新创建进程并重新创建这个Activity。官方文档的Activity的生命周期流程图如下:

 

Activity的生命周期官方文档.png

  2-3 Activity生命周期流程图

  前面说了那么多,还是来一个自己总结的流程图吧。

 

Activity生命周期流程图.png

这里说明一下,前面说onSaveInstanceState和onPause谁先执行谁后执行不能确定,但根据我在Android5.1系统上测试情况来看都是在onPause先调用onSaveInstanceState后调用。

三、Activity的启动模式

    Activity的启动模式有四种:standard模式、singleTop模式、singleTask模式和singleInstance模式。这四种模式对应四种对Activity实例不同的管理方式。
    给Activity指定启动模式有两种办法:通过Intent的FLAG_ACTIVITY_XXX标志来指定启动模式。如下:

 

Intent intent = new Intent();
intent.setClass(MainActivity.this, FirstActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

另一种就是在AndroidManifest.xml中通过launchMode属性指定启动模式。比如:

 

<activity android:name=".FirstActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task1"/>

  3-1 standard模式

    标准模式,系统默认的启动方式。它的特点如下:
    a、每启动一个Activity实例就会创建一个Activity实例入栈。不管当前任务栈中是否已经存在这个实例了。
    b、谁启动了这个Activity,那么这个Activity实例会进入启动它的Activity所在的任务栈中。比如,Activity A启动Activity B,A指定的任务栈是a,B是标准模式,指定的任务栈是b,此时B的实例进入的任务栈a而不是指定任务栈b。这说明,这种模式下taskAffinity是无效的。虽然b对应输出的taskAffinity就是它指定的名字但是任务栈的ID还是A对应的任务栈的。这里也说明了任务栈另一个问题,比较任务栈是否相同只看它的taskId是否相同,不看taskAffinity。
    c、由于在标准模式具有b特性,因此如果使用非Activity类型的Context(如果ApplicationContext)启动标准模式的Activity将会报错。因为非Activity类型的Context没有任务栈。如图所示:

 

非Activity类型的Context启动标准模式的Activity.png

 

从错误提示中,可以看出标准模式下Activity的c特点。解决这种错误的方法就是使用Intent的Flag标志,设置成FLAG_ACTIVITY_NEW_TASK,这样启动的时候就会为它创建一个新的任务栈。实际上是以singleTask的模式启动的。

  3-2 singleTop模式

    栈顶复用模式。它的特点如下:
    a、跟标准模式一样,谁启动这种Activity它就进入谁的任务栈中,不受taskAffinity影响。
    b、启动这种模式的Activity会先检查启动它的Activity所在任务栈的栈顶是否存在相同Activity,如果存在,则不重新创建,直接调用待启动Activity的onNewIntent的方法。如果栈顶不存在,则重新创建,不管该任务栈的栈中是否有这个Activity实例。
    使用代码验证一下:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".BaseActivity"/>
        <activity android:name=".FirstActivity"
            android:launchMode="singleTop"
            android:taskAffinity="com.carol.practice.task1"/>
    </application>
</manifest>

MainActivity是标准模式,FirstActivity是singleTop模式,并且制定任务栈名是"com.carol.practice.task1。

 

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

在MainActivity中点击按钮进入FirstActivity。

 

public class FirstActivity extends BaseActivity{
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        dumpTaskAffinity();
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

FirstActivity中也有一个按钮,单击之后,自己启动自己。
其输出结果如下:

 

singleTop模式输出.png

 

从结果可以看出,第一、FirstActivity进入的是MainActivity所在的任务栈;第二、FirstActivity位于栈顶,在自己启动自己时FirstActivity先进入暂停状态然后依次调用onNewIntent和onResume并没有调用onCreate、onStart方法,说明没有被重新创建

  3-3 singleTask模式

    栈内复用模式。它的特点如下:
    a、受taskAffinity影响。它只会进入taskAffinity指定的任务栈中。
    b、指定的任务栈不存在,则先创建任务栈然后将Activity实例入栈;指定的任务栈存在,则检查栈内是否存在Activity实例,如果存在,则将位于该Activity实例之上的其他所有Activity实例出栈,将自己置于栈顶的位置。如果栈内不存在,则创建Activity实例。

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".BaseActivity"/>
        <activity android:name=".FirstActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task1"/>
    </application>

</manifest>

将FirstActivity的启动模式改为singleTask模式,taskAffinity指定为"com.carol.practice.task1",使其和MainActivity的taskAffinity不一致。

 

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

从MainActivity中切换到FirstActivity

 

public class FirstActivity extends BaseActivity{
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        dumpTaskAffinity();
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });
    }
}

从FirstActivity中切换到MainActivity。
输出结果如下:

 

singleTask模式输出.png

 

第一,MainActivity启动FirstActivity,FirstActivity实例进入的任务栈是FirstActivity指定的任务栈不是MainActivity的任务栈;第二,从FirstActivity启动MainActivity,MainActivity重新创建实例并进入FirstActivity所在的任务栈,因为MainActivity是标准启动模式;第三,再次重MainActivity启动FirstActivity时位于FirstActivit实例所在的任务栈栈顶的MainActivity先进入暂停状态然后依次调用FirstActivity的onNewIntent,onRestart、onStart和onResume,最后MainActivity被销毁。我们在singleTop模式下发现只是调用onNewIntent和onResume,为什么在singleTask模式还调用了onRestart和onStart呢?这跟Activity实例所处的状态有关,singleTop模式下,Activity实例已经在栈顶了,而现在的Activity实例是在停止状态,根据生命周期流程图从停止状态回到运行状态时需要一次调用onRestart、onStart和onResume的。

  3-4 singleInstance模式

    单例模式。在介绍它的特点之前,我们先来看两个例子(Activity A标准模式 , Activity B singleInstance模式。在Android5.1上测试):
情况1:APP启动时先启动A,创建任务栈A,然后A启动B,B单独创建一个任务栈B。再从B启动A,A重新创建实例进入任务栈A。由于singleInstance的独占任务栈的特性,A无法进入任务栈B,系统让其直接进入之前的任务栈A并重新创建A实例。每次从B启动A都是这样复用任务栈A重新创建A实例。这个可以理解。
情况2:APP启动时先启动B,创建任务栈B,然后B启动A,A单独创建一个任务A。再从A启动B,B复用之前的实例,接着B启动A,根据情况1应该是复用任务栈A重新创建A实例,可现在输出结果是复用任务栈A也复用A实例,不重新创建A实例。这是为什么呢?情况2这种,A都不符合标准模式的特性了(每次启动都会创建实例)。贴上我观察到的输出结果:

 

singleInstance模式输出结果.png

 

从输出结果可以看到,MainActivity第一创建完成之后,后面启动都不再重新创建了。造成这种差异性就是APP运行时第一次启动的Activity是标准模式还是singleInstance模式。如果第一次启动的Activity是标准模式,那么标准模式Activity和singleInstance模式的Activity之间的切换,标准模式的Activity会复用它所在的任务栈并重新创建实例;如果第一次启动的Activity是singleInstance模式,这个又要分情况。
在情况2中,A占用一个任务栈A,B占用一个任务栈B, B启动A,如果任务栈A只有A,不论多少个,A复用任务栈A也复用实例A,不再重新创建A实例;如果任务栈A不止有A实例还有C实例D实例,那么B启动A时都会重新创建A实例。这里就不再帖代码了。只对singleInstance模式的特点总结如下:
    a、singleInstance模式下的Activity独占一个任务栈,全局唯一。由于任务栈中只有一个Activity实例,因此,可见并获得焦点时它是栈顶运行状态;可见但失去焦点时它是栈低暂停状态;不可见时它是栈低停止状态。
    b、APP启动时,第一次启动的是standard模式下的Activity A,创建任务栈A,后面从A启动singleInstance模式的Activity B,因为a特点不论B是否重新指定taskAffinity,都会重新创建一个任务栈B。然后每次从B启动A时,A都会重新创建实例进入任务栈A。
    c、APP启动时,第一次启动的是singleInstance模式的Activity B,创建任务栈B。然后从B启动standard模式的Activity A,如果A不存在会单独创建一个任务栈A,如果存在,则看任务栈A中是只有A实例(不论多少个)还是除A实例之外还有别实例。只有A实例则直接复用不重新创建;除A实例之外还有的别实例则每次都重新创建A实例。这种违反standard模式特性的情况,只在这里适用。

  3-5 启动模式与startActivityForResult

    前面介绍四种启动模式时我们在代码中要从一个Activity A跳转到另一个Activity B都是调用方法startActivity,如果我们想从B返回到A时把B中数据也传递给A则在A中调用方法startActivityForResult启动B(startActivityForResult的第二个参数requestCode必须是大于-1的数,不然不会回调onActivityResult),这样返回时Activity A的onActivityResult方法就会被调用,该方法会带回B使用setResult方法给A的intent对象。代码如下:

 

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivityForResult(intent, 0);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.i(TAG, "requestCode=" + requestCode + " resultCode=" + resultCode);
        if(resultCode == 100){
            String str = data.getStringExtra("result");
            Log.i(TAG, "FirstActivity delivered result : " + str);
        }
    }
}

 

public class FirstActivity extends BaseActivity{
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("result", "i am FirstActivity");
                setResult(100, intent);
                finish();
            }
        });
    }
}

注意这里是B返回A,比如B自己finish或按back键,不是B启动A。B启动A是不会调用A的onActivityResult方法的。
    那使用startActivityForResult启动Activity,在不同的启动模式会跟startActivity启动Activity有什么不同吗?我们通过代码来验证下:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"> 
        <activity android:name=".LaunchModeActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".StandardActivity"/>
        <activity android:name=".SingleTopActivity"
            android:launchMode="singleTop"/>
        <activity android:name=".SingleTaskActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task1"/>
        <activity android:name=".SingleInstanceActivity"
            android:launchMode="singleInstance"
            android:taskAffinity="com.carol.practice.task2"/>
    </application>
</manifest>

 

public class LaunchModeActivity extends BaseActivity implements View.OnClickListener{
    private static final String TAG = "LaunchModeActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.launchmode_activity_layout);
        Button standardBtn = (Button)findViewById(R.id.btn_standard_activity);
        standardBtn.setOnClickListener(this);
        Button singleTopBtn = (Button)findViewById(R.id.btn_singleTop_activity);
        singleTopBtn.setOnClickListener(this);
        Button singleTaskBtn = (Button)findViewById(R.id.btn_singleTask_activity);
        singleTaskBtn.setOnClickListener(this);
        Button singleInstanceBtn = (Button)findViewById(R.id.btn_singleInstance_activity);
        singleInstanceBtn.setOnClickListener(this);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.i("BaseActivity", "requestCode=" + requestCode + " resultCode=" + resultCode);
        if(resultCode >= 100) {
            String str = data.getStringExtra("result");
            Log.i("BaseActivity", "delivered result : " + str);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId())
        {
            case R.id.btn_standard_activity:
                Intent standardIntent = new Intent(LaunchModeActivity.this, StandardActivity.class);
                startActivityForResult(standardIntent, 100);
                break;
            case R.id.btn_singleTop_activity:
                Intent singleTopIntent = new Intent(LaunchModeActivity.this, SingleTopActivity.class);
                startActivityForResult(singleTopIntent, 101);
                break;
            case R.id.btn_singleTask_activity:
                Intent singleTaskIntent = new Intent(LaunchModeActivity.this, SingleTaskActivity.class);
                startActivityForResult(singleTaskIntent, 102);
                break;
            case R.id.btn_singleInstance_activity:
                Intent singleInstanceIntent = new Intent(LaunchModeActivity.this, SingleInstanceActivity.class);
               startActivityForResult(singleInstanceIntent, 103);
                break;
            default:
                break;
        }
    }
}

 

public class StandardActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.standard_activity_layout);

        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(StandardActivity.this, LaunchModeActivity.class);
                startActivityForResult(intent, 100);
            }
        });
    }
}

 

public class SingleTopActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.singletop_activity_layout);

        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SingleTopActivity.this, SingleTopActivity.class);
                startActivityForResult(intent, 101);
            }
        });
    }
}

 

public class SingleTaskActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.singletask_activity_layout);
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SingleTaskActivity.this, LaunchModeActivity.class);
                startActivityForResult(intent, 102);
            }
        });
    }
}

 

public class SingleInstanceActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.singleinstance_activity_layout);

        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SingleInstanceActivity.this, LaunchModeActivity.class);
                startActivityForResult(intent, 103);
            }
        });
    }
}

startActivityForResult和standard模式的Activity的输出结果:

 

startActivityForResult与standard模式的输出结果.png

 

从输出结果看,startActivityForResult与startActivity启动standard模式的Activity是一样的,符合standard模式的特点。

startActivityForResult和singleTop模式的Activity的输出结果:

 

startActivityForResult和singleTop模式的输出结果.png

 

从这个输出结果我们发现,startActivityForResult与startActivity启动singleTop模式的Activity有所不同了,当SingleTopActivity实例已经位于栈顶时,SingleTopActivity使用startActivityForResult自己启动自己还是会重新创建实例。如果使用startActivity自己启动自己就不会重新创建实例。

startActivityForResult和singleTask模式的Activity的输出结果:

 

startActivityForResult和singleTask模式的输出结果.png

 

从结果来看,跟standard模式的输出一样,但是我是指定了SingleTaskActivity的启动模式和taskAffinity的呀。系统并没有为SingleTaskActivity重新创建任务栈,还是使用的LaunchModeActivity的任务栈,而且最后一步从LaunchModeActivity中启动SingleTaskActivity时也是重新创建了实例,没有复用任务栈里的实例。

startActivityForResult和singleInstance模式的Activity的输出结果:

 

startActivityForResult和singleInstance模式的输出结果.png

 

这个和singleTask模式的输出结果是一致的,也是没有重新创建任务栈和复用任务栈中已经存在的SingleInstanceActivity实例。

因此,从这四种输出结果可以得出结论:
使用startActivityForResult(参数requestCode大于-1)启动Activity时会忽略launchMode和taskAffinity的作用,改为standard启动模式。launchMode和taskAffinity只对startActivity有效。
以上测试都是基于Android5.1系统。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值