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" />
      
  • 相关阅读:
    LeetCode 123. Best Time to Buy and Sell Stock III (stock problem)
    精帖转载(关于stock problem)
    LeetCode 122. Best Time to Buy and Sell Stock II (stock problem)
    LeetCode 121. Best Time to Buy and Sell Stock (stock problem)
    LeetCode 120. Triangle
    基于docker 搭建Elasticsearch5.6.4 分布式集群
    从零开始构建一个centos+jdk7+tomcat7的docker镜像文件
    Harbor实现容器镜像仓库的管理和运维
    docker中制作自己的JDK+tomcat镜像
    docker镜像制作---jdk7+tomcat7基础镜像
  • 原文地址:https://www.cnblogs.com/AronJudge/p/14729455.html
Copyright © 2011-2022 走看看