zoukankan      html  css  js  c++  java
  • Activity的生命周期和启动模式

    Activity的生命周期和启动模式

    本博客是对 任玉刚先生的 Android开发艺术探索 的总结。仅供个人学习使用

    如需系统学习,请购买正版书籍。

    《Android开发艺术探索(博文视点出品)》(任玉刚)【摘要 书评 试读】- 京东图书 (jd.com)

    第一章 Activity的生命周期和启动模式

    前言

    除了Window, Dialog和Toast,我们能见到的界面还有 Avtivity。

    1.1 Activity的生命周期全面分析

    两部分

    • 典型情况下的生命周期
      • 有用户参与的情况下,Activity所经历的生命周期的改变
    • 异常情况下的生命周期
      • Activity被系统回收或者由于当前设备的Configuration发生改变从而导致Activity被销毁重建

    1.1.1 典型情况下的生命周期分析

    Activity的正常生命周期:

    img
    • onCreate方法:Activity正在被创建,用于初始化,譬如加载布局、初始化活动所需数据,在活动第一次创建时候被调用;

    • onResart方法:Activity正在重新启动,当前Activity从不可见重新变为可见时,onRestart就会被调用。

    • onStart方法:Activity正在被启动,尚未显示在前台们无法进行交互,由不可见变为可见时调用;

    • onResume方法:Activity已经可见了,准备好与用户进行交互时;

    • onPause方法:Activity正在停止,另一个活动来到前台,部分可见;若快速回到当前Activity,那么onResume就会被调用,可以做一些数据存储,停止动画等工作;

    • onstop方法:Activity即将停止,活动完全不可见,轻量级的资源回收;

    • onDestroy方法:Activity即将被销毁,活动被销毁之前调用,回收工作和资源释放;

    • onResart方法:Activity正在重新启动,当前Activity从不可见重新变为可见时,onRestart就会被调用。

    附加具体说明:

    ​ 1.针对一个特定的Activity,第一次启动,回调如下:onCreate —> onStart —> onResume

    ​ 2.当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause —> onStop

    ​ 3.当用户再次回到原来的Activity,回调如下:onRestart —> onStart —> onResume

    ​ 4.当用户按back键的时候回调如下:onPause —> onStop —> onDestroy

    ​ 5.从整个生命周期来说,onCreate和onDestroy是配套的,标示着Activity的创建和销毁,只可能有一次调用,从Activity是否可见来说,onStart和onStop是配套的,随着用户的操作和设备屏幕的点亮和熄灭,这两个方法可能被调用多次,从Activity是否在前台来说onResume和onPause是配套的,随着用户操作或者设备的点亮和熄灭,这两个方法可能被多次调用。

    ​ 6.问:onStart和onResume,onPause和onStop看起来的确差不多,有什么实质的不同?

    ​ 答:这两个配对的回调分别代表不同的意义,onStart和onStop是从Activity是否可见这个角度来回调的,onPause 和 onResume 是从 Activity是否位于前台的角度来回调的。除了这种区别,在实际的使用中,没有其他明显的区别。

    ​ 7.问:假设当前Activity为A,如果用户打开了一个新的Activity为B,那么B的onResume和A的onPause谁先执行?

    ​ 答:肯定是栈顶的Activity需要先onPause后,新的Activity才能启动。这是因为Instrumentation 通binder向ActivityManagerService(AMS)发请求,AMS内部维护着一个ActivityStack,并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。在ActivityStack中的resumeTopActivityLnnerLocked方法中告诉我们需要先Pause,在ApplicationThread的scheduleLaunchActivity方法中,告诉了我们新的Activity的创建过程。

    ​ onPause(A) onCreate(B) onstart(B) onResume(B) onStop(A)

    ​ 8.模拟调用顺序。

    ​ 我们知道onPause和onStop都不能做耗时的操作,尤其是onPause,这也意味着,我们应当尽量的在onStop中做操作,从而使新的Activity尽快显示出来并且换到前后台。(配合相关代码)

    1.1.2 异常情况下的生命周期分析

    ​ 当资源相关的系统配置发生改变以及系统内存不足的时候,Activity就有可能被杀死,下面分析这两种情况。
    (1)情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

    ​ 情形:当应用程序启动时,系统会根据当前设备的情况去加载合适的Resources资源,比如说横屏手机和竖屏手机会拿着两张不同的图片(设定了landscape或者portrait状态下的图片),比如之前Activity处于竖屏,我们突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity。

    ​ 回答:在onStop之前系统会调用onSaveInstanceState来保存当前Activity的状态(正常情况下是调用这个方法),和onPause没有既定的时序关系,Activity会被销毁,其onPause,onStop,onDestroy均会被调用,可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建。如果被重建了,我们就取出之前的数据恢复,从时序上来说,onRestoreInstanceState的调用时机应该在onStart之后。
    ​ 恢复的内容:系统会默认我们保存当前的Activity视图架构,并且为我们恢复这些数据,比如文本框中用户输入的数据,ListView滚动的位置,这些View相关的状态系统都会默认恢复。保存的流程:Activity委托Window,Window委托顶级容器、顶级容器是一个ViewGroup器,再去一一通知他的子元素来保存数据、上层委托下层,父容器委托子容器,去处理一件事件。

    ​ TextView源码:TextView源码的onSaveInstanceState和onRestoreInstanceState中保存了保存了TextView自己的文本选中和文本内容等。

    ​ 举例:验证数据存储和恢复的情况。onRestoreInstanceState一旦被调用,其参数Bundl的savedInstanceState一定有值,我们不用额外的判断是否为空但是onCreate不行,onCreate如果正常启动的话,其参数Bundler onSaveInstanceState为null,所以需要一些额外的判断,这两个方法我们选择任意一个都是可以进行数据恢复的,但是建议我们使用onRestoreInstanceState去恢复数据系统只在Activity异常终止的情况下才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发。

    public class MainActivity extends AppCompatActivity {
        private static String TAG = "MainActivity";
        private EditText editText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            editText = (EditText) findViewById(R.id.edt_test);
            if(savedInstanceState!=null){
                String test = savedInstanceState.getString("extra_test");
                Log.d(TAG, "[OnCreate]Restore onCreate: "+test);
            }
        }
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            Log.d(TAG, "onSaveInstanceState: ");
            outState.putString("extra_test","testing");
        }
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            String test = savedInstanceState.getString("extra_test");
            Log.d(TAG, "[onRestoreInstanceState]Restore extra_test "+test);
        }
    }
    

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

    ​ 存储和恢复与情形一一致,Activity按照优先级的从高往低,可以分为三种:

    ​ 1. 前台Activity : 正在和用户交互的Activity,优先级最高

    ​ 2. 可见但非前台Activity : 比如对话框,导致Activity可见但是位于后台无法和用户直接交互

    ​ 3. 后台Activity : 已经被暂停的Activity,比如执行了onStop,优先级最低

    ​ 当系统内存不足时,系统会按照优先级去杀死Activity所在的进程。如果一个进程中没有在四大组件在执行,那么这个进程将很快被系统杀死。

    如何保活?

    ​ 将后台工作放在Service中从而保证了进程有一定的优先级,这样就不会轻易的被杀死。

    系统配置发生改变后,Activity会被重新创建,那我们有没有什么办法不重新创建呢?

    ​ 系统配置中有很多内容,如果当某项内容发生改变后,我们不想屏幕旋转时候系统重新创建,就可以给activity的configChangs属性加上orientation这个值。其他属性还有:

    android:configChanges="orientation|screenSize"
    

    如果我们没有在 Activity的configChanges属性中指定该选项的话,当配置发生改变后就会导致Activity重新创建。只需要在 AndroidMenifest.xml中加入Activity声明即可。

    常用的只有

    • local 切换系统语言
    • orientation 方向
    • keyboardHidden 切除键盘和隐藏键盘

    1.2 Activity的启动模式

    形形色色的启动模式标志位实在是太容易被混淆了。

    1.2.1 Activity的LaunchMode

    ​ 通过在AndroidMAnifest.xml中标签中指定android:launchMode来确定启动方式

    四种启动模式:

    • standard:
      标准模式,每次启动一个Activity都会重新创建一个实例。采用栈的方式进行存储。

    • singleTop:
      栈顶复用模式,在这个模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建。onNewIntent方法会被回调,通过此方法的参数,我们可以取出当前请求的信息。这个Activity的onCreate,onStart不会被系统调用。

    • singleTask:
      栈内复用模式,这是一种单实例模式,在这种模式下,只要Activity在一个栈内存在,那么多次启动此Activity都不会创建实例。
      三个例子:
      1.目前任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例都不存在,所以系统会先创建任务栈S2,然后创建D的实例将其入栈到S2;
      2.假设D所需的任务栈为S1,其他情况如如上面的一样,那么由于S1已经存在,所以系统会直接创建D的实例并将其引入到S1中;
      3.如果D所需要的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会被重新创建,系统会把D切换到栈顶并且调用其oNnNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。

    • singleInstance
      单实例模式,处理其他程序与我们程序共享这个活动的实例。这是一种加强的singleTask的模式,他除了具有singleTask的所有属性之外,还加强了一点,那就是具有此模式下的Activity只能单独的处于一个任务栈中,有些像Java中的static。eg:有两个任务栈,前台任务栈的情况为AB,而后台任务栈的情况是CD,这里假设CD的启动模式都是singleTask,现在请求启动D,那么整个后台任务站都会被切换到前台,这个时候整个后退列表变成了ABCD,当用户按back键的时候,列表中的Activity会一一出栈,如左图所示。
      什么是Activity所需的任务栈?TaskAffinity翻译成任务相关性,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配合使用,第一种情况:TaskAffinity是具有该模式Activity目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。第二种情况:现在有2个应用A和B,A启动了B的一个Activity C ,然后按Home键回到桌面,然后再单击B的桌面图标,这个时候并不是启动; B的主Activity,而是重新显示了已经被应用A启动的Activity C,或者说,C从A的任务栈转移到了B的任务栈中。

    如何指定Activity的启动模式?

    1)AndroidManifest为Activity指定启动模式:

        <activity android:name=".MainActivity"
            android:launchMode="singleTask"
            android:label="@string/app_name">
    

    2)Intent中设置标志位来为Activity指定启动模式。

      Intent intent = new Intent(MainActivity.this,Main2Activity.class);
      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      startActivity(intent);
    

    总结:第二种优先级高于第一级

    上述的方式在能力限定范围有所不同。

    • 第一种无法直接设定Intent.FLAG_ACTIVITY_CLEAR_TOP标识;

    • 第二种无法指定singleInstance模式。

      • intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        

    1.2.2 Activity的Flags

    标志位包括:

    FLAG_ ACTIVITY_ NEW _ TASK singleTask启动模式

    FLAG_ ACTIVITY_ SINGLE _ TOP singleTop启动模式

    FLAG_ ACTIVITY_ CLEAR _ TOP 如果被启动的Activity采用standard模式启动,它和它之上的Activity都要出栈,singleTask具有此属性。

    FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 这个标记的Activity不会出现在历史的Activity的列表中,当某些情况我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。

    1.3 IntentFilter的匹配规则

    Activity的启动分为两种

    • 显示调用
      • 明确对象的组件信息,包名类名
    • 隐式调用
      • 不需要明确信息,隐式调用需要intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity,IntentFilter中的过滤信息action,category,data。
        • 为了匹配过滤列表,需要同时匹配过滤列表中的action,category,data信息,否则匹配失败,一个过滤列表中的action,category,data可以有多个,必须完全匹配action,category,data;
        • Activity可以有多个intent-filter,一个intent只要能匹配一组intent-filter即可成功启动Activity。
    <intent-filter>
     <action android:name="com.liuguilin.activitysample.c" />
    <action android:name="com.liuguilin.activitysample.d" />
     <category android:name="com.liuguilin.category.c" />
    <category android:name="com.liuguilin.category.d" />
     <data android:mimeType="text/plain" />
     </intent-filter>
    

    完全与之相匹配的是:

    Intent intent = new Intent(“com.liuguilin.activitysample.c”);
    intent.addCategory("com.liuguilin.category.c");
    intent.setDataAndType(Uri.parse("file//abc"),"text/plain");
    startActivity(intent);
    

    1. action的匹配规则

    ​ Intent中必须有一个action且必须能够和过滤规则中的某个action相同,即 "com.liuguilin.activitysample.c"或"com.liuguilin.activitysample.d"中的一个相同即可成功匹配。区分大小写。有系统预定义了一些action,同时我们也可以在应用中定义自己的action。

    2. category的匹配规则

    ​ category要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能和过滤规则中的任何一个category相同。(分类功能肯定是全约束)
    ​ 为了匹配前面的过滤规则中的category,我们可出下面的Intent,intent.addcategory (“com.ryg.category.c”)或者Intent.addcategory (“com rcategory.d”)亦或者不设category。为什么不设置category也可以匹配呢?原因是系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category,所以这个category就可以匹配前面的过滤规则中的第三个category。

    3. data的匹配规则

    数据格式:
    data由两部分组成

    • mimeType

      • 媒体类型,比如image/jpeg,audio/mpeg4-generic等,可以表示图片,文本,视频等不同的媒体格式。
    • URI而URI包含的数据可就多了,下面的URI的结构:

      <scheme>://<host>"<port>/[<path>|<pathPrefix>|<pathPattern>]
      

      ​ Scheme:URI的模式,比如http、file、content等;

      ​ Host:URI的主机,比如www.google.com;

      ​ Port:URI中的端口号;

      ​ Path、pathPattem 和 pathPrefix:这三个参数表述路径信息。

      content://com.liuguilin.project:200/folder/subfolder/etc      http://www.baidu.com:80/search/info
      

      1. 过滤规则及匹配:
      过滤规则1:

       <intent-filter>          <data android:mimeType="image/*"/>      </intent-filter>
      

      指定了媒体类型为所有类型的图片,Intent中的mimeType属性必须为“image/*”才能匹配,虽然没有指定URL,但是有默认值,URL的默认值为content和file。

      匹配规则:

      intent.setDataAndType(Uri.parse("file://abc"),"image/*");
      

      原因:
      URI的默认值为content和file,虽然没有指定URI,但是Intent中的URI部分的scheme必须为content或者file才能匹配。
      2. 过滤规则及匹配:
      过滤规则2:

      <intent-filter>    <data android:mimeType="video/mpeg" android:scheme="http" .../>    <data android:mimeType="audio/mpeg" android:scheme="http" .../></intent-filter>
      

      匹配规则:

      intent.setDataAndType(Uri.parse("http://abc"),"video/png")intent.setDataAndType(Uri.parse("http://abc"),"audio/png"); 
      

      这两种特殊写法,作用是一样的

      <intent-filter>
      	<data android:scheme="file"  android:host="www.baidu.com">
      </intent-filter>
      
      <intent-filter>
      	<data android:scheme="file"/>
      	<data android:host="www.baidu.com">
      </intent-filter>
      

      Intent-filter 的匹配规则对于Serviece和BroadcastReceiver也是同样道理,不过系统对于Service的建议Service尽量显式调用。

      判断Activity能否匹配我们的隐式Intent

      ​ PackageManager提供的resolveActivity方法,或者Intent的resolveActivity方法,如果它们找不到匹配的Activity就会返回Null。否则会报错。PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity不同的是,它返回所有匹配成功的Activity信息。
      Activity入口的Intent:

       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
      
  • 相关阅读:
    (八)DVWA之SQL Injection--SQLMap&Burp测试(Medium)
    (五)SQLMap工具检测SQL注入漏洞、获取数据库中的数据
    (四)SQLMap之Tamper篡改脚本的类型、作用、适用场景
    (七)DVWA之SQL Injection--SQLMap测试(Low)
    (三)SQLMap工具-使用选项的操作命令&功能
    20190923-13Linux企业真实面试题 000 021
    20190923-12Linux软件包管理 000 020
    20190923-11Linux crond 系统定时任务 000 019
    20190923-10Linux进程线程类 000 018
    20190923-09Linux磁盘分区类 000 017
  • 原文地址:https://www.cnblogs.com/AronJudge/p/14729455.html
Copyright © 2011-2022 走看看