温故而知新-Android四大组件之Activity

Activity简介

Activity是Android一个非常重要的用户接口(四大组件之一),是可见的,主要是用户和应用程序之间进行交互的接口。在每个Activity中都可以放很多控件,所以也可以把Activity看作控件的容器。它负责了我们的界面显示,实际开发中我们通过setContentView(R.layout.id)设置界面显示的视图。在Android的项目结构设计中,Activity就相当于MVC设计模式中的View层。在Android的四大组件设计中,为了方便开发者进行开发使用,Android的开发者对四大组件通过生命周期进行管理,我们只需要继承Activity进行重写这些生命周期来管理和合理使用Activity。

首先要注册一个Activity清单配置
  <application android:allowbackup="false"   
    android:icon="@mipmap/ic_launcher" 
    android:label="@string/app_name"    
    android:roundicon="@mipmap/ic_launcher_round" 
    android:supportsrtl="true" 
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
       <intent-filter>
          <action android:name="android.intent.action.MAIN"/>
        <category  android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    </activity>
个别参数解释

1.allowBackup:是否允许备份应用的数据,当备份数据的时候,它的数据会被备份下来。如果设为false,那么绝对不会备份应用的数据,即使是备份整个系统,默认是true。

2.icon&roundIcon:现在Android新建项目后会自动设置两个图标,icon和roundicon,一个是普通图标,一个是圆形图标。

3.supportsRtl:声明你的application是否愿意支持从右到左(原来RTL就是right-to-left 的缩写...)的布局,如果设置为true,targetSdkVersion设置为17或更高,各种RTL的API将被激活,系统使用您的应用程序可以显示RTL布局。如果targetSdkVersion设置为16或更低的设置为false,RTL的API将被忽略或没有影响您的应用程序将具有相同的行为无论对用户现场的选择相关的布局方向(你的布局会从左至右),此属性的默认值是false。

4.activity :注册的Activity

5.intent-filter:过滤器intent-filter 设置action ="android.intent.action.MAIN"、category ="android.intent.category.LAUNCHER" ,意思就是设置MainActivity为app的入口函数。

下面我们来一起看看Android官方文档上提供的Activity的生命周期图:
Activity生命周期图
方法介绍
  • onCreate()方法
    这个方法我们必须实现,当我们创建一个activity的时候,系统会调用这个方法。重要的是,我们必须通过setContentView()来设定activity的显示视图。(不可见)

  • onStart()方法
    在我们创建了视图之后调用,在向用户展示之前调用。然后调用onResume方法。(不可见)

  • onResume()方法:
    onResume方法是activity进行可见状态,能够与用户进行交互。(可见可用)

  • onPause()方法
    当我们离开这个activity时候系统调用这个方法。注意:它不意味着activity被销毁(destroy)。暂停状态,记得小时候打游戏,按暂停,游戏界面就会停止不动,属于可见状态,但是不能用,其实原理基本类似。(可见但不可用)

  • onRestart:Activity的重启,由不可见变为可见

  • onStop()方法
    停止状态,当一个activity被另一个activity完全覆盖的时候,它仍然保留着信息,但是已经对用户不可见。(不可见)

  • onDestroy()方法
    此时activity已经被销毁,activity至此生命周期完全结束。(销毁)

注意点
  • 其中onCreate和onDestory为完整的生命周期,onStart和onStop为可见生命周期,onResume和onPause为前台生命周期。

  • 当用户打开新的Activity或切换回到桌面的时候,回调为onPause->onStop,但是若Activity采用的为透明主题,则不会回调onStop。

  • 一般情况下,Activity有不可见变为可见,onRestart才会调用。

  • onStart和onStop控制Activity在可见和不可见的状态之间转换,onResume和onPause控制Activity在前台或非前台之间转换。

  • 当由Activity A ->Activity B时,回调的顺序为onPause(A)->onCreate(B)->onStart(B)->onStop(A),因此不可以在onPause上做重量级操作。

  • 如果应用长时间处于stopped状态并且此时系统内存极为紧张的时候,系统就会回收Activity,此时系统在回收之前会回调onSaveInstanceState方法来保存应用的数据Bundle。当该Activity重新创建的时候,保存的Bundle数据就会传递到onRestoreSaveInstanceState方法和onCreate方法中,这就是onCreate方法中Bundle savedInstanceState参数的来源(onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原)。
测试Activity的生命周期:
  1. 界面从“死亡”-->“运行"
    构造器-->onCreate()--->onStart()-->onResume()

  2. 界面从“运行”-->“死亡"
    onPause()-->onStop()-->onDestroy()

  3. 界面从“运行”-->“停止"
    onPause()-->onStop()

  4. 界面从“停止” -->“运行"
    onRestart()-->onStart()-->onResume()

  5. 界面从“运行”-->“暂停"
    onPause()

  6. 界面从“暂停” -->“运行"
    onResume()

在实际的开发中,我们根据activity的生命周期来实现我们的逻辑处理。通常在开发中,在onCreate()方法中进行一些变量的初始化工作,包括变量的初始化、控件的初始化、控件设置监听等。当一个activity由于失去焦点时再次重新获取焦点调用onResume方法。在onResume()方法中我们可以处理一些比如界面的更新操作。

onSaveInstanceState何时调用

在activity被杀掉之前调用保存每个实例的状态,以保证该状态可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (传入的Bundle参数是由onSaveInstanceState封装好的)中恢复。这个方法在一个activity被杀死前调用,当该activity在将来某个时刻回来时可以恢复其先前状态。例如,如果activity B启用后位于activity A的前端,在某个时刻activity A因为系统回收资源的问题要被杀掉,A通过onSaveInstanceState将有机会保存其用户界面状态,使得将来用户返回到activity A时能通过onCreate(Bundle)或者onRestoreInstanceState(Bundle)恢复界面的状态。

不要将这个方法和activity生命周期回调如onPause()或onStop()搞混淆了,onPause()在activtiy被放置到背景或者自行销毁时总会被调用,onStop()在activity被销毁时被调用。一个会调用onPause()和onStop(),但不触发onSaveInstanceState的例子是当用户从activity B返回到activity A时:没有必要调用B的onSaveInstanceState(Bundle),此时的B实例永远不会被恢复,因此系统会避免调用它。一个调用onPause()但不调用onSaveInstanceState的例子是当activity B启动并处在activity A的前端:如果在B的整个生命周期里A的用户界面状态都没有被破坏的话,系统是不会调用activity A的onSaveInstanceState(Bundle)的。

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

  2. 可见但非前台Activity——比如Activity中弹出的对话框,导致Activity可见但是位于后台无法和用户直接交互。

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

当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onResoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。

Activity任务栈和四种启动模式
每个应用会有一个Activity任务栈,存放已启动的Activity
  • standard:默认的启动模式,该模式下会生成一个新的Activity,同时将该Activity实例压入到栈中(不管该Activity是否已经存在在Task栈中,都是采用new操作)。例如: 栈中顺序是A B C D ,此时D通过Intent跳转到A,那么栈中结构就变成 A B C D A,点击返回按钮的 显示顺序是 D C B A,依次摧毁。

  • singleTop:在singleTop模式下,如果当前Activity D位于栈顶,此时通过Intent跳转到它本身的Activity(即D),那么不会重新创建一个新的D实例(走onNewIntent()),所以栈中的结构依旧为A B C D,如果跳转到B,那么由于B不处于栈顶,所以会新建一个B实例并压入到栈中,结构就变成了A B C D B。应用实例:三条推送,点进去都是一个activity。

  • singleTask:在singleTask模式下,Task栈中只能有一个对应Activity的实例。例如:现在栈的结构为A B C D,此时D通过Intent跳转到B(走onNewIntent()),则栈的结构变成了:A B。其中的C和D被栈弹出销毁了,也就是说位于B之上的实例都被销毁了。如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法,而是调用onNewIntent方法。通常应用于首页,首页肯定得在栈底部,也只能在栈底部。

  • singleInstance:singleInstance模式下会将打开的Activity压入一个新建的任务栈中。例如:Task栈1中结构为:A B C,C通过Intent跳转到了D(D的启动模式为singleInstance),那么则会新建一个Task 栈2,栈1中结构依旧为A B C,栈2中结构为D,此时屏幕中显示D,之后D通过Intent跳转到D,栈2中不会压入新的D,所以2个栈中的情况没发生改变。如果D跳转到了C,那么就会根据C对应的启动模式在栈1中进行对应的操作,C如果为standard,那么D跳转到C,栈1的结构为A B C C,此时点击返回按钮,还是在C,栈1的结构变为A B C,而不会回到D。

给Activity指定启动模式的两种方式

1、第一种是通过AndroidMenifest为Activity指定启动模式:launchMode

        <activity
            android:name=".activity.SecondActivity"
            android:configChanges="screenLayout"
            android:launchMode="singleTask"
            />

2、第二种是通过Intent中设置标志位来为Activity指定启动模式:addFlags

     Intent intent = new Intent();
        intent.setClass(this,SecondActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

这两种方式都可以为Activity指定启动模式,但是二者还是有区别的。首先,优先级上第二种方式比第一种要高,当两种同时存在时,以第二种方式为准;其次,上述两种方式在限定范围上有所不同,比如,第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种方式无法为Activity指定singleInstance模式。

TaskAffinity任务相关性

这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈名字为应用的包名。我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相当于没有指定。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。

下面可以做个小案例:应用包名为 "pjs.com.kaifa",MainActivity启动模式为standard,SecondActivity和ThirdActivity启动模式为singleTask并且设置了taskAffinity属性值为"pjs.com.task"(MainActivity没有设置taskAffinity)。意思为MainActivity默认在"pjs.com.kaifa"任务栈中,而SecondActivity和ThirdActivity启动后在"pjs.com.task"任务栈中。

  package="pjs.com.kaifa"

  <activity android:name=".activity.MainActivity"
            android:launchMode="standard"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".activity.SecondActivity"
            android:configChanges="screenLayout"
            android:taskAffinity="pjs.com.task"
            android:launchMode="singleTask" />

        <activity android:name=".activity.ThirdActivity"
            android:configChanges="screenLayout"
            android:taskAffinity="pjs.com.task"
            android:launchMode="singleTask"
            ></activity>

我们运行项目在从MainActivity点击按钮(MainActivity中的按钮)进入SecondActivity中再点击(SecondActivity中的按钮)进入ThirdActivity中,日志中可以SecondActivity和SecondActivity实例在"pjs.com.task"任务栈中,MainActivity在"pjs.com.kaifa"任务栈中。(第三个任务栈是桌面任务栈)

 Running activities (most recent first):
      TaskRecord{1b1913f #154 A=pjs.com.task U=0 sz=2}
        Run #3: ActivityRecord{33200b08 u0 pjs.com.kaifa/.activity.ThirdActivity t154}
        Run #2: ActivityRecord{223dd797 u0 pjs.com.kaifa/.activity.SecondActivity t154}
      TaskRecord{28963a0c #153 A=pjs.com.kaifa U=0 sz=1}
        Run #1: ActivityRecord{242298cb u0 pjs.com.kaifa/.activity.MainActivity t153}
      TaskRecord{2890917a #49 A=com.android.incallui U=0 sz=1}
        Run #0: ActivityRecord{397b3f36 u0 com.android.incallui/.InCallActivity t49}

configChanges属性

我们知道当系统发生改变后,Activity会被重新创建,那么有么有办法不重新创建呢?答案是有的,可以给Activity指定configChanges属性。比如不想让Activity再屏幕旋转的时候重新创建,就可以给configChanges属性添加 orientation这个值,如果想指定多个值可以用“|”连接起来。

<manifest中配置>
<activity android:name=".activity.MainActivity"
           android:launchMode="standard"
           android:configChanges="orientation|screenSize"
           >
           <intent-filter>
               <action android:name="android.intent.action.MAIN" />
               <category android:name="android.intent.category.LAUNCHER" />
           </intent-filter>
       </activity>

<activity中重写三个方法>
@Override
   public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
       super.onSaveInstanceState(outState, outPersistentState);
       Log.e("TAG", "onSaveInstanceState");
   }

   @Override
   protected void onRestoreInstanceState(Bundle savedInstanceState) {
       super.onRestoreInstanceState(savedInstanceState);
       Log.e("TAG", "onRestoreInstanceState");
   }

   @Override
   public void onConfigurationChanged(Configuration newConfig) {
       super.onConfigurationChanged(newConfig);
       Log.e("TAG", "onConfigurationChanged, newConfig:"+newConfig.orientation);
   }
06-20 16:52:36.400 20760-20760/pjs.com.kaifa E/TAG: onConfigurationChanged, newConfig:2
06-20 16:52:38.730 20760-20760/pjs.com.kaifa E/TAG: onConfigurationChanged, newConfig:1
06-20 16:52:41.320 20760-20760/pjs.com.kaifa E/TAG: onConfigurationChanged, newConfig:2
06-20 16:52:42.510 20760-20760/pjs.com.kaifa E/TAG: onConfigurationChanged, newConfig:1

看下上面配置,在清单中配置 android:configChanges="orientation|screenSize",在activity中重写onSaveInstanceState、onRestoreInstanceState、onConfigurationChanged这三个方法。然后横竖屏来回切换并没有调用onSaveInstanceState和onRestoreInstanceState这俩方法,而是调用了onConfigurationChanged方法,这个时候我们就可以做一些自己的特殊处理了。

属性和含义.png
Intent Flag启动模式

intent 在跳转页面的时候可以设置intent.setFlags()

  • FLAG_ACTIVITY_CLEAR_TOP:例 如现在的栈情况为:A B C D 。D此时通过intent跳转到B,如果这个intent添加FLAG_ACTIVITY_CLEAR_TOP标记,则栈情况变为:A B。如果没有添加这个标记,则栈情况将会变成:A B C D B。也就是说,如果添加了FLAG_ACTIVITY_CLEAR_TOP标记,并且目标Activity在栈中已经存在,则将会把位于该目标 activity之上的activity从栈中弹出销毁。这跟上面把B的Launch mode设置成singleTask类似。简而言之,跳转到的activity若已在栈中存在,则将其上的activity都销掉。

  • FLAG_ACTIVITY_NEW_TASK:例 如现在栈1的情况是:A B C。C通过intent跳转到D,并且这个intent添加了FLAG_ACTIVITY_NEW_TASK标记,如果D这个Activity在 Manifest.xml中的声明中添加了Task affinity,系统首先会查找有没有和D的Task affinity相同的task栈存在,如果有存在,将D压入那个栈,如果不存在则会新建一个D的affinity的栈将其压入。如果D的Task affinity默认没有设置,则会把其压入栈1,变成:A B C D,这样就和不加FLAG_ACTIVITY_NEW_TASK标记效果是一样的了。注意如果试图从非activity的非正常途径启动一个 activity(例见下文“intent.setFlags()方法中参数的用例”),比如从一个service中启动一个activity,则intent比如要添加FLAG_ACTIVITY_NEW_TASK标记(编者按:activity要存在于activity的栈中,而非activity的途径启动activity时必然不存在一个activity的栈,所以要新起一个栈装入启动的activity)。简而言之,跳转到的activity根据情况,可能压在一个新建的栈中。

  • FLAG_ACTIVITY_NO_HISTORY: 例如现在栈情况为:A B C。C通过intent跳转到D,这个intent添加FLAG_ACTIVITY_NO_HISTORY标志,则此时界面显示D的内容,但是它并不会压 入栈中。如果按返回键,返回到C,栈的情况还是:A B C。如果此时D中又跳转到E,栈的情况变为:A B C E,此时按返回键会回到C,因为D根本就没有被压入栈中。简而言之,跳转到的activity不压在栈中。

  • FLAG_ACTIVITY_SINGLE_TOP:和Activity的Launch mode的singleTop类似。如果某个intent添加了这个标志,并且这个intent的目标activity就是栈顶的activity,那么将不会新建一个实例压入栈中。简而言之,目标activity已在栈顶则跳转过去,不在栈顶则在栈顶新建activity。

Activity的启动

1、显示启动
何为显示启动,就是在创建Intent的时指定要启动的activity的类。所以前提是我们已经知道Activity的类名称,这就运用在我们在开发app中进行activity的跳转。

Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);

2、隐式启动
android系统给我们提供了许多服务,如打电话、发短信。但是在我们的应用中我们不知道具体的activity类名称,此时我们就需要用到隐式的activity启动方法。

//打电话
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:555-2368"));
startActivity(intent);

//发短信
Intent intent=new Intent();
intent.setAction("android.intent.action.SEND");
intent.setData(Uri.parse("mms:110"));
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);

//打开网页
Intent intent=new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
有回调的跳转
/*
*ActivityA跳ActivityB
*/
Intent intnt = new Intent(this,ActivityB.class);
startActivityForResult(int reqCode, Intent intent): 带回调启动Activity

/*
*ActivityB销毁 返回ActivityA
/
 setResult(int resultCode, Intent data): 设置要返回的结果
 finish(): 结束当前Activity

/*
*ActivityA 接收之后ActivityB返回的参数
/
onActivityResult(int reqCode, int resultCode, Intent data): 回调方法
通过data获取返回的数据 进行操作

推荐阅读更多精彩内容