一手遮天 Android - Activity: Activity 堆栈
一手遮天 Android - Activity: Activity 堆栈
示例如下:
/activity/ActivityDemo5.java
/**
* Activity 堆栈
* 系统通过堆栈来管理多个 activity,第一个进栈的叫做栈底,最后一个进栈的叫做栈顶,后进先出,这个堆栈叫做 task,通过 activity 对象的 getTaskId() 方法可以获取当前 activity 所属的 task 的标识
* 在 AndroidManifest.xml 中设置 activity 的 taskAffinity 属性(格式是 xxx.xxx.xxx 的形式),用于将同 taskAffinity 的 activity 分组到一个 task 中
* 在系统的最近程序列表中显示的不是 app 记录,而是 task 记录,若要 taskAffinity 生效,还需要设置 Intent.FLAG_ACTIVITY_NEW_TASK 或者 allowTaskReparenting,说明如下:
* 1、通过 startActivity() 启动 activity 的时候指定 Intent.FLAG_ACTIVITY_NEW_TASK 可以使 taskAffinity 生效
* 需要启动的 activity 的 taskAffinity 和当前的 activity 所属的 taskAffinity 不一样,则会新创建一个 task 来启动这个 activity
* 需要启动的 activity 的 taskAffinity 和当前的 activity 所属的 taskAffinity 一样,则不会启动这个 activity
* 注:如果没有 Intent.FLAG_ACTIVITY_NEW_TASK,则 taskAffinity 属性无效,新启动的 activity 会压入当前的 activity 所属的 task
* 2、通过在 AndroidManifest.xml 中设置 activity 节点的 allowTaskReparenting 为 true 可以使 taskAffinity 生效
* 允许重定义 activity 的父级,跨 app 场景使用
* 当 activity 被一个外部应用启动时,此 activity 就会被放入启动它的 task1 中
* 如果之后此 activity 的原来本身应用的同 taskAffinity 的 task2 启动了它,则此 activity 会从 task1 中移出,并压入到 task2 的栈顶
* 通过 startActivity() 启动 activity 的时候,如果是复用堆栈中已有 activity 的话,则会调用该 activity 的 onNewIntent() 方法
*
*
* 通过 startActivity() 启动 activity 的时候可以指定 intent 的 flags,挑几个常用的说明如下
* 1、Intent.FLAG_ACTIVITY_NO_HISTORY - 不在堆栈记录中保存,当 back 到此 activity 时会自动调用其 onDestroy() 方法
* 2、Intent.FLAG_ACTIVITY_CLEAR_TOP - 堆栈里如果有此 activity,则会将离栈顶最近的此 activity 和其上的全部 activity 移出堆栈,然后新建此 activity 并压入堆栈
* 3、Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP - 堆栈里如果有此 activity,则会将离栈顶最近的此 activity 之上的全部 activity 移出堆栈,然后调用此 activity 的 onNewIntent() 方法
* 4、Intent.FLAG_ACTIVITY_NEW_TASK - 结合 taskAffinity 使用,参见上面的说明
*
*
* 有一个使用场景,就是 task 进入后台(比如按了 home 键),然后再回到前台(比如点击最近列表里的项,或点击 app 图标)
* 对于此种场景可以通过在 AndroidManifest.xml 中设置 activity 节点的一些属性来控制 task 堆栈的一些行为,说明如下
* 1、alwaysRetainTaskState - 如果在后台时间过长,再回来时,系统可能会将除了栈底之外的所有 activity 全部清除。而如果将栈底的 activity 的此属性标记为 true 的话,则可以避免这个情况的发生
* 2、clearTaskOnLaunch - 如果栈底 activity 被标记为 true,则返回前台后,堆栈中除了栈底外的其他 activity 都将会被销毁(会调用这些 activity 的 onDestroy())
* 注:我这里测试,此属性只有通过点击 app 图标返回前台时有效,而通过点击最近列表里的项返回前台时无效
* 3、finishOnTaskLaunch - 返回前台后,标记为 true 的 activity 将会被销毁(会调用这些 activity 的 onDestroy())
* 注:我这里测试,此属性只有通过点击 app 图标返回前台时有效,而通过点击最近列表里的项返回前台时无效
*
*
* 在 AndroidManifest.xml 中设置 activity 节点的 launchMode 属性(standard, singleTop, singleTask, singleInstance),分别说明如下:
* 1、standard - 默认值
* 比如当前堆栈为 A1 -> B1,然后通过 startActivity() 打开 B,则堆栈结果为 A1 -> B1 -> B2
* 2、singleTop - 如果通过 startActivity() 打开的 activity 在堆栈的栈顶的话,则复用此栈顶实例,会调用其 onNewIntent() 方法
* 比如当前堆栈为 A1 -> B1,然后通过 startActivity() 打开 B,则堆栈结果为 A1 -> B1(会调用 B1 的 onNewIntent() 方法)
* 3、singleTask - 如果通过 startActivity() 打开的 activity 在堆栈中存在的话,则将此 activity 上方的 activity 全部出栈,然后复用此 activity 实例,会调用其 onNewIntent() 方法
* 比如当前堆栈为 A1 -> B1 -> C1,然后通过 startActivity() 打开 B,则堆栈结果为 A1 -> B1(会调用 B1 的 onNewIntent() 方法)
* 注:其效果与 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 的效果是一样的
* 4、singleInstance - 如果通过 startActivity() 打开的 activity 在堆栈中不存在的话,则新建一个堆栈保存此 activity,并且此堆栈永远只有这一个 activity
* 比如 task1 启动这个 activity 的话,则创建这个 activity 并将其归属于一个新建的 task2,然后再启动其他 activity 的话,会将其他 activity 压入 task1 堆栈,然后再启动这个 activity 的话就会调用 task2 堆栈中的这个 activity 对象的 onNewIntent() 方法
*
*
* 注:如果需要比较灵活的控制堆栈中的 activity 的话,建议自己再额外维护一个 activity 集合,通过调用 activity 的 finish() 来控制它的出栈
*/
package com.webabcd.androiddemo.activity;
import android.content.Intent;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.webabcd.androiddemo.R;
import com.webabcd.androiddemo.utils.Helper;
import java.util.Date;
public class ActivityDemo5 extends AppCompatActivity {
private TextView mTextView1;
private Button mButton1;
private Button mButton2;
private Button mButton3;
private Button mButton4;
private Button mButton5;
private Button mButton6;
private Button mButton7;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activity_activitydemo5);
mTextView1 = findViewById(R.id.textView1);
mButton1 = findViewById(R.id.button1);
mButton2 = findViewById(R.id.button2);
mButton3 = findViewById(R.id.button3);
mButton4 = findViewById(R.id.button4);
mButton5 = findViewById(R.id.button5);
mButton6 = findViewById(R.id.button6);
mButton7 = findViewById(R.id.button7);
sample();
}
// 当本 activity 被通过 startActivity() 打开时,而且是复用的堆栈里已有的对象,则会执行此方法(此种场景是不会执行 onCreate() 的)
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mTextView1.append("\n");
mTextView1.append("onNewIntent");
}
private void sample() {
mTextView1.setText(Helper.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
mTextView1.append("\n");
mTextView1.append("taskId: " + this.getTaskId()); // 获取当前 activity 对象所在的 task(堆栈)的 id
// 将另一个 activity 压入堆栈
// 假设当前堆栈为 A -> B,然后将 C 压入堆栈,然后将 A 压入堆栈
// 则堆栈结果为 A1 -> B1 -> C1 -> A2
mButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5.this, ActivityDemo5_2.class);
startActivity(intent);
}
});
// 将当前 activity 移出堆栈
// 假设当前堆栈为 A -> B -> C,然后将 C 移出堆栈
// 则堆栈结果为 A -> B
mButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
// 将另一个 activity 压入堆栈,并将当前 activity 移出堆栈
// 假设当前堆栈为 A -> B,然后将 C 压入堆栈,再将 B 移出堆栈
// 则堆栈结果为 A -> C
mButton3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ActivityDemo5.this, ActivityDemo5_2.class));
finish();
}
});
// 将另一个 activity 压入堆栈(Intent.FLAG_ACTIVITY_NO_HISTORY 方式)
// 假设当前堆栈为 A,然后将 B(Intent.FLAG_ACTIVITY_NO_HISTORY 方式)压入堆栈,然后将 C 默认方式压入堆栈,然后关闭 C
// 则关闭 C 后会自动调用 B 的 onDestroy() 方法,堆栈结果为 A
mButton4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5.this, ActivityDemo5_2.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(intent);
}
});
// 将另一个 activity 压入堆栈(Intent.FLAG_ACTIVITY_CLEAR_TOP 方式)
// 假设当前堆栈为 A1 -> B1 -> C1 -> A2 -> B2 -> C2 -> A3,然后将 B(Intent.FLAG_ACTIVITY_CLEAR_TOP 方式)压入堆栈
// 则堆栈结果为 A1 -> B1 -> C1 -> A2 -> B3(也就是说堆栈里如果有 B,则会将离栈顶最近的 B 和其上的全部 activity 移出堆栈,然后将新 B 压入堆栈)
mButton5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5.this, ActivityDemo5_2.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
});
// 将另一个 activity 压入堆栈(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 方式)
// 假设当前堆栈为 A1 -> B1 -> C1 -> A2 -> B2 -> C2 -> A3,然后将 B(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 方式)压入堆栈
// 则堆栈结果为 A1 -> B1 -> C1 -> A2 -> B2(也就是说堆栈里如果有 B,则会将离栈顶最近的 B 之上的全部 activity 移出堆栈,然后将这个 B 作为栈顶显示,同时调用这个 B 的 onNewIntent() 方法)
mButton6.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5.this, ActivityDemo5_2.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
});
// 将另一个 activity 压入堆栈(Intent.FLAG_ACTIVITY_NEW_TASK 方式)
// 假设在 AndroidManifest.xml 中设置了 activity A 的 taskAffinity 为 com.webabcd.androiddemo.task1,activity B 的 taskAffinity 为 com.webabcd.androiddemo.task2,activity C 未指定 taskAffinity
// 假设当前 task 为 A1 -> B1 -> C1 -> A2,然后打开 B(Intent.FLAG_ACTIVITY_NEW_TASK 方式),然后再默认方式打开 C,再默认方式打开 A
// 则会出现两个 task(即两个堆栈):task1 为 A1 -> B1 -> C1 -> A2;task2 为 B2 -> C2 -> A3(注:此时如果想通过 Intent.FLAG_ACTIVITY_NEW_TASK 方式打开 B 的话,是不会有反应的)
// 此时,当前 app 在系统的最近程序列表中会出现 2 个项,分别是 task1 和 task2,可以自行切换。如果进入后台后,再通过点击 app 图标返回前台,则打开的是 task1
//
// 注:两个 activity 切换时,如果发生了 task 的改变,那么中间会闪一下白屏或黑屏,要改善这个问题的话,可以在 application 级别指定如下主题
// <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
// <item name="android:windowDisablePreview">true</item>
// </style>
mButton7.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5.this, ActivityDemo5_2.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
}
}
\activity\ActivityDemo5_2.java
/**
* Activity 堆栈(请参见 activity/ActivityDemo5 中的说明)
*/
package com.webabcd.androiddemo.activity;
import android.content.Intent;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.webabcd.androiddemo.R;
import com.webabcd.androiddemo.utils.Helper;
import java.util.Date;
public class ActivityDemo5_2 extends AppCompatActivity {
private TextView mTextView1;
private Button mButton1;
private Button mButton2;
private Button mButton3;
private Button mButton4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activity_activitydemo5_2);
mTextView1 = findViewById(R.id.textView1);
mButton1 = findViewById(R.id.button1);
mButton2 = findViewById(R.id.button2);
mButton3 = findViewById(R.id.button3);
mButton4 = findViewById(R.id.button4);
sample();
}
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mTextView1.append("\n");
mTextView1.append("onNewIntent");
}
private void sample() {
mTextView1.setText(Helper.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
mTextView1.append("\n");
mTextView1.append("taskId: " + this.getTaskId()); // 获取当前 activity 对象所在的 task(堆栈)的 id
mButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5_2.this, ActivityDemo5_3.class);
startActivity(intent);
}
});
mButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mButton3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ActivityDemo5_2.this, ActivityDemo5_3.class));
finish();
}
});
mButton4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5_2.this, ActivityDemo5_3.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(intent);
}
});
}
}
\activity\ActivityDemo5_3.java
/**
* Activity 堆栈(请参见 activity/ActivityDemo5 中的说明)
*/
package com.webabcd.androiddemo.activity;
import android.content.Intent;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.webabcd.androiddemo.R;
import com.webabcd.androiddemo.utils.Helper;
import java.util.Date;
public class ActivityDemo5_3 extends AppCompatActivity {
private TextView mTextView1;
private Button mButton1;
private Button mButton2;
private Button mButton3;
private Button mButton4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activity_activitydemo5_3);
mTextView1 = findViewById(R.id.textView1);
mButton1 = findViewById(R.id.button1);
mButton2 = findViewById(R.id.button2);
mButton3 = findViewById(R.id.button3);
mButton4 = findViewById(R.id.button4);
sample();
}
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mTextView1.append("\n");
mTextView1.append("onNewIntent");
}
private void sample() {
mTextView1.setText(Helper.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
mTextView1.append("\n");
mTextView1.append("taskId: " + this.getTaskId()); // 获取当前 activity 对象所在的 task(堆栈)的 id
mButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5_3.this, ActivityDemo5.class);
startActivity(intent);
}
});
mButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mButton3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ActivityDemo5_3.this, ActivityDemo5.class));
finish();
}
});
mButton4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityDemo5_3.this, ActivityDemo5.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(intent);
}
});
}
}
/layout/activity_activity_activitydemo5.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/red">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"/>
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity"/>
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="关闭此 activity"/>
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,并关闭此 activity"/>
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,通过 Intent.FLAG_ACTIVITY_NO_HISTORY 的方式"/>
<Button
android:id="@+id/button5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,通过 Intent.FLAG_ACTIVITY_CLEAR_TOP 的方式"/>
<Button
android:id="@+id/button6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,通过 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 的方式"/>
<Button
android:id="@+id/button7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,通过 Intent.FLAG_ACTIVITY_NEW_TASK 的方式"/>
</LinearLayout>
/layout/activity_activity_activitydemo5_2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/green">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"/>
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity"/>
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="关闭此 activity"/>
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,并关闭此 activity"/>
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,通过 Intent.FLAG_ACTIVITY_NO_HISTORY 的方式"/>
</LinearLayout>
/layout/activity_activity_activitydemo5_3.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/blue">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"/>
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity"/>
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="关闭此 activity"/>
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,并关闭此 activity"/>
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:textAllCaps="false"
android:text="跳转到另一个 activity,通过 Intent.FLAG_ACTIVITY_NO_HISTORY 的方式"/>
</LinearLayout>