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