Android Activity
本文总结《第一行代码 Android》第3版的内容
环境:
Android Studio Giraffe | 2022.3.1 Patch 3
Activity 是什么?
Activity 简单将就是UI界面,包含两部分 Activity 类 和应用布局文件,如果是 Compose 则另说,一般入门Android,都是从Activity开始的。
我们写一个程序,常常找应用启动入口,像 java 的 main()函数,那么 Activity 的应用入口在哪?下面通过创建项目认识。
在创建 Android 项目时,要选择 No Activity,是因为 Android Studio 2022 中如果选择 Empty Activity,会默认使用 Compose UI。在创建完成后,手动添加“Empty Activity”:
如果勾选 Launcher Activity,那么该 Activity 一般就是应用启动的时候,首先显示的。
在创建 Activity 之后,会自动在 AndroidManifest 注册 Activity:
应用入口在 AndroidManifest.xml 中一般会有标识:
<activity
android:name=".FirstActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Acttivity 的基本用法
在Android Studio 添加 Activity 之后,会创建两个文件:一个类,一个xml文件。类用来写逻辑,xml写UI布局。
修改 xml 布局,使用LinearLayout,默认的太复杂,添加 Button 控件
<?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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstActivity">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"/>
</LinearLayout>
然后在 Activity中添加逻辑:
// button 点击事件
button1.setOnClickListener{
Toast.makeText(this,"你点击了按钮",Toast.LENGTH_SHORT).show()
一个最简单的应用就完成了。
使用 Toast 消息提示
Toast 用于提示用户,用法如上面,第三个参数有两种 Toast.LENGTH_SHORT 和 Toast.LENGTH_LONG,显示时长不一样。
Activity 界面添加 Menu
步骤1: 添加 Menu 资源文件
<?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="添加"/>
<item
android:id="@+id/remove_item"
android:title="删除"/>
</menu>
步骤二:在 Activity中 重写 onCreateOptionsMenu() 方法
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
}
效果:
销毁Activity
在按返回键时,Activity 可以被销毁,在代码中使用 finish() 方法
不同 Activity 中跳转
首先理解什么是 Intent?
在Android中,Intent是一种消息传递机制,用于在组件之间进行通信,如启动活动、启动服务、传递数据等。根据其使用方式,Intent可以分为显式Intent和隐式Intent。
显式Intent:显式Intent是指明确指定要启动的组件(如Activity、Service等)的名称。这种Intent通常用于应用内部的组件间通信。例如,从一个Activity启动另一个Activity。
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
在上述代码中,我们明确指定了要启动的Activity是SecondActivity,因此这是一个显式Intent。
FirstActivity 跳转到 SecondActivity 使用 显式 Intent。
隐式Intent:隐式Intent并没有明确指定要启动的组件,而是指定了一种动作(Action)和数据(Data),然后由系统解析这个Intent,找到合适的组件来处理这个Intent。这种Intent通常用于启动其他应用的组件。例如,打开网页、拨打电话等。
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.baidu.com")
startActivity(intent)
在上述代码中,我们指定了一个动作ACTION_VIEW和一个数据https://www.baidu.com,系统会找到能处理这个动作和数据的应用(如浏览器)来启动。因此这是一个隐式Intent。
如果让我们的程序能够响应隐式Intent,需要在 AndroidManifest.xml 中注册:
<activity
android:name=".ThirdActivity"
android:exported="true" >
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https"/>
</intent-filter>
</activity>
调用方法:
// 隐式Intent
val intent = Intent("com.alex.activitytest.ACTION_START")
intent.addCategory("com.alex.activitytest.MY_CATEGORY")
startActivity(intent)
不同Activity传递数据
FirstActivty 启动 SecondActivity 并传递数据:
// 启动Activity并传递数据
val data="Hello SecondActivity"
val intent = Intent(this,SecondActivity::class.java)
intent.putExtra("extra_data",data)
startActivity(intent)
SecondActivity 接收数据:在 onCreate方法中
val extraData=intent.getStringExtra("extra_data")
Toast.makeText(this,extraData,Toast.LENGTH_SHORT).show()
如果 SecondActivity 需要向 FirstActivty 返回数据,需要三步,在 FirstActivty 调用的时候:把 startActivity(intent)
替换为
startActivityForResult(intent,1)
SecondActivity 返回数据:
val intent = Intent()
intent.putExtra("data_return","Hello, FirstActivity")
setResult(RESULT_OK,intent)
// 销毁当前Activity
finish()
FirstActivty 接收数据:
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")
}
}
}
startActivityForResult 与 onActivityResult 被废弃了,可以参考这篇文章进行更新:
startActivityForResult被标记为废弃?Activity Result API闪亮登场!
Activity 生命周期
Android 使用 task 来管理 Activity,一个任务就是一组存放在返回栈里的 Activity的集合。启动Activity,Activity入栈,Activity销毁就会出栈。显示的Activity总是在栈顶。
Activity 的四种状态
- 运行状态
Activity处于栈顶 - 暂停状态
比如一个Activity上弹出一个对话框,此时Activity处于暂停状态 - 停止状态
Activity 不处于栈顶,并且完全不可见 - 销毁状态
Activity 从返回栈中移除
Activity 生命周期
- onCreate()。会在Activity第一次被创建的时候调用。应该在这个方法中完成Activity的初始化操作,比如加载布局、绑定事件等。
- onStart()。这个方法在Activity由不可见变为可见的时候调用。
- onResume()。这个方法在Activity准备好和用户进行交互的时候调用。此时的Activity一定位于返回栈的栈顶,并且处于运行状态。
- onPause()。这个方法在系统准备去启动或者恢复另一个Activity的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶Activity的使用。
- onStop()。这个方法在Activity完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新Activity是一个对话框式的Activity,那么onPause()方法会得到执行,而onStop()方法并不会执行。
- onDestroy()。这个方法在Activity被销毁之前调用,之后Activity的状态将变为销毁状态。
- onRestart()。这个方法在Activity由停止状态变为运行状态之前调用,也就是Activity被重新启动了。
结合GPT,谈谈应用场景:
onCreate():
应用场景: 初始化 Activity,设置布局,初始化成员变量和数据绑定。
例子: 在一个音乐播放器应用中,在 onCreate() 中加载布局,初始化播放控制按钮和音轨信息。
onStart():
应用场景: 让 Activity 对用户可见,进行一些轻量级的资源初始化或恢复操作。
例子: 在新闻应用中,当 Activity 变为可见时,可以在 onStart() 中开始加载新闻列表。
onResume():
应用场景: 当 Activity 与用户交互前,恢复暂停的动画,初始化相机等。
例子: 在相机应用中,在 onResume() 方法中打开相机预览。
onPause():
应用场景: 暂停或调整不再需要的操作,如暂停动画,释放相机资源。
例子: 如果用户在编辑照片的应用中接到电话,onPause() 可用于暂停编辑进度或保存状态。
onStop():
应用场景: 当 Activity 不再可见时,释放或调整资源和操作,如停止播放媒体,取消网络请求。
例子: 在视频播放应用中,当用户离开播放界面,可以在 onStop() 中停止视频播放并释放播放器资源。
onDestroy():
应用场景: 清理资源,如关闭数据库连接,注销广播接收器。
例子: 在社交应用中,当 Activity 销毁时,关闭数据库连接和注销监听网络变化的广播接收器。
onRestart():
应用场景: 在 Activity 从停止状态回到活动状态时调用,用于重新初始化在 onStop() 中释放的资源。
例子: 在购物应用中,如果用户从购物车界面切换到其他应用然后返回,可以在 onRestart() 中刷新购物车数据。
如果从网络请求新闻列表数据,可以写在 onStart 或 onResume 方法中。如果你希望每次 Activity 变得可见时都刷新数据,那么 onStart() 是合适的地方。如果你希望在 Activity 每次回到用户交互前台时刷新数据,则 onResume() 更为合适。此外,考虑到网络请求可能会耗时并影响用户体验,推荐使用异步处理(如使用 AsyncTask 或 Retrofit 等网络库)来进行网络请求,避免阻塞主线程。
Activity 保存状态数据
如果 Activity 在停止状态时,有可能被回收,再返回时,如果没有数据,Activity可能无法正常显示,此时需要保存状态,可以使用 onSaveInstanceState方法,确保在回收前保存
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val tempData = "Something you just typed"
outState.putString("data_key", tempData)
}
在onCreate() 恢复数据:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(tag, "onCreate")
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
val tempData = savedInstanceState.getString("data_key")
Log.d(tag, "tempData is $tempData")
}
...
}
Activity 四种启动模式
Android 中Activity的四种启动模式及使用场景如下:
- Standard
用途: 每次启动 Activity 时都会创建一个新的实例,无论该 Activity 是否已经存在于任务栈中。
应用场景: 这是默认模式。适用于大多数标准的 Activity,如显示列表项的详细信息。例如,在邮件应用中,用户点击一个邮件时,每次都会创建一个新的邮件详情 Activity,即使是同一个邮件。
2. SingleTop
用途: 如果新的 Activity 已经是任务栈的顶端,则不会创建新的实例,而是复用栈顶的实例,并通过 onNewIntent() 方法接收新的 Intent。
应用场景: 适用于需要接收新信息且频繁启动但不需要多实例的 Activity。例如,在聊天应用中,如果用户已经在聊天界面,新消息的通知点击后只更新当前的聊天界面,而不重新创建 Activity。
- SingleTask
用途: 只创建一个实例,该实例会在新的任务栈中。如果 Activity 已经存在,则把这个 Activity 之上的所有其他 Activity 出栈,使这个 Activity 显示在最前。
应用场景: 适用于作为应用中单一入口的 Activity,如主页。例如,在浏览器应用中,主页 Activity 设置为 singleTask,无论从哪里启动,都只有一个主页实例。
4. SingleInstance
用途: 类似于 singleTask,但系统会为这种 Activity 创建一个新的任务栈,并且在这个栈中只有这个 Activity 实例。
应用场景: 适用于需要与应用完全分离的模块,例如,一个应用内的浮动小窗口或者是需要与其他应用共享的 Activity。例如,一款具有浮动小窗功能的音乐播放器,其播放控制界面可以设置为 singleInstance 模式。
Activity最佳实践
通过BaseActivity知晓当前在哪一个Activity
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BaseActivity", javaClass.simpleName)
}
}
全局管理Activity,随时退出程序
新建单例类ActivityCollector
package com.alex.activitytest
import android.app.Activity
/**
* @Author : alex
* @Date : on 2023/12/28 15:59.
* @Description :描述
*/
object ActivityCollector {
private val activities = ArrayList<Activity>()
fun addActivity(activity: Activity){
activities.add(activity)
}
fun removeActivity(activity: Activity){
activities.remove(activity)
}
fun finishAll(){
for(activity in activities){
if(!activity.isFinishing){
activity.finish()
}
}
activities.clear()
}
}
这个方法在测试的时候不行,比如点击按钮执行 ActivityCollector.finishAll(), 系统会重启App,即时加了下面的代码也一样。
android.os.Process.killProcess(android.os.Process.myPid())
可参考:https://blog.csdn.net/shulianghan/article/details/116402759