第一行代码第三章读书笔记界面Activity

一、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的状态

  1. 运行状态

Activity在栈顶时,为运行状态;

  1. 暂停状态

Activity不在栈顶但是可见时,为暂停状态;

  1. 停止状态

Activity不可见时,为停止状态;

  1. 销毁状态

Activity从栈中移除后,为销毁状态。

10.3 Activity的生命周期

官方的生命周期图
在这里插入图片描述
从图中可以看出Activity的生命周期共有7个函数

  1. onCreate

Activity被创建时执行;

  1. onStart

Activity可见时执行;

  1. onResume

Activity可操作时执行;

  1. onPause

Activity不可操作时执行;

  1. onStop

Activity 不可见时行;

  1. onDestory

Activity 被销毁时执行;

  1. 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的启动模式一共有四种:

  1. standard(标准模式)

该模式为Activity的默认启动模式,是指所有的Activity启动时,都会被压入返回栈。

  1. singleTop(单一顶部模式)

此模式是指,当返回栈的顶部如果就是当前Activity时,系统并不会再重新启动一个新的Activity对象压入返回栈。

  1. singleTask(单一任务模式)

此模式是指,当返回栈中已有该Activity的对象时,系统不会再重新启动一个新的Activity对象,而是将已有的Activity对象置为栈顶,该Activity对象以上的Activity对象也将从该返回栈中移除。

  1. 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,在这个类中定义的方法都是顶层方法,顶层方法在应用中任何地方都可以正常调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值