Android进阶:Activity的生命周期和启动模式

news2024/10/6 4:06:19

Activity的生命周期和启动模式

作为Android四大组件之中存在感最强的组件,Activity应该是我们在学习Android中第一个碰到的新概念。在日常开发过程中我们肯定会用到Activity,但是关于Activity的一些细节问题运行机制我们可能还有一些不清楚的问题。今天我们就来详细说明Activity的生命周期和启动模式。

内容概要

在本篇文章中,将会介绍以下比较具体的内容:

  1. context对象是什么
  2. 什么是失去焦点和不可见
  3. Activity的生命周期
  4. 异常状态下该如何处理Activity的重新创建
  5. 什么是任务和任务栈
  6. 五种启动模式和任务亲和性
  7. 清除返回堆栈

Activity的生命周期

1. 什么是Activity的生命周期和上下文对象Context?

什么是Activity的生命周期?

简单来说,Activity的生命周期是指一个Activity从其从创建到销毁的过程中会处于不同的状态,每个状态也有其对应的回调方法,这些回调会让 Activity 知晓某个状态已经更改:系统正在创建、停止或恢复某个 Activity,或者正在销毁该 Activity 所在的进程。

上下文对象Context是什么?

这里为了帮助我们理解生命周期的意义,我觉得有必要简单介绍一下Context。

我们有没有考虑过一个问题,为什么我们在启动Activity时不能直接New 一个Activity使用,而是需要先创建一个Activity类然后用Intent配合startActivity启动呢?说到底这是因为Activity(包括Android中的许多其他组件)是一个系统级的组件,它不能单独出来运行,而是需要依赖于Android系统来运行和进行交互,因此我们在创建它的时候就需要根据当前的Android系统的环境来进行配置,具体来说包括你的手机屏幕大小等参数。

那该如何获取当前Android系统环境下的各种参数呢?这时候就需要用到Context类了,我们先看看Google官方的介绍:

Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

简单来说,Context是有关应用程序环境的全局信息的接口。它允许访问特定于应用程序的资源和类,以及对应用程序级操作(如启动活动、广播和接收意图等)的向上调用。说到底,startActivity这个方法也是在Context接口中定义的。许多系统级的操作基本都要用到Context对象。

我觉得这也是生命周期存在的意义:让Activity与Android更好地交互

2.在Activity的生命周期中跳转

什么是失去焦点和不可见?

我们经常会在文档中看到失去焦点和应用不可见等表述,其实简单来说(对于Activity来说),失去焦点就是指Activity不在前台和我们交互了但是我们仍可以看间这个Activity,比如说我们在切换应用时的窗口:在这里插入图片描述
这里我们仍可以看到我们的QQ音乐APP中的页面,这就代表它仍然可见,但是此时我们并没有和QQ音乐APP在进行交互,这就叫做QQ音乐的Activity失去了焦点。那不可见的意思就是我们完全看不到QQ音乐APP的Activity的页面了。

具体的七个回调方法

Activity在一般情况下会执行以下七个回调方法

  1. onCreate():表示Activity正在被创建,必须实现此回调,它会在系统首次创建 Activity 时触发。Activity 会在创建后进入“已创建”状态。

  2. onRestart():表示Activity正在重新启动。一般情况下,当当前Activity从不可见新变为可见状态时,onRestart方法就会被调用。

  3. onStart():表示Activity正在被启动,即将开始,此时Activity已经可见了,但是还没有出现在前台和我们进行交互。可以理解为Activity已经显示出来了但是我们还看不到。从官网的资料来说:当 Activity 进入“已开始”状态时,系统会调用此回调。onStart() 调用使 Activity 对用户可见,因为应用会为 Activity 进入前台并支持互动做准备。例如,应用通过此方法来初始化维护界面的代码。

  4. onResume():表示Activity已经可见了,并且出现在前台并开始活动。这是应用与用户互动的状态。应用会一直保持这种状态,直到某些事件发生,让焦点远离应用。此类事件包括接到来电、用户导航到另一个 Activity,或设备屏幕关闭。

  5. onPause():表示Activity正在停止,正常情况下,onStop回调将紧接着被执行。系统将此方法视为用户将要离开 Activity 的第一个标志(尽管这并不总是意味着 Activity 会被销毁);此方法表示 Activity 不再位于前台(尽管在用户处于多窗口模式时 Activity 仍然可见)。

    需要额外提醒的是:我们最好不要在onPause方法中执行太过耗时的操作,因为只有onPause执行完后,新Activity的onResume方法才会执行。如果我们要释放资源的话,最好还是在onDestroy中给执行。

  6. onStop():表示Activity即将停止,可以做一些稍微重量级级的回收工作。在 onStop() 方法中,应用应释放或调整在应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从精确位置更新切换到粗略位置更新。

  7. onDestroy():表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里我们可以做一些回收工作和最终资源的释放。

下图是我从Google官网翻译而来的流程图,我标注出了Activity仍然可见的部分:
在这里插入图片描述

这七个回调中明显有三个配对:

  • onCreate – onDestroy 从Activity创建与销毁的角度来说
  • onStart – onStop 从Activity的启动与停止的角度来说
  • onResume – onPause 从Activity是否获得了焦点的角度来说

3.特殊情况下生命周期的跳转和处理

除了用户正常交互状态下Activity的生命周期会发生变化以外,还有一些特殊情况也会导致Activity生命周期发生变化。这与我们在探讨Context中的内容有联系,由于Activity是依赖于Android环境的,所以当Android环境发生变化时Activity有时也需要发生变化。

内存不足,系统需要释放内存

这种情况就是优先级更高的Activity需要内存但是系统可用的内存不够,系统就会释放掉优先级低的Activity来清出空间。系统永远不会直接终止 Activity 以释放内存,而是会终止 Activity 所在的进程。系统不仅会销毁 Activity,还会销毁在该进程中运行的所有其他内容。 系统终止给定进程的可能性取决于当时进程的状态。反之,进程状态取决于在进程中运行的 Activity 的状态。

下面是官方给出的一张表,展示了进程状态、Activity 状态以及系统终止进程的可能性之间的关系:

系统终止进程的可能性进程状态Activity 状态
较小前台(拥有或即将获得焦点)已创建 或 已开始 或 已恢复
较大后台(失去焦点)已暂停
最大后台(不可见)已停止
最大已销毁

系统配置发生了变化

有很多事件会触发配置更改。最显著的例子或许是横屏和竖屏之间的屏幕方向变化。其他情况,如语言或输入设备的改变等,也可能导致配置更改。
当配置发生更改时,Activity 会被销毁并重新创建。原始 Activity 实例将触发 onPause()、onStop() 和 onDestroy() 回调。系统将创建新的 Activity 实例,并触发 onCreate()、onStart() 和 onResume() 回调。

简单来说就是,当发生一些事件,比如说屏幕方向变化时,Activity就会先销毁然后重新进行创建,生命周期如图所示:
在这里插入图片描述

这里有两个新出现的回调方法onSaveInstanceState 和 onRestoreInstabceState,前者是用来保存被销毁的Activity的状态的,后者是用来在新创建的Activity中恢复原来的Activity的状态的。

4.处理特殊情况下Activity的重新创建

面对由于系统配置发生变化而重新创建Activity,我们会丢失掉一部分UI界面的信息,这会极大影响用户体验,所以我们需要采取一定的手段来恢复UI的信息或者避免Activity被重新创建。这里我们提供五个方法:

  1. 使用ViewModel

    ViewModel是Google官方提供的一个类,这个类的设计思想遵循MVVM架构,就是视图和数据分离,Activity专注于显示视图,ViewModel专注于处理和保存业务。具体来说,ViewModel可以和Activity进行绑定,只要Activity没有被完全销毁,ViewModel就不会重新创建从而可以避免因为配置改变而导致UI数据丢失。

    具体的使用可以查看我的这篇博客➡ViewModel的使用

  2. 使用onSaveInstanceState方法配合onCreate和onRestoreSavedInstanceState两个方法

    我们在上面的那个异常状态下Activity的重建过程的流程图我们可以看到Activity在异常状态下被销毁前会执行onSaveInstanceState方法,这个方法就是用来存储一些数据的。那我们存储好的数据应该在哪里重新读取呢?

    一般的话是在onCreate方法或者onRestoreSavedInstanceState。这两个方法都会接受一个Bundle对象,这个Bundle对象里就是存储了之前被销毁前用onSaveInstanceState方法存储的数据。这里给出一个示例:

    public class MainActivity extends AppCompatActivity {
        
        ActivityMainBinding binding;
    
        Integer lastData = new Integer(0);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
            
            binding.tvData.setText(String.valueOf(lastData));
            binding.btPlus.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    lastData++;
                    binding.tvData.setText(String.valueOf(lastData));
                }
            });
    
            binding.btSubstract.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    lastData--;
                    binding.tvData.setText(String.valueOf(lastData));
                }
            });
            
        }
    
        @Override
        protected void onSaveInstanceState(@NonNull Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putInt("lastdata",lastData);
        }
    
        @Override
        protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            lastData = new Integer(savedInstanceState.getInt("lastdata"));
            binding.tvData.setText(String.valueOf(lastData));
        }
        
    }
    

    这里我们是在onRestoreInstanceState方法中恢复数据的做法,这也是官方比较推荐的做法。关于在onRestoreInstanceState恢复和onCreate中恢复的区别的话主要是有两个。第一是onRestoreInstanceState方法是在onCreate方法之后执行的;第二是onRestoreInstanceState方法只有在之前已经调用过onSaveInstanceState方法之后才会被执行,而onCreate方法无论如果都会被执行。在具体使用场景体现在onRestoreInstanceState方法中接收的Bundle对象是一定不为空的,而onCreate中的Bundle可能是为null,所以我们在onCreate方法中使用Bundle得先判断一下。

    这里我们在给出用onCreate方法恢复数据的示例:

    public class MainActivity extends AppCompatActivity {
    
    ActivityMainBinding binding;
    
    int lastData = 0;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState != null){
            lastData = savedInstanceState.getInt("lastdata",0);
        }
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
    
        binding.tvData.setText(String.valueOf(lastData));
        binding.btPlus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                lastData++;
                binding.tvData.setText(String.valueOf(lastData));
            }
        });
    
        binding.btSubstract.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                lastData--;
                binding.tvData.setText(String.valueOf(lastData));
            }
        });
    
    }
    
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("lastdata",lastData);
     }
    
    
    }
    
  3. 数据持久化

    数据持久化的做法和前面的viewModel思想类似,如果轻量级的数据我们可以用SharedPreference保存,若是复杂的数据我们可以使用Room数据库,Room数据库的用法:
    👇
    Room数据库的使用这里我们就不详细介绍了。

  4. 指定configChanges参数

    上面三种做法的思想都是保存上一次被销毁的Activity中的数据,而下面做法则是为了避免Activity被重新创建。可以为特定配置更改停用 activity 重新创建。为此,请将配置类型添加到 AndroidManifest.xml 的 < activity > 条目中的 android:configChanges。

    下面是一些参数的意义:

    说明
    density显示密度的更改,例如当用户指定不同的显示比例或其他屏幕当前处于活跃状态时。
    fontScale字体缩放比例的更改,例如当用户选择新的全局字体大小时。
    keyboardHidden键盘无障碍功能的更改,例如当用户显示硬件键盘时。
    locale语言区域的更改,例如当用户选择显示文本所用的新语言时。
    orientation屏幕方向的更改,例如用户旋转设备时。
    screenLayout屏幕布局的更改,例如在其他屏幕变为活动状态时。
    screenSize当前可用屏幕尺寸的更改。
    uiMode界面模式的更改,例如当用户将设备放到桌面或车载基座上时,或者夜间模式发生变化时。

    如果需要更多信息可以访问☞官网

    这里需要额外提一嘴的就是屏幕方向的改变orientation属性:

    注意:如果应用面向 Android 3.2(API 级别 13)或更高版本的系统,则还应声明 “screenLayout” 和 “screenSize” 配置,因为当设备在纵向模式与横向模式之间切换时,屏幕布局和屏幕大小可能会发生变化。

    当我们指定了configChanges里的值之后,发生相应的配置变化后将不会导致Activity被重新创建,取而代之的是将会执行onConfigurationChanged的回调方法。onConfigurationChanged() 回调方法会收到一个 Configuration 对象,其中指定了新的设备配置。读取 Configuration 对象中的字段以确定合适的新配置。

    注意:需要说明的是虽然官网上说不会重新创建Activity,但是经过我测试UI界面还是会重新加载(就是说由竖屏变横屏时UI界面仍然会重新加载),但是Activity确实没有被重新创建,也就是说UI上的数据确实没有丢失。

  5. 指定screenOrientation参数 (不推荐)

    我们最常遇到的配置更改应该就是屏幕方向的改变了,我们可以设置在manifest清单文件中指定Activity的screenOrientation或者调用setRequestedOrientation设置Activity的屏幕方向,一旦指定了这个参数,那么即使是屏幕方向改变了,Activity也完全不会被销毁。但是这种方法可能也会影响用户体验,所以一般不推荐使用。

Activity的启动模式

什么是任务和任务栈?

在介绍启动模式之前,我们有必要简要介绍一下任务和任务栈的概念。

在 Android 应用程序中,任务(Task)是指一组相关联的 Activity 组成的集合,这些Activity按照每个Activity 打开的顺序排列在一个返回堆栈中。

在这里插入图片描述

而任务栈(Task Stack)则是用来保存和管理这些任务的栈。

在 Android 系统中,每个任务栈都有一个相应的状态,例如前台或后台,也就是指当前任务栈所在的进程是否处于用户界面上的最前端,也就是被称为前台。Android 系统只会将一个任务栈置于前台,而其他任务栈则会被置于后台,因此在任意时刻只能有一个任务栈处于前台状态。

当用户切换到另一个应用时,当前应用中的任务栈将被置于后台,而前台将切换到另一个应用程序。用户可以通过按下 Home 键、最近使用的应用程序按钮、强制停止当前应用程序或者调用其他应用程序等方式,来切换前台任务栈。

这里也贴出官方给出的任务的定义:

任务是一个整体单元,当用户开始一个新任务或通过主屏幕按钮进入主屏幕时,任务可移至“后台”。在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈会保持不变,当其他任务启动时,当前任务只是失去了焦点,如图 2 所示。这样一来,任务就可以返回到“前台”,以便用户可以从他们离开的地方继续操作。举例来说,假设当前任务(任务 A)的堆栈中有 3 个 Activity,当前 Activity 下有 2 个 Activity。用户按主屏幕按钮,然后从应用启动器中启动新应用。主屏幕出现后,任务 A 转到后台。当新应用启动时,系统会启动该应用的任务(任务 B),该任务具有自己的 Activity 堆栈。与该应用互动后,用户再次返回到主屏幕并选择最初启动任务 A 的应用。现在,任务 A 进入前台,其堆栈中的所有三个 Activity 都完好如初,堆栈顶部的 Activity 恢复运行。此时,用户仍可通过以下方式切换到任务 B:转到主屏幕并选择启动该任务的应用图标(或者从最近使用的应用屏幕中选择该应用的任务)。这就是在 Android 上进行多任务处理的一个例子。

提到的图二:
图二

五种启动模式(实际上是5+1)

我们可以在清单文件中声明启动模式,也可以在Intent中指定标志位来声明启动模式。清单文件中我们可以设置launchMode属性,可以指定四种不同的启动模式:

  1. standard(默认模式):标准模式。默认的启动模式。每次启动 Activity 都会创建一个新的实例,并放入当前任务栈的栈顶。

  2. singleTop(栈顶复用模式):如果新的 Activity 已经位于栈顶,那么不会创建新的实例,而是直接使用现有的实例。如果新的 Activity 不在栈顶,那么会创建新的实例并加入到栈顶。

  3. singleTask(栈内复用模式):如果新的 Activity 已经存在于该Activity需要的任务栈中,那么将把位于它之上的所有 Activity 弹出栈顶,使其成为栈顶 Activity,并且不会创建新的实例。如果新的 Activity 不在其需要的任务栈中,那么将创建新的实例并加入任务栈的栈顶。 需要注意的是,这个模式和taskAffinity(任务亲和性)属性密切相关,如果没有指定taskAffinity,那将不会启动新的Task

  4. singleInstance(单实例模式):在单独的任务栈中启动 Activity,并且这个任务栈中只会有这个 Activity 的实例。如果已经存在一个实例,那么会直接使用它,而不是创建新的实例。
    在这里插入图片描述

  5. singleInstancePerTask:这个启动模式在官网的中文文档中没有提到,但是实际上是在Android12中新增的。

    在使用singleInstancePerTask模式启动一个Activity时,系统会检查当前任务栈中是否已经存在该Activity的实例,如果已经存在,那么该Activity实例所在的任务栈将被移动到前台并恢复,而不是创建新的Activity实例。如果该Activity实例不在当前任务栈中,则会创建新的任务栈,并在该任务栈中创建该Activity实例。

    需要注意的是,singleInstancePerTask模式的Activity只能作为任务的根Activity存在,即该Activity是任务的第一个启动的Activity。这是与singleInstance模式不同的地方,singleInstance模式的Activity可以被添加到任意任务栈中。但是与singleTask模式类似,singleInstancePerTask模式的Activity也可以通过FLAG_ACTIVITY_NEW_DOCUMENT和FLAG_ACTIVITY_MULTIPLE_TASK标志位在不同的任务栈中创建多个实例。

    如果你使用singleInstancePerTask模式来启动一个Activity,并且该Activity已经存在于同一任务栈中的另一个实例,则会调用该Activity的onNewIntent方法而不是onCreate方法,因为该Activity已经存在于该任务栈中。

而在用startActivity进行启动时,我们可以通过指定Intent的标志位来指定,可以设置三种模式:

  1. FLAG_ACTIVITY_NEW_TASK

    在新任务中启动 Activity。如果您现在启动的 Activity 已经有任务在运行,则系统会将该任务转到前台并恢复其最后的状态,而 Activity 将在 onNewIntent() 中收到新的 intent。

    这与上一节中介绍的 “singleTask” launchMode 值产生的行为相同。

  2. FLAG_ACTIVITY_SINGLE_TOP

    如果要启动的 Activity 是当前 Activity(即位于返回堆栈顶部的 Activity),则现有实例会收到对 onNewIntent() 的调用,而不会创建 Activity 的新实例。
    这与上一节中介绍的 “singleTop” launchMode 值产生的行为相同。

  3. FLAG_ACTIVITY_CLEAR_TOP

    如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其他 Activity,并通过 onNewIntent() 将此 intent 传送给它的已恢复实例(现在位于堆栈顶部)。
    launchMode 属性没有可产生此行为的值。但是需要说明的是 singleTask模式默认就有clearTop的效果

    FLAG_ACTIVITY_CLEAR_TOP 最常与 FLAG_ACTIVITY_NEW_TASK 结合使用。将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将其置于能够响应 intent 的位置。如果指定 Activity 的启动模式为 “standard”,系统也会将其从堆栈中移除,并在它的位置启动一个新实例来处理传入的 intent。这是因为当启动模式为 “standard” 时,始终会为新 intent 创建新的实例。

任务亲和性(TaskAffinity)

上面的SingleTask启动模式提到了Activity需要的任务栈,那什么是Activity需要的任务栈呢?实际上这和任务亲和性有关,“亲和性”表示 Activity 倾向于属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务。但是我们也可以指定任务亲和性:

	  <activity
            android:name=".SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="my.task"
            android:exported="false" />

需要额外说明的是,我们自定义的taskAffinity的名称也有一定的命名规范,taskAffinity字符串的值至少需要包含一个小数点,也就是说,最简单的形式:“xx.xx”。而且该值必须不同于manifest元素中声明的默认软件包名称。

亲和性会在两种情况下发挥作用:

  • 当启动 Activity 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 标记时。
    默认情况下,新 Activity 会启动到调用 startActivity() 的 Activity 的任务中。它会被推送到调用方 Activity 所在的返回堆栈中。但是,如果传递给 startActivity() 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 标记,则系统会寻找其他任务来容纳新 Activity。通常会是一个新任务,但也可能不是。如果已存在与新 Activity 具有相同亲和性的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务。关于额外的信息,官网有说到:

    如果此标记导致 Activity 启动一个新任务,而用户按下主屏幕按钮离开该任务,则必须为用户提供某种方式来返回到该任务。有些实体(例如通知管理器)总是在外部任务中启动 Activity,而不在它们自己的任务中启动,因此它们总是将 FLAG_ACTIVITY_NEW_TASK 添加到传递给 startActivity() 的 intent 中。如果您的 Activity 可由外部实体调用,而该实体可能使用此标记,请注意用户可以通过一种独立的方式返回到所启动的任务,例如使用启动器图标

  • 当TaskAffinity 和 allowTaskReparenting 配对使用时。
    当一个应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到B的任务栈中。

    举例来说,假设一款旅行应用中定义了一个报告特定城市天气状况的 Activity。该 Activity 与同一应用中的其他 Activity 具有相同的亲和性(默认应用亲和性),并通过此属性支持重新归属。当您的某个 Activity 启动该天气预报 Activity 时,该天气预报 Activity 最初会和您的 Activity 同属于一个任务。不过,当旅行应用的任务进入前台运行时,该天气预报 Activity 就会被重新分配给该任务并显示在其中。

    这样可能还是比较抽象,这里借由开发艺术探索里的例子:比如现在有两个应用A和B,A启动了B的一个ActivityC,然后按Home键回到桌面,然后再单击B的桌面图标,这个时候并不是启动了B的主Activity,而是重新显示了已经被应用A启动的ActivityC,或者说,C从A的任务栈转移到了B的任务栈中。可以这么理解,由于A启动了C,这个时候C只能运行在A的任务栈中,但是C属于B应用,正常情况下,它的TaskAffinity值肯定不可能和A的任务栈相同。所以,当B被启动后,B会创建自己的任务栈,这个时候系统发现C原本所想要的任务栈已经被创建了,所以就把C从A的任务栈中转移过来了,这也是该属性名为“重新归属性”的原因吧。

清除返回堆栈

如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。

当然我们也可以通过修改一些属性来修改:

  • alwaysRetainTaskState
    如果在任务的根 Activity 中将该属性设为 “true”,则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity。
  • clearTaskOnLaunch
    如果在任务的根 Activity 中将该属性设为 “true”,那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState 正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。
  • finishOnTaskLaunch
    该属性与 clearTaskOnLaunch 类似,但它只会作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根 Activity。如果将该属性设为 “true”,则 Activity 仅在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/503076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

调用百度文心AI作画API实现中文-图像跨模态生成

作者介绍 乔冠华&#xff0c;女&#xff0c;西安工程大学电子信息学院&#xff0c;2020级硕士研究生&#xff0c;张宏伟人工智能课题组。 研究方向&#xff1a;机器视觉与人工智能。 电子邮件&#xff1a;1078914066qq.com 一&#xff0e;文心AI作画API介绍 1. 文心AI作画 文…

全开源ChatGPT聊天机器人商业版源码/支持魔改/完全开放源代码

&#x1f388; 限时活动领体验会员&#xff1a;可下载程序网创项目短视频素材 &#x1f388; ☑️ 品牌&#xff1a;ChatGPT ☑️ 语言&#xff1a;PHP ☑️ 类型&#xff1a;ChatGPT ☑️ 支持&#xff1a;PCWAP &#x1f389; 有需要的朋友记得关赞评&#xff0c;需要的底部获…

C++哈希

目录 一、认识哈希表 1.unordered_set和unordered_map 2.哈希表的概念 二、闭散列哈希表的实现 1.底层本质 &#xff08;1&#xff09;哈希表的存储结构 &#xff08;2&#xff09;元素的插入与查找 &#xff08;3&#xff09;哈希冲突 &#xff08;4&#xff09;负载…

深入浅出C++ ——线程库

文章目录 线程库thread类的简单介绍线程函数参数原子性操作库 mutex的种类std::mutexstd::recursive_mutexstd::timed_mutexstd::recursive_timed_mutex lock_guard与unique_locklock_guardunique_lock condition_variable 线程库 thread类的简单介绍 在C11之前&#xff0c;涉…

“广东省五一劳动奖章”获得者卫晓欣:“她”力量让新兴技术更获认可

近日&#xff0c;2023年广东省庆祝“五一”国际劳动节暨五一劳动奖表彰大会顺利召开&#xff0c;大会表彰了2023年全国和省五一劳动奖、工人先锋号代表。 其中&#xff0c;来自FISCO BCOS开源社区产业应用合作伙伴广电运通的创新中心总监卫晓欣&#xff0c;凭借在区块链领域的…

分布式锁Redisson对于(不可重入、不可重试、超时释放、主从一致性)四个问题的应对

文章目录 1 Redisson介绍2 Redisson快速入门3 Redisson可重入锁原理4 Redisson锁重试和WatchDog机制5 Redisson锁的MutiLock原理 基于setnx实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中&#xff…

Ai作图可控性演进——从SD到MJ

背景 Ai作图从Diffusion模型开始&#xff0c;作图进入稳步发展快车道。然后用过diffusion系列作图的同学对产图稳定性&#xff0c;以及可控性都会颇有微词。diffusion系列作图方法在宏观层面上确实能够比较好的做出看上去还不错的图。然后当你细抠细节时候&#xff0c;发现这东…

远程服务器搭建jupyter lab并在本地访问

1、安装jupyter pip install jupyter 可以直接在base环境下安装 2、配置jupyter 2.1 密钥生成 进入python交互模式&#xff0c;输入以下代码&#xff1a; from jupyter_server.auth import passwd passwd()然后输入密码&#xff0c;得到一串密钥&#xff0c;保存一下 2.2…

Java多线程入门到精通学习大全?了解几种线程池的基本原理、代码示例!(第五篇:线程池的学习)

本文介绍了Java中三种常用的线程池&#xff1a;FixedThreadPool、CachedThreadPool和ScheduledThreadPool&#xff0c;分别介绍了它们的原理、代码示例以及使用注意事项。FixedThreadPool适用于并发量固定的场景&#xff0c;CachedThreadPool适用于执行时间短的任务&#xff0c…

Linux C/C++后台开发面试重点知识

Linux C/C后台开发面试重点知识 文章转载自个人博客: Linux C/C后台开发面试重点知识 查看目录 一、C 面试重点 本篇主要是关于 C 语言本身&#xff0c;如果是整个后台技术栈的学习路线&#xff0c;可以看这篇文章: Linux C 后台开发学习路线 对于 C 后台开发面试来说&…

27岁转行学云计算值得吗?能就业不?

27岁转行学云计算值得吗&#xff1f;能就业不&#xff1f; 首先&#xff0c;云计算当然值得转行了&#xff0c;如此肯定的观点&#xff0c;应该没有人会反对吧&#xff0c;尤其是对IT行业的现状以及就业市场有所了解的人。如果你对这一点有所怀疑也很正常&#xff0c;只要通过各…

Spring Boot集成ShardingSphere分片利器 AutoTable (一)—— 简单体验 | Spring Cloud 45

一、背景 Sharding是 Apache ShardingSphere 的核心特性&#xff0c;也是 ShardingSphere 最被人们熟知的一项能力。在过去&#xff0c;用户若需要进行分库分表&#xff0c;一种典型的实施流程&#xff08;不含数据迁移&#xff09;如下&#xff1a; 用户需要准确的理解每一张…

详解快速排序的类型和优化

详解快速排序的优化 前言快排的多种写法霍尔法实现快排代码部分 挖坑法思路讲解代码部分 双指针法思路讲解代码部分 针对排序数类型的优化针对接近或已经有序数列和逆序数列三数取中代码实现 随机数 针对数字中重复度较高的数三路划分思路讲解代码部分 根据递归的特点进行优化插…

JSP招投标管理系统myeclipse开发mysql数据库WEB结构java编程

一、源码特点 JSP 招投标管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 JSP招投标管理系统myeclipse开发mysql数据库W 二、功能介绍 主要功能&#xff1a; …

BPMN2.0 任务-接收任务手动任务

接收任务 接收任务是一个简单的任务,它等待特定消息的到来。 当流程执行到接收任务时,流程状态将提交给持久性存储。流程将保持这种等待状态,直到流程引擎接收到特定的消息,这将触发接收任务之外流程的继续进行。 接收任务用左上角有一个消息图标的标准BPMN 2.0任务(圆角…

C++新特性总结

&#xff08;智能指针&#xff0c;一些关键字&#xff0c;自动类型推导auto&#xff0c;右值引用移动语义完美转发&#xff0c;列表初始化&#xff0c;std::function & std::bind & lambda表达式使回调更方便&#xff0c;c11关于并发引入了好多好东西&#xff0c;有&am…

vivado工程转换到quartus下联合modelsim仿真

vivado用习惯了&#xff0c;现在快速换到quartus下仿真测试。写一个操作文档&#xff0c;以fpga实现pcm编码为例。 目录 一、建立工程 1、准备源码和仿真文件 2、新建工程 3、加载源文件 4、选择器件 5、仿真器配置 6、工程信息 二、配置工程 7、设置顶层文件 8、配置…

【多线程】初识线程,基础了解

目录 认识线程 概念 什么是线程&#xff1f; 为啥要有线程 进程和线程的区别 Java 的线程 和 操作系统线程 的关系 创建线程 1.继承 Thread 类 2.实现 Runnable 接口 3.通过匿名内部类方式创建Thread与实现Runnable 4.Lmabda表达式 Thread 类及常见方法 Thread 的常见构造方法…

点评项目最后一篇:基于HyperLogLog实现UV统计

文章目录 1 HyperLogLog2 测试百万数据的统计 1 HyperLogLog 首先我们搞懂两个概念&#xff1a; UV&#xff1a;全称Unique Visitor&#xff0c;也叫独立访客量&#xff0c;是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站&#xff0c;只记录1次。…

无线键盘有几种连接方式?(USB接收器连接(无线2.4g)、蓝牙连接、wi-fi连接、红外线连接)

文章目录 无线键盘有哪几种连接方式&#xff1f;各连接方式优缺点 无线键盘有哪几种连接方式&#xff1f; 无线键盘有以下几种连接方式&#xff1a; 通过USB接收器连接&#xff08;无线2.4g&#xff09;&#xff1a;无线键盘通过USB接收器与电脑连接&#xff0c;一般需要插入电…