探究Activity

号外、号外,以后本博客的相关代码,都会在github中的AndroidKnowledgeSummary展现。当然了,有的时候有,有的就不必写或是没法儿写。github这么牛X的地方,得参与起来!
手头有几本书,《Android群英传》、《Android开发艺术探索》、《第一行代码》、+Android官方开发指南。接下来可能会尽量按照公认的知识点顺序,系统化的总结下Android的内容。但也不排除插播其他流行框架、新技术、甚至是胡诌的东西。反正就是怎么爽怎么来,是不是有点儿小任性…O(∩_∩)O哈哈~
本篇主要内容:1、怎么启动一个Activity。2、Activity生命周期(异常处理)。3、intent过滤器。4、Activity的Flags。5、Activity的Affinity。6、adb命令行查看activity任务栈信息。

1、为什么先是Activity

  • 关于Android系统是什么,我也不够水平讲,看下图,来自于Android官方平台架构介绍
    Android系统
  • Activity是Android四大组件之一,也是用户直接感受到的,一般叫“活动”,我们甚至可以直接叫“界面”。
  • 正常情况下,从桌面启动一个app,都是进入一个“界面”,所以我们就先从先看到的东西开始说起。
  • 应用组件是 Android 应用的基本构建基块。分为四大,Activity、Service、ContentProvider、 BroadcastReceiver。

2、启动Activity

  • 如果我们使用Android Studio,通过“New Project”创建一个新的Android project,在app目录下src/main的AndroidManifest.xml文件中(此处说的是Project窗口模式下)。MainActivity就是我们打开app后启动的第一个Activity,intent-filter表示的是使用“隐式Intent”启动Activity。下面会详细介绍,此处我们先去看“显式”启动的方式。
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
  • 显示启动Activity,也是我们最常用的方式。经典方法如下。
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);

如果使用Android Studio中New Activity创建SecondActivity的话,这时就可以直接打开Second Activity了。
Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的操作,还可以在不同的传递数据。一般用于启动Activity、Service、Broadcast。分为显式隐式 两种情况,上面的用法就是显式写法的一种情况。
下面是隐式写法的一种
根据上面已经有的内容,在AndroidManifest.xml中修改SecondActivity的标记。

<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=".SecondActivity">
    <intent-filter>
        <action android:name="com.breezehan.knowledge.second" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.breezehan.knowledge.second");
                startActivity(intent);
            }
        });
    }
}

点击MainActivity中的button就可以启动SecondActivity了。
其中只是setAction就成功了,Category好像没用,那我们把在上方代码中12 行添加一行,成品如下:

Intent intent = new Intent();
intent.setAction("com.breezehan.knowledge.second");
intent.addCategory("com.breezehan.knowledge.mycategory");
startActivity(intent);

运行,点击MainActivity中的button。
不好,崩溃了!

Process: com.breezehan.knowledge, PID: 7521
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.breezehan.knowledge.second cat=[com.breezehan.knowledge.mycategory] }

没有Activity可以响应这个Intent,因为我们在刚刚的Intent中添加了一个Category,但是SecondActivity的中并没有声明,不存在去哪儿响应。
所以在SecondActivity的清单文件中改动。这样就可以启动了。前面开始的时候没有addCategory也能成功,是因为startActvity的时候会自动添加一个默认的android.intent.category.DEFAULT。

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.breezehan.knowledge.second" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.breezehan.knowledge.mycategory" />
    </intent-filter>
</activity>

3、更多隐式Intent相关

隐式 Intent 指定能够在可以执行相应操作的设备上调用任何应用的操作。 如果您的应用无法执行该操作而其他应用可以,且您希望用户选取要使用的应用,则使用隐式 Intent 非常有用。

接收隐式Intent是根据 < intent-filter > 过滤器决定的,每个应用组件可以声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作action、数据data和类别category指定自身接受的 Intent 类型。 仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。
分析 < intent-filter > 内部元素:

  1. < action >
    要指定接受的 Intent 操作,也可以声明多个此类元素。例如:
<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

要通过此过滤器,您在 Intent 中指定的操作必须与过滤器中列出的某一操作匹配。

An < intent-filter > element must contain one or more < action > elements.

2.< category>
一个包含应处理 Intent 组件类型的附加信息的字符串。 至少必须有一个Default(android.intent.category.DEFAULT),也可以声明多个此类元素。

In order to receive implicit intents, you must include the CATEGORY_DEFAULT category in the intent filter.

例如:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

若要使 Intent 通过类别测试,则 Intent 中的每个类别均必须与过滤器中的类别匹配。反之则未必然,Intent 过滤器声明的类别可以超出 Intent 中指定的数量,且 Intent 仍会通过测试。 因此,不含类别的 Intent 应当始终会通过此测试,无论过滤器中声明何种类别均是如此。

注:Android 会自动将 CATEGORY_DEFAULT 类别应用于传递给 startActivity() 和 startActivityForResult() 的所有隐式 Intent。因此,如需 Activity 接收隐式 Intent,则必须将 “android.intent.category.DEFAULT” 的类别包括在其 Intent 过滤器中(如上文的 < intent-filter > 示例所示)。

3.< data >
使用一个或多个指定数据 URI 各个方面(scheme、host、port、path 等)和 MIME 类型的属性,声明接受的数据类型。
可以不声明,也可以是多个。

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />

< scheme>://< host >:< port>/< path>

例如:

content://com.example.project:200/folder/subfolder/etc

在此 URI 中,架构是 content,主机是 com.example.project,端口是 200,路径是 folder/subfolder/etc。
下面是官方Intent 和 Intent 过滤器介绍
在 < data> 元素中,上述每个属性均为可选,但存在线性依赖关系:

  • 如果未指定架构,则会忽略主机。
  • 如果未指定主机,则会忽略端口。
  • 如果未指定架构和主机,则会忽略路径。

将 Intent 中的 URI 与过滤器中的 URI 规范进行比较时,它仅与过滤器中包含的部分 URI 进行比较。 例如:

  • 如果过滤器仅指定架构,则具有该架构的所有 URI 均与该过滤器匹配。
  • 如果过滤器指定架构和权限,但未指定路径,则具有相同架构和权限的所有 URI 都会通过过滤器,无论其路径如何均是如此。
  • 如果过滤器指定架构、权限和路径,则仅具有相同架构、权限和路径的 URI 才会通过过滤器。

注:路径规范可以包含星号通配符 (*),因此仅需部分匹配路径名即可。

数据测试会将 Intent 中的 URI 和 MIME 类型与过滤器中指定的 URI 和 MIME 类型进行比较。 规则如下:

  • 仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 Intent 才会通过测试。
  • 对于包含 URI 但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 Intent,仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。
  • 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型、但不含 URI 的 Intent 才会通过测试。
  • 仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 URI 类型和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。 如果 Intent 的 URI 与过滤器中的 URI 匹配,或者如果 Intent 具有 content: 或 file: URI 且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。 换言之,如果过滤器只是列出 MIME 类型,则假定组件支持 content: 和 file: 数据。

比如一些社交共享应用的片段。

<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

我们可以在自己的MainActivity中测试情况。下面是简单的几种情况。
MainActivity的button

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.breezehan.knowledge.second");
                intent.setDataAndType(Uri.parse("http://www.baidu.com"), "image/*");
//                intent.setData(Uri.parse("http://www.baidu.com"));
//                intent.setType("image/*");
//                intent.addCategory("com.breezehan.knowledge.mycategory");
//                intent.setAction(Intent.ACTION_SEND);
//                intent.setType("text/plain");
//                intent.setDataAndType(Uri.parse("http:"),"text/plain");
//                intent.setDataAndType(Uri.parse("content:"),"text/plain");
//                intent.setAction(intent.ACTION_SEND_MULTIPLE);
//                intent.setType("image/*");
                if (intent.resolveActivity(getPackageManager()) != null) {
                    startActivity(intent);
                }
            }
        });

AndroidManifest.xml中

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.breezehan.knowledge.second" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.breezehan.knowledge.mycategory" />

        <data android:scheme="http" android:mimeType="image/*"/>
    </intent-filter>
</activity>

4、Activity生命周期

先上一个官图,来点儿说服力。
官方Activity4lifecycle
onCreate:创建Activity时调用。初始化工作,如setContentView加载布局,初始化数据等。
onRestart:Activity从不可见(停止)状态回到可见时调用。重新启动。
onStart:在 Activity 即将对用户可见之前调用。此时用户还看不到界面。
onResume:开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。跟onStart的区别:onStart时Activity还在后台没用出现;onResume时Activity已经可见。
onPause:即将启动或恢复另一个Activity时调用。通常会执行一些释放CPU资源和保存数据的操作。但一定要迅速,因为它执行后才会进入新的Activity。
onStop:在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。注意 如果在onResume之后出现的是一个对话框,那么只会执行onPause,不会执行onStop,因为部分可见。
onDestroy:在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。
以上7种状态,基本上可以分为3种情况:
1. Activity的整个生命周期 ,onCreate和onDestroy之间。在onCreate种初始化,在onDestory种释放资源。
2. Activity的可见生命周期,onStart和onStop之间。
3. Activity的前台生命周期,onResume和onPause之间。可以与用户交互。

下面写个例子,直观感受下Activity生命周期。
改造刚才的MainActivity,在layout的activity_main的加一个button。
在MainActivity和SecondActivity总override上面的7种方法。并打印log日志。
如MainActivity中:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: ");
        setContentView(R.layout.activity_main);
        ...
        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,SecondActivity.class));
            }
        });
    }

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

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

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

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

    @Override
    protected void onStop() {
        super.onStop();
        Log.i(TAG, "onStop: ");
    }

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

SecondActivity也是如此。这里Android Studio有个快捷键。在设置TAG常量的地方,打出logt后直接TAB键就会自动生成。而像onDestroy中打出logi,再TAB键也会自动生成。注意了,此处又装X哈哈!
此时点击按钮button查看生命周期。
首次运行进入时,

5456-5456/com.breezehan.knowledge I/MainActivity: onCreate:
5456-5456/com.breezehan.knowledge I/MainActivity: onStart:
5456-5456/com.breezehan.knowledge I/MainActivity: onResume:

点击button进入SecondActivity时,含上面的打印部分。

5456-5456/com.breezehan.knowledge I/MainActivity: onCreate:
5456-5456/com.breezehan.knowledge I/MainActivity: onStart:
5456-5456/com.breezehan.knowledge I/MainActivity: onResume:
5456-5456/com.breezehan.knowledge I/MainActivity: onPause:
5456-5456/com.breezehan.knowledge I/SecondActivity: onCreate:
5456-5456/com.breezehan.knowledge I/SecondActivity: onStart:
5456-5456/com.breezehan.knowledge I/SecondActivity: onResume:
5456-5456/com.breezehan.knowledge I/MainActivity: onStop:

这里可以看出一个Activity的生命周期,和打开另一个Activity时的情况。如当前从Main打开进入Second时,onPause(Main)->onCreate(Second)->onStart(Second)->onResume(Second)->onStop(Main)。
连续放回两次的完整打印。

5456-5456/com.breezehan.knowledge I/MainActivity: onCreate:
5456-5456/com.breezehan.knowledge I/MainActivity: onStart:
5456-5456/com.breezehan.knowledge I/MainActivity: onResume:
5456-5456/com.breezehan.knowledge I/MainActivity: onPause:
5456-5456/com.breezehan.knowledge I/SecondActivity: onCreate:
5456-5456/com.breezehan.knowledge I/SecondActivity: onStart:
5456-5456/com.breezehan.knowledge I/SecondActivity: onResume:
5456-5456/com.breezehan.knowledge I/MainActivity: onStop:
5456-5456/com.breezehan.knowledge I/SecondActivity: onPause:
5456-5456/com.breezehan.knowledge I/MainActivity: onRestart:
5456-5456/com.breezehan.knowledge I/MainActivity: onStart:
5456-5456/com.breezehan.knowledge I/MainActivity: onResume:
5456-5456/com.breezehan.knowledge I/SecondActivity: onStop:
5456-5456/com.breezehan.knowledge I/SecondActivity: onDestroy:
5456-5456/com.breezehan.knowledge I/MainActivity: onPause:
5456-5456/com.breezehan.knowledge I/MainActivity: onStop:
5456-5456/com.breezehan.knowledge I/MainActivity: onDestroy:

此时我们再新建一个DialogActivity,是通过new-activity-emptyActivity创建的,我们在清单文件中修改如下:

<activity android:name=".DialogActivity" android:theme="@style/Theme.AppCompat.Dialog">

</activity>

从Main中启动DialogActivity,会出现下图情况。
dialog
点击屏幕任意区域,DialogActvity即退出。过程日志如下。

27325-27325/com.breezehan.knowledge I/MainActivity: onCreate:
27325-27325/com.breezehan.knowledge I/MainActivity: onStart:
27325-27325/com.breezehan.knowledge I/MainActivity: onResume:
27325-27325/com.breezehan.knowledge I/MainActivity: onPause:
27325-27325/com.breezehan.knowledge I/MainActivity: onResume:

说明此种情况并不是完全可见,所以Main只会onPause。

5、生命周期的异常情况

上面的官方Activity生命周期图中所说,当memory不足时会kill掉Activity,导致重新创建。这个我们不容易模拟,但是横竖屏切换时,也是会导致Activity重新创建的。
我们可以试验下,进入MainActivity,然后点击模拟器的屏幕旋转按钮。日志会出现下面的情景。

com.breezehan.knowledge I/MainActivity: onCreate:
com.breezehan.knowledge I/MainActivity: onStart:
com.breezehan.knowledge I/MainActivity: onResume:
com.breezehan.knowledge I/MainActivity: onPause:
com.breezehan.knowledge I/MainActivity: onStop:
com.breezehan.knowledge I/MainActivity: onDestroy:
com.breezehan.knowledge I/MainActivity: onCreate:
com.breezehan.knowledge I/MainActivity: onStart:
com.breezehan.knowledge I/MainActivity: onResume:

再点击旋转,会继续onDestory->onCreate。那么我们就有疑问了,如果当然页面用户已经产生一些交互(编辑、动作、EditText等)。用户不小心使屏幕自动旋转了,不能重新填写处理吧。这里,Activity为我们提供了onSaveInstanceState和onRestoreInstanceState(其实onCreate的参数bundle也参与)。注意的情况,像EditText等系统本身已实现了onSaveInstanceState方法,所以不会真的丢失数据。
我们在MainActivity中改造。添加下面的代码。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: ");
        if (savedInstanceState != null) {
            Log.i(TAG, "savedInstanceState: "+savedInstanceState.getString("saveState"));
        }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        String tempData = "Something is saved.";
        outState.putString("saveState", tempData);
        Log.i(TAG, "onSaveInstanceState: ");
    }

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

此时我们运行成功后,切换屏幕旋转。会有类似下面的日志。

com.breezehan.knowledge I/MainActivity: onCreate:
com.breezehan.knowledge I/MainActivity: onStart:
com.breezehan.knowledge I/MainActivity: onResume:
com.breezehan.knowledge I/MainActivity: onPause:
com.breezehan.knowledge I/MainActivity: onSaveInstanceState:
com.breezehan.knowledge I/MainActivity: onStop:
com.breezehan.knowledge I/MainActivity: onDestroy:
com.breezehan.knowledge I/MainActivity: onCreate:
com.breezehan.knowledge I/MainActivity: savedInstanceState: Something is saved.
com.breezehan.knowledge I/MainActivity: onStart:
com.breezehan.knowledge I/MainActivity: onRestoreInstanceState:
com.breezehan.knowledge I/MainActivity: onResume:

这里我只是旋转了一下。onSaveInstanceState一定会在onStop之前;onRestoreInstanceState(如果有的话)在onStart之后。如果此时你在Main中启动Second,会发现onSaveInstanceState方法还是走了,但是返回的时候onRestoreInstanceState并没有走onRestoreInstanceState,且onCreate中savedInstanceState==null。也就说onRestoreInstanceState的参数值一定不为空。

6、Activity启动模式

任务是说一系列Activity的集合,但这些Activity根据各自的打开顺序排列在堆栈(即返回栈)中。而返回栈是一种后进先出的数据结构,最后入栈的总是在栈顶。
可以简单理解为Activity处于任务栈(返回栈)中。
在清单文件中声明 Activity 时,可以使用 < activity> 元素的 launchMode 属性指定 Activity 应该如何与任务关联。standard、singleTop、singleTask、singleInstance。

6.1 standard
标准模式,系统默认模式。每次都会重新创建活动一个实例,不管这个实例是否存在。谁启动的这个Activity,那么这个Activity就会和启动它的那个Activity在一个任务栈中。
我们创建一个FirstActivity,launchMode不填写,即默认standard。

public class FirstActivity extends AppCompatActivity {
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: "+this.toString());
        setContentView(R.layout.activity_first);
        ((TextView)findViewById(R.id.textView)).setText(this.toString());
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(FirstActivity.this,FirstActivity.class));
            }
        });
    }
}

点击button多次

FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@cdbda87
FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@3dc837c
FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@714b12d

也就是说A是standard模式,那么会在一个栈中产生AAA…的情况。

6.2 singleTop
栈顶复用模式。如果要启动的Activity处于栈顶,则不会重新创建,同时会调用onNewIntent方法。
即如果ABCD四个Activity,A栈低,D栈顶。如果D为singleTop模式,那么D中再启动D,栈内情况依旧是ABCD。但是如果此时启动C,就会是ABCDC,即使C也是singleTop模式。
6.3 singleTask
栈内复用模式。如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。否则系统创建新任务并实例化 Activity,位于新任务栈最底部。
有几种情况。(配合taskAffinity使用,此处不深究,简单就是指定Activity所处的任务栈名称)

  • 假如ABC处于S1任务中,这时启动D(D是singleTask模式,且指定任务栈是S2),由于此时S2和D都不存在,所以会先创建S2,然后在S2中创建D。
  • 假如ABC处于S1任务中,这时启动D(D是singleTask模式,且指定任务栈是S1),则只是创建D,并入栈S1,ABCD。
  • 假如ADBC处于S1任务中,这时启动D(D是singleTask模式,且指定任务栈是S1),则只是调用D的onNewIntent方法,将D置于栈顶。但是有clearTop的效果,导致BC出栈,此时S1栈为AD。

    6.4 singleInstance
    单实例模式。栈中只有一个Activity,处于单独的任务栈中。如果启动A,然后启动B(singleInstance),然后启动C,此时是两个栈,当前栈AC,单独栈中B。如果此时点击返回键,会先到A,因为两者处于同一任务栈中,再点返回键,才会到B。

7、Intent的标志flags

启动 Activity 时,可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。常用如下:

  • FLAG_ACTIVITY_SINGLE_TOP
    此标志位跟launchMode的singleTop模式效果一致。
  • FLAG_ACTIVITY_NEW_TASK
    官方说与 “singleTask”launchMode 值相同的行为。你要相信吗,稍等验证。
  • FLAG_ACTIVITY_CLEAR_TOP
    使用此标志启动时,会使在同一任务栈中,把位于当前Activity上面的所有Activity出栈,销毁。
    没有对应的launchMode 。一般会和FLAG_ACTIVITY_NEW_TASK同时使用。

我们来验证下FLAG_ACTIVITY_NEW_TASK行为。
改造刚才的FirstActivity。

    Intent intent = new Intent(FirstActivity.this,FirstActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
    ...
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d(TAG, "onNewIntent: ");
    }

多次点击button。

com.breezehan.knowledge D/FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@5ded4f5
com.breezehan.knowledge D/FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@bfc3e92
com.breezehan.knowledge D/FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@8113ecb

不对啊,为什么每次都创建新的实例了,不是说和singleTask一样吗?!
我们再写一个OtherActivity,能启动FirstActivity。
FirstActivity中

public class FirstActivity extends AppCompatActivity {
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: "+this.toString()+"\ttaskId:"+getTaskId());
        setContentView(R.layout.activity_first);
        ((TextView)findViewById(R.id.textView)).setText(this.toString());
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this,OtherActivity.class);
                startActivity(intent);
            }
        });
    }

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

OtherActivity如下

public class OtherActivity extends AppCompatActivity {
    private static final String TAG = "OtherActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: "+this.toString()+"\ttaskId:"+getTaskId());
        setContentView(R.layout.activity_other);
        TextView textView = (TextView) findViewById(R.id.textView);
        textView.setText(this.toString());
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(OtherActivity.this,FirstActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        });
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d(TAG, "onNewIntent: ");
    }
}

顺序first->other->first

FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@18b9f2b taskId:249
OtherActivity: onCreate: com.breezehan.knowledge.launchmode.OtherActivity@f41828a taskId:249
FirstActivity: onCreate: com.breezehan.knowledge.launchmode.FirstActivity@3059cf4 taskId:249

What?什么情况,完全没反应吗?即使我们执行adb shell dumpsys activity也是如此。

Running activities (most recent first):
TaskRecord{6946e03 #249 A=com.breezehan.knowledge U=0 StackId=1 sz=3}
Run #2: ActivityRecord{ba9939e u0 com.breezehan.knowledge/.launchmode.FirstActivity t249}
Run #1: ActivityRecord{9b4d225 u0 com.breezehan.knowledge/.launchmode.OtherActivity t249}
Run #0: ActivityRecord{9444454 u0 com.breezehan.knowledge/.launchmode.FirstActivity t249}

那么我们再OtherActivity改造如下:

 button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(OtherActivity.this,FirstActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(intent);
    }
});

最后打印的日志还是跟上面差不多。但是在first->other->first两次点击后,执行adb shell dumpsys activity。

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
mFullscreen=true
mBounds=null
Task id #255
mFullscreen=true
mBounds=null
mMinWidth=-1
mMinHeight=-1
mLastNonFullscreenBounds=null
TaskRecord{3032265 #255 A=com.breezehan.knowledge U=0 StackId=1 sz=1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.breezehan.knowledge/.launchmode.FirstActivity }
Hist #0: ActivityRecord{1f342fc u0 com.breezehan.knowledge/.launchmode.FirstActivity t255}
Intent { flg=0x14400000 cmp=com.breezehan.knowledge/.launchmode.FirstActivity }
ProcessRecord{e43ef5c 17311:com.breezehan.knowledge/u0a94}

Running activities (most recent first):
TaskRecord{3032265 #255 A=com.breezehan.knowledge U=0 StackId=1 sz=1}
Run #0: ActivityRecord{1f342fc u0 com.breezehan.knowledge/.launchmode.FirstActivity t255}

此时taskRecord中只有一个FirstActivity,点击一次返回键,直接退出app。

8、处理关联 taskAffinity

“关联”指示 Activity 优先属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此关联。 因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务栈中。 不过,您可以修改 Activity 的默认关联。 在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。
可以使用 < activity> 元素的 taskAffinity 属性修改任何给定 Activity 的关联。
taskAffinity 属性取字符串值,该值必须不同于在 < manifest> 元素中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。
白话就是,你不设置这个选项,默认是跟启动自己的activity处于同一任务栈中,如果设置了taskAffinity 的值且不跟包名相同,待启动的Activity会处于名字和taskAffinity相同的任务栈中 。当然要跟下面的情况一起使用哦,单独无效。
在两种情况下,关联会起作用:

  • 启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志(此处等同设置singleTask)。
    此时,会开启一个新任务(如果不存在taskAffinity中设置的任务),在任务中启动Activity。但是如果已存在taskAffinity指定的任务,则在关联的任务中启动Activity。如下OtherActivity的清单文件中,设置taskAffinity=”com.breezehan.knowledge.other”。first->other运行,启动了新的任务栈。

Running activities (most recent first):
TaskRecord{a0dac3a #258 A=com.breezehan.knowledge.other U=0 StackId=1 sz=1}
Run #1: ActivityRecord{4e7230e u0 com.breezehan.knowledge/.launchmode.OtherActivity t258}
TaskRecord{12830eb #257 A=com.breezehan.knowledge U=0 StackId=1 sz=1}
Run #0: ActivityRecord{8fd516b u0 com.breezehan.knowledge/.launchmode.FirstActivity t257}

  • Activity 将其 allowTaskReparenting 属性设置为 “true”。
    在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。
    例子:应用A,启动应用B中的ActvitityC(清单文件中allowTaskReparenting = true)。然后”HOME”键回到主页,打开应用B,此时不会启动B中的MainActivity,而是直接显示的ActivityC。因为两者包名不一,默认taskAffinity不一样。

    白话版:
    1、如果上述设置,应用A中ActvitityA启动应用B的ActvitityC,此时ActvitityA、ActvitityC同处于应用A的任务栈中。
    2、此时HOME后,启动应用B,则会直接展现ActvitityC,此时ActvitityC跑到了应用B的任务栈中。
    你看allowTaskReparenting 就是会重定位所在任务栈。

上述的FirstActivity改造如下。

Intent intent = new Intent();
intent.setAction("com.breezehan.taskaffinity.reparent");
startActivity(intent);

新建一个Module,除了MainActivity,新建一个ReparentActivity。

<activity android:name=".ReparentActivity"
    android:allowTaskReparenting="true">
    <intent-filter>
        <action android:name="com.breezehan.taskaffinity.reparent" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

安装两个应用,在FirstActivity中启动ReparentActivity,然后HOME,启动TaskAffinity应用,发现确实是显示的ReparentActivity。
FirstActivity->ReparentActivity时,ReparentActivity跟FirstActivity同一栈中。

Running activities (most recent first):
TaskRecord{4a3f11c #288 A=com.breezehan.knowledge U=0 StackId=1 sz=2}
Run #1: ActivityRecord{66421bf u0 com.breezehan.taskaffinity/.ReparentActivity t288}
Run #0: ActivityRecord{2765673 u0 com.breezehan.knowledge/.launchmode.FirstActivity t288}

mResumedActivity: ActivityRecord{66421bf u0 com.breezehan.taskaffinity/.ReparentActivity t288}

FirstActivity点击HOME键后,启动TaskAffinity,ReparentActivity移动到自己的栈中了。

Running activities (most recent first):
TaskRecord{30ce4f #289 A=com.breezehan.taskaffinity U=0 StackId=1 sz=2}
Run #1: ActivityRecord{66421bf u0 com.breezehan.taskaffinity/.ReparentActivity t289}
TaskRecord{4a3f11c #288 A=com.breezehan.knowledge U=0 StackId=1 sz=1}
Run #0: ActivityRecord{2765673 u0 com.breezehan.knowledge/.launchmode.FirstActivity t288}
mResumedActivity: ActivityRecord{66421bf u0 com.breezehan.taskaffinity/.ReparentActivity t289}

详细Demo请到我的githubAndroidKnowledgeSummary中查看,app和taskAffinity两个module来展现。

9、清理返回栈

下方为Android官方API指南的内容

如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。

您可以使用下列几个 Activity 属性修改此行为:

  • alwaysRetainTaskState
    如果在任务的根 Activity 中将此属性设置为 “true”,则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。
  • clearTaskOnLaunch
    如果在任务的根 Activity 中将此属性设置为 “true”,则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。
  • finishOnTaskLaunch
    此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 “true” 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值