Android开发学习笔记——四大组件之Activity

Android开发学习笔记——四大组件Activity

  • 基本使用
    • 创建方法
    • 显示启动和隐式启动
      • 显式启动
        • startActivity
        • startActivityForResult
      • 隐式启动
        • action
        • category
        • data
      • Activity间通信
  • 生命周期
    • Activity的四种状态
      • Active/running
      • paused
      • stoped
      • killed
    • Activity的生命周期
      • 整个生命周期
      • 可见生命周期
      • 前景生命周期
    • Activity的生命周期变化
    • Activity状态恢复
  • 启动模式
    • Android任务栈
    • 启动模式
      • 4种启动模式和使用场景
        • Standard
        • SingleTop
        • SingleTask
        • SingleInstance
      • 设置方式
        • 静态设置
        • 动态设置
  • 总结

众所周知,Android开发中存在着四大组件,包括:Activity、Service、BroadcastReceiver和ContentProvider。其中Activity作为四大组件中为用户提供可视化界面的组件,是我们在开发中四大组件使用最为频繁的应用组件,一个应用也通常是由多个彼此松散联系的Activity组成。

基本使用

创建方法

Activity作为一个为用户提供可视化界面的组件,那么首先我们就来介绍下如何一个Activity,如何创建一个简单的用户界面。
实际上,任何Activity的创建都可以大概分为以下几个步骤:

  • 创建xml文件,实现界面布局;
  • 继承Activity或者其子类如FragmentActivity、AppCompatActivity等实现Activity类,并重写onCreate方法,使用setContentView为Activity指定对应的xml布局;
  • 在AndroidMainfest.xml文件中声明Activity。

经过这三个步骤,我们就能够创建一个简单的Activity了。实际上,在我们使用AndroidStudio进行开发时,创建一个Activity极其简单,我们只要创建一个Activity,就会自动生成对应的xml布局,并在AndroidMainfest中完成声明。如图所示:
Android开发学习笔记——四大组件之Activity_第1张图片
如上图,AndroidStudio还为我们提供了一些常用的Activity的实现,如LoginActivity和SettingsActivity等,我们选择EmptyActivity即可,然后AndroidStudio就会自动为我们创建Activity子类、xml文件并在AndroidManifest中声明,我们只需要在xml文件中创建布局并在Activity类中实现所需逻辑即可。
创建Activity,继承了AppCompatActivity,并重写onCreate方法,调用setContentView方法为Activity指定了布局,代码如下:

class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
    }
}

创建xml文件:


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.TestActivity">
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello world"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

androidx.constraintlayout.widget.ConstraintLayout>

在AndroidManifest中声明Activity:

<activity android:name=".activity.TestActivity">activity>

经过这几个步骤,我们就创建了一个简单的Activity,如果我们需要实现更加复杂的布局和功能,就只需要在xml和自定义Activity类中修改布局,进行功能实现即可。

显示启动和隐式启动

根据上文所述,我们已经创建了一个简单的Activity,那么我们应该如何去启动Activity呢?在Android开发中,提供了两种Activity的启动方式,分别为显示启动和隐式启动。

显式启动

显式启动即显式指定需要启动的Activity,我们能够确定当前启动Activity是哪一个。
其使用方式主要是通过startActivity和startActivityForResult来实现的。

startActivity

startActivity()的使用方式很简单,我们只需要传入一个intent参数即可,intent类是一个消息传递对象,主要用于传递信息。使用startActivity显式启动Activity时,我们为intent指定目标Activity即可,代码如下:

val intent = Intent(this, MainActivity::class.java)
startActivity(intent)

当然实际上,intent还能为Activity传递更多信息,包括字符串、数组等,具体将在下文中Activity的通信中讲到。

startActivityForResult

在实际开发过程中,我们可能需要从上一个Activity中返回数据结果并进行处理,此时我们就可以使用startActivityForResult来启动Activity,然后再重写onActivityResult方法对返回结果进行处理。startActivityForResult的使用方法完全相同,只是多了一个requestCode的参数用于区分请求的Activity。代码如下:

val intent = Intent(this, MainActivity::class.java)
startActivityForResult(intent, 101)//通常使用一个静态常量来指定requestcode

然后,重写onActivityResult方法即可监听到从上一个Activity的返回,代码如下:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == 101){
        Log.e("test", "back from mainActivity")
    }
}

从MainActivity中返回后log输出结果如下:
log

隐式启动

实际上,隐式启动也是通过startActivity和startActivityForResult来实现的,但与显式启动相反,隐式启动无需指定某个Activity,只会说明启动的Activity的过滤条件,只要满足声明的条件的Activity都会被启动,我们无法完全预知启动的将是那个Activity。其过滤条件主要有action、data和category,这三个过滤条件的匹配方式均有所不同。
要实现Activity的隐式启动,我们首先需要认识< intent-filter >标签,所有的过滤条件都将在其中声明,其实在我们创建项目的时候,AndroidStudio会自动为我们创建一个启动Activity作为项目的入口。

<activity android:name=".activity.TestActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    intent-filter>
activity>

我们可以看到,在这个Activity中,存在两个过滤条件,分别为action和category,其实就是将该Activity声明为入口。当我们自定义action、category和data时,实现方式是相同的,只需在AndroidMainfest中为Activity声明对应的过滤条件即可,然后再使用隐式Intent启动Activity。使用隐式Intent,我们无需指定Activity的类名,只需要在创建对象时,或创建对象后为其添加对应的过滤条件即可。方法如下:

  • addCategory
  • setData
  • setAction

action、category和data的使用方式相差无几,其主要区别就是匹配规则。下面,我们来分别介绍下,其各自的匹配规则。

action

action的匹配规则如下:

  • intent-filter中可以设置多条action
  • intent中必须指定action否则匹配失败且intent中action最多只有一条
  • intent中的action和intent-filter中的action必须完全一样时(包括大小写)才算匹配成功
  • intent中的action只要与intent-filter其中的一条匹配成功即可

category

category的匹配规则如下:

  • category在intent-filter中可以有多条
  • category在intent中也可以有多条
  • intent中所有的category都可以在intent-filter中找到一样的(包括大小写)才算匹配成功
  • 通过intent启动Activity的时候如果没有添加category会自动添加android.intent.category.DEFAULT,如果intent-filter中没有添加android.intent.category.DEFAULT则会匹配失败

data

data的匹配规则如下:

  • intent-filter中可以设置多个data
  • intent中只能设置一个data
  • intent-filter中指定了data,intent中就要指定其中的一个data
  • setType会覆盖setData,setData会覆盖setType,因此需要使用setDataAndType方法来设置data和mimeType

尽管,使用显式和隐式方式都能够启动Activity,但在实际的开发过程中,我们一般还是使用显式方式,因为使用隐式启动是不可控的,我们无法完全确定实际启动的将是哪个Activity,具有一定的不确定因素。一般来说,我们只使用隐式方式来启动系统相关功能,如:短信、电话等。

Activity间通信

无论是使用显示启动还是隐式启动,我们在启动过程中,都可能需要在启动的时候为目标Activity传参。我们可以从上文看到,在Activity的启动时,我们都是使用intent来指定或者过滤目标Activity的,但是实际上,Intent的功能远不止如此,在Intent类中,还提供了一系列的传递参数的方法(主要是putExtra),我们可以将需要传递的参数存入Intent对象中,然后再目标Activity中再从Intent中取出即可。
基本使用方法如下:

//传递参数
val intent = Intent(this, MainActivity::class.java)
intent.putExtra("test_string", "test message")
intent.putExtra("test_int", 1001)
startActivity(intent)

//获取参数
val str = intent.getStringExtra("test_string")
val num = intent.getIntExtra("test_int", 0)

我们可以看到,我们只需要调用intent的putExtra()方法,其存在两个参数,第一个是参数名,第二个就是需要传递参数;在目标Activity中,我们只需要调用intent对应类型的get方法,然后根据参数名即可获取到传递的参数值。
putExtra方法拥有很多重载方法,可以传入包括String、int、boolean等多种类型,也包括可序列化的object对象。
同时,我们也可以通过intent获取到startActivityForResult上一个Activity的值,代码如下:

//传入返回值
val intent = Intent()
intent.putExtra("test_string", "result message")
intent.putExtra("test_bool", true)
setResult(Activity.RESULT_OK, intent)
finish()

....
//获取返回值
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
   super.onActivityResult(requestCode, resultCode, data)
   if (requestCode == 101){
       val str = data?.getStringExtra("test_str")
       val flag = data?.getBooleanExtra("test_bool", false)
       Log.e("test", "back from mainActivity, str:$str---flag:$flag")
   }
}

生命周期

通过上面的介绍,我们对Activity的实际使用有了一定的了解,但是,要理解Activity我们必须对Activity的生命周期有一个完整的认识。首先,我们来看下Android官方提供的Activity的生命周期图:
Android开发学习笔记——四大组件之Activity_第2张图片

Activity的四种状态

Activity在整个生命周期中,存在四种状态,包括:Active/running、paused、stoped和killed

Active/running

当一个新 Activity 启动入栈后,它显示在屏幕最前端,处于栈的最顶端(Activity栈顶),此时Activity处于一个用户可见并且可交互的状态,这个状态叫做激活状态,即Active/running,通常为用户正在交互的Activity。

paused

当 Activity失去焦点, 被一个新的非全屏的Activity 或者一个透明的Activity 被放置在栈顶,此时的状态叫做暂停状态(Paused)。此时它依然与窗口管理器保持连接,Activity依然保持活力(保持所有的状态,成员信息,和窗口管理器保持连接),但是在系统内存极端低下的时候将被强行终止掉。所以它仍然可见,但已经失去了焦点故不可与用户进行交互。

stoped

当一个Activity被另外的Activity完全覆盖掉,此时它依然保持所有状态和成员信息,但是不可见也不可交互,其窗口被隐藏,这被称作停止状态,即stoped。当系统内存需要被用在其他地方的时候,Stopped的Activity将被强行终止掉。

killed

从图中我们可以看到,如果一个Activity是Paused或者Stopped状态,如果系统需要内存,可以要求Activity结束或者直接终止它的进程,此时Activity被弹出任务栈,从内存中删除,进入killed状态。当该Activity再次显示给用户时,它必须重新启动和重置前面的状态。

Activity的生命周期

在Activity的生命周期中,我们需要主要关注以下三个生命周期循环。

整个生命周期

Activity的整个生命周期,我们从图中可以看到,其发生在从onCreate方法到onDestory方法之间,Activity所有的任务都发生在这两者之间,一般而言,在开发过程中,我们通常会在onCreate中对布局和数据进行初始化操作,在onDestory方法中释放资源。

可见生命周期

Activity的可见生命周期发生在onStart到onStop方法之间,在这期间,Activity都是可见的,但是不一定有焦点,可以与用户进行交互操作。

前景生命周期

Activity的前景生命周期发生在onResume和onPause方法之间,在前景生命周期中,Activity位于前台,用户可见且能够进行交互操作。

Activity的生命周期变化

要想了解Activity的整个的生命周期变化,首先我们需要了解Activity生命周期中的几个主要回调方法。

方法名 说明
onCreate Activity第一次创建时调用,通常用于进行一些初始化操作
onRestart 当Activity进入stoped状态后再次启动时调用,如从上一个Activity返回
onStart 当Activity可见时调用
onResume 当Activity进入栈顶,成为前台Activity,即将开始和用户交互时调用
onPause 当Activity退出前台时,无法获取用户焦点后调用,可见
onStop 当Activity不可见时调用
onDestory Activity被销毁时调用,通常进行一些资源回收操作

根据上述图表,我们可以大致描述一下,Activity的生命周期变化的整个流程。当我们启动一个Activity时,onCreate被调用,Activity被创建,然后调用onStart方法,Activity进入可见生命周期,此时Activity可见但不可交互;当Activity进入前台时,调用onResume方法,Activity进入前景生命周期,可见且可交互;此时,如果另一个不透明Activity进入前台,原Activity退出前台,调用onPause,失去焦点,无法再进行交互;接着调用onStop结束可见生命周期,进入stoped状态。此时,如果退出上一个Activity,原Activity将依次调用onRestart、onStart、onResume重新启动,当Activity结束时,调用onDestroy方法,进入killed状态。
一些常见场景下的生命周期变化:

  • ActivityA启动->ActivityB启动->退出ActivityB
    ActivityA启动:AonCreate()->AonStart()->AonResume()
    ActivityB启动:AonPause()->BonCreate()->BonStart()->BonResume()->AonStop()
    ActivityB退出:BonPause()->AonRestart()->AonStart()->AonResume()->BonStop()->BonDestroy()
  • ActivityA启动->透明Activity
    透明Activity:AonPause()->BonCreate()->BonStart()->BonResume()
  • ActivityA启动->Home键返回
    Home键返回:onPause()->onSaveInstanceState->onStop
  • ActivityA启动->打开Dialog
    生命周期无变化

Activity状态恢复

当Activity暂停或停止,该Activity的活动状态会被保持,因为Activity对象在暂停或停止时仍保留在内存中 - 有关其成员和当前状态的所有信息仍然存在。因此,Activity会默认保留用户在活动中所做的任何更改,以便当活动返回到前台时(当它“恢复”时),那些更改仍然存在。
但是,当系统在非用户主动情况下销毁Activity时(比如屏幕方向切换),系统不能完好无损地恢复它的活动状态。在这种情况下,我们可以通过实施额外的回调方法来确保保留有关活动状态的重要信息,这就是onSaveInstanceState和onRestoreInstanceState/onCreate。
通过重写onSaveInstanceState来保存Activity状态信息:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putString("test", "test message")
}

恢复Activity的状态信息可以通过调用onCreate或是onRestoreInstanceState方法来实现:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
    if (savedInstanceState != null){
        Log.e("test", "----${savedInstanceState?.getString("test")}")
    }
}

....
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)
    Log.e("test", "${savedInstanceState.getString("test")}")
}

需要注意的是,在使用onCreate方法进行恢复时,我们需要进行非空判断,因为onCreate方法无论是否执行过onSaveInstanceState都会执行的,而onRestoreInstanceState是只有在onSaveInstanceState被调用后才会调用,因此不需要判空。
常见场景:

  • 当用户按下HOME键时
  • 长按HOME键,选择运行其他的程序时。
  • 关闭屏幕时。
  • 屏幕方向切换时。
  • 电话打入时。

启动模式

Android任务栈

要深入理解Android的启动模式,首先我们需要了解Android任务栈。其实Android是使用任务栈来管理活动的,从我们开发过程中,我们就可以发现,启动多个Activity后,然后退出,会依次返回之前的Activity,这和栈的先进后出的模式相同,因此我们可以推测Activity是通过栈来管理的。而事实确实如此,这就是任务栈,也叫返回栈。在默认情况下,应用创建时会创建一个默认的任务栈,每当启动一个Activity时,这个Activity就会在任务栈中入栈,并处于栈顶位置,而每当销毁一个Activity时,就会出栈,栈中后面的Activity就会处于栈顶位置,进而显示到当前界面,当最后一个Activity也被销毁时,应用就会退出,任务栈也会被销毁。如下图所示:
Android开发学习笔记——四大组件之Activity_第3张图片
注意:在一个应用中可能存在多个任务栈。

启动模式

从上文可知,每次启动一个Activity,都会将Activity入栈,然后只有当栈中所有Activity都销毁的时候,任务栈才会被销毁从而退出应用。那么如果我们启动了很多个Activity呢?那不是需要点击很多次返回键将Activity出栈,才能退出应用吗?这无疑是很影响用户体验的。
与此同时,如果我们启动了多次同一个Activity,那么岂不是需要将一个Activity创建多个实例入栈吗?这无疑是对内存资源的巨大浪费。
为了解决以上问题,Android为我们提供了解决方案就是使用不同的启动模式,我们知道在默认情况下,每次启动一个Activity都会入栈,而使用不同的启动模式,就会出现不一样的变化。Android提供了四种启动模式:Standard、SingleTop、SingleTask和SingleInstance

4种启动模式和使用场景

Standard

standard即标准启动模式,也是默认启动模式,在这种模式下,每启动一个Activity,系统都会创建一个Activity实例入栈,在任务栈中可能存在多个相同的Activity的实例。

SingleTop

singleTop即栈顶复用模式,在这种模式下,启动该Activity时,系统会首先判断当前任务栈中的栈顶是否已存在该Activity的实例,如果不是就会创建Activity实例入栈,如果是就不再创建新的实例,而是直接复用已存在的Activity实例并调用onNewIntent方法(不会重新调用onCreate和onStart方法,因为Activity的什么周期没有发生变化)。

应用场景:推送消息显示界面、

SingleTask

singleTask即栈内复用模式,在这个模式下,我们需要指定Activity的任务栈(使用taskAffinity属性设置,默认为包名),启动该Activity时,系统会先判断当前是否存在该任务栈,如果不存在会直接创建任务栈并创建Activity实例入栈;如果任务栈存在,系统只要发现栈中已存在该Activity的实例,无论在栈中的什么位置,都不再创建新的Activity实例,而是直接复用已有的Activity实例并将栈中该Activity上面的Activity全部出栈将其调到栈顶位置,同时调用onNew Intent方法;但如果不存在该Activity实例,那么创建Activity实例并入栈。
应用场景:App的主页

SingleInstance

singleInstance即单例模式,该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
应用场景:系统功能如拨打电话、通讯录等全局调用的均是一个页面

设置方式

设置Activity的启动方式分为静态设置和动态设置。

静态设置

静态设置其实就是通过AndroidManifest文件来设置。我们知道,Activity都需要在AndroidManifest中进行注册,我们可以通过设置其launchMode属性来设置Activity的启动模式(默认为Standard),利用taskAffinity设置任务栈名(默认为包名)。代码如下:

<activity
     android:name=".TestActivity"
     android:launchMode="standard|singleTop|singleTask|singInstance"
     android:taskAffinity="${applicationId}.singleTask">
activity>

动态设置

动态设置是通过启动Activity时的intent进行设置的,通过调用intent的addFlags方法,设置不同的Flag即可设置不同的启动模式
使用的Flag常量,如下表:

Flag 说明
FLAG_ACTIVITY_SINGLE_TOP 指定启动模式为栈顶复用模式(SingleTop )
FLAG_ACTIVITY_NEW_TASK 指定启动模式为栈内复用模式(SingleTask)
FLAG_ACTIVITY_CLEAR_TOP 所有位于其上层的Activity都要移除,SingleTask模式默认具有此标记效果

代码如下:

val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)

需要说明的是,如果同时存在静态设置和动态设置,就会采用动态设置的启动模式进行启动。动态设置无法设置singleInstance模式。

总结

Activity的内容很多,而且作为Android开发的四大组件之一,同时也是我们开发中最常用的一个组件,想要深入理解Activity的各个方面的知识,即了解这些知识是远远不够的,还需要加深理解,需要去了解下相关的源码,了解下相关内容的实现原理。

你可能感兴趣的:(Android开发学习笔记,Android)