Android——Activity学习(上)

作者: 英勇青铜5 | 来源:发表于2016-10-12 18:56 被阅读727次

    学习资料:

    • 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鲁班算法:)

    本人很菜,有错误请指出

    共勉 :)

    相关文章

      网友评论

      • 4305f6116899:你是不是singTask和singleInstance搞混了,不造你写错了还是搞混了,在4.1.1注意那一块 最后一行是singleInstance模式不是singleTask 还有在用标志位启动activity时FLAG_ACTIVITY_NEW_TASK是singleInstance模式
        英勇青铜5:@林肯 我又看了下,感觉我没有理解错啊 。这些是我在android 开发艺术探索上看的,刚又在网上搜了下,感觉没问题啊。FLAG_ACTIVITY_NEW_TASK就是singleTask模式吧
      • dfe147f4a102:写得很全
      • 贼噶人:弹出Dialog和Popwindow Activity不会onPause吧
        英勇青铜5:@贼噶人 走吧,记得是不走onStop()
      • 老西子:面试要是能答的这么详细就好了
        英勇青铜5:@一直在路上向前走 面试很少问这么详细吧
      • Anderson大码渣:写得很全
        英勇青铜5:@anderson大码渣 :smile::smile:
      • a841b6c2d7ca:,谢谢
        英勇青铜5:@十年布局只为娶你 :smile: :smile: :smile:
      • Hero_here:很详细,👍
        英勇青铜5:@Hero_here :smile::smile::smile:
      • 巴图鲁:不错
        英勇青铜5:@巴图鲁 感觉这位同学走错片场了:smile:

      本文标题:Android——Activity学习(上)

      本文链接:https://www.haomeiwen.com/subject/bgiiyttx.html