Activity
主要负责与用户的交互
新建activity时选项有三:Generate Layout File、Launcher Activity、Backwards Compatibility。
- Generate Layout File : 自动为新Activity创建对应布局文件。
- Launcher Activity : 是否将新Activity设置为主Activity。
- Backwards Compatibility : 是否让项目启用向下兼容的模式。
设计布局
<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" //引用id则无+,定义id则需要
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"/>
</LinearLayout>
//在setContentView()中传入布局id来为activity加载布局
setContentView(R.layout.first_layout)
注册Activity
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.activitytest">
<application
...>
//表明是为FirstActivity注册,因为前面已经给定包名,因此省略
<activity android:name=".FirstActivity"
//指定标题栏内容
android:label="This is FirstActivity">
<intent-filter>
//将此Activity作为主Activity,在应用被点击打开时先打开此Activity
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Toast
提供短小信息提醒工具
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
//使用Toast前需要为其找到一个触发点,通过findViewById()方法获取布局文件中的元素的实例
//findViewById返回一个继承自View的泛型对象,因此需要显式声明为Button对象
//但由于已经引入了kotlin-android-extensions插件,
//此插件根据布局文件中定义的控件id自动生成了一个具有相同名称的变量
//因此可直接使用该变量,无需使用findViewById
val button1: Button = findViewById(R.id.button1)
//注册监听器
button1.setOnClickListener {
//创建出一个Toast对象 传入上下文、文本内容、显示时间作为参数,通过show方法显示
//Activity为Context对象,即上下文,传入this
//显示时间有内置常量可选:Toast.LENGTH_SHORT,Toastt.LENGTH_LONG
Toast.makeText(this, "You clicked Button 1", Toast.LENGTH_SHORT).show()
}
}
添加菜单
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {//同样隐含语法糖 背后调用getItemId
R.id.add_item -> Toast.makeText(this, "You clicked Add",
Toast.LENGTH_SHORT).show()
R.id.remove_item -> Toast.makeText(this, "You clicked Remove",
Toast.LENGTH_SHORT).show()
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//添加菜单,此处隐含kotlin语法糖,即背后调用get和set方法。
//menuInflater实际上是调用了父类的getMenuInflater()方法得到的MenuInflater对象,
//再调用inflate方法,
//参数1表示使用哪个资源文件来创建菜单,
//参数2表示将菜单项添加到哪个Menu对象当中。
menuInflater.inflate(R.menu.main,menu)
return true
}
销毁Activity
button1.setOnClickListener {
finish()//效果同按back
}
使用Intent穿梭Activity
指明当前组件想要执行的动作,还可在不同组件间传递数据、
启动Activity、启动Service、发送广播等...
Intent又分显式和隐式
显式使用Intent
button1.setOnClickListener {
//Intent有多个构造函数的重载,以下为其中一种,接受两个参数,
//参数1为启动Activity的上下文,
//参数2为目标Activity,
//此构造函数构建出Intent的“意图”
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
}
隐式使用Intent
不明确指定想要启动哪个Activity,而是指定了Action、Category等信息,让系统判断应该启动哪个Activity。
<activity android:name=".SecondActivity" >
<intent-filter>
//指明SecondActivity可以响应
//com.example.activitytest.ACTION_START这个action
<action android:name="com.example.activitytest.ACTION_START" />
//<category>标签则包含了一些附加信息,
//更精确地指明了当前Activity能够响应的Intent中还可能带有的category。
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
只有当Intent中的action和category都精准命中时,才能启动该Activity
button1.setOnClickListener {
//此时仍可命中,因为category是default,
//而startActivity则是自带默认category
//1个intent只可指定一个action,但可指定多个category
val intent = Intent("com.example.activitytest.ACTION_START")
//没有满足条件的activity可被启动
intent.addCategory("com.example.activitytest.MY_CATEGORY")
startActivity(intent)
}
隐式Intent还可打开其他应用的Activity或者打开网页
button1.setOnClickListener {
//Intent.ACTION_VIEW为系统内置的一个动作
val intent = Intent(Intent.ACTION_VIEW)
//Uri.parse将字符串转化为Uri对象,再通过setData方法传入Intent
intent.data = Uri.parse("https://www.baidu.com")
startActivity(intent)
}
指定Activity能够响应的数据
//data标签内可添加内容
android:scheme。用于指定数据的协议部分,如上例中的https部分。
android:host。用于指定数据的主机名部分,如上例中的www.baidu.com部分。
android:port。用于指定数据的端口部分,一般紧随在主机名之后。
android:path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内
容。
android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
<activity android:name=".ThirdActivity">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
//使得ThirdActivity可以响应一个打开网页的intent,但as认为所有能响应action.VIEW的activity需要在category添加BROWSABLE(实现deep link)
<data android:scheme="https" />
</intent-filter>
</activity>
使用Intent传输数据
//发送数据
button1.setOnClickListener {
val data = "Hello SecondActivity"
val intent = Intent(this, SecondActivity::class.java)
//作为键值对,参数1是键值,参数2为数据
intent.putExtra("extra_data", data)
startActivity(intent)
}
//传输数据
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.second_layout)
//此处调用父类的getIntent,获取启动SecondActivity的intent,再通过getExtra获取数据
val extraData = intent.getStringExtra("extra_data")
Log.d("SecondActivity", "extra data is $extraData")
}
}
返回数据到上一个Activity
//startActivityForResult()
button1.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
//参数1为intent,参数2为请求码,在回调中判断数据的来源,且值唯一
startActivityForResult(intent, 1)
}
//返回数据
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.second_layout)
button2.setOnClickListener {
val intent = Intent()
intent.putExtra("data_return", "Hello FirstActivity")
//向上一个Activity返回数据
//参数1一般为RESULT_OK或RESULT_CANCELED
//参数2为intent
setResult(RESULT_OK, intent)
//销毁Activity
finish()
}
}
}
//在Activity被销毁后,会回调发起跳转的Activity中的onActivityResult(),
//参数1为请求码,参数2为处理结构
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
1 -> if (resultCode == RESULT_OK) {
val returnedData = data?.getStringExtra("data_return")
Log.d("FirstActivity", "returned data is $returnedData")
}
}
}
//以上操作为按压按钮时才能出现,如果按返回键则无法触发,因此重写onBackPressed()
override fun onBackPressed() {
val intent = Intent()
intent.putExtra("data_return", "Hello FirstActivity")
setResult(RESULT_OK, intent)
finish()
}
Activity生命周期
- 返回栈
android中的activity是可以层叠的,每启动一个就会启动一个新的activity覆盖在旧的activity上,按back则可销毁,旧activity则会重新出现。
实际上android使用task(任务)来管理activity的,一个任务即一组存放在栈内的activity的集合,该栈被称为返回栈。
每当按下Back或调用finish,就会销毁一个activity,使得处在栈顶的activity就会出栈,前一个入栈的activity就会重新处于栈顶位置,系统总是显示位于栈顶的activity给用户
- 状态
运行状态:当一个activity处于栈顶时即是处于运行状态。
暂停状态:activity不处于栈顶,但仍可视,仍存活。除非内存极低,不然不会被回收。
停止状态:activity不处于栈顶,且不可视。系统为该activity保留相应状态和成员变量。在其他地方需要内存时,该activity可能会被收回。
销毁状态:activity从返回栈中被移除就进入销毁状态 - 生存期
onCreate(): 在activity第一次被创建时调用,用于完成activity的初始化操作,如加载布局,绑定事件等。
onStart():在activity由不可见变为可见时调用。
onPause():在系统启动或恢复另一个activity时调用此方法,该方法通常会将一些消耗cpu的资源释放掉,并保存一些关键数据。该方法执行速度需要快,避免影响新的栈顶activity的使用。
onStop(): 在activity完全不可见时调用,当启动的activity是对话框式的activity,则调用onPause,而onStop不会被调用
onDestory():在activity被销毁前调用,之后activity会变为销毁状态
onRestart():在activity由停止状态变为运行状态前调用,即activity被重新启动
activity还能分为三种生存期
- 完整生存期:在onCreate()到onDestory()之间经历的就是完整生存期,onCreate完成各种初始化操作,onDestory完成释放内存的操作。
- 可见生存期:onStart()到onStop()之间经历的就是可见生存期,在此期间,activity总是对用户可见,即使无法与用户交互,但仍可通过这两个方法分别进行加载资源和释放资源。
- 前台生存期:onResume和onPause之间为前台生存期,在此期间activity总为运行状态,与用户进行交互。
以上各种on方法调用顺序:
当打开activity1时,依次调用onCreate、onStart、onResume,
跳转打开activity2,a1依次调用onPause、onStop,
返回a1,a1依次调用onRestart、onStart、onResume,
在a1打开对话框式activity3,a3仅部分遮挡,因此a1仅调用onPause,
关闭a3后,a1仅调用onResume,
按back 销毁a1,a1调用onPause、onStop、onDestory。
恢复被销毁Activity的数据
activity调用onStop可能因为内存不够被销毁了,想要保存数据,需要重写onSaveInstanceState
//bundle有点雷同intent,以键值对形式保存数据
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val tempData = "Something you just typed"
outState.putString("data_key", tempData)
}
//onCreate 参数内存bundle,默认为null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(tag, "onCreate")
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
//读出bundle中的数据,即可自行选择重新填入
val tempData = savedInstanceState.getString("data_key")
Log.d(tag, "tempData is $tempData")
}
}
Activity的启动模式
- standard:默认启动模式,新activity进入栈中并被置于栈顶,因此会出现不断创建同一个新的activity覆盖在旧的但完全相同的activity上。
- single top:当需创建的新activity已经在栈顶有了,则不再创建,直接使用。但当被创建的activity不在栈顶则还是会出现同样的问题。
<activity
android:name=".FirstActivity"
//修改启动模式
android:launchMode="singleTop"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- single task:创建新的activity时,会检查是否已经存在于栈中,如果存在,则直接使用,且将该activity上面的所有activity全部出栈。
例:a1启动,跳转a2,在a2再跳转a1,a1调用的是restart,且此时a2调用destory被销毁。 - single instance:使用一个新的栈来管理该activity,如a2使用single instance来启动activity,a1,a3为默认,
从a1跳转至a2,则此时出现两个栈,栈1栈顶为a1,栈2栈顶为a2,
在a2启动a3,a3则处于栈1栈顶,
此时按back返回,销毁a3,a1为栈1栈顶,因此显示在最前,
再按back,销毁a1,此时栈1为空,因此显示栈2栈顶的a2,
再按back,销毁栈2,退出应用。
Activity的最佳实践
- 知道当前处于哪个activity:自定义基类BasicActivity,在构造函数里添加:
//获取当前实例的class对象,再通过simpleName获取当前实例类名
Log.d("BaseActivity", javaClass.simpleName)
再使得所有activity继承该基类,则可每跳转一个activity则可自动打印其名字
- 一次性清空所有activity
object ActivityCollector {
private val activities = ArrayList<Activity>()
//将新的activity加入唯一的arraylist中
fun addActivity(activity: Activity) {
activities.add(activity)
}
//将activity移出集合
fun removeActivity(activity: Activity) {
activities.remove(activity)
}
//销毁集合中所有activity,但清除前需要判断是否已经被销毁(back),最后销毁完还需要清空集合
fun finishAll() {
for (activity in activities) {
if (!activity.isFinishing) {
activity.finish()
}
}
activities.clear()
}
}
//在基类中添加activity单例类的方法
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BaseActivity", javaClass.simpleName)
ActivityCollector.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
}
class ThirdActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("ThirdActivity", "Task id is $taskId")
setContentView(R.layout.third_layout)
button3.setOnClickListener {
//此时便可一键销毁所有activity
ActivityCollector.finishAll()
//杀死当前进程
android.os.Process.killProcess(android.os.Process.myPid())
}
}
}
- 启动Activity最佳方法:
有时需要启动a2,需要同时传入的数据要求不清晰
class SecondActivity : BaseActivity() {
...
//与java的静态方法雷同
companion object {
fun actionStart(context: Context, data1: String, data2: String) {
val intent = Intent(context, SecondActivity::class.java)
intent.putExtra("param1", data1)
intent.putExtra("param2", data2)
context.startActivity(intent)
}
}
}
//启动时就十分简洁
button1.setOnClickListener {
SecondActivity.actionStart(this, "data1", "data2")
}
标准函数with
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
//with有两个参数,参数1为object,即任意对象,参数2为lambda,且会传入参数1的上下文
val result = with(StringBuilder()) {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
//打印出所有string
println(result)
标准函数run
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
//与with不同在不需要接受对象,且run是在某个对象的基础上调用,但都会将该对象上下文传入lambda,
//并将最后一行作为返回值
val result = StringBuilder().run {
append("Start eating fruits.\n")
for (fruit in list) {
www.blogss.cn
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
println(result)
标准函数apply
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
//整体类似,但仅能返回对象本身,因此仍需自行调用toString
val result = StringBuilder().apply {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
静态方法和单例类
//单例类,同静态方法,可通过Util.doAction()调用
object Util {
fun doAction() {
println("do action")
}
}
//非单例类,需要实例化才能使用doAction1,但仍可直接调用Util.doAction2
class Util {
fun doAction1() {
println("do action1")
}
//companion object实际上是在类的内部创建了一个伴生类,
//而kotlin又保证每个类仅有一个伴生类对象
companion object {
fun doAction2() {
println("do action2")
}
}
}
定义静态方法
以上方法不支持在java中使用,因为其并不是真正的静态方法
- 注解:给单例类或companion中的方法加上@JvmStastic注解,kotlin则会将其编译成真正的静态方法
class Util {
fun doAction1() {
println("do action1")
}
companion object {
@JvmStatic
fun doAction2() {
println("do action2")
www.blogss.cn
}
}
}
- 顶层方法:没有声明在任何类中的方法,kotlin会将所有顶层方法编译成静态方法。kotlin中直接使用函数名调用,而java中需要以 文件名.函数名来调用。(创建file类型的kotlin文件,将方法写入其中即可)