【四大组件】-- 活动 Activity

活动

活动是什么

Activity是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或多个Activity。

活动的相关操作

手动创建活动

  1. 创建空的安卓项目:项目类型选择“Add No Activity”
  2. 创建Activity:右键点击项目的java目录,选择新建空的Activity,并且不要勾选Generate Layout File和Launcher Activity这两个选项;
    勾选Generate Layout File表示会自动为FirstActivity创建一个对应的布局文件,勾选Launcher Activity表示会自动将FirstActivity设置为当前项目的主Activity。
  3. 重写onCreate()方法:
class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}
  1. 创建布局资源文件:在项目的app/src/main/res目录新建layout目录,然后新建布局资源文件,名称为first_layout,根元素选择LinearLayout;通过可视化布局编辑器编辑布局文件。
    一个简单的布局资源文件对应的源码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"
/>

</LinearLayout>

如果你需要在XML中引用一个id,就使用@id/id_name;
如果你需要在XML中定义一个id,则要使用@+id/id_name;
android:layout_width指定了当前元素的宽度,这里使用match_parent表示让当前元素和父元素一样宽;
android:layout_height指定了当前元素的高度,这里使用wrap_content表示当前元素的高度只要能刚好包含里面的内容就行;
android:text指定了元素中显示的文字内容。

  1. 加载布局资源文件:在onCreate()方法中加入如下代码
class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout) // 加载布局资源文件
    }
}

项目中添加的任何资源都会在R文件中生成一个相应的资源id,因此我们刚才创建的first_layout.xml布局的id现在已经添加到R文件中了。在代码中引用布局文件的方法你也已经学过了,只需要调用R.layout.first_layout就可以得到first_layout.xml布局的id,然后将这个值传入setContentView()方法即可。

  1. 在AM中注册活动
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">
    <application
        ...>
        <activity android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

在标签中,我们使用了android:name来指定具体注册哪一个Activity,那么这里填入的.FirstActivity是什么意思呢?其实这不过是com.example.activitytest.FirstActivity的缩写而已。由于在最外层的标签中已经通过package属性指定了程序的包名是com.example.activitytest,因此在注册Activity时,这一部分可以省略,直接使用.FirstActivity就足够了。

配置主Activity的方法其实就是在标签的内部加入标签,并在这个标签里添加和这两句声明即可。
另外需要注意,如果你的应用程序中没有声明任何一个Activity作为主Activity,这个程序仍然是可以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般是作为第三方服务供其他应用在内部进行调用的。

还可以使用android:label指定Activity中标题栏的内容,标题栏是显示在Activity最顶部的;需要注意的是,给主Activity指定的label不仅会成为标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。

活动中使用Toast

什么是Toast:Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间。

首先需要定义一个弹出Toast的触发点,正好界面上有个按钮,那我们就让这个按钮的点击事件作为弹出Toast的触发点吧:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.first_layout)
    val button1: Button = findViewById(R.id.button1) // 可省略
    button1.setOnClickListener {
        Toast.makeText(this, "You clicked Button 1", Toast.LENGTH_SHORT).show()
    }
}

第一个
参数是Context,也就是Toast要求的上下文,由于Activity本身就是一个Context对象,因此这里直接传入this即可。第二个参数是Toast显示的文本内容。第三个参数是Toast显示的时长,有两个内置常量可以选择:Toast.LENGTH_SHORT和Toast.LENGTH_LONG。

Kotlin编写的Android项目在app/build.gradle文件的头部默认引入了一个kotlin-android-extensions插件,这个插件会根据布局文件中定义的控件id自动生成一个具有相同名称的变量,我们可以在Activity里直接使用这个变量,而不用再调用findViewById()方法。

活动中使用Menu

新建Menu布局文件;
重写onCreateOptionsMenu()方法;
定义菜单响应事件:重写onOptionsItemSelected()方法;

销毁一个活动

按回退键,或者在代码中执行finish()方法。

使用Intent实现活动间启动

什么是Intent:Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity、启动Service以及发送广播等场景。
Intent大致可以分为两种:显式Intent和隐式Intent。

显示启动

Intent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls)。这个构造函数接收两个参数:
第一个参数Context要求提供一个启动Activity的上下文;
第二个参数Class用于指定想要启动的目标Activity,通过这个构造函数就可以构建出Intent的“意图”。
那么接下来我们应该怎么使用这个Intent呢?Activity类中提供了一个startActivity()方法,专门用于启动Activity,它接收一个Intent参数,这里我们将构建好的Intent传入startActivity()方法就可以启动目标Activity了。

button1.setOnClickListener {
    val intent = Intent(this, SecondActivity::class.java)
    startActivity(intent)
}

隐式启动

隐式Intent则含蓄了许多,它并不明确指出想要启动哪一个Activity,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的Activity去启动。
通过在标签下配置的内容,可以指定当前Activity能够响应的action和category。
使用隐式Intent,不仅可以启动自己程序内的Activity,还可以启动其他程序的Activity,这就使多个应用程序之间的功能共享成为了可能。
还可以通过intent.setData(Uri uri);接口设置欲访问的数据。
与此对应,我们还可以在标签中再配置一个标签,用于更精确地指定当前Activity能够响应的数据。标签中主要可以配置以下内容。
android:scheme。用于指定数据的协议部分,如上例中的https部分。
android:host。用于指定数据的主机名部分,如上例中的www.baidu.com部分。
android:port。用于指定数据的端口部分,一般紧随在主机名之后。
android:path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
只有当标签中指定的内容和Intent中携带的Data完全一致时,当前Activity才能够响应该Intent。

活动间数据传递

  1. 传递数据给下一个活动:将数据封装在intent对象中,例如需要传递字符串对象,使用Intent.putExtra(String key, String value),接受端活动中调用父类的getIntent()方法可获得传递过来的intent,然后通过intent.getStringExtra(String key)即可获得value。
  2. 传递数据给上一个活动:
    启动活动:在启动活动时,使用startActivityForResult(Intent intent, int requestCode)方法,第二个参数请求码用于标识是哪次接口调用开启的活动,然后销毁后返回的结果。
    活动销毁前封装要返回的结果:将要返回的结果通过Intent对象封装,活动销毁前调用setResult(RESULT_OK, intent)接口,设置要返回的结果。第一个参数是状态码,用于向上一个活动返回处理结果,一般取值为RESULT_OK RESULT_CANCLED。
    销毁活动:调用finish()接口。
    接收结果:活动销毁后,会在上一个活动的onAcitivityResult(int requestCode, int resultCode, Intent data)方法中接收到刚刚销毁的活动返回的结果。

活动的生命周期

返回栈

其实Android是使用任务(task)来管理Activity的,一个任务就是一组存放在栈里的Activity的集合,这个栈也被称作返回栈(back stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个Activity时,处于栈顶的Activity就会出栈,前一个入栈的Activity就会重新处于栈顶的位置。系统总是会显示处于栈顶的Activity给用户。

活动的状态

运行状态:当一个Activity位于返回栈的栈顶时,Activity就处于运行状态。

暂停状态:当一个Activity不再处于栈顶位置,但仍然可见时,Activity就进入了暂停状态。
你可能会觉得,既然Activity已经不在栈顶了,怎么会可见呢?这是因为并不是每一个Activity都会占满整个屏幕,比如对话框形式的Activity只会占用屏幕中间的部分区域。处于暂停状态的Activity仍然是完全存活着的,系统也不愿意回收这种Activity(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种Activity。

停止状态:当一个Activity不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的Activity有可能会被系统回收。

销毁状态:一个Activity从返回栈中移除后就变成了销毁状态。系统最倾向于回收处于这种状态的Activity,以保证手机的内存充足。

活动的生存期

七个关键回调函数:
onCreate():在Activity第一次被创建的时候调用。你应该在这个方法中完成Activity的初始化操作,比如加载布局、绑定事件等。
onStart():由不可见变为可见的时候调用。
onResume():在Activity准备好和用户进行交互的时候调用。此时的Activity一定位于返回栈的栈顶,并且处于运行状态。
onPause():这个方法在系统准备去启动或者恢复另一个Activity的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶Activity的使用。
onStop():在Activity完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新Activity是一个对话框式的Activity,那么onPause()方法会得到执行,而onStop()方法并不会执行。
onDestroy():在Activity被销毁之前调用,之后Activity的状态将变为销毁状态。
onRestart():在Activity由停止状态变为运行状态之前调用,也就是Activity被重新启动了。

基于以上关键回调函数,活动的三种生存期:
完整生存期:在onCreate()方法和onDestroy()方法之间所经历的就是完整生存期。
可见生存期:在onStart()方法和onStop()方法之间所经历的就是可见生存期。在可见生存期内,Activity对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的Activity不会占用过多内存。
前台生存期:Activity在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,Activity总是处于运行状态,此时的Activity是可以和用户进行交互的,我们平时看到和接触最多的就是这个状态下的Activity。

活动的启动流程

onCreate(): 首次被创建时触发,可以做一些初始化工作,如布局加载。
onStart(): 正在被启动,还没有显示在前台。
onRestart(): 正在重新启动,一般情况是活动从不可见重新变为可见状态时,被调用。
onResume(): 表示已经可见了,并且出现在前台并且开始活动。准备和用户交互的时候调用,此时活动一定在返回栈栈顶
onPause(): 表示正在停止,仍然可见。onPause中不可以进行耗时操作,会影响到新的活动的显示。在系统准备去启动或者恢复另一个活动的时候调用,通常该函数用于释放一些系统资源以及保存数据
onStop(): 表示活动即将停止,已经不可见了,位于后台。在活动完全不可见的时候调用
onDestroy(): 表示即将销毁,可以做一些回收资源的工作。

活动的回收和重建

被回收时保存状态数据:被回收前调用onSaveInstanceState(Bundle outState)方法保存数据,通过调用Bundle对象的putString(),putInt()等方法保存,传入key和value。
被重建时恢复状态数据:被重建时通过onCreate(Bundle savedInstanceState)方法恢复数据,通过调用Bundle对象的getString(“key”),getInt(“key”)等方法恢复状态数据。
通过Bundle在活动间传递数据:可以将数据保存在Bundle中,然后再将其保存在Intent中,实现活动间信息的传递。

如何在活动销毁前保存状态

由于系统资源不足,会自动回收某些停止状态的活动,当按返回键后,其实还是会重新显示出来这些已经被回收销毁的活动,这是因为此时会重新创建活动onCreate(),但是需要思考:如何在活动重建后已经能够让用户看到销毁前一些输入的信息?这就需要在销毁前保存活动的数据信息。
保存数据:通过onSaveInstanceState(Bundle outState)接口。Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。
恢复数据:onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null,但是如果在Activity被系统回收之前,你通过onSaveInstanceState()方法保存数据,这个参数就会带有之前保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。

活动的启动模式

启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。

standard

在standard模式下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity的新实例。

singleTop

当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。

singleTask

当Activity的启动模式指定为singleTask,每次启动该Activity时,系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有其他Activity统统出栈,如果没有发现就会创建一个新的Activity实例。

singleInstance

指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)
那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个Activity是允许其他程序调用的,如果想实现其他程序和我们的程序可以共享这个Activity的实例,应该如何实现呢?使用前面3种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下,会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。

可以通过在AndroidManifest.xml中通过给标签指定android:launchMode指定启动模式。

  1. 标准模式:standard
    每启动一次Activity, 就会创建一个新的Activity实例并置于栈顶。谁启动了这个Activity, 那么这个Activity就运行在启动它的那个Activity所在的栈中。
  2. 栈顶复用模式:singleTop
    如果需要新建的Activity位于任务栈栈顶, 那么此Activity的实例就不会重建, 而是重用栈顶的实例。
    如果栈顶不是新建的Activity,就会创建该Activity新的实例, 并放入栈顶。
  3. 栈内复用模式:singleTask
    该模式是一种单例模式, 即一个栈内只有一个该Activity实例。 该模式, 可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中, 即singleTask的Activity可以指定想要加载的目标栈。 singleTask和taskAffinity配合使用, 指定开启的Activity加入到哪个栈中。

在这种模式下, 如果Activity指定的栈不存在, 则创建一个栈, 并把创建的Activity压入栈内;
如果Activity指定的栈存在, 如果其中没有该Activity实例, 则会创建Activity并压入栈顶;
如果其中有该Activity实例, 则把该Activity实例之上的Activity杀死清除出栈, 重用并让该Activity实例处在栈顶, 然后调用onNewIntent()方法。
4. 单例模式:singleInstance
该种启动模式下,会启用一个新的返回栈管理此种活动,以满足不同应用共享某活动的情况。
作为栈内复用模式( singleTask) 的加强版,打开该Activity时, 直接创建一个新的任务栈, 并创建该Activity实例放入新栈中。 一旦该模式的Activity实例已经存在于某个
栈中, 任何应用再激活该Activity时都会重用该栈中的实例。

可以在启动Activity时, 通过Intent的addFlags()方法设置启动模式,启动模式对应的Flags:
(1)FLAG_ACTIVITY_NEW_TASK 其效果与指定Activity为singleTask模式一致。
(2)FLAG_ACTIVITY_SINGLE_TOP 其效果与指定Activity为singleTop模式一致。
(3)FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的Activity, 当它启动时, 在同一个任务栈中所有位于它上面的Activity都要出栈。 如果和singleTask模式一起出现,若被启动的Activity已经存在栈中, 则清除其之上的Activity, 并调用该Activity的onNewIntent方法。 如果被启动的Activity采用standard模式, 那么该Activity连同之上的所有Activity出栈, 然后创建新的Activity实例并压入栈中。

活动的优先级

资源内存不足导致优先级低的互动会被杀死。从高到低:
前台活动
可见但非前台活动
后台活动

常见面试问题:

说下Activity生命周期?
Activity的启动流程?
onStart 和 onResume、onPause 和 onStop 的区别?
Activity A 启动另一个Activity B 会调用哪些方法?如果B是透明主题的又或是个DialogActivity呢?
此问题考察当活动A依然可见时,onStop()是不会调用的。
Activity A跳转Activity B,再按返回键,生命周期执行的顺序?
横竖屏切换,按home键,按返回键,锁屏与解锁屏幕,跳转透明Activity界面,启动一个 Theme 为 Dialog 的 Activity,弹出Dialog时Activity的生命周期?

说下onSaveInstanceState()方法的作用 ? 何时会被调用?
onSaveInstanceState(),onRestoreInstanceState的调用时机?
Activity的onNewIntent()方法什么时候会执行?
onCreate和onRestoreInstance方法中恢复数据时的区别?

activity的启动模式和使用场景?
activty间传递数据的方式?
Activity之间传递数据的方式Intent是否有大小限制,如果传递的数据量偏大,有哪些方案?
显示启动和隐式启动?
跨App启动Activity的方式,注意事项?

scheme使用场景,协议格式,如何使用?
用于定义本Activity能够响应的启动申请所携带的协议格式。

ANR 的四种场景?
耗时2个月,终于串通ANR的一切
Activity任务栈是什么?
有哪些Activity常用的标记位Flags?
Activity的数据是怎么保存的,进程被Kill后,保存的数据怎么恢复的?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值