Android——Activity学习(上)

学习资料:

  • Android群英传
  • Android开发艺术探索

Activity是与用户交互的第一接口,感觉说是四大组件之首也不为过。基本上除了Window,Dialog,Toast外,屏幕能见到的界面也就是Activity。在onCreate()方法中,通过setContentView()给一个Activity指定一个显示的界面,并以此做为基础提供用于交互的接口。系统是利用Activity栈来管理Activity

本篇为,主要学习Activity的生命周期,启动模式,任务栈,学习Activity工作过程


1. Activity的形态 <p>

Activity的一大特点就是具有多种形态,一般就是在在4种形态间进行切换,以此来进行控制生命周期

  • Active/Running
    Activity此时位于Activity的栈顶,能够与用户交互,是可见的

  • Paused
    Activity_1失去焦点,当一个新的非全屏的Acticty_2或者透明的Activity_3放置在栈顶,并不能完全遮盖住Activity_1,此时为Paused。这时Activity_1失去了与用户交互的能力,但所有的状态信息、成员变量都还保持着,只有在系统内存极低的情况下,才会被系统回收掉

  • Stoped
    Activity_1Activity_2完全覆盖或者用户点击HOME健切换到了桌面,Activity_1便进入Stoped形态。此时,虽然Acticity_1不可见,但却依然保持了所有的状态信息和成员变量

  • Killed
    Activity被系统回收l,这时就处于Killed形态

用户的不同动作,会让Actiivty在这四种形态间切换。而开发者,虽然可以控制Activity如何“生”,却无法控制Activity何时“死”


2. 生命周期 <p>

正常情况下,一般认为Activity有7个生命周期方法。Activity正常时,生命周期比较容易理解,可一旦出现异常,生命周期就会开始微妙起来,尤其如果此时还搭配Fragment,那就。。。


2.1 正常情况下的生命周期 <p>

由于同时看的两本书进行的学习,《Android开发艺术探索》《Android群英传》两本书对知识的描述方式还是有些不同的。学习过程中,就根据书的描述方式把知识点也梳理成两个部分,开发艺术探索知识梳理群英传知识梳理,不过涉及的知识点的本质是一样的,两种描述方式都学习也可以加深些印象 :)


2.1.1开发艺术探索知识梳理 <p>

《Android开发艺术探索》是从经典的生命周期图出发

经典生命周期图
  • onCreate()
    生命周期的第一个方法,Activity正在创建。在这个方法中一般会做一些初始化工作

  • onStart()
    标识Activity正在启动,此时Activity还没有出现在前台,无法和用户进行交互。可以理解为,Activity虽然已经显示了,但没有获得焦点,我们看不到

  • onResume()
    到了这个方法,Activity已经可见了,并且出现在前台已经开始活动。可以理解为此时Activity获得焦点

  • onPause()
    表示Activity_1正在停止,正常情况下,紧接着onStop()就会被调用。此时可以做一些存储数据,关闭动画等工作。但不能太耗时。由Activity_1跳转Activity_2时,只有Activity_1onPause()方法执行完毕,Activity_2onResume()方法才会执行。如果太耗时,会影响Activity_2的显示

  • onStop()
    表示Activity即将停止。同样可以做一些不耗时的稍微轻量级回收工作

  • onRestart()
    表示Activity_1正在重启。一般情况下,Activity_1由不可见重新变为可见时,onRestart()便会被调用

  • onDestroy()
    表示Activity即将被销毁。Activity生命周期的最后一个回调方法,可以做一些回收工作和最终的资源释放


注意

  1. 当由Activity_1跳转Activity_2时,如果Activity_2是透明的,Activity_1只会走到onPause()方法,不会回调onStop()方法。弹出Dialog或者PopupWindow也是,只要Activity_1没有被完全遮盖看不到,就不会回调执行到onStop()生命周期方法

  2. 从整个生命周期来说,onCreate()onDestroy()是配对的;从Activity是否可见来说,onStart()onStop()是配对的;从Activity是否在前台获得焦点来说,onResume()onPause()是配对的

  3. onDestroy()方法回调了,并不意味着Activity已经被完全销毁被系统回收了。只是即将被销毁,还是会在内存中存留一会,而这个即将到底需要多久,可能并不是每次都一样,这也是为啥说开发者,虽然可以控制Activity如何“生”,却无法控制Activity何时“死”的原因


问题

  • onStart()和onResume(),onPause()和onStop()有啥实质不同?
    主要是从不同的回调时机角度来看。onStart()onStop()是从是否可见的角度回调,而onResume()onPause()从是否在前台获得焦点的角度回调的。除了这两种区别,实际并没有其他的明显区别

  • 由当前Activity_A跳转到Activity_B时,AonPause()BonResume()哪个先进行回调执行?
    直接用代码测试,代码很简单,不再给出,Log信息

    A.onPause和B.onResume执行顺序

    结论:A的onPause()先只执行,B的onResume()后执行


2.1.2 群英传知识梳理 <p>

生命周期状态图

在上图中,有6个生命周期状态,和形态有区别,但只有Resumed,Paused,Stopped3个状态是稳定的,另外3个是过度状态,很快就过去了

  • Rusumed
    这个状态对应Active/Ruuning形态,此时的Activity位于栈顶

  • Paused
    Activity有一部分被挡住,就会进入这个状态,这个状态下的Activity不会接收用户的输入

  • Stopped
    Activity完全被覆盖时,会进入此状态,此时Activity不可见,仅在后台运行

  • Destroyed
    当回调了onDestroy()方法时,Activity也就进入了Destroyed状态,对应于Killed形态


2.2 异常情况下的生命周期 <p>

主要考虑两种情况:

  1. 资源相关的系统配置发生改变导致Activity被杀死并重新创建
  2. 资源内存不足导致低优先级的Activity被杀死

2.2.1 资源相关的系统配置发生改变导致Activity被杀死并重新创建 <p>

这种情况,有个典型的情景便是,横竖屏切换

在默认情况下,Activity不做任何特殊处理,当系统配置发生改变后,Activity就会被销毁并重新创建

异常情况下Acticity重建

当系统配置改变后,Activity在异常情况下被销毁,onPause(),onStop(),onDestroy()便会被依次调用,由于是异常情况,在onStop()方法回调之前,会先回调onSaveInstanceState()方法来保存Acticity的状态

onSaveInstanceState()方法只有在Activity异常终止的情况下在onStop()前会被回调。但这个方法和onPause()没有特定的关系,可能在onPause()之前,也可能在其后。在onSaveInstanceState()方法中,Activity的信息保存在Bundle

Acticity重建时,系统在onStart()方法后会先调用onRestoreInstanceState()方法,并把onSaveInstanceState()保存信息的Bundle对象作为参数同时传递给onRestoreInstanceState()onCreate()方法。

onRestoreInstanceState()配合onCreate()方法,可以用来判断Activity是否被重建。如果重建了,就可以取出Activity销毁前保存的数据,然后恢复


onSaveInstanceState()onRestoreInstanceState()方法中,系统会自动做一定量的保存和恢复工作。当Activity在异常情况下需要重建时,系统会默认保存当前Activity的视图结构,并且在Activity重启后恢复这些数据,例如文本框用户输入的数据,ListView滚动的位置等,这种类似的View的状态系统都能默认保存和恢复。不同的Vie,能保存和恢复的数据不同,具体要看特定的ViewonSaveInstanceState()onRestoreInstanceState()方法对哪些数据做了操作

关于保存和恢复View层次结构,系统的工作流程:

1.在Activity在被意外终止时,Activity调用onSaveInstance去保存数据

  1. 数据信息保存之后Acticity会委托Window去保存数据
  2. Winodw接到委托后,会再委托它上面的顶层容器去保存数据。顶层容器是一个ViewGroup,一般很可能就是DecorView
  3. 顶层容器收到委托后,再一一去通知子元素保存数据

这是典型的委托思想


Actvitiy重建的时候,onSaveInstanceState()中存储的特定View的状态信息或者自己存储的信息,可以在onCreate()或者onRestoreInstanceState()两个方法选择其一。官方的建议是使用onRestoreInstanceState()

两者的区别:

  • onRestoreInstanceState()一旦被回调,其参数Bundle saveInstanceState一定有值,不需要再去判null
  • onCreate()当为正常启动时,其参数Bundle saveInstanceStatenull;异常情况下,重建时不为null,需要增加额外的判断

2.2.2 资源内存不足导致低优先级的Activity被杀死 <p>

这种情况下的数据存储和恢复过程与上面的情况完全一致。

Activity优先级可以分为三种情况:

  1. 前台Activity:正在与用户进行交互,优先级最高
  2. 可见但非前台Activity:典型场景就是弹出一个对话框,Activity虽然可见但失去了焦点,但此时位于后台无法和用户进行交互
  3. 后台Activity:已经被暂停的Activity,回调了onStop()方法,完全不能在屏幕看到,优先级最低

系统出现内存不足时,就会根据上面的优先级去杀死目标Activity所在的进程,并使用onSaveInstanceState()存储数据,使用onRestoreInstanceState()恢复数据。

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

上面提到的方法只能起到不会轻易的被杀死,但最终还是会被杀死。系统进程保活,貌似是一个伪命题,没有办法能够做到绝对的保活,尤其到了7.0之后,具体的分析可以在D_clock爱吃葱花同学关于 Android 进程保活,你所需要知道的一切学习查看


2.2.3 横竖屏切换的影响 <p>

代码很简单不再贴出来,就是重写MainActivity的7个生命周期方法和onSaveInstanceState()以及onRestoreInstanceState()方法,加入了Log

默认情况下:

  • 竖屏切换横屏时,Activity回调的生命周期方法?

    竖屏切换横屏时

  • 横屏切换竖屏时,Activity回调的生命周期方法?

    横屏切换为竖屏

网上有人说,横屏切为竖屏时,生命周期方法会走两遍,感觉说的不对。我测试了多次,并把测试的app卸载重装,还是走了一遍

默认情况下,横竖屏切换测试打印的Log信息是一样的


在上面默认的情况下,横竖屏进行切换会重建Activity,如果不想重建,可以在AndroidManifest.xml文件中给Activity指定configChangs属性

代码:

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

同时给MainActivity加了两个confingChangesorientation|screenSize,原因下面的表格给出

除了重写MainActivity的生命周期方法和onSaveInstanceState()以及onRestoreInstanceState()方法外,再重写一个onConfigurationChanged()方法

代码:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    Log.e("Activity","&&&&---Activity_A--->onConfigurationChanged");
}

运行后,横竖屏进行切换,都只会打印下面一条Log信息:

加入configChanges属性后

加入了configChanges后,Actiivty没有被重建,也没有回调onSaveInstanceState()以及onRestoreInstanceState()方法,只是回调了onConfigurationChanged()方法


configChnanges常用属性值含义

属性 含义
orientation 屏幕方向发生改变,旋转了手机屏幕
sreenSize 当屏幕的尺寸信息发生了改变。旋转手机屏幕时,屏幕尺寸会发生改变,API13添加,在13后想要Activity不重建,需要加入这个属性,否则还是会重建
locale 设备的本地位置发生改变时,一般指切换了手机系统语言
keyboardHidden 键盘的可访问性发生改变
uiMode 用户界面模式发生改变,例如切换夜晚模式

经我测试,感觉网上有的说法有问题,orientation|keyboardHidden组合并不能起到防止Activity重建;单独使用orientation时,无论是横屏还是竖屏切换,都没有调用onConfigurationChanged()方法,和默认情况下,回调方法是一样的。


3. Activity任务栈 <p>

栈:后进先出(Last In Fisrt Out)的的线性表

一个Adnroid应用通常会包含有多个Activity,各个Acticity通过Intent进行连接。Android系统通过栈结构来保存整个一个应用的Activity,栈底的元素是整个任务的栈的发起者

当一个APP启动后,如果当前环境中不存在该APP的任务栈,系统就会创建一个任务栈,这个栈也称为Task,表示若干个Activity的集合。这个Task就会对整个APP中启动的Activity及进行控制管理。

任务栈涉及到一个TaskAffinity参数,翻译为任务相关性。这个参数标识了一个任务栈的名字。默认情况下,所有Acticity需要的任务栈的名字为应用的包名也可以单独为每一个Activity指定TaskAffinity属性,但这个属性值不能和包名相同。TaskAffinity主要和singlTask启动模式或者allowTaskReparenting属性使用,在其他情况下,并没有意义

注意:一个Task中的Activity可以来自不同APP,同一个APP的Activity也可以不在同一个Task中

任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity处于暂停状态,用户通过切换将后台任务栈再次调到前台

前台任务栈和后台任务栈的实际场景没有查到,哪位同学知道,请告诉一声

根据不同ActivityTask中不同位置,来决定该Activity状态

在正常的和谐情况下,一个Actvity_A中启动了Activity_BB就会置于Task的顶端,并处于活动状态。A依然保留Task中,处于停止状态。当用户按下返回键或者调用了B.onDestroy()方法,B就从栈顶移除,A重新位于栈顶,恢复活动状态

但这种和谐终究会被特权破坏掉,这种特权便是给Activity设置启动模式


4. 启动模式 <p>

Android中有4中启动模式,但有两种设置启动模式的方式,AndroidManifest.xml文件指定和Itent Flag两种方式


4.1 在AndroidManifest中的设置启动模式 <p>

通过使用android:launchMode标签可以给Actiivity来设置启动模式

四种模式:

  • standard:标准模式,默认
  • singeTop:栈顶复用模式
  • singleTask:栈内复用模式
  • singleInstance:单实例模式

4.1.1 standard标准模式 <p>

系统的默认模式,每次启动一个Activity都会重新创建一个新的Activity对象实例,无论这个Activity是否存在。被创建的Activity的生命周期符合典型情况下的Activity生命周期。

在一个任务栈中,可以有很多个实例,每个实例也可以属于不同的任务栈。

在这种模式下,谁启动了Activity_B,那么B就运行在启动的它的那个Activity所在的栈中。

standard标准模式

A启动BB启动C


MainActivity中,有一个Button,点击按钮后,启动自身

standard模式,启动自身

这时,无论MainAcivity是否已经在任务栈存在,都会启动几次创建几次


注意:
当使用ApplicationContext去启动一个standard模式的Activity时,会报错:

E/AndroidRuntime: FATAL EXCEPTION: main 
Process: com.szlk.customview, PID: 8800
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

standard模式下的Activity默认会进入到启动它的Activity栈中,但非Activity类型的Context并没有任务栈

解决的方式就是为Activity指定FLAG_ACTIVITY_NEW_TASK标记位,启动时就会创建一个新的任务栈,但这就是singleTask模式,而不再是standard模式


4.1.2 singleTop 栈顶复用模式 <p>

Activity_A中启动指定为singleTop模式的Activity_B,在启动时,系统会先检查任务栈当前栈顶的Activity是不是B,如果不是B无论栈内是否已经有了B实例,就创建一个新的B出来;如果是,就引用这个已经存在的B。这就是栈顶复用。

这种模式通常用于接收到消息后显示的界面。例如QQ接到了10条xia消息,总不能一次就弹出10个Activity


singleTop模式虽然不会创建已经存在于栈顶的Activity_B,但会回调BonNewIntent()方法,通过此方法可以拿到当前请求的信息。但此时BonCreate()onStart()不会再回调

singleTop栈顶复用模式

例子:
A,B,C,D四个singleTop模式的ActivityA在栈底,D位于栈顶

如果再次启动D后,栈内仍然有4个ActivityABCD
如果再次启动A后,栈内有会5个AcitvityABCDA


4.1.3 singleTask栈内复用模式 <p>

是一种单实例模式,想要启动一个singleTask模式的Activity_D时,系统会先检测D所需要的任务栈是否存在:

  • 若栈不存在,就创建一个任务栈并把D压入栈中

  • 若栈存在,看D的实例是否存在。只要D在一个栈中存在,即使是多次启用D,也不会重新创建实例,直接将D置于栈顶,系统只是回调onNewIntent()方法,并且在D所在的栈中,如果D上面有其他的Activity也会被销毁,这里是指的同一个APP启动这个D


例子:

  1. 目前任务栈T1中的情况为ABC,启动singleTask模式的Activity_DD所需的任务栈为T2,系统便会先创建T2,再创建D并将其压入T2栈中
  2. 目前任务栈T1中的情况为ABC,启动singleTask模式的Activity_DD所需的任务栈为T1,系统直接将D压入T1
  3. 目前任务栈T1中的情况为ADBC,再次启动singleTask模式的Activity_D,此时D不会再被重建,系统直接把D切换到栈顶,并回调DonNewIntent()方法,由于singleTask默认具有clearTop的功能,最终T1栈内便是AD

注意:
如果要启动的singleTask模式的D已经存在于一个后台任务栈中了,那么D所在的整个后台任务栈,都是被切换到前台

singleTask任务栈,启动D的回退过程

上面是启动D,下面看启动C

启动C的回退过程

退出整个应用的一种实现思路:

使用这个模式可以用来退出整个应用:将MainActivity设置为singleTask模式,在想要退出应用的某个Activity_某中启动MainActivity,从而将任务栈中MainActivity之上Activity都给清除,然后重写MainActivityonNewIntent()方法,加入finish()


4.1.4 singleInstance 单实例模式 <p>

这是一种加强的singleTask模式,具有singleTask的特性外,使用这个模式的Activity只能单独位于一个任务栈内。例如,singleInstance模式的Activity_A启动后,系统为A创建一个新的任务栈,随后A单独存在于这个任务栈,由于栈内复用性,后续的请求,都不会再会创建新的Activity,除非这个独特的任务栈被系统销毁

singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题


注意:

在一个singleTask或者singleInstanceActivity_A中通过startActivityForResult()方法启动Activity_B,系统直接回返回给A一个Activity_RESULT_CANCELED而不会再等待B返回结果。

这是因为系统在FarmeWork层对两种启动模式做了限制,设计者认为不同的Task间,默认不能传递数据。如果要传,只能通过Intent来绑定数据


4.1.5 四种启动模式常用的场景 <p>

  • singleTop
    适合用来接收到通知之后,打开Activity用来显示内容
    例如,某个APP一次推送了10条新闻,不能每看一个新闻就打开一个

  • singleTask
    适合APPMainActivity使用
    可以用来在某个Activity一次就退出整个APP

  • singleInstance
    适合需要与APP分离开的页面
    例如闹铃提醒,将闹铃提醒与闹铃设置分离

关于四种模式可以看看Activity启动模式图文详解:standard, singleTop, singleTask以及singleInstance

还有一个TaskAffinityallowTaskReparenting搭配使用,以后再深入学习


4.2 Intent Flag 标志位启动模式<p>

上面的方式都是通过在AndroidManifest.xml使用android:launchMode=""来指定启动模式,也可以通过Intent来设置标志位来指定启动模式

private void startActivity(Class<? extends Activity> activity) {
    Intent intent = new Intent(MainActivity.this, activity);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

两种方式的区别:

  • 使用Flag标志位优先级比在AndroidManifest.xml清单文件指定高,两种都指定时,以Flag为准
  • 两种方式在限定范围有不同,清单文件没有办法设定FLAG_ACTIVITY_CLEAR_TOP标识,Flag标志位的方式无法指定singleInstance模式

常用的标记位:

  • FLAG_ACTIVITY_NEW_TASK
    Activity指定singleTask模式,和在清单文件中指定该启动模式效果一样

  • FLAG_ACTIVITY_SINGLE_TOP
    Activity指定singleTop模式

  • FLAG_ACTIVITY_CLEAR_TOP
    使用此标记位Activity_A的启动时,在同一个任务栈中所有位于A上面的Activity都会被移出栈。这个标记位一般和singleTask一起出现
    如果启动目标Activity_A采用的是singleTask模式,若目标A存在,会回调它的onNewIntent()方法。如果启动目标A采用的是standard标准模式,那么A连同A之上的Activity都要出栈,系统会创建新的A实例并压入栈顶

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    使用这个标记的Activity不会出现在历史Activity列表中。等同于在清单文件中指定Activity的属性android:excludeFromRecents="true"

还有很多,以后用到慢慢学习


5. 清空任务栈 <p>

系统提供了清空任务栈的方法。一般在AndroidManifest.xml清单文件中的<activity>标签使用几种属性来清理任务栈

  • clearTaskOnLaunch
    每次返回使用这个属性的Activity_A都会将同一任务栈内A之上的其他Activity清除。通过这个属性,可以让这个Task每次初始化的时候,都只有一个Activity

  • finishOnTaskLaunch
    clearTaskOnLaunch是作用于别人身上,而finishOnTaskLaunch则作用于自己身上。当离开这个Activity所处的Task,用户再返回时,该Activity就会finish

  • android:alwaysRetainTaskState
    这个属性给Task一个免死金牌,将Activity_A这个属性设置为true后,A所在的Task将不接受任何清理命令,一直保持当前的Task状态

这几个属性,都没有用过。。。


6. 最后 <p>

Activity涉及到的知识点还有很多,以后需要慢慢再学习

Activity的学习计划分为上,下,本篇为打算学习Activity工作过程,看了一下书,全是the fucking source code ,所以打算下一篇博客先记录学习一个关于图片压缩的Luban鲁班算法:)

本人很菜,有错误请指出

共勉 :)

推荐阅读更多精彩内容