Android复习(Android基础-四大组件)—— Activity

  • Activity作为四大组件之首,是使用最为频繁的一种组件,中文直接翻译为"活动",不过如果被翻译为"界面"会更好理解。正常情况,除了Window,Dialog和Toast , 我们能见到的界面只有Activity。

1. 进程模式

  • APP进程的级别,由其活跃的处于栈顶的组件的活动状态(比如生命周期)决定。
  • Activity的进程模式分为:前台进程、可见进程、Service进程、后台进程、空白进程
  1. 前台进程适用于目前操作所需的进程。在不同的情况下,进程可能因为其所包含的各种应用组件而被视为前台进程。以下任一条件成立,就是前台进程。
  • 当前进程Activity正在与用户进行交互。(已调用onResume)
  • 当前Service正在执行回调的代码 生命周期(Service.onCreate() , Service.onStart(), Service.onDestory())
  • 进程有一个正在运行的BroadcastReceiver(BroadcastReceiver.onReceive()正在执行)
  1. 可见进程:前台进程是一个Dialog;进程有一个Service,这个Service正在和一个可见的Activity绑定。
  • Activity在屏幕上对用户可见,但是失去了焦点,调用了onPause,暂时无法操作。
  • Service正在通过Service.startForeground()。
  • 实现特定功能的系统服务:动态壁纸,输入法服务。
  1. 服务进程:服务进程包含了一个已使用startService()方法启动的Service的进程。

  2. 后台进程:当Activity的onStop被调用,但是onDestroy并没有被调用

  3. 空白进程:当系统需要内存的时候,会暂时将背景进程清除,就成为了空白进程。

  • 进程优先级:前台进程 > 可见进程 > service进程 > 后台进程 > 空进程
  • 系统内存不足时,会删除优先级低的进程。

2. Activity的生命周期

  1. onCreate:表示Activity正在被创建,这是生命周期的第一个方法。可以做一些初始化的工作(加载界面布局资源,初始化Activity所需数据)
  2. onRestart:表示Activity正在被重新启动。一般情况下,当当前Activity从不可见更新为可见状态的时候,onRestart就会被调用
  3. onStart:表示Activity正在被启动,即将开始。这时Activity已经可见,但是没有出现在前台,还无法和用户交互了。
  4. onResume:表示Activity已经可见,并且出现在前台并开始活动。要注意这个和onStart的对比。onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
  5. onPause:表示Activity正在暂停,正常情况下,紧跟着onStop就会被调用。在特殊情况下,如果快速再回到当前Activity,那么onResume会被调用。(这种属于特殊情况,一般不会发生)。在这可以做一些存储数据,停止动画等操作,但是注意不能太耗时,不然会影响到新Activity的显示,onPause执行完,新的Activity的onResume才会执行。
  6. onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太过耗时。
  7. onDestroy:表示Activity即将被销毁,这是生命周期中的最后一个回调。在这里,我们可以做一些回收工作和最终资源的释放。
    在这里插入图片描述

2.1 典型情况下的生命周期

  1. 打开新的Activity或者切换到桌面的时候,生命周期的回调?
    在这里插入图片描述
  2. onStart 和 onResume、onPause 和 onStop实质有什么不同?
    在这里插入图片描述
  3. 假设当前Activity为A,如果这时用户打开了一个新的ActivityB,那么B的onResume和A的onPause哪个先执行呢?
    在这里插入图片描述

2.2 异常情况下的生命周期

  • Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些异常情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity可能会被杀死。

2.2.1 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

  1. 首先要对系统的资源加载机制有一定的理解。把一张图片放在drawable目录后,可以通过Resources去获取这张图片。同时为了兼容不同的设备,我们可能还需要在其他一些目录放置不同的图片,比如drawable-mdpi、drawable-hdpi 等等。 这样,当应用程序启动的时候,系统就会根据设备去加载合适的Resources资源。比如说横屏手机和竖屏手机会**拿到两张不同的图片。
  2. 突然旋转屏幕,由于**系统配置发生了变化,在默认情况下,Activity就会被销毁并且重新创建,当然我们也可以组织重新创建我们的Activity
    在这里插入图片描述

2.2.2 情况2:资源内存不足导致优先级低的Activity被杀死

  • 这种情况不好模拟,但是其数据存储和恢复过程和情况1完全一致。这里我们简单描述一下Activity的优先级情况。
  • 前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程
  1. 前台Activity——正在和用户进行交互的Activity,优先级最高
  2. 可见但非前台Activity——比如Activity弹出了一个对话框,导致Activity可见,但是位于后台无法和用户直接交互。
  3. 后台Activity——已被暂停的Activity,比如执行了onStop,优先级最低。

如果一个进程中没有四大组件在执行,那么这个进程很快就会被系统杀死。因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证有一定的优先级,这样就不会轻易被杀死。

3. Activity的重建机制

  • 重建机制就是在onStop()后,调用onSaveInstanceState将Activity的某些状态保存下来。然后在重新创建onStart()之后调用onRestoreInstanceState()把这状态显示出来。
  1. onSaveInstanceState:这个方法将要保存的数据 以键值对形式 保存在Bundle对象中。并把保存下来的bundle对象作为参数同时传递给onRestoreInstanceState和onCreate,保存了当前Activity的视图结构。(API29之后一定会调用在onStop之后)
  2. onRestoreInstanceState:一旦被调用,Bundle参数一定有值,不用额外判空,但onCreate正常启动Bundle参数为null。

3.1 onSaveInstanceState & onRestoreInstanceState

  • 主要是 onSaveInstanceStateonRestoreInstanceState方法。
  • 系统为我们做了一定的恢复工作。当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity 的视图结构,并且在 Activity重启后为我们恢复这些数据。
  • 例如:文本框(TextView)中用户输入的数据、ListView滚动的位置等。这些View相关的状态系统都能够默认为我们恢复。
  • 针对某一特定的View系统能够为我们恢复哪些数据,我们可以查看View的源码。 和Activity一样,每个View都有onSaveInstanceStateonRestoreInstanceState。 看一下它们的具体实现,就可以知道系统自动为每个View恢复哪些数据。
  • 拿TextView来说,我们分析一下它保存了哪些数据。
@Override
public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();

    // Save state if we are forced to
    final boolean freezesText = getFreezesText();
    //文本框是否选中
    boolean hasSelection = false;
    int start = -1;
    int end = -1;
    //如果文本框不为空
    if (mText != null) {
        start = getSelectionStart();
        end = getSelectionEnd();
        if (start >= 0 || end >= 0) {
            // Or save state if there is a selection
            //表示选中
            hasSelection = true;
        }
    }

    if (freezesText || hasSelection) {
        SavedState ss = new SavedState(superState);

        if (freezesText) {
            if (mText instanceof Spanned) {
                final Spannable sp = new SpannableStringBuilder(mText);

                if (mEditor != null) {
                    removeMisspelledSpans(sp);
                    sp.removeSpan(mEditor.mSuggestionRangeSpan);
                }

                ss.text = sp;
            } else {
            //保存文本框上的数据
                ss.text = mText.toString();
            }
        }

        if (hasSelection) {
            // XXX Should also save the current scroll position!
            ss.selStart = start;
            ss.selEnd = end;
        }

        if (isFocused() && start >= 0 && end >= 0) {
            ss.frozenWithFocus = true;
        }

        ss.error = getError();

        if (mEditor != null) {
            ss.editorState = mEditor.saveInstanceState();
        }
        return ss;
    }

    return superState;
}

在这里插入图片描述

3.2 Activity的configChanges属性

  • 上面分析了系统的数据存储和恢复机制,我们知道,当系统配置发生改变的时候,Activity会被重新创建,有没有办法不重新创建呢?系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity。可以给Activity指定configChanges属性。
android:configChanges="orientation"
  • 如果我们想指定多个值,可以用 " |" 连接起来。
    在这里插入图片描述
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytext" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ActivityText" >
        <activity
            android:name=".MainActivity"
            //我们常用的只有locale,orientation,keyboardHidden这三个选项
            android:configChanges="orientation|screenSize"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

3.3 重建机制中保存和恢复数据

保存数据

  • Activity通过onSaveInstanceState()方法来保存状态,onSaveInstanceState()执行在onStop()后。
  • 然后Activity会委托Window去保存数据,接着Window再委托它上面的等级容器去保存数据。
  • 顶层容器是一个ViewGroup,一般来说可能是一个DecorView
  • 最后顶层容器再去通知它的子元素来保存数据,这样整个保存数据过程就完成了。
    Activity里面有两个数据结构专门保存状态
  1. View States保存View的状态
  2. Instance State存储View states 以及开发者在 onSaveInstanceState中手动保存的Activity成员变量。

恢复数据

  • 通过onRestoreInstanceState方法来恢复状态,在保存期间,会自动收集View Hieracht(视图层次)中每一个实现了状态保存和恢复方法的View状态,这些数据会在onRestoreInstanceState方法时回传给View,并且回传是根据view的id来逐一匹配。

注意事项

  1. 为了成功保存状态,要求在View内部实现保存和恢复的算法。(原生的View都有做到,自定义View需要自己来重写实现)
  2. 为了成功恢复状态,要给View赋对应的ID
  3. 如果需要保存Activity的成员变量,重写方法时,需要保留基类的实现。

4. Activity的启动模式

4.1 LaunchMode

在这里插入图片描述

  1. standard(标准模式)
    在这里插入图片描述
  2. singleTop(栈顶复用模式)
    在这里插入图片描述
    在这里插入图片描述
  3. singleTask(栈内复用模式)
    在这里插入图片描述

如果D所需的任务栈为S1,并且当前任务栈S1的情况为 ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈而并调用onNewIntent 方法,同时由于singleTask 默认具有clearTop的效果,会导致栈内所有在D上面的 Activity全部出栈,于是最终S1中的情况为 AD。这一点比较特殊,在后面还会对此情况详细分析。

  1. singleInstance(单实例模式)
    在这里插入图片描述
    在这里插入图片描述
  • 设置启动模式
//通过在Intent中设置标志位来为Activity指定启动模式
Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

在这里插入图片描述

4.2 复用调用的方法 onNewIntent()

  1. 当ActivityA的LaunchModeSingleTop,并且ActivityA在栈顶,现在启动ActivityA,会调用onNewIntent()方法 。
  • 生命周期顺序为:onResume—>onPause—>onNewIntent—>onResume
  1. 当ActivityA的LaunchModeSingleInstance,SingleTask
  • 如果之前栈内没有它,就会去创建一个它的实例,也就是会调用 onCreate()→onStart()→onResume()
  • 如果之前栈内有它的实例存在的话,生命周期的调用变成了onNewIntent()→onRestart()→onStart()→onResume 。
  • 只对以上的情况,再次启动它们的时候才会调用,即只对startActivity有效。如果仅仅从后台切换到前台而不再次启动的情形,不会触发onNewIntent。
  1. 当ActivityA的LaunchMode为SingleTop,并且ActivityA在栈顶,现在启动ActivityA,会调用onNewIntent()方法 。
  • 生命周期顺序为:onResume—>onPause—>onNewIntent—>onResume
  1. 当ActivityA的LaunchMode为SingleInstance,SingleTask。
    • 如果之前栈内没有它,就会去创建一个它的实例,也就是会调用 onCreate()→onStart()→onResume()
    • 如果之前栈内有它的实例存在的话,生命周期的调用变成了onNewIntent()→onRestart()→onStart()→onResume 。
  • 只对以上的情况,再次启动它们的时候才会调用,即只对startActivity有效。如果仅仅从后台切换到前台而不再次启动的情形,不会触发onNewIntent。
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    setIntent(intent);         //设置新的intent
    int data = getIntent().getIntExtra("tanksu", 0);//此时的到的数据就是正确的了
}

5. Activity的TaskAffinity

  • 我们在上文中,多次提到了某个Activity所需的任务栈,什么是Activity所需要的任务栈?这要从一个参数说起:TaskAffinity [afineting] ,可以翻译为任务相关性。这个参数标识了一个Activity所需要的任务栈的名字
  • 如果没有显示指明taskAffinity,那么它的taskAffinity就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名
    在这里插入图片描述
android:taskAffinty="com.ning.task1"
  • taskAffinity的作用就是指定想要的任务栈。
  1. 当启动模式设置为standard或singleTop时,taskAffinity是不起作用的。待启动的 Activity 会跟随源 Activity 的任务栈,即使你显式声明了不一样的taskAffinity。
  2. 当启动模式设置了 singleTask或者 singleInstance时,taskAffinity就会新建任务栈来存储待启动的 Activity 实例。

5.1 TaskAffinity和allowTaskReparenting结合

  • TaskAffinity属性主要是用于和allowTaskReparenting属性配对使用。
  • 另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再调到前台。
  • allowTaskReparenting允许Activity在任务栈之间进行迁移。
    具体点来说,就是一个Activity现在是处于某个Task当中的,但是它与另外一个Task具有相同的affinity值。当另外这个任务切换到前台的时候,该Activity就可以转移到切换到前台的这个任务当中。allowTaskReparenting默认是继承至application中的allowTaskReparenting=false,不可以。
  1. 当一个应用A启动应用B的某个Activity之后,如果这个Activity的allowTaskReparenting属性为true的话。那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。
  2. 具体一点说就是,比如有两个应用A和B,A启动了B的一个ActivityC,然后按Home 键回到桌面,然后再单击B的桌面图标,这个时候不是启动了B上的 主Activity,而是重新显示了已经被应用A启动的ActivityC。或者说,C从A的任务栈转移到了B的任务栈中。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

5.2 使用FLAG_ACTIVITY_NEW_TASK标记

  • 单独的FLAG_ACTIVITY_NEW_TASK并不等于singleTask,它仅表示寻找Activity所需的任务栈压入。(即TaskAffinity指定的任务栈)
  • FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP 也不等价于启动模式singleTask
  • 当调用startActivity()方法来启动一个Activity时,默认是将它放入到当前的Task(任务)当中。但是,如果在Intent中加入了FLAG_ACTIVITY_NEW_TASK 的话,情况就会变的复杂起来。首先,系统会去检查这个 Activity要求的affinity是否与当前任务栈相同。
  1. 如果相同,把Activity放到当前的Task当中。
  2. 如果不同,先去检查是否有一个名字与该Activity的Affinity相同的Task。
    1. 如果有,这个Task被调到前台,同时将这个Activity显示到这个Task的顶端。
    2. 如果没有,系统将会尝试为这个Activity创建一个新的Task。需要注意的是,如果一个Activity在manifest文件中声明的启动模式是”singleTask”,那么他被启动的时候,行为模式会和前面提到的指定FLAG_ACTIVITY_NEW_TASK一样。

5.3 任务栈和返回栈

  • 任务栈和返回栈是独立存在的。
  1. 任务栈:Activity想要的任务栈,taskAffinity的作用就是指定想要的任务栈,是APP层面的一个东西。
  • 任务栈一般有前台任务栈后台任务栈之分。比如我们手机中打开了飞书,按下Home键之后打开了抖音,此时抖音就在前台任务栈,飞书就在后台任务栈。
  1. 返回栈:它是Activity层面的,用户页面的返回依赖的是返回栈,而不是任务栈。 一个返回栈中可能会包含来自不同任务栈的Activity,返回栈就是为了维护正确的回退栈关系。
    在这里插入图片描述

6. Activity的FLAG

  • 标记位只能在Activity中设置
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  1. FLAG_ACTIVITY_NEW_TASK:这个标记位的作用是为Activity指定"singleTask"启动模式(近似于)。

    • 在不设置taskAffinity的情况下,单独设置FLAG_ACTIVITY_NEW_TASK并没有任何意义,不会创建新的任务栈,每次都会创建新的Activity实例,不会栈内复用。
    • 在一个新的任务栈中启动Activity。如果这个Activity想要的任务栈已经存在,并且其中已经运行着待启动的Activity,那么这个任务栈就会被带到前台,并回调onNewIntent。这个行为和singleTask一致。
  2. FLAG_ACTIVITY_SINGLE_TOP:这个标记位的作用是为Activity指定"singleTop"启动模式,其效果和在XML中指定该启动模式相同

  3. FLAG_ACTIVITY_CLEAR_TOP:具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个标记位一般会和singleTask启动模式一起出现。如果此时被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶

    • NEW_TASK+CLEAR_TOP != singleTask,因为在standard模式下启动,会把栈内本身的Activity也删除,所以不等于signleTask。
  4. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标识位的Activity不会出现在历史Activity的列表中,当某些情况下我们用户通过历史列表回到我们的Activity的时候这个标记比较有用。他等同于在XML中指定Activity属性:

android:excludeFromRecents="true"
  1. FLAG_ACTIVITY_NO_HOSTORY:被指定的Activity在跳转到其他Activity后,将它从任务栈中移除。

7. IntentFilter

  • 我们知道启动Activity,一般有显示调用和隐式调用两种。显示调用需要明确指定被启动对象的组件信息,包括包名和类名。而隐式调用则不需要。
  • 这里简单介绍一下隐式调用,隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息。
  1. 只有一个Intent同时匹配了action类别、category类别、data类别才算完全匹配,只有完全匹配才能启动目标Activity。
  2. 一个Activity可以有多个IntentFilter,一个Intent只要能匹配任何一组IntentFilter即可成功启动对应的Activity。
    在这里插入图片描述

8. Activity的数据传输

  1. 使用Intent的putExtra传递(传递数据大小有限制,1M左右,根据Binder)
  2. SharedPreference进行数据传递
  3. 使用静态变量传递数据
  4. 使用Bundle对象
  5. 使用Activity销毁时传递数据(onActivityResult)
  6. 使用序列化对象Seriazable

9. Activity的finish()

  • finish()用于结束Activity进程,关闭Activity但不关闭栈。
  • 调用finish()方法后,先pause()当前Activity,会通过AMS的ApplicationThread.scheduleDestroyActivity方法,向ActivtyThread(主线程)发送一个H.DESTROY_ACTIVITY消息
  • 主线程会调用handleDestroyActivity来处理这个消息
  • 在通过层层调用,回调Activity的onDestroy方法。
  • 业务场景
  • 比如在订单列表A点击新建订单进入新建订单ActivityB中,点击保存后,跳转到订单详细C中。在C中我们点击返回键要求返回到A中。

10. 相关问题

10.1 进程模式相关问题

  1. 何谓进程模式?
    在这里插入图片描述

10.2 生命周期相关问题

  1. onPause() 和 onStop()的区别?
    在这里插入图片描述
  2. A启动B,然后B退回A,两者的生命周期变化?
    在这里插入图片描述
  3. 为什么先Activity的onResume()执行,然后才是调用源Activity的onStop()方法呢?
    在这里插入图片描述
  4. MainActivity跳转到DialogActivity(盖不住MainActivity)再跳转到secondActivity的生命周期?
    在这里插入图片描述
  5. 从secondActivity返回到DialogActivity的生命周期?
    在这里插入图片描述
  6. 弹出Dialog对Activity的生命周期有什么影响?
    在这里插入图片描述

10.3 重建机制相关问题

  1. onSaveInstanceState方法调用的时机?
    在这里插入图片描述
  2. 什么时候会发生重建?
    在这里插入图片描述
  3. 为什么会有重建机制?
    在这里插入图片描述
  4. 优先级较低的Activity在内存不足的被回收后,怎样恢复到销毁前的状态?
    在这里插入图片描述

10.4 启动模式相关问题

  1. Activity的启动模式,A是standard,B是singleInstance, A启动B,又启动A会有几个对象?
    在这里插入图片描述
  2. 启动模式和FLAG的区别?
    在这里插入图片描述

10.5 Activity和application有什么区别?

  • Activity是UI界面的抽象,而application是应用程序的抽象。两者都是Context的子类。
  • 应用程序每次启动的时候,系统会为其创建一个application对象且只有一个(单例类),用来存储一些系统的信息,相当于一个容器。
  • 启动application时,系统会创建一个PID(进程ID),所有的Activity都在这个进程上运行,在application创建时会初始化一个全局变量,同一个应用的activity,都可以获取到这个变量。

10.6 onStop和onDestroy()回调延时及延时10S的问题?

为什么回调会延时

  • Activity调用流程:打开ActivityA -> 打开ActivityB -> 关闭ActivityB -> 回到ActivityA
  • 由于要关闭的Activity B或者要打开的Activity A 往主线程的MessageQueue中连续不断地post大量的msg。
  • 导致主线程一直在不断地进行消息循环处理 没有停歇。 因此APP不能向AMS发起IPC来进行ActivityB的销毁。
  • 所以finish ActivityB之后,onDestroy不会被及时回调。具体延时多久,要看主线程中堆积的msg什么时候被处理完。

延时10S

  • Android系统安排了一套流程来保证即使正常流程被阻断以后,ActivityB还是能被销毁。

  • 在关闭Activity B返回Activity A的时候,当AMS侧发起 IPC通知 APP侧的Activity A执行resume的时候,同时也会向AMS自己的主线程发送一个msg,该msg延时10s后执行。

  • 该msg的具体内容与 正常流程APP空闲时段需要执行的任务一致,当然也包括销毁B。

  • 这样Activity B的onDestroy方法也就在延时10s后调用执行。

  • 我们就该在写代码的时候,及时关闭、清理、移除不必要的主线程消息,并且尽可能的保证每个消息处理时间不要太长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值