一. 起因:
遇到一个bug, 从应用A用intent调起应用B的两个Activity, B1和B2的时候, 会遇到如下bug:
二. 复现步骤:
启动A, A调起B1, home, 启动A, A调起B2, 启动A, A调起B1.
这样bug出现, 第二次调起B1的时候, 出现的是B2的界面. 以后一直调起B1, 出现的都是B2的界面.
发现在后面每次调起B1的时候会有log:
Class not found when unmarshalling: com.XXX.XXX
java.lang.ClassNotFoundException: com.XXX.XXX
at java.lang.Class.classForName(Native Method)
......
03-14 15:01:43.108 1897 4129 E Parcel :... 19 more
03-14 15:01:43.108 1897 4129 E Parcel : Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
三. 解决办法:
我在A启动B的时候, 将
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
改为用:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
四. 原因
启动A单独作为一个task启动, 然后当A启动B1的时候, B1新建一个task, 包名命名com.B. 当A启动B2的时候, 需要放在另一个task中, 但是发现了此时有com.B (因为B1和B2是属于同一个app, 包名一样都是com.B, 会放在同一个task中). 因而在task com.B中又放了一个B2. 此时此task中有B1, B2. 栈顶是B2. 当A又试图发intent启动B1的时候, 由于是 FLAG_ACTIVITY_NEW_TASK格式的(类似singleTask), 因而只要B1的实例在com.B中存在, 就不会再实例化, 因而会将 com.B 这个task拿到前面, 但是此时这个task里的栈顶是B1. 因而会报错, 并且我们看到的界面是B2, 并且以后无论用同样方法启动B1多少次, 都看到的是B2.
改成加了Intent.FLAG_ACTIVITY_CLEAR_TOP后, A再次启动B1的时候, com.B这个task里会将在B1顶部的所有实例都销毁掉, 所以task里就只有B1了, 这时B1可以启动并且没有错误了, 看到的当前Activity也是B1.
五. 知识点
https://developer.android.com/guide/components/activities/tasks-and-back-stack.html
1. task:
A task is a collection of activities that users interact with when performing a certain job.
The device Home screen is the starting place for most tasks. When the user touches an icon in the application launcher (or a shortcut on the Home screen), that application's task comes to the foreground. If no task exists for the application (the application has not been used recently), then a new task is created and the "main" activity for that application opens as the root activity in the stack.
A task is a cohesive unit that can move to the "background" when users begin a new task or go to the Home screen, via the Home button.
通常, 用户从桌面的图标启动一个app, 就是一次task的开始.
2. launch mode:
Using the manifest file
When you declare an activity in your manifest file, you can specify how the activity should associate with tasks when it starts.
Using Intent flags
When you call startActivity(), you can include a flag in the Intent that declares how (or whether) the new activity should associate with the current task.
As such, if Activity A starts Activity B, Activity B can define in its manifest how it should associate with the current task (if at all) and Activity A can also request how Activity B should associate with current task. If both activities define how Activity B should associate with a task, then Activity A's request (as defined in the intent) is honored over Activity B's request (as defined in its manifest).
两种方法改变launch mode: 1 写在manifest里, 2 在intent中设置flag. 如果二者都设置了, 以intent中的flag为准.
3. manifest里的值:
standard(default): 创建新activity的实例放在当前task中, 并且一个activity可以实例化多次, 每个实例可能属于不同的task, 一个task里可能包含多个实例.
singleTop: 和standard一样, 特别的是: 如果当前task的栈顶是这个activity的实例, 那么就不新建实例了.
For example, suppose a task's back stack consists of root activity A with activities B, C, and D on top (the stack is A-B-C-D; D is on top). An intent arrives for an activity of type D. If D has the default"standard"launch mode, a new instance of the class is launched and the stack becomes A-B-C-D-D. However, if D's launch mode is"singleTop", the existing instance of D receives the intent throughonNewIntent(), because it's at the top of the stack—the stack remains A-B-C-D. However, if an intent arrives for an activity of type B, then a new instance of B is added to the stack, even if its launch mode is"singleTop".
singleTask: The system creates a new task and instantiates the activity at the root of the new task. 但是, 如果这个activity的实例已经在其他task中存在了, 那么不会创建新的实例, 用已有的实例. 就是, 同一时间只能存在一个实例.
singleInstance: 和singleTask一样, 但是这个activity is always the single and only member of its task; any activities started by this one open in a separate task. 这个task中只有着一个activity
4. flag的值:
FLAG_ACTIVITY_NEW_TASK: 在新的task里开始一个Activity的实例. 如果task已经存在, 那么这个task被 brought to the foreground. 类似singleTask.
FLAG_ACTIVITY_SINGLE_TOP: 类似singleTop
FLAG_ACTIVITY_CLEAR_TOP: If the activity being started is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it are destroyed and this intent is delivered to the resumed instance of the activity (now on top), through onNewIntent()).
FLAG_ACTIVITY_CLEAR_TOPis most often used in conjunction withFLAG_ACTIVITY_NEW_TASK. When used together, these flags are a way of locating an existing activity in another task and putting it in a position where it can respond to the intent.
总结:
系统提供了两种切换:
第一种是在AndroidManifest.xml文件中声明Activity自身的启动属性, 另一种是启动时给intent中添加不同的flag.
前者包括:
android:launchMode=standard/singleTop/singleTask/singleInstance
android:clearTaskOnLaunch=true/false
android:finishOnTaskLaunch=true/false
android:allowTaskReparent=true/false
后者包括:
Intent.FLAG_ACTIVITY_NEW_TASK
Intent.FLAG_ACTIVITY_RESER_TASK_IF_NEEDED
Intent.FLAG_ACTIVITY_CLEAR_TOP
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
Intent.FLAG_ACTIVITY_NO_HISTORY
Intent.FLAG_ACTIVITY_SINGLE_TOP
功能分为三类, 第一类是为了完成在Task之间切换, 第二类是完成在当前Task中改变Activity的顺序, 第三类是为了在Task切换时Task内部重排所属的Activity