Android开发艺术探索
                                                                   第一章:Activity的生命周期和启动模式

            除了Window(悬浮窗),Dialog(对话框),Toast(用户点击消息),我们能见到的就只有Activity。主要包括了生命周期(正常和异常情况下)、启动模式以及IntentFilter的匹配规则(隐式启动)分析。

(一)Activity的生命周期全面分析

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

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

                                     《Android开发艺术探索》之Activity的生命周期和启动模式(一)_android

(1)Activity的正常生命周期:

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

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

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

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

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

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

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

(2)附加具体说明:

      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是否可见这个角度来回调的,除了这种区别,在实际的使用中,没有其他明显的区别。

      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的创建过程。

      8.模拟调用顺序。

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

                  《Android开发艺术探索》之Activity的生命周期和启动模式(一)_android_02

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自己的文本选中和文本内容等。

                                                                《Android开发艺术探索》之Activity的生命周期和启动模式(一)_android_03

        举例:验证数据存储和恢复的情况。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,优先级最低

        如何保活?

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

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

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

                        《Android开发艺术探索》之Activity的生命周期和启动模式(一)_android_04

               《Android开发艺术探索》之Activity的生命周期和启动模式(一)_启动模式_05

(二)Activity的启动模式
2.1 Activity的LaunchMode

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

(1)standard:

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

(2)singleTop(解决重复创建栈顶活动问题)

       栈顶复用模式,在这个模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建。onNewIntent方法会被回调,这个Activity的onCreate,onStart不会被系统调用。假设现在栈内的情况为ABCD,其中ABCD为四个Activity,A位于栈底,D位于栈顶,这个时候假设要再启动D,如果D的启动模式为singleTop,那么站栈内的情况仍然是ABCD,如果D的启动模式是standard,那么由于D会被重新创建,导致情况就是ABCDD。

(3)singleTask

      栈内复用模式,这是一种单实例模式,在这种模式下,只要Activity在一个栈内存在,那么多次启动此Activity都不会创建实例。系统首先会去寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例把A放进栈中,如果存在A所需要的栈,这个时候就要看A是否在栈中有实例存在,如果实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并且把A压入栈中。

      三个例子:

      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。

            《Android开发艺术探索》之Activity的生命周期和启动模式(一)_IntentFilter_06

(4)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的任务栈中。

2.2 如何指定Activity的启动模式?

2.2.1指定方式

(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);

        总结:第二种优先级高于第一级;上述的方式在能力限定范围有所不同。Eg:第一种无法直接设定Intent.FLAG_ACTIVITY_CLEAR_TOP标识;第二种无法指定singleInstance模式。

自己画个栈可以理解,比较简单。

2.2.2 Activity的Flags

       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

标志位包括:

FLAG_ ACTIVITY_ NEW _ TASK  ‘singleTask’启动模式
FLAG_ ACTIVITY_ SINGLE _ TOP ‘singleTop’启动模式
FLAG_ ACTIVITY_ CLEAR _ TOP 它连同它之上的Activity都要出栈,singleTask具有此属性。

(三)IntentFilter的匹配规则
3.1 Intent隐式调用的特点

       隐式调用需要intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity,IntentFilter中的过滤信息有action,category,data。(1)为了匹配过滤列表,需要同时匹配过滤列表中的action,category,data信息,否则匹配失败,一个过滤列表中的action,category,data可以有多个,必须完全匹配action,category,data;(2)一个Activity钟可以有多个intent-filter,一个intent只要能匹配一组intent-filter即可成功启动Activity。

eg:
<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);

3.2 action的匹配规则

      Intent中必须有一个action且必须能够和过滤规则中的某个action相同,即 "com.liuguilin.activitysample.c"或"com.liuguilin.activitysample.d"中的一个相同即可成功匹配。

3.3 category的匹配规则

     category要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能和过滤规则中的任何一个category相同。

    eg:为了匹配前面的过滤规则中的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.3  data的匹配规则

      可分开写,可连着写。

(1)数据格式:

data由两部分组成,mimeType和URI,前者是媒体类型,比如image/jpeg等,可以表示图片等,而URI包含的数据可就多了,下面的URI的结构:

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

       Scheme:URI的模式,比如http、file、content等;Host:URI的主机,比如www.google.com;Port:URI中的端口号;Path、pathPattem 和 pathPrefix:这三个参数表述路径信息。

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

(2)过滤规则1及匹配:

过滤规则1:

     <intent-filter>

          <data android:mimeType="image/*"/>

      </intent-filter>

匹配规则:

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

原因:

   URI的默认值为content和file,虽然没有指定URI,但是Intent中的URI部分的scheme必须为content或者file才能匹配。

(3)过滤规则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"); 

Service尽量显式调用。

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

       PackageManager提供的queryIntentActivities和Intent 的resolveActivity方法可以检测有没有匹配的隐式Intent。

Activity入口的Intent:

 <action android:name="android.intent.action.MAIN" />

 <category android:name="android.intent.category.LAUNCHER" />