号外、号外,以后本博客的相关代码,都会在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官方平台架构介绍
- 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 > 内部元素:
- < 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生命周期
先上一个官图,来点儿说服力。
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,会出现下图情况。
点击屏幕任意区域,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、清理返回栈
如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
您可以使用下列几个 Activity 属性修改此行为:
- alwaysRetainTaskState
如果在任务的根 Activity 中将此属性设置为 “true”,则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。 - clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 “true”,则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。 - finishOnTaskLaunch
此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 “true” 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。