作者Talent•C
转载请注明出处
前言
上篇文章介绍了Android Studio
开发环境的 安装使用、项目工程创建、工程目录结构和log窗口 的使用技巧等;那么,今天我们来学习Android
四大组件之一的Activity
组件,涉及到的代码,希望大家可以自己敲一遍,这样印象会更加深刻一些。
Activity
(活动)是什么?
Activity(活动) 是一种可以包含用户界面的组件,主要用于和用户进行交互的。一个应用程序可以包含零个或者多个,但不包含的应用程序非常少,毕竟一个应用程序是给用户使用,可以让用户看得见的。其活动与概念与OC
中的 UIViewController 基本一致。
Activity
如何创建?
1、创建一个ActivityTest
的项目工程,在 “Add an Activity to Mobile” 页面”选择 “Add No Activity” , 点击 “Finish” 等待一会,项目编译成功,将项目目录从默认使用 Android” 模式改成 “Project” 模式,我们在 “app/src/main /java/com.activitytest.chuliangliang.activitytest(项目包名)” 目录下没有任何Activity
,原因是我们上面选择的是 “Add No Activity” 选项。下面我们手动创建Activity
。
2、右键”com.activitytest.chuliangliang.activitytest”(或者Mac 下 command+n) “new” –> “activity” –>”Empty Activity”, 此时会出现一个界面,分别有如下可操作的地方:
- Activity name: 新建的
Activity
的文件名。 - layout name: 对应的UI布局文件。
- Package name: 包名(一般默认即可, 也可自行修改)。
- Generate layout file: 是一个复选框, 表示是否创建布局文件
- Launcher Activity: 是一个复选框,表示是否将当前
Activity
设置为项目的主Activity
。 - Backwards Compatibility(Appcompat): 是一个复选款,代表是否为项目启用向下兼容的模式。
我们将活动名设置为 FirstActivity , 取消创建布局文件(“Generate layout file”),取消作为主活动(“Launcher Activity”),选中向下兼容(“Backwards Compatibility(Appcompat)”);为了更加了解如何创建活动,我们将手动一步一步创建。
创建完活动后,我们打开新创建的活动会有如下代码
可以看到 “onCreate”方法很简单,就是调用了一下父类的方法,这是系统默认为我们实现的,后面我们可以在这里加一些我们自己的代码。
3、接下来我们为这个活动创建一个布局文件
在”app/src/main/res” 目录中右键–> “New” –> “Directory” 创建一个名为”layout”的目录用来存放项目的所有的布局文件,然后在这目录上右键 –> “Layout reseource file” 创建一个名为”first_layout”的文件 根源元素现在为”LinearLayout”(线性布局) 默认。
创建完成打开这个文件,在你工作区上是一个可视化的UI控件界面的操作区域,我们可以在底部通过”Design/Text”,切换文件打开方式,我们使用”text”的方式打开,同时我们可以在右侧”Preview”区域实时预览UI样式。
使用text模式打开后我们会看到
此时右侧预览区域显示啊Activity
效果为空白的,我们在布局文件添加如下代码
此时右侧预览区域中的Activity
上会显示一个button。关于布局约束我们后面会慢慢来看。
布局文件我们创建好了,可是问题来了, Activity
是如何加载这布局的呢? 打开 FirstActivity 文件,
添加如下代码:
目前为止Activity的基本创建工作完成了,那么这个Activity
如何在项目中加载的呢?
4、打开”AndroidManifest.xml”文件 ,这个文件里包含了整个项目的配置及需要注册的活动,不过这里我们已经看到Android Studio
已经帮我们注册好了。
可以看到,所有注册活动的声明都是放在<application>
标签内的,这一步是由Android Studio
自动帮我完成,在<activity>
标签中使用”android:name”来指定是哪一个活动,这里的”.FirstActivity” 是 “com.activitytest.chuliangliang.activitytest.FirstActivity”的简写,因为在package属性中已经表明了包名,所以这里可以简写成”.FirstActivity”。虽然我们向程序中注册了活动,但是我们并没有指定首先启动的是哪一个活动,所以程序还是不能启动的,配置主活动的方法在上篇文章我们已经有过了接触,就是在<activity>
中添加<intent-filter>
标签并在这个标签中添加 <action android:name="android.intent.action.MAIN" />
和 <category android:name="android.intent.category.LAUNCHER" />
属性即可。
代码示例:
这样的话我们的”FirstActivity”就是我们的主活动了,我们来运行一下程序,点击桌面的程序图标就可以启动程序了,如果程序中只注册了活动但没有指定主活动,这个程序是可以正常安装的,但是无法再启动器中看到或打开这个程序,这种程序通常作为第三方服务为其他程序提供服务的,如支付宝的快捷支付。
在这里补一下关于Android Studio
的运行程序的操作介绍,在上篇文章中忘记介绍这个了, 是我疏忽了,大伙多多担待~~~
顶部工具栏中 有一个锤子的图标,作用是编译项目, 在其后面有一个下拉框我们可以选在运行的程序,我们这里选在app, 在其右侧有一个三角的按钮,点击它开始运行程序。
程序运行起来后,我们会在屏幕上看见一个名字为 Button1 的按钮。
现在你已经掌握了手动创建Acticity
的全部过程了。是不是还有一些小激动呢~ 接下来我们让这个程序更有意思一点。
Activity
中使用Toast控件
Toast 是 Android
中提供的一种非常好的提醒方式,可以将一些短小的信息提示给用户,一段时间后自动消失,不会占用屏幕空间。使用Toast必须要现有一个触发点,那我们正好借助屏幕上的button(按钮),点一下按钮显示一下提示,Android
中为按钮添加点击事件有两种方式,一种是通过布局文件中设置按钮事件;另一种是使用代码设置,我们这里使用代码设置点击事件,关于第一种方法我们在学习UI控件用法的时候会详细介绍。
代码示例:
再次运行,点击按钮在屏幕底部出现Toast提示, 内容为”点击我了”,过一会会自动消失。这就是Toast控件的使用方法。
findViewById()
获取布局文件中定义的元素控件,这里我们传入 R.id.button_1 来得到按钮实例,button_1 值是在布局文件中通过 “android:id” 设置的属性值, 然后我们通过按钮的 “setOnClickListener()” 为按钮注册一个监听器,点击按钮是就会执行监听器中的 onClick() 方法,从而弹出 “Toast”。
“Toast” 是通过静态方法 “makeText()” 创建的,然后调用 “show()” 方法显示, “makeText()” 需要三个参数,第一个就是 Context对象, 活动本身就是一个 Context对象, 第二个是显示的文本内容, 第三个是显示的时间,有两个内置常量可以选择 Toast.LENGTH_SHORT 和 Toast.LENGTH_LONG。
Activity
的销毁
我们已经学会了如何创建一个活动,那么我们又有何销毁一个活动呢?
销毁一个活动很简单,只要按一下Back键就可以想回当前活动了,不过有时希望点击按钮等方式可以销毁一个活动,我们就需要借助活动的一个 finish() 方法了,我们在活动中调用一下 finish() 方法就可以销毁当前活动了。
我们将上述按钮的时间修改为如下代码:
运行程序,点击按钮会退出到桌面,当前活动已销毁。
Activity
间的切换
只有一个活动的应用是不是太过于简单了,一般的程序都是由多个活动组成,不管我们创建多少个活动创建的过程都是一样的,唯一的区别就是不需要设置为主活动。
我们创建第二个活动名称为 SecondActivity, 我们这回选在自动创建布局文件,创建过程就不在介绍了。创建完后发现第二个活动布局文件和我们刚刚创建的不一样,是因为默认的布局约束不是线性布局,我们将第一个活动的布局文件中的内容全部拷贝出来替换第二个布局文件的内容,修改一下按钮的文字和id属性,我们在第二个活动的 onCreate() 中发现系统自动帮我们加载了布局文件了, 同时在 AndroidManifest.xml 文件中页自动帮我们注册了第二个活动, 是不是很方便啊。
Intent
是Android
程序中各个组件进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent
一般可被用于启动活动、启动服务以及发送广播等场景,我们这里暂时不介绍服务和广播。活动间的切换(跳转)使用 Intent
进行,活动间的切换有两种跳转方式: 显式Intent 和 隐式Intent。
1.显式Intent:Intent
有多个构造函数的重载,其中 Intent(Context packageContext,Class <?> cls)。这个构造函数接收两个参数,第一个参数启动活动的上下文,第二个参数想要启动的目标活动。然后使用 startActivity() 方法启动活动,代码示例如下:
运行程序,点击按钮就可以跳转到第二个活动了。
2.隐式Intent:
相对于 显式Intent,隐式Intent 比较含蓄,它并不明确指明要启动哪一个活动,而是指定了一系列更为抽象的 action 和 category 等信息,由系统去分析找出合适的活动并启动。
那么系统是如何找出合适的活动呢? 系统会根据我们初始化 Intent 时,传如的 action 和 category 等信息来确定是哪一个活动。
我们需要在 AndroidManifest.xml 文件中指定 SecondActivity 的 action 和 category 属性。
代码示例:
设置了 SecondActivity 的 action 和 category 属性后我们需要跳转了,我们还是在 FirstActivity 点击按钮跳转到 SecondActivity 中,代码示例:
运行程序,点击按钮跳转到 SecondActivity。
注意:
使用 隐式Intent 时要注意初始化Intent时使用的 “com.activitytest.chuliangliang.ACTION_START” 和 Category 字符串要与 AndroidManifest.xml 中设置完全一致,否则会报错导致程序死掉。
拓展:
隐式Intent 有很多功能,使用 隐式Intent 不仅可以启动自己程序中的活动还可以启动其他程序的活动,使得Android
程序间的功能共享称为可能,比如调用浏览器去显示网页,就没必要自己再去实现一个浏览器只需要调用系统浏览器就好。
代码示例: 我们在 SecondActivity 中添加一个按钮,点击这按钮跳转浏览器
SecondActivity 的布局文件内容:
SecondActivity 中为按钮添加点击事件, 打开浏览器 并访问 http://baidu.com 网址:
运行程序,进入 SecondActivity 中点击按钮就会打开浏览器页面并加载网址,隐式Intent 可以设置传递的数据类型,这里就不一一演示了。
Activity
间传递数据
有时我们在Activity
切换的同时还需要传递一些数据,Intent 传递数据有两种: 向下一个活动传递数据 和 返回数据给上一个活动。
1.向下一个活动传递数据:Intent
在启动活动时可以传递数据,Intent
中提供了一系列 putExtra()
方法的重载,可以把我们想要传递的数据暂存在Intent
中,启动一个活动后,只需要把这些数据从Intent
中读取出来即可。
我们新建一个Activity
名为 “ThirdActivity”, 在 SecondActivity 添加按钮, 点击按钮启动 ThirdActivity, 并传递一个字符串过去,然后在 ThirdActivity 中打印出来。
代码示例:
SecondActivity 中 新添加的按钮点击事件:
ThirdActivity 中接收数据代码:
运行程序,点击 SecondActivity 中的按钮 跳转到 ThirdActivity 中,我们可以看到屏上会显示Toast提示,同时也可以在log窗口看到印出来了我们传递的字符串。
Intent
中的putExtra()
通过key存放需要传递的数据, 数据可以有很多中数据类型的数据,例如字符串、基本数据类型等等,具体可以查阅API文档;在另外一个活动通过这个key取出数据,例如我传递的是字符串所以我们通过getStringExtra()
方法取出数据。
2.返回数据给上一个活动:
既然Intent
可以向下一个活动传递数据,那么能不能将数据返回给上一个活动呢? 答案是肯定的。不过并不是用启动活动的方式传递数据,查看api你会发现还有另外一个启动活动的方法:startActivityForResult()
,通过名字就可以猜到带有返回值的启动方式。
我们将 SecondActivity 跳转代码稍作修改,代码示例
我们在 ThirdActivity 中接收 SecondActivity 传递过来的数据并输出数据;在 ThirdActivity 中点击按钮返回到 SecondActivity 中并输出返回给 SecondActivity 的数据。
ThirdActivity 中代码示例:
那么现在 ThirdActivity 中已经向 SecondActivity 返回数据了, 在 SecondActivity 中如何接收数据呢?接下来我们看看 SecondActivity 如何接收数据,其实简单只需要重写Activity
的 onActivityResult()
方法,重写方法可以使用 control + O (windows 系统: Ctrl+O)快捷键会弹出一个对话框,在里面找到 onActivityResult()
回车即可。
代码示例:
运行程序,我们从 SecondActivity 进入 ThirdActivity ,可以打印传递的数据信息, 然后点击 ThirdActivity 中的按钮返回到 SecondActivity 我们页接收到了传递回来的数据。
拓展:
我们是点击 ThirdActivity 中的按钮退回到 SecondActivity 中的,但是实际运用中用户还可以通过点击手机上的 Back键 返回 SecondActivity,我们该怎么办? 我们将上述代码稍加改动,重写Activity
的 onBackPressed()
方法,此方法会在用户点击Back键时调用。
代码示例:
再次运行程序,返回点击back键返回到 SecondActivity 时同样可以得数据。
Activity
的生命周期
掌握活动的生命周期对于任何 Android开发者 来说都是非常重要的,当你了解了Activity
的生命周期后,就可以写出更加连贯流畅的程序,并在如何管理应用资源方面发挥的游刃有余,你的程序将会拥有更好的用户体验。
返回栈:
通过上面的示例,大家可以看出来多个Activity
是可以层叠的,每启动一个活动都是覆盖在原有活动的上面,点击Back键时销毁最上面的活动,下一个活动就会重新显示在屏幕上。Android
是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被成称作返回栈(Back Stack)。栈是一种先进后出的数据结构,在默认情况下,每当我们新启动一个活动时,它会在返回栈中入栈,并处于返回栈最顶端,当我们按下 Back键 或者调用 finish() 方法去销毁一个活动时,处于栈顶的活动就会出栈,这时前一个入栈的活动会重新回到栈顶的位置,系统就会显示处于栈顶的活动给用户。
活动的状态:
每个活动在其生命周期中最多会有四中状态: 运行状态 、暂停状态 、停止状态 、销毁状态。
(1)运行状态:
当一个活动位于返回栈顶时,这时活动就处于活动状态。系统最不愿意收回的就是处于运行状态的活动,因为会给用户带来非常差的用户体验。
(2)暂停状态:
当一个活动不再处于栈顶位置,但仍可见,这时活动就处于暂停状态。如在这个活动上面存在一个非全屏的对话框形式的活动时,这个活动就会处于暂停状态;系统也不愿意回收此种状态的活动,只有在内存极低的情况,系统才会考虑回收。
(3)停止状态:
当一个活动不再处于栈顶,并且完全不可见,这时就进入了停止状态。系统会保存活动的相应状态和临时数据等信息,但这不是完全可靠的,当内存不足时可能被系统回收。
(4)销毁状态:
当一个活动从返回栈移除后就变成了销毁状态。系统最倾向将处于这种状态的活动回收。
活动的生命周期:Activity
定义了7个回调方法,覆盖了活动的生命周期的每个环节。
(1)onCreate():
这个方法我们都已经看过很多次了,每次创建Activity
时都在里面做了一些操作,它会在活动第一次被创建的时候调用。一般在它中做一些初始化操作。
(2)onStart():
这个方法在活动由不可见变为可见的时候调用。
(3)onResume():
这个方法在活动准备好和用户进行交互的时候调用,此时活动一定处于返回栈顶部,并且处于运行状态。
(4)onPause():
这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法执行一定要快,不然会影响都新的栈顶的活动的使用。
(5)onStop():
这个方法在活动完全不可见的时候调用,它和onPause()
方法主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()
方法是不会执行的。
(6)onDestory():
这个方法在活动将要被销毁时调用,之后活动的状态变为销毁状态。
(7)onRestart():
这个方法是活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
以上7个方法中除了onRestart()
方法,其他都是两两相对的,从而又可以将活动分为 三种生存期: 完整生存期 、可见生存期 和 前台生存期。
(1)完整生存期:
活动在onCreate()
方法和 onDestory()
方法之间所经历的,就是完整生存期,一般会在onCreate()
方法中执行各种初始化操作,在onDestory()
方法中执行释放内存的操作。
(2)可见生存期:
活动在onStart()
方法 与 onStop()
方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便可能无法与用户进行交互。我们可以通过这两个方法合理管理那些对用户可见的资源。比如在onStart()
加载资源,在onStop()
释放资源,从而保证处于停止状态的活动不会占用过多资源。
(3)前台生存期:
活动在onResume()
方法 与 onPause
方法之间所经历的,就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时活动是可以与用户进行交互的,我们平时看到和接触最多的就是这个状态下的活动。
这里建议大家可以动手写写代码(在Activity
中重写上面的7中方法即可),亲自体验一下活动的生命周期,充分了解活动的生命周期的一些列过程。
这里给大家一段代码示例用来改变一个普通的Acitivity
样式 使其变为对话框的样式(类似于OC
中 UIAlertView )。
创建一个名为”DialogActivity”的Activity
实践应用: 活动被回收了怎么办?
我们在上面提到过,在活动进入停止状态时,是有可能被系统回收的。那么在如下场景,在活动A中启动活动B,活动A进入了停止状态,此时如果内存极度不足,系统就会将活动A回收了,然后用户按下Back键,返回到活动A,会出现什么问题呢? 其实还是会显示活动A的,只不过并不会执行活动A的onStart()
方法,而是执行活动A的onCreate()
方法,因为活动A在这种情况下会被重新创建一次。
这里看上去没什么问题,但是有一个重要问题,活动A中可能存在的临时数据和状态都会丢失了,例如活动A中有一个文本输入框,里面输入了一些文字,启动了活动B,然后出现内存不足活动A被回收,然后这时用户点击Back键返回活动A中发现文本框内刚才输入的文字没有了,原因就是活动A被重新创建了。
问题我们知道了,那应该如何解决呢?
通过查阅文档可以看出Activity
提供了一个onSaveInstanceState(Bundle outState)
回调方法,这个方法可以保证活动在回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时得不到保存的临时数据的问题。
代码示例:
onSaveInstanceState(Bundle outState)
方法中会携带一个参数 Bundle
类型,Bundle
提供了一些列保存数据的方法。如使用outState.putString()
保存字符串,此方法需要两个参数第一个是key,第二个是值,与Intent
传递数据类似;数据是保存下来了,那么我们应该在哪里进行恢复呢? 不知道大家有没有注意到在onCreate(Bundle savedInstanceState)
方法中也携带了一个Bundle
类型参数,这个参数就是保存我们之前保存的所有数据,我们只需要从里面读取出来就可以了,如果之前未保存过数据,savedInstanceState
为null
。
代码示例:
细心的你一定会发现,Bundle
对数据的保存和读取与Intent
传递数据的很相似,其实Activity
间传递数据可以用Intent
与Bundle
结合起来使用,具体的代码我就不演示了,大家自己试试看。
Activity
的启动模式
在实际项目中我们需要根据特定的需求为每个活动指定恰当的启动模式。启动模式一共分为四种: standard(默认) 、singleTop 、singleTask 和 singleInstance。可以在 AndroidManifest.xml 文件中 <activity>
标签指定 android:lanuchMode 属性来选择启动模式。
代码示例: 将 FirstActivity 设置 standard 启动模式。
下面我们分别看一下这四种启动模式:
(1)standard:
standard 是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种模式启。我们上述所有例子都是使用这个模式启动的。在 standard (默认情况)模式下,每当启动一个新的活动,它就会在栈顶中入栈,并处于栈顶的位置,系统不会在乎这个活动是否已经在返回栈中存在。
(2)singleTop:
在某些场景下你会觉得 standard 模式不太合理。活动已经明明在栈顶了,为什么再次启动的时候还需要创建新的活动实例呢? 此时系统又提供了一种启动模式 singleTop ;singleTop 启动模式下, 在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
我们只要修改 AndroidManifest.xml 文件中 <activity>
标签的 android:launchMode 属性为 singleTop
即可为指定活动设置启动模式。
(3)singleTask:
使用 singleTop 模式可以很好的解决重复创建栈顶活动问题,但是如果上述活动并未处于栈顶,还是会创建多个活动,那么有没有办法让一个活动在整个程序的上下文中只存在一个实例呢?这就需要借助 singleTask 模式;当前活动的启动模式指定为 singleTask 模式,每次启动该活动时系统首先会在返回栈中查找是否已经创建过给活动的实例,如果发现已存在的活动实例则直接使用,并把在这个活动之上的所有活动统统出栈,如果没有就创建一个新的活动实例。
(4)singleInstance:
singleInstance 模式应该是四中模式中最特殊也是最复杂的一种了,需要多花一点时间来理解这个模式。不同于以上三种模式,指定为 singleInstance 模式的活动会启用一个新的返回栈来管理这个活动,(其实如果 singleTask 模式指定了不同的 taskAffinity,也会启动一个新的返回栈)。什么情况会使用到这模式?假如我们程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序可以共享这个活动实例时, 前三种模式肯定是做不到的,因为每个应用程序都会与自己的返回栈,同一个活动在不同返回栈入栈是必然是创建了新的实例。而使用 singleInstance 模式就可以解决这问题,这种模式会有一个单独的返回栈来管理这个活动,也就解决了实例共享的问题。
Activity
的实践技巧
关于活动的知识基本介绍完毕,不过距离熟练应用可能还是有一段距离,下面我们来看几个小技巧,这些小技巧在实际运用中会非常实用。
1.快速知晓当前是哪一个Activity:
我们进入公司时,大部分都是接手他人的项目,假如让你修改某个界面上的非常简单的东西,却花费很多时间才能找到对应的活动,我们就以 ActivityTest 这个工程为例
我们创建一个 BaseActivity 类,让所有活动都继承这个类, 因为我们不需要将 BaseActivity 在程序中注册,我们就创建一个普通的Class就好,修改父类为 AppCompatActivity
类并重写onCreate()
方法,在onCreate()
中输出活动的名字。
代码示例:
(2)随时随地快速退出程序
假如目前你停留在 ThirdActivity 页,想要退出程序,需要点击至少3次Back键才可以,按home键只是把程序挂起,并不是退出程序。如果我们程序需要有一个注销或者退出的功能怎么办?
解决思路非常简单,只要用一个集合将创建的活动保存起来,进行管理就可以了。
我们创建一个ActivityController
类作为活动的集合管理器,代码如下
接下来我们修改一下 BaseActivity 类的代码:
运行程序进入到 ThirdActivity 页面点击退出程序按钮,在按钮事件中调用ActivityController.finishAll();
即可退出程序。
(3)启动活动的最佳写法
相信我们启动活动的方法大家都非常熟练了,但是在实际开发中往往会遇到合作的问题,比如你开发 FirstActivity 活动中的功能, 其他队友开发 SecondActivity 界面, 当 FirstActivity 跳转 SecondActivity 时需要传递数据,如果此时还是按照以前的方式去写,那么开发 SecondActivity 的人需要到 FirstActivity 中查看传递数据是什么格式及key 是什么,比较麻烦。那么我们在 SecondActivity 添加一个静态方法用来构建Intent
并启动 SecondActivity。
代码示例: SecondActivity 中的代码
修改 FirstActivity 中启动 SecondActivity 的代码。
代码示例:
现在只需要一行代码就可以启动 SecondActivity。一些代码上的使用技巧就介绍这么多吧。
总结
学习总是很累的,但是学习也会使我更加充实,以上就是我学习Android
中Activity
组件的学习笔记,学习的时只用了半天时间,整理笔记却用了1天时间,虽然是在整理笔记,但在不知不觉间是使自己对于Activity
的理解更加深入和扎实了,学习不光是大脑的事情,学习也是手的事情,我们要在学习中多动脑,多动手,深入实际的去编写代码。
本文使用的全部代码示例Demo 下载