一、Activity
activity是Android的四大组件之一,也是用户打交道最多的组件。
1. 手动创建Activity
package com.example.myfirstactivity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
2. 创建xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮"/>
</LinearLayout>
3. 关联Activity与xml
setContentView(R.layout.activity_first)
4. 在AndroidManifest.xml声明Activity
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyFirstActivity"
tools:targetApi="31">
<activity
android:name=".FirstActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
5. 弹第一个Toast
val btnOne: Button = findViewById<Button>(R.id.btn_one)
btnOne.setOnClickListener {
Toast.makeText(this, "第一个Toast", Toast.LENGTH_SHORT).show()
}
6. Menu菜单
6.1 创建Menu的xml文件
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="add"/>
<item android:id="@+id/remove_item"
android:title="remove"/>
</menu>
6.2 加载Menu
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main, menu)
return true
}
6.3 给菜单添加点击事件
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add_item -> Toast.makeText(this, "添加条目", Toast.LENGTH_SHORT).show()
R.id.remove_item -> Toast.makeText(this, "移除条目", Toast.LENGTH_SHORT).show()
}
return true
}
7. 销毁Activity
7.1 xml文件中添加退出按钮
<Button
android:id="@+id/btn_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="退出"/>
7.2 添加逻辑
val btnTwo : Button = findViewById<Button>(R.id.btn_two)
btnTwo.setOnClickListener {
finish()
}
8. 意图Intent
8.1 显示意图
创建第二个Acitivity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="SecondActivity"/>
</RelativeLayout>
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
}
}
val btnThree : Button = findViewById<Button>(R.id.btn_three)
btnThree.setOnClickListener {
startSecondActivity()
}
fun startSecondActivity() {
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
}
8.2 隐式意图
给SecondActivity添加action和category
<activity
android:name=".SecondActivity"
android:label="SecondActivity"
android:exported="false">
<intent-filter>
<action android:name="com.example.myfirstactivity.FIRST_ACTION"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
定义使用隐式意图启动SecondActivity
fun startSecondActivityOtherWay(){
val intent = Intent("com.example.myfirstactivity.FIRST_ACTION")
startActivity(intent)
}
此时编译器报错,也没看懂错误原因,试着运行此代码,发现并不能启动SecondActivity,有错误日志
2025-05-14 07:49:08.329 14293-14293 AndroidRuntime com.example.myfirstactivity E FATAL EXCEPTION: main
Process: com.example.myfirstactivity, PID: 14293
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.myfirstactivity.FIRST_ACTION }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2239)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1878)
at android.app.Activity.startActivityForResult(Activity.java:5589)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:780)
at android.app.Activity.startActivityForResult(Activity.java:5547)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:761)
at android.app.Activity.startActivity(Activity.java:6045)
at android.app.Activity.startActivity(Activity.java:6012)
at com.example.myfirstactivity.FirstActivity.startSecondActivityOtherWay(FirstActivity.kt:36)
at com.example.myfirstactivity.FirstActivity.onCreate$lambda$3(FirstActivity.kt:30)
at com.example.myfirstactivity.FirstActivity.$r8$lambda$OVRtJuvI0FAybpzROaxsnBzOdwk(Unknown Source:0)
at com.example.myfirstactivity.FirstActivity$$ExternalSyntheticLambda3.onClick(D8$$SyntheticClass:0)
at android.view.View.performClick(View.java:7659)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1213)
at android.view.View.performClickInternal(View.java:7636)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:30156)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
可以看到并没有找到action为"com.example.myfirstactivity.FIRST_ACTION"的Activity,经过查询资料得知:
从Android 14开始,隐式Intent只能传递给已声明为导出(android:exported=“true”)的组件8。若需向未导出组件发送Intent,必须使用显式Intent,否则系统将抛出安全异常。
修改AndroidManifest.xml代码如下
<activity
android:name=".SecondActivity"
android:label="SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="com.example.myfirstactivity.FIRST_ACTION"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
代码中编译不报错了,也可以正常启动SecondActivity。
既然修改exported参数后,能解决问题,那得了解一下exported。
exported 用于声明Android 的四大组件能否被其它应用隐式调用
当为true 时,表示可以被其它应用隐式调用;
当为false时,表示不可以被其它应用隐式调用。
总结:为了避免有歧义,建议声明四大组件时,都进行显示声明是否可以被其它应用隐式调用。
通过以上步骤,可以使用隐式意图启动一个Activity。在第一步时,不仅添加action,还添加了category。它的作用是什么?在后面启动SecondActivity时,并没有看到使用它,为什么也可以正常启动Activity呢?
category是用于补充说明组件的意图。它可以包含多个,当Intent中所有category均被匹配时,才可以触发组件行为。在上面的例子中,我们声明了,它是category 的默认值,当启动SecondActivity时,startActivity会自动添加该category到Intent中,所以没看到使用它,但是也可以正常启动SecondActivity。下面我们再给SecondActivity添加一个category,尝试启动SecondActivity。
fun startSecondActivityOtherWay(){
val intent = Intent("com.example.myfirstactivity.FIRST_ACTION")
intent.addCategory("com.example.myfirstactivity.FIRST_CATEGORY")
startActivity(intent)
}
此时并没有启动成功,原因则是在AndroidManifest.xml文件中SecondActivity并没有被匹配上,因为并没有声明它,添加上它,程序即可正常运行。
在解决上述问题时,我的理解和书本有些不一样。
我认为在AndroidManifest.xml文件中,给SecondActivity添加,如果在Intent中没有添加此category,则Activity不能启动,但是当我添加完后,Intent只有Action时,一样可以启动该Activity。仔细阅读后,才发现是Intent中所有category均被匹配时,才可以触发组件行为。
8.3 其它隐式Intent的调用
在手机中打开百度网页
fun startBaiduActivity() {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.baidu.com")
startActivity(intent)
}
其中Intent.ACTION_VIEW是系统的action,它是字符串常量"android.intent.action.VIEW"。
Uri是Android统一资源标识符,此处使用它的parse函数,解析网址"https://www.baidu.com",告诉系统打开这个网页。
在手机上启动打电话页面
fun startCallActivity(){
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
}
9. Activity之间传递数据
9.1 向下一个Activity传递数据
传递数据
//请求码,用于知道数据是从哪个页面传递过来的
val REQUEST_CODE = 1
fun startActivityTransferData(){
val data = "FirstActivity data"
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("data_key",data)
startActivityForResult(intent,REQUEST_CODE)
}
接受数据
fun getFirstActivityData() {
val firstActivityData = intent.getStringExtra("data_key")
Log.e(TAG, "第一个Activity传递过来的数据是$firstActivityData" )
}
运行结果
第一个Activity传递过来的数据是FirstActivity data
9.2 返回数据给上一个Activity
在SecondActivity添加要返回的数据
private fun backData() {
val intent = Intent()
intent.putExtra("back_key", "SecondActivity Data")
setResult(RESULT_OK, intent)
finish()
}
在FirstActivity中接收返回的数据
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode){
REQUEST_CODE -> if (resultCode == RESULT_OK) {
val backData = data?.getStringExtra("back_key")
Log.e(TAG, "onActivityResult: $backData")
}
}
}
返回的结果是:
onActivityResult: SecondActivity Data
10. Activity的生命周期
10.1 返回栈
Activity可以在当前页面启动下一个页面,又在下一个页面启动下下一个页面,所有的页面就会被系统堆放在一起,当点击返回键时,页面会从最上面一个又一个的移除,通常把这些页面堆放在一起的地方叫做返回栈。
10.2 Activity的状态
- 运行状态
Activity在栈顶时,为运行状态;
- 暂停状态
Activity不在栈顶但是可见时,为暂停状态;
- 停止状态
Activity不可见时,为停止状态;
- 销毁状态
Activity从栈中移除后,为销毁状态。
10.3 Activity的生命周期
官方的生命周期图
从图中可以看出Activity的生命周期共有7个函数
- onCreate
Activity被创建时执行;
- onStart
Activity可见时执行;
- onResume
Activity可操作时执行;
- onPause
Activity不可操作时执行;
- onStop
Activity 不可见时行;
- onDestory
Activity 被销毁时执行;
- onRestart
Activity从不可见到可见时执行。
10.4 Activity异常场景下时间的保存与恢复
当Activity处于停止状态时,手机如果内存不足,系统会将该Activity的内存回收,此时如果我们想保存此Activity上的某些数据时,可以在onSaveInstanceState()函数中保存;当该界面再次可见时,可以通过onRestoreInstanceState()或者onCreate()恢复。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
//恢复数据方式二
if (savedInstanceState != null) {
val saveData = savedInstanceState.getString("save_key", "default_data")
}
}
override fun onSaveInstanceState(
outState: Bundle,
outPersistentState: PersistableBundle
) {
super.onSaveInstanceState(outState, outPersistentState)
//保存数据
outState.putString("save_key","save_data")
}
override fun onRestoreInstanceState(
savedInstanceState: Bundle?,
persistentState: PersistableBundle?
) {
super.onRestoreInstanceState(savedInstanceState, persistentState)
//恢复数据方式一
if (savedInstanceState != null) {
val saveData = savedInstanceState.getString("save_key", "default_data")
}
}
}
11. Activity的启动模式
Activity的启动模式一共有四种:
- standard(标准模式)
该模式为Activity的默认启动模式,是指所有的Activity启动时,都会被压入返回栈。
- singleTop(单一顶部模式)
此模式是指,当返回栈的顶部如果就是当前Activity时,系统并不会再重新启动一个新的Activity对象压入返回栈。
- singleTask(单一任务模式)
此模式是指,当返回栈中已有该Activity的对象时,系统不会再重新启动一个新的Activity对象,而是将已有的Activity对象置为栈顶,该Activity对象以上的Activity对象也将从该返回栈中移除。
- singleInstance(单一实例模式)
此模式是指,当将Activity设置为该模式时,启动该Activity时,系统会先创建一个返回栈单独放该Activity对象,其它Activity对象则单独在一个返回栈中。
12. Activity管理小技巧
12.1 知道当前启动的是哪个Activity
写一个BaseActivity继承ComponentActivity,让应用内所有Activity都继承BaseActivity,在BaseActivity的onCreate函数中,将当前Activity的名字打印出来即可。当启动新的Activity时,我们就可以看到当前启动的是哪个Activity
open class BaseActivity : ComponentActivity() {
private val TAG = "BaseActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: " + javaClass.simpleName)
}
}
javaClass表示获取当前对象的Class对象,simpleName表示获取当前对象的类名。
在MainActivity中启动FirstActivity
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
val btnStart = findViewById<Button>(R.id.btn_start)
btnStart.setOnClickListener {
FirstActivity.actionStartActivity(this)
}
}
}
在FirstActivity中启动SecondActivity
class FirstActivity : BaseActivity() {
companion object {
fun actionStartActivity(context: Context) {
val intent = Intent(context, FirstActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_first)
val btnStart = findViewById<Button>(R.id.btn_start)
btnStart.setOnClickListener {
SecondActivity.actionStartActivity(this)
}
}
}
SecondActivity中什么也不做,当人它也可以启动其它Activity,一样让其它Activity继承BaseActivity。
class SecondActivity : BaseActivity() {
companion object{
fun actionStartActivity(context: Context) {
val intent = Intent(context, SecondActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_second)
}
}
App运行结果如下:
2025-05-23 12:36:31.403 5324-5324 BaseActivity com.example.mysecondapp D onCreate: MainActivity
2025-05-23 12:36:55.191 5324-5324 BaseActivity com.example.mysecondapp D onCreate: FirstActivity
2025-05-23 12:36:58.021 5324-5324 BaseActivity com.example.mysecondapp D onCreate: SecondActivity
上面代码中有这样一段代码
companion object{
fun actionStartActivity(context: Context) {
val intent = Intent(context, SecondActivity::class.java)
context.startActivity(intent)
}
}
调用它时则是:
SecondActivity.actionStartActivity(this)
可以看到是在companion object代码块中定义一个启动当前Activity的方法,那为啥要在companion object中定义该方法呢?那是因为我在调用这个方法时,直接使用了该Activity的类名.actionStartActivity(),可以看出来,它跟Java中的静态方法使用类似。其实companion object代码块就相当于定义Kotlin中的静态方法。
12.2 在项目中随时随地的退出该应用
在前面的学习中,我们知道Activity的启动时,会将每个Activity对象压入返回栈中,所以在应用内启动了N个Activity,那退出该应用时,就需要按返回键N+1(首页)次,那我们想在启动多个Activity后,一键退出该应用时,该怎么做呢?
import android.app.Activity
object ActivityControl {
//activity集合
val activitys = ArrayList<Activity>()
//当有Activity启动时添加该Activity到集合中
fun addActivity(activity: Activity) {
activitys.add(activity)
}
//当有Activity销毁时从集合中移除该Activity
fun removeActivity(activity: Activity) {
activitys.remove(activity)
}
//销毁所有的已启动的Activity
fun finishAllActivity() {
for (activity in activitys) {
if (!activity.isFinishing) {
activity.finish()
}
}
}
}
在BaseActivity中调用addActivity和removeActivity函数
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: " + javaClass.simpleName)
ActivityControl.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
ActivityControl.removeActivity(this)
}
在SecondActivity中一键退出该应用
val btnExit = findViewById<Button>(R.id.btn_exit)
btnExit.setOnClickListener {
ActivityControl.finishAllActivity()
}
13. Kotlin的标准函数和静态方法
13.1 标准函数
基础写法
private fun eatFruit() {
val fruits = listOf<String>("苹果", "香蕉", "樱桃", "荔枝")
val stingFruits = StringBuilder()
stingFruits.append("开始吃水果,吃了")
for (fruit in fruits) {
stingFruits.append(fruit)
}
stingFruits.append("都吃完了。")
println(stingFruits.toString())
}
标准函数with
fun eatFruitWith() {
println("标准函数with")
val fruits = listOf<String>("苹果", "香蕉", "樱桃", "荔枝")
//返回函数的最后一行代码
val result = with (StringBuilder()){
append("开始吃水果,吃了")
for (fruit in fruits) {
append(fruit)
}
append("都吃完了。")
toString()
}
println(result)
}
标准函数run
fun eatFruitRun() {
println("标准函数run")
val fruits = listOf<String>("苹果", "香蕉", "樱桃", "荔枝")
val stringFruits = StringBuilder()
//返回函数最后一行代码
val result = stringFruits.run {
append("开始吃水果,吃了")
for (fruit in fruits) {
append(fruit)
}
append("都吃完了。")
toString()
}
println(result)
}
标准函数apply
fun eatFruitApply() {
println("标准函数apply")
val fruits = listOf<String>("苹果", "香蕉", "樱桃", "荔枝")
val stringFruits = StringBuilder()
//返回的是该对象
val result = stringFruits.apply {
append("开始吃水果,吃了")
for (fruit in fruits) {
append(fruit)
}
append("都吃完了。")
}
println(result.toString())
}
13.2 静态方法
静态方法就是不需要创建实例就可以调用的方法,Java语言中只需要在方法前添加static即可,调用时通过类名就可以直接调用该方法。而Kotlin中,前面学到的单例类内的方法,就可以像Java中的静态方法一样调用。如果在Kotlin中想让某一个函数像Java中的静态方法,也可以使用companion object代码块来定义函数。
如果想要真正Kotlin的静态方法,则有两种方式,注解和顶层方法
13.2.1 注解
注解只能加载单例类的函数上和companion object函数上。
object SingletonClass {
@JvmStatic
fun singletonFun(){
println("这是一个单例类的函数。")
}
}
object SingletonClass {
@JvmStatic
fun singletonFun(){
println("这是一个单例类的函数。")
}
}
13.2.2 顶层方法
在创建Kotlin类时,选择File,在这个类中定义的方法都是顶层方法,顶层方法在应用中任何地方都可以正常调用。