移动应用开发期末复习
目录
- 题型
- 第一章 项目结构与配置
- 安卓项目结构文件夹
- 清单文件的功能
- Intent
- 第二章 Android四大组件
- Activity
- 生命周期方法
- 启动模式
- 状态保存
- 跳转与数据传递
- Service
- 生命周期
- 启动方式
- 绑定服务
- BroadcastReceiver
- 类型
- 注册方式
- 自定义广播实现
- 代码实践
- ContentProvider
- 内容URI结构
- 访问数据的组件
- MIME类型格式getType()
- 通配符
- 第三章 通知与后台任务
- 通知机制
- 创建通知渠道
- 通知构建
- 多媒体处理
- 音频播放
- 视频播放
- 第四章 数据存储与通信
- SharedPreferences
- 数据存取流程
- 文件存储
- SQLite
- SQLiteOpenHelper
- SQLiteDatabase
- 数据库操作
- 网络与数据格式
- JSON与XML对比
- JSON解析库
- 第五章 UI开发
- 常用组件/控件
- 显示类
- 输入类
- 多媒体类
- 布局容器
- Fragment
- Fragment状态
- 动态加载Fragment
- 平板大尺寸
- LinearLayout(水平/垂直)
- 属性与操作
- 文本属性
- 图片资源引用
- 动态替换Fragment
- 布局管理
- 适配不同屏幕
- LinearLayout特性
- 题目
- 选择题
- 简答题(4题、20分)
- Android基础入门
- Android常见页面布局
- Android常见界面控件
- 程序活动单元Activity
- 广播机制
- 服务
- 简述Android数据存储的方式
- 使用内容提供者共享数据
- 共用选项题(2大题,2分/小题,共20分)
- 综合题(10分)
- 2025.6.5考试
题型
- 单选题:15题、30分
- 多选题:10题、20分 全对满分,少选得半分,选错不得分
- 共用选项题:2大题,2分/小题,共20分
- 简答题:4题、20分
- 综合题:10分
第一章 项目结构与配置
基于Linux内核
安卓的体系结构
Android是一种基于Linux的软件平台和操作系统,采用了软件堆层(Software Stack)的架构,由下往上分别是Linux内核层、硬件抽象层、系统运行时库层(又称为中间件层)、应用程序框架层和系统应用层。
安卓程序打包发布到应用商店,所用的扩展名是 .apk。
AndroidStudio依赖于gdk和sdk(软件开发包)。
Android应用开发特色
- 四大组件:Activity、Service、BroadcastReceiver、ContentProvider
- 丰富的系统控件
- SQLite数据库:轻量级、运算速度极快的嵌入式关系型数据库。支持SQL语法,还可通过Android封装好的API进行操作。
- 强大的多媒体:如音乐、视频、录音、拍照等。
- 可以部署机器学习(深度学习)模型:TensorFLow Lite或PyTorch(ONNX Runtime for Android)
四大组件
- Activity
- Service
- BroadcastReceiver
- ContentProvider
安卓项目结构文件夹
- app: 项目中的代码、资源等内容几乎都是放置在这个目录下的。
- .gitignore: 这个文件是用来将指定的目录或文件排除在版本控制之外的。
- build.gradle: 这是项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的。
- gradle.properties: 这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
- gradlew和gradlew.bat: 这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。
- local.properties: 这个文件用于指定本机中的Android SDK路径,通常内容都是自动生成的,我们并不需要修改。
- settings.gradle: 这个文件用于指定项目中所有引入的模块。
- libs: 如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。
- java: java目录是放置我们所有Java代码的地方(Kotlin代码也是放在这里),展开该目录,你将看到系统帮我们自动生成了一个MainActivity文件。
- res: 项目中使用到的所有图片、布局、字符串等资源都存放在这个目录下。
- AndroidManifest.xml: 这是整个Android项目的配置文件。
- build.gradle: 这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相
关的配置。
- drawable开头的目录都是用来放图片的。
- mipmap开头的目录都是用来放应用图标的。
- values开头的目录都是用来放字符串、样式、颜色等配置的。
- layout开头的目录都是用来放布局文件的。
安卓系统中定义的单位
分析app/build.gradle文件
日志工具的使用
清单文件的功能
清单文件的功能:
- 注册组件
- 权限声明
Intent
跳转与数据传递
第二章 Android四大组件
Activity
生命周期方法
onCreate
onStart
onResume
onPause
onStop
onDestroy
onRestart
15-认识Android中Activity的生命周期
启动模式
Activity-四种启动模式详解
4种启动模式:
- standard
- singleTop
- singleTask
- singleInstance
返回栈(任务栈)
activty4种启动模式:
standard:每启动一个activty都会在返回栈顶创建一个新的示例;
singleTop:启动activity时先判断要启动的activity实例是否位于栈顶,位于栈顶就复用,不在栈顶就创建一个新的实例;
singleTask:启动activity时先判断当前栈中是否有该activity,有就直接复用,并把该activity上的实例全部出栈;
singleInstance:启动activity时,会启动一个新的任务栈管理该activity实例。
📚
状态保存
onSaveInstanceState
跳转与数据传递
显式Intent
隐式Intent
putExtra
16.1-Android中Activity之间的跳转1–显式跳转
显式Intent
隐式Intent
可选是指设置intent的时可选的,设置过滤条件的时候时必选的。
老师ppt:
显示Intent
为按钮 button1 设置点击事件监听器。当用户点击按钮时,会执行大括号内的代码。
创建一个 Intent 对象,指定其动作为 ACTION_VIEW(系统预定义的 “查看” 操作)。
设置意图的数据为百度的 URL。Uri.parse() 将字符串 URL 转换为 Uri 对象(Android 处理统一资源标识符的标准格式)。
启动意图,触发系统查找能够处理该 URL 的应用(通常是浏览器)。
- android:name=“.SecondActivity”
指定 Activity 的类名(完整路径为com.example.ch3_activitytest.SecondActivity)。 - android:exported=“true”
声明该 Activity 可被其他应用启动。若设为 false,则只能被当前应用内部的组件调用。 - <action …>
定义一个自定义 Action(com.example.ch3_activitytest.ACTION_START)。其他应用可通过该 Action 隐式启动 SecondActivity。 - <category …>
添加 DEFAULT 类别,表示该 Activity 支持处理默认的隐式 Intent。若无此类别,无法通过隐式 Intent 启动该 Activity。
Action 决定 “做什么”,Category 决定 “在什么场景下做” 或 “由谁来做”。两者结合才能让 Intent 精准匹配到目标组件,既避免误匹配,又能满足安全和业务需求。
自己总结:
1、显示直接写你要启动哪个,一般用启动本应用中的Activity之间的数据
2、隐式根据类别 数据等匹配启动,常见于启动系统中的某些特定的动作,比如打电话.
为什么有了 Action 还需要 Category?
一句话总结:Action 决定 “做什么”,Category 决定 “在什么场景下做” 或 “由谁来做”。
🍊
创建intent
val intent = Intent("com.example.ACTION_SHOW") // 设置 Action
intent.addCategory(Intent.CATEGORY_DEFAULT) // 添加系统默认 Category
intent.addCategory("com.example.CATEGORY_PRIVATE") // 添加自定义 Category// 启动 Activity
startActivity(intent)
<intent-filter> 配置
<intent-filter><action android:name="com.example.ACTION_SHOW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="com.example.CATEGORY_PRIVATE" />
</intent-filter>
⭐️putExtra()
Service
生命周期
- onCreate
- onStartCommand
- onBind
- onUnbind
- onDestroy
37.3-Android中Service的生命周期
非绑定式Service的生命周期
两种方法停止service:
一种是stopService方法;
一种是stopself方法。
绑定式Service生命周期
解绑不一定销毁;
销毁一定解绑。
启动方式
startService
bindService
两种启动方式的区别
绑定服务
ServiceConnection
onServiceConnected
一句话总结:
Service 绑定是组件与 Service 通信的方式,ServiceConnection监听连接状态,onServiceConnected 在连接成功时提供 Service 实例,让组件可以调用 Service 的方法。
Service的作用
BroadcastReceiver
类型
标准广播
有序广播
注册方式
- 动态注册:在代码中注册。
- 静态注册:在AndroidManifest.xml中注册。
动态注册
class MainActivity : AppCompatActivity() {// 声明广播接收器变量(延迟初始化)lateinit var timeChangeReceiver: TimeChangeReceiveroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 2. 创建IntentFilter并添加系统时间变化的Actionval intentFilter = IntentFilter()intentFilter.addAction("android.intent.action.TIME_TICK") // 每分钟发送一次的系统广播// 初始化广播接收器实例timeChangeReceiver = TimeChangeReceiver()// 3. 注册广播接收器(动态注册)registerReceiver(timeChangeReceiver, intentFilter)}override fun onDestroy() {super.onDestroy()// 4. 解除广播接收器注册,防止内存泄漏// 必须与registerReceiver成对出现unregisterReceiver(timeChangeReceiver)}// 1. 定义广播接收器类(逻辑上的第一步:先设计接收器)// 内部类可访问外部Activity成员(如this@MainActivity)inner class TimeChangeReceiver : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {// 处理接收到的广播if (intent.action == "android.intent.action.TIME_TICK") {// 每分钟触发一次Toast提示Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show()}}}
}
静态注册
// 1. 定义广播接收器类,继承自 BroadcastReceiver
// 该类需配合 AndroidManifest.xml 中的静态注册使用
class BootCompleteReceiver : BroadcastReceiver() {// 2. 重写 onReceive() 方法,系统在接收到匹配的广播时会调用此方法// context: 上下文对象,用于访问系统服务// intent: 携带广播信息的意图对象override fun onReceive(context: Context, intent: Intent) {// 3. 显示 Toast 提示(在屏幕上短暂显示消息)// context: 上下文// "Boot Complete": 提示文本// Toast.LENGTH_LONG: 提示持续时间(约3.5秒)Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show()}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.broadcasttest"><!-- 1. 声明接收系统启动完成广播的权限 --><!-- 若无此权限,即使注册了接收器也无法收到广播 --><uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><!-- 2. 注册系统启动广播接收器 --><!-- android:name: 指定接收器类名(需与代码中的类名匹配) --><!-- android:enabled: 允许系统实例化此接收器 --><!-- android:exported: 允许接收来自应用外部的广播(系统广播属于外部) --><receiverandroid:name=".BootCompleteReceiver"android:enabled="true"android:exported="true"><!-- 3. 过滤器:指定接收器监听的广播类型 --><!-- 此处仅接收系统启动完成的广播 --><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter></receiver></application>
</manifest>
自定义广播实现
继承BroadcastReceiver
代码实践
广播发送与接收
IntentFilter
1.创建广播接收器
// 1. 定义自定义广播接收器,继承自系统广播接收器基类
// 需配合 AndroidManifest.xml 静态注册或代码动态注册使用
class MyBroadcastReceiver : BroadcastReceiver() {// 2. 重写 onReceive() 方法,当接收到匹配的广播时自动调用// context: 上下文对象,用于获取系统服务或启动组件// intent: 携带广播数据的意图对象,包含 action 和 extrasoverride fun onReceive(context: Context, intent: Intent) {// 3. 显示短提示消息(实际场景需根据广播类型执行不同逻辑)// 注意:onReceive() 在主线程执行,避免耗时操作(如网络请求)Toast.makeText(context, // 上下文"received in MyBroadcastReceiver", // 提示文本Toast.LENGTH_SHORT // 显示时长(约2秒)).show()}
}
2.注册广播接收器
静态注册
3.发送广播
一句话总结:
创建广播接收器→
注册(动态 / 静态)→
用 Intent + sendBroadcast 等方法发送→
在接收器 onReceive 处理 。
ContentProvider
教师ppt:
内容URI结构
authority
path
访问数据的组件
ContentResolver
// 获得一个cursor对象
// contentResolver 是 ContentResolver 类型的对象,用于与内容提供者(ContentProvider)交互,实现跨进程数据查询等操作
// query 方法用于向指定内容提供者发起查询请求,参数含义分别是:
// uri:内容提供者中数据的唯一标识,指定要查询哪个内容提供者里的哪些数据,比如联系人、短信等对应不同固定 Uri
// projection:要查询返回的列(字段)列表,类似 SQL 里的 SELECT 后面跟的字段,传 null 表示查询所有列
// selection:查询条件,类似 SQL 里的 WHERE 子句,传 null 表示不做条件限制
// selectionArgs:配合 selection 使用,用于替换 selection 里的占位符(如 ?),避免 SQL 注入,传 null 表示没有占位符参数
// sortOrder:结果排序规则,类似 SQL 里的 ORDER BY 子句,传 null 表示使用默认排序
val cursor = contentResolver.query(uri,projection,selection,selectionArgs,sortOrder
)// 通过cursor来读取数据
// moveToNext 方法会将游标(cursor)移动到结果集的下一行,初始时游标在第一行之前,第一次调用就会移动到第一行;返回值为 true 表示移动成功(有数据行),false 表示已到末尾(没有更多数据行了)
while (cursor.moveToNext()) {// getColumnIndex 方法根据列名("column1")获取该列在结果集中的索引位置,后续通过索引去取对应数据// getString 方法根据列索引,获取当前行对应列的字符串类型数据val column1 = cursor.getString(cursor.getColumnIndex("column1"))// getInt 方法根据列索引,获取当前行对应列的整型数据val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
// 关闭 cursor,释放相关资源,比如数据库连接、内存等,避免出现内存泄漏等问题,使用完 cursor 后务必调用
cursor.close()
// 1. 创建 ContentValues 对象,用于存储待插入的数据
// "column1" to "text": 键值对表示列名和对应的值(字符串类型)
// "column2" to 1: 键值对表示列名和对应的值(整型)
val values = contentValuesOf("column1" to "text", "column2" to 1) // 2. 向 ContentProvider 发起插入请求
// uri: 指定插入数据的目标 URI(如 content://com.example.provider/table)
// values: 包含待插入数据的 ContentValues 对象
// 返回值: 插入新记录的 URI(通常包含新记录的 ID)
contentResolver.insert(uri, values)
// 1. 创建 ContentValues 对象,存储要更新的数据
// "column1" to "": 将 column1 的值更新为空字符串
val values = contentValuesOf("column1" to "")// 2. 向 ContentProvider 发起更新请求
// uri: 指定更新数据的目标 URI(如 content://com.example.provider/table)
// values: 包含要更新数据的 ContentValues 对象
// "column1 = ? and column2 = ?": WHERE 子句,使用占位符 ? 防止 SQL 注入
// arrayOf("text", "1"): 占位符对应的实际参数值,按顺序替换 ?
// 返回值: 受影响的行数(更新成功的记录数量)
contentResolver.update(uri, values, "column1 = ? and column2 = ?",
arrayOf("text", "1"))
// 1. 向 ContentProvider 发起删除请求
// uri: 指定删除数据的目标 URI(如 content://com.example.provider/table)
// "column2 = ?": WHERE 子句,使用占位符 ? 防止 SQL 注入
// arrayOf("1"): 占位符对应的实际参数值,替换 ?
// 返回值: 受影响的行数(删除成功的记录数量)
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
MIME类型格式getType()
vnd.android.cursor.dir
vnd.android.cursor.item
vnd.android.cursor.dir
vnd.android.cursor.item
vnd.android.cursor.dir/ :表示返回一组数据
vnd.android.cursor.item/ :表示返回单条数据
通配符
*(任意长度字符)
#(任意长度数字)
第三章 通知与后台任务
通知机制
创建通知渠道
NotificationChannel
通知渠道
创建通知渠道
(内容同上)
//1、创建一个NotificationChannel对象,为通知渠道设置名称、描述和重要性级别。
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
//2、创建一个NotificationManager。
NotificationManager notificationManager = getSystemService(NotificationManager.class);
//3、使用NotificationManager.createNotificationChannel()方法将通知渠道添加到系统
中。
notificationManager.createNotificationChannel(channel);
//1、创建一个NotificationChannel对象,为通知渠道设置名称、描述和重要性级别。
val channel = NotificationChannel(Channel_ID, name, importance)
//2、创建一个NotificationManager。
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//3、使用NotificationManager.createNotificationChannel()方法将通知渠道添加到系统
中。
manager.createNotificationChannel(channel)
通知构建
NotificationCompat.Builder
// 1. 获取系统通知服务,用于发送和管理通知
// Context.NOTIFICATION_SERVICE: 通知服务的系统标识符
// NotificationManager: 负责通知的创建、发送和取消等操作
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager// 2. 使用 NotificationCompat.Builder 创建通知(兼容低版本 Android)
// context: 上下文对象,用于获取资源和系统服务
// channelId: 通知渠道 ID(Android 8.0+ 必须指定,用于分类通知)
val notification = NotificationCompat.Builder(context, channelId).setContentTitle("This is content title") // 设置通知标题(显示在顶部).setContentText("This is content text") // 设置通知内容(显示在标题下方).setSmallIcon(R.drawable.small_icon) // 设置状态栏和通知栏的小图标(必填).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon)) // 设置通知左侧的大图标(可选).build() // 构建通知对象// 3. 发送通知
// 1: 通知的唯一标识符(用于更新或取消通知)
// notification: 要发送的通知对象
manager.notify(1, notification)
场景类比:快递包裹(PendingIntent)
假设你要寄一个快递(PendingIntent),四个参数就像这样:
context = 寄件人地址
快递从哪里发出(应用环境),快递公司(系统)需要知道从哪取件。
requestCode = 包裹编号
同一个地址(context)可能寄多个包裹,用编号区分不同包裹(如:001、002)。
即使包裹内容(intent)相同,编号不同就是不同的包裹。
intent = 包裹内容
里面装的是什么(如手机、衣服),对应要执行的操作(如启动 Activity、发送广播)。
flags = 快递特殊要求
例如:
FLAG_IMMUTABLE = 包裹封条不可拆(安全性)。
FLAG_UPDATE_CURRENT = 如果已有相同编号的包裹,用新包裹替换旧的。
FLAG_ONE_SHOT = 包裹只能签收一次,签收后失效。
// 创建点击通知后要启动的 Activity 的意图
val intent = Intent(this, NotificationActivity::class.java)// 将 Intent 包装为 PendingIntent,允许系统在用户点击通知时执行该意图
// 注意:最后一个参数 0 表示默认标志位,建议改用 FLAG_IMMUTABLE 提高安全性
val pi = PendingIntent.getActivity(this, 0, intent, 0)// 获取系统通知服务
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager// 构建通知内容
val notification = NotificationCompat.Builder(context, channelId).setContentTitle("This is content title").setContentText("This is content text").setSmallIcon(R.drawable.small_icon).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon)).setContentIntent(pi) // 设置点击通知后执行的 PendingIntent.build()// 发送通知(ID=1 用于标识该通知,可用于后续更新或取消)
manager.notify(1, notification)
使用了 FLAG_IMMUTABLE(安全推荐)
多媒体处理
音频播放
MediaPlayer类
prepare
start
stop
一个简单的音乐播放器示例代码
class MainActivity : AppCompatActivity() {// 1. 创建一个MediaPlayer对象(类成员变量,保证全局可访问)private val mediaPlayer = MediaPlayer()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 初始化MediaPlayer(包含设置数据源和准备操作)initMediaPlayer()// 2. 调用setDataSource()设置音频文件路径 + 3. 调用prepare()进入准备状态private fun initMediaPlayer() {// 获取资源管理器(用于访问assets目录文件)val assetManager = assets// 从assets目录打开音频文件(路径为"music.mp3")val fd = assetManager.openFd("music.mp3")// 2. 设置数据源(指定文件描述符、起始偏移量、文件长度)mediaPlayer.setDataSource(fd.fileDescriptor, fd.startOffset, fd.length) // 3. 同步调用prepare()进入准备状态(会阻塞线程,建议在子线程调用)mediaPlayer.prepare()}// 播放按钮点击事件play.setOnClickListener {if (!mediaPlayer.isPlaying) {// 4. 调用start()方法开始播放mediaPlayer.start() }}// 暂停按钮点击事件pause.setOnClickListener {if (mediaPlayer.isPlaying) {// 4. 调用pause()方法暂停播放mediaPlayer.pause() }}// 停止按钮点击事件stop.setOnClickListener {if (mediaPlayer.isPlaying) {// 4. 调用reset()方法停止播放(重置状态)mediaPlayer.reset() // 重新初始化播放器(相当于重新设置数据源和准备)initMediaPlayer()}}}override fun onDestroy() {super.onDestroy()// 5. 释放MediaPlayer对象资源(必须在Activity销毁时调用)// 先停止播放(虽然reset()已停止,但显式调用更安全)mediaPlayer.stop() // 释放底层音频资源,防止内存泄漏mediaPlayer.release() }
}
视频播放
VideoView组件
支持res/raw目录资源
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 1. 创建视频文件的URI(通过android.resource方案访问raw目录资源)// android.resource://包名/资源ID → 固定格式val uri = Uri.parse("android.resource://$packageName/${R.raw.video}")// 2. 设置视频数据源(URI指向raw目录下的video.mp4文件)videoView.setVideoURI(uri)play.setOnClickListener {if (!videoView.isPlaying) {videoView.start() // 3. 若视频未播放,调用start()开始播放}}pause.setOnClickListener {if (videoView.isPlaying) {videoView.pause() // 3. 若视频正在播放,调用pause()暂停播放}}replay.setOnClickListener {if (videoView.isPlaying) {videoView.resume() // 4. 如果视频正在播放,调用resume()从暂停状态恢复播放(重新播放)}}override fun onDestroy() {super.onDestroy()videoView.suspend() // 5. Activity销毁时调用suspend()暂停播放并释放资源(非释放性销毁,可恢复)}}
}
第四章 数据存储与通信
SharedPreferences
数据存取流程
edit
putXxxxx
commit
存储到SharedPreferences:
// 1. 获取 SharedPreferences 实例(用于存储键值对数据)
// "data": 文件名(存储在 /data/data/包名/shared_prefs/data.xml)
// MODE_PRIVATE: 文件私有模式(其他应用无法访问)
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()// 2. 向编辑器中添加键值对数据(支持多种数据类型)
editor.putString("name", "Tom") // 存储字符串类型数据
editor.putInt("age", 28) // 存储整数类型数据
editor.putBoolean("married", false) // 存储布尔类型数据// 3. 提交修改(异步方式)
// apply(): 异步写入磁盘,无返回值(推荐,不阻塞UI线程)
// commit(): 同步写入磁盘,返回布尔值表示成功/失败
editor.apply()
从SharedPreferences中读取:
// 1. 获取一个SharedPreferences对象
// "data": 文件名(对应之前写入的XML文件)
// MODE_PRIVATE: 确保只能访问当前应用的SharedPreferences
val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)// 2. 读取数据:从SharedPreferences中获取对应类型的值
// getString("name", ""): 获取名为"name"的字符串,默认值为空字符串
// getInt("age", 0): 获取名为"age"的整数,默认值为0
// getBoolean("married", false): 获取名为"married"的布尔值,默认值为false
val name = prefs.getString("name", "")
val age = prefs.getInt("age", 0)
val married = prefs.getBoolean("married", false)
文件存储
写入文件的步骤:
fun save(inputText: String) {try {// 1. 获取应用程序的上下文Context对象(通过openFileOutput()隐式调用)// 注意:在Activity中可直接调用openFileOutput(),因Activity继承自Context// 2. 调用Context对象的openFileOutput()方法创建一个FileOutputStream对象// "data": 文件名(存储在应用内部目录)// MODE_PRIVATE: 文件私有模式(其他应用无法访问)val output = openFileOutput("data", Context.MODE_PRIVATE)// 3. 通过FileOutputStream对象将数据写入文件中(需借助流桥接器和缓冲器)// OutputStreamWriter: 字节流 → 字符流的桥接器(指定UTF-8编码)// BufferedWriter: 为字符流添加缓冲区,提高写入效率val writer = BufferedWriter(OutputStreamWriter(output))// 使用use函数自动管理流的生命周期(自动调用close())writer.use {// 写入文本内容到缓冲区(最终通过桥接器转为字节写入文件)it.write(inputText)}// 4. 关闭FileOutputStream、OutputStreamWriter、BufferedWriter对象// 注意:writer.use{}已自动关闭writer和底层的outputStream// 此行output.close()冗余,可删除(重复关闭可能导致异常)output.close()} catch (e: IOException) {// 异常处理:打印IO错误堆栈信息e.printStackTrace()}
}
读取文件的步骤:
fun load(): String {val content = StringBuilder()try {// 1. 获取应用程序的上下文Context对象(通过openFileInput()隐式调用)// 注意:在Activity中可直接调用openFileInput(),因Activity继承自Context// 2. 调用Context对象的openFileInput()方法创建一个FileInputStream对象// "data": 要读取的文件名(位于应用内部存储目录)val input = openFileInput("data")// 3. 通过InputStreamReader()创建一个InputStreamReader对象// InputStreamReader: 字节流 → 字符流的桥接器(默认使用UTF-8编码)val reader = BufferedReader(InputStreamReader(input))// 4. 通过BufferedReader()传入上述InputStreamReader对象,创建带缓冲的字符读取器// BufferedReader: 为字符流添加缓冲区,提高读取效率// 5. 用BufferedReader的forEachLine()或readLine()读取每一行的数据// forEachLine: Kotlin扩展函数,遍历每一行并执行lambda表达式// content.append(it): 将每行内容追加到StringBuilderreader.use {reader.forEachLine {content.append(it)}}// 6. 关闭FileInputStream、InputStreamReader、BufferedReader对象// 注意:reader.use{}已自动关闭reader和底层的inputStream// 无需手动调用close()} catch (e: IOException) {// 异常处理:打印IO错误堆栈信息e.printStackTrace()}return content.toString()
}
SQLite
SQLiteOpenHelper
onCreate
onUpgrade
创建数据库:
// 1. 创建一个类并继承SQLiteOpenHelper
// MyDatabaseHelper: 自定义数据库帮助类,负责管理数据库创建和版本更新
class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) {// 定义创建表的SQL语句(Book表:包含ID、作者、价格、页数、书名字段)private val createBook = "create table Book (" +"id integer primary key autoincrement," +"author text," +"price real," +"pages integer," +"name text)"// 3. 在onCreate()方法中,编写SQL语句来创建表// 首次创建数据库时调用(数据库文件不存在时触发)override fun onCreate(db: SQLiteDatabase) {db.execSQL(createBook) // 执行SQL创建表Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()}// 4. 在onUpgrade()方法中,编写SQL语句来升级表// 数据库版本号增加时调用(需手动修改构造函数中的version参数)override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {// 当前为空实现(实际开发中需添加表结构更新逻辑,如:// db.execSQL("DROP TABLE IF EXISTS Book");// onCreate(db);}
}
升级数据库
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {// 1. 先删除旧表(简单粗暴的升级方式,会丢失所有数据)// 注意:实际生产环境中不建议直接删除表,应采用数据迁移策略db.execSQL("drop table if exists Book")db.execSQL("drop table if exists Category")// 2. 重新创建表结构(调用onCreate方法)// 由于旧表已删除,新表将是空的(数据丢失)onCreate(db)
}
升级数据库的最佳写法
第一版:
// 第一版:仅创建 Book 表
private val createBook = "create table Book (" +"id integer primary key autoincrement," +"author text," +"price real," +"pages integer," +"name text)"override fun onCreate(db: SQLiteDatabase?) {db?.execSQL(createBook) // 首次创建数据库时只创建 Book 表Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {// 第一版无需升级逻辑(初始版本)
}
第二版:
// 定义创建 Book 表的 SQL 语句,包含 id(主键自增)、author、price、pages、name 字段
private val createBook = "create table Book (" +"id integer primary key autoincrement," +"author text," +"price real," +"pages integer," +"name text)"// 定义创建 Category 表的 SQL 语句,包含 id(主键自增)、category_name、category_code 字段
private val createCategory = "create table Category (" +"id integer primary key autoincrement," +"category_name text," +"category_code integer)"// 重写 onCreate 方法,数据库首次创建时调用,用于初始化表结构
override fun onCreate(db: SQLiteDatabase?) {// 执行创建 Book 表的 SQL 语句,db 为可空类型,使用 ?. 安全调用db?.execSQL(createBook)// 执行创建 Category 表的 SQL 语句db?.execSQL(createCategory)// 弹出 Toast 提示,告知用户表创建成功Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}// 重写 onUpgrade 方法,数据库版本升级时调用,用于更新表结构
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {// 判断旧版本号是否小于等于 1,若是则执行创建 Category 表的操作// 说明从版本 1 升级到更高版本(这里是第 2 版)时,需要补充创建 Category 表if (oldVersion <= 1) {db?.execSQL(createCategory)}// 此处第 2 版代码中这部分可能是未完整显示,从逻辑看是为后续版本升级预留或示例// 比如后续第 3 版要给 Book 表加字段时,会补充对应的判断逻辑
}
第三版:
// 定义创建 Book 表的 SQL 语句,在原有基础上新增 category_id 字段
private val createBook = "create table Book (" +"id integer primary key autoincrement," +"author text," +"price real," +"pages integer," +"name text," +"category_id integer)"// 定义创建 Category 表的 SQL 语句,和第 2 版一致,包含 id、category_name、category_code 字段
private val createCategory = "create table Category (" +"id integer primary key autoincrement," +"category_name text," +"category_code integer)"// 重写 onCreate 方法,数据库首次创建时调用,用最新的表结构初始化
override fun onCreate(db: SQLiteDatabase?) {// 执行创建带 category_id 字段的 Book 表的 SQL 语句db?.execSQL(createBook)// 执行创建 Category 表的 SQL 语句db?.execSQL(createCategory)// 弹出 Toast 提示表创建成功Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}// 重写 onUpgrade 方法,处理数据库版本升级时的表结构变更
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {// 若旧版本号 <= 1,执行创建 Category 表操作,适配从第 1 版升级的情况if (oldVersion <= 1) {db?.execSQL(createCategory)}// 若旧版本号 <= 2,执行给 Book 表添加 category_id 字段的操作// 说明从版本 2 升级到第 3 版时,需要补充添加该字段,保证表结构更新if (oldVersion <= 2) {db?.execSQL("alter table Book add column category_id integer")}
}
版本 | 数据库结构变更 | onCreate() 逻辑 | onUpgrade() 逻辑 |
---|---|---|---|
第一版 | 仅创建 Book 表 | 执行 createBook | 空(无需升级) |
第二版 | 新增 Category 表 | 同时执行 createBook 和 createCategory | 若 oldVersion <= 1,则创建 Category 表 |
第三版 | Book 表新增 category_id 字段 | 创建带 category_id 的 Book 表 | 1. 若 oldVersion <= 1,创建 Category 表 2. 若 oldVersion <= 2,添加 category_id 字段 |
SQLiteDatabase
update
delete
insert
query
rawQuery
execSQL
rawQuery
execSQL
数据库操作
getWritableDatabase
一句话总结getReadableDatabase() 和 getWritableDatabase() 的核心区别:
getReadableDatabase():能读则读,不能读也不崩溃(降级处理)。
getWritableDatabase():必须能写,否则报错(强一致性)。
网络与数据格式
JSON与XML对比
?
结构
可读性
带宽
对比维度 | JSON | XML |
---|---|---|
结构 | 基于键值对({key:value} )和数组,语法紧凑,贴近编程语言数据结构(如对象、列表) | 基于标签 <tag></tag> 嵌套,需显式定义标签,结构高度文档化(类似 HTML 标签逻辑) |
可读性 | 对开发者友好(熟悉代码语法即可理解),但复杂嵌套时纯数据块可能难直观关联业务语义 | 标签语义化强(标签名可自定义说明),人工阅读时结构清晰;但冗余标签会增加视觉和理解负担 |
带宽 | 无冗余标签,数据体积小,传输效率高(适合网络请求、API 交互) | 标签重复(每个数据项需开闭标签),文件体积大,传输占带宽更高(大数据量场景劣势明显) |
JSON解析库
GSON
JSONObject
GSON
JSONObject
第五章 UI开发
常用组件/控件
显示类
TextView
ProgressBar
WebView
TextView
⭐️属性 layout_width & layout_height
- 指定控件的宽度和高度
- 这两个必须配置!
- 3种可选值:
- match_parent:“让这个视图的大小和其父视图一样大”
- wrap_content:“让这个视图的大小足够大以适应其内容”
- 固定值:建议使用 dp(density-independent pixel)或 sp(scale-independent pixel)
⭐️属性 android:gravity
- 指定控件内容(文字、图片……)的对齐方式
- 可选值有top、bottom、start、end、center,可以用“|”来指定多个值
ProgressBar
⭐️属性 android:visibility
- 所有控件都具有的属性
- 可选值:visible、invisible和gone
- 可在代码中设置控件的可见性:setVisibility(),可传入的值有View.VISIBLE、View.INVISIBLE和View.GONE
⭐️水平进度条
- style=“?android:attr/progressBarStyleHorizontal”
- android:max=“100”
WebView
(ppt第11章)
输入类
EditText
多媒体类
VideoView
ImageView
VideoView
VideoView
ImageView
布局容器
Fragment
Fragment状态
- 生命周期
- 运行
- 暂停
- 停止
- 销毁
动态加载Fragment
事务管理
// 主Activity类,继承自AppCompatActivity
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main) // 设置布局文件// 步骤1:初始化按钮并设置点击事件val button: Button = findViewById(R.id.button)button.setOnClickListener {// 步骤1.1:创建待添加Fragment的实例(点击时替换为AnotherRightFragment)replaceFragment(AnotherRightFragment())}// 步骤1.2:Activity加载时默认添加RightFragmentreplaceFragment(RightFragment())}// 动态添加/替换Fragment的方法private fun replaceFragment(fragment: Fragment) {// 步骤2:获取FragmentManager(管理Activity中的Fragment)val fragmentManager = supportFragmentManager// 步骤3:开启事务(所有Fragment操作必须在事务中执行)val transaction = fragmentManager.beginTransaction()// 步骤4:替换Fragment// 参数1:容器ID(R.id.rightLayout对应布局中的FrameLayout)// 参数2:待添加的Fragment实例transaction.replace(R.id.rightLayout, fragment)// 步骤5:提交事务(使变更生效)transaction.commit()}
}
平板大尺寸
LinearLayout(水平/垂直)
垂直
水平
LinearLayout使用layout_gravity属性对齐的效果
LinearLayout使用layout_weight属性计算屏占比
属性与操作
文本属性
android:text
图片资源引用
android:src
动态替换Fragment
replace
事务提交
动态加载Fragment
布局管理
适配不同屏幕
限定符如large
LinearLayout特性
水平/垂直排列
LinearLayout(水平/垂直)
题目
【Android开发】移动程序设计期末复习练习题(二)
Android复习题
选择题
单选题:15题、30分
多选题:10题、20分 全对满分,少选得半分,选错不得分在这里插入图片描述
onCreate 创造
onStart 开始
onResume 恢复
onPause 暂停
onStop 停止
onDestroy 销毁
onRestart 重新开始 stop->
记忆:都是on开头的
另一种:startService()
简答题(4题、20分)
Android基础入门
3.简述Android系统架构包含的层次以及各层的特点。
答:
Android系统架构从高到低分为四层,分别是应用程序层、应用程序框架层、核心类库、Linux内核
应用程序层:一个核心应用程序的集合,安装在手机中的应用程序都属于这一层。
应用程序框架层:主要提供了构建应用程序时用到的各种API。
核心类库:包含了系统库和Android运行环境
Linux内核:为Android设备的各种硬件提供了底层的驱动。
Android常见页面布局
列举Android中的常用布局,并简述他们各自的特点。
答:
RelativeLayout(相对布局):通过相对位置的方式来指定布局内子控件的位置。
LinerLayout(线性布局):通过android:orientation属性来指定子控件水平垂直排列。
TableLayout(表格布局):采用行列的方式来管控布局,一个TableRow代表一行。
FrameLayout(帧布局):该布局会在屏幕上创建一块空白的区域,添加到该区域的每一个子控件占一帧,这些帧会一个一个叠加在一起,后加入的控件会叠加在上一个控件上层。默认情况下,帧布局中所有控件会与左上角对齐。
Android常见界面控件
简述实现Button按钮的点击事件的方式有哪几种?
答:
1、在布局文件中指定onClick属性的方式设置点击事件。
2、使用匿名内部类的方式设置点击事件。
3、通过为Activity实现OnClickListener接口的方式设置点击事件。
程序活动单元Activity
简述Activity的生命周期的方法及什么时候被调用。
答:
onCreate():Activity创建时调用,通常做一些初始化配置
onStart():Activity即将可见时调用。
onResume():Activity获取焦点时调用
onPause():当前Activity被其他Activity覆盖或锁屏时调用
onDestroy():Activity销毁时调用。
onRestart():Activity从停止状态到再次启动时调用。
简述Activity的四种启动模式及其特点。
答:
1.standard:每次启动均创建新实例;
2.singleTop:若目标 Activity 已在栈顶,则复用实例,否则创建新实例。
3.singleTask:若任务栈中存在实例,复用并清空其上方所有 Activity,否则创建新实例。
4.singleInstance:启动activity时,会启动一个新的任务栈管理该activity实例,全局唯一实例。
简述Activity、Intent、IntentFilter的作用
答:
Activity:界面组件,处理用户交互,通过任务栈管理实例。
Intent:组件间通信工具,分显式和隐式,用于传递数据。
IntentFilter:隐式 Intent 的匹配规则,在Manifest中声明,通过 Action、Data 等过滤可触发的组件。
广播机制
简述广播机制的实现过程。
答:
1.创建广播接收器:写一个类继承 BroadcastReceiver,实现 onReceive() 方法,处理广播逻辑。
2.注册广播接收器:分动态注册和静态注册。
3.发送广播:用 Intent 类发送,标准广播通过 sendBroadcast() ,有序广播通过sendOrderedBroadcast() 。
4.接受广播:在onReceive() 处理接收到的广播。
建 注 发 收
简述有序广播和无序广播的区别。
答:
无序广播:接收器 几乎同时”收到,无先后;无法被截断;无序广播用 sendBroadcast() 发送;
有序广播:按优先级,执行完才传下一个;可以被截断;有序广播用 sendOrderedBroadcast() 发送。
服务
简述Service的两种启动方式的区别。
答:
startService:用于启动服务,一次启动,无限次运行;操作停止时,服务停止。
bindService:用于绑定服务,组件绑定到它时运行;多个组件可绑定,多个组件解绑时销毁服务。
简述Service的生命周期。
答:
非绑定式Service:onCreate(),onStartCommand(),onDestroy;
绑定式Service:onCreate(),onBInd(),onUnbind(),onDestroy;
简述Android数据存储的方式
答:
SharedPreferences:轻量级键值对存储,用于简单配置。
文件存储:分内部和外部,存大量数据。
SQLite 数据库:结构化存储,适合复杂数据。
使用内容提供者共享数据
简述内容提供者的工作原理。
答:
当B程序需要操作A程序数据库中的数据,一般需要A程序使用ContentProvider暴露数据,才能被其他程序操作。B程序通过ContentResolver操作A程序暴露出来的数据,而A程序会将操作结果返回给ContentResolver,然后ContentResolver再将操作结果返回给B程序。
简述内容观察者的工作原理。
答:
使用ContentObserver观察A程序的数据时,首先要在A程序的ContentProvider中调用ContentResolver的notifyChange()方法。调用此方法后,当B程序操作A程序中的数据时,A程序会向“消息中心”发送数据变化的消息,此时C程序会观察到“消息中心”的数据有变化,会触发ContentObserver的onChange()方法。
共用选项题(2大题,2分/小题,共20分)
也就是说每道题5个空
1、在MainActivity组件对应的布局文件里,定义了两个Button按钮,其id分别设置为button1和button2,单击按钮后,分别启动对应的SecondActivity组件和ThirdActivity组件。从MainActivity跳转到ThirdActivity时,往ThirdActivity传递一个Bundle类型的数据。当从ThirdActivity返回到MainActivity时,同时返回给MainActivity一个字符串数据。
(1)调用其他Activity的主调组件MainActivity的代码如下:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取id为button1的按钮控件Button button1 =_____(1-1)findViewById_______(R.id.button1);button1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent();//隐式调用SecondActivityintent.setAction("_________(1-2)android.intent.action.ysdy_________"); //启动SecondActivity,第2个参数为请求码_______(1-3)startActivityForResult____________(intent, 2); }} );//获取id为button2的按钮控件Button button2=findViewById(_____(1-4)R.id.button2_________);button2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent();//显示调用ThirdActivityintent.setClass(MainActivity.this, _____(1-5)ThirdActivity.class_________); //设置Bundle类型数据Bundle bundle = new Bundle(); bundle.putString("name", "张三");bundle.___ (1-6)putInt____("age", 20);//通过意图对象携带数据intent.putExtra("data", bundle); //启动ThirdActivity并请求数据回传,第2个参数为请求码startActivityForResult(intent, 3); }} );}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult( requestCode, resultCode, data);//判断是哪个Activity返回的数据,使用结果码if(resultCode==3) {//获取返回的数据String string = data.getStringExtra("hello");}}}
(2)其中,在清单文件里,对被调用的第2个Activity组件配置如下:
<activityandroid:name=".SecondActivity"android:label="隐式调用SecondActivity"><intent-filter><action android:name="android.intent.action.ysdy" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>
(3)第2个Activity组件的代码如下:
public class SecondActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_second);}
(4)第3个Activity组件接收从MainActivity传递的捆绑数据,返回前设置非捆绑数据和结果码,其代码如下:
public class ThirdActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_third);String receiver="接收的数据如下: \n";Intent intent=getIntent();Bundle data= intent.getBundleExtra("data"); //获取捆绑数据receiver+="name: "+data.getString("name")+"\n";receiver+="age: "+data.getInt("age");//在屏幕上显示接收到的数据Toast.makeText(this, receiver, Toast.LENGTH_LONG).show();//通过意图对象携带返回数据intent=new Intent();intent.___ (1-7)putExtra______("hello","How are you?"); //设置返回数据和结果码_____(1-8)startActivityForResult_______(3,intent); }}
2、编写一个程序,根据不同的Uri获取联系人表中的相关信息如下所示:
(1)通过ContactsContract.Contacts.CONTENT_URI的Uri获取Contacts表中的联系人id和姓名,其字段分别为ContactsContract.Contacts._ID、ContactsContract.Contacts.DISPLAY_NAME。
(2)通过ContactsContract.CommonDataKinds.Phone.CONTENT_URI的Uri获取Data表中的联系人id和电话,其字段分别为ContactsContract.CommonDataKinds.Phone.CONTACT_ID、ContactsContract.CommonDataKinds.Phone.NUMBER。
请根据上述系统联系人数据库的相关信息,编写一个程序,用于读取系统联系人的姓名和电话,并将读取的信息显示在界面中。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private TextView textView;private Button query_contacts;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);query_contacts = findViewById(R.id.query_contacts);query_contacts.setOnClickListener(this);textView = findViewById(R.id.tv);}@Overridepublic void onClick(View v) {//动态申请读取手机通讯录权限ActivityCompat.___ (2-1)requestPermissions______(MainActivity.this, new String[]{"android.permission.READ_CONTACTS"}, 1);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == 1) {for (int i = 0; i < permissions.length; i++) {if (permissions[i].equals("android.permission.READ_CONTACTS")&& grantResults[i] == PackageManager.___ (2-2)PERMISSION_GRANTED______) {Toast.makeText(MainActivity.this,"已获取权限", Toast.LENGTH_SHORT).show();fetchContactInformation();}else{finish();}}}}//读取手机联系人信息的方法public void fetchContactInformation() {String id,name,phoneNumber;//定义一个字符串变量,用于保存查询结果String contacts = "";//获取内容解析器对象,用于对内容提供者的数据进行操作ContentResolver contentResolver = this.___ (2-3)getContentResolver()______;//首先查询手机联系人表获取联系人的id和姓名Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,null, null, null, null);while(cursor.moveToNext()) {//获取联系人idid = cursor.getString(cursor.getColumnIndex(___ (2-4)ContactsContract.Contacts._ID______));//获取联系人姓名name = cursor.getString(cursor.getColumnIndex(___ (2-5)ContactsContract.Contacts.DISPLAY_NAME______));contacts+= "姓名:"+name+"\n";//然后查询手机联系人数据表获取联系人的电话号码//根据id查询相应联系人的电话Cursor phoneCursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, ___ (2-6)ContactsContract.CommonDataKinds.Phone.CONTACT_ID______ + "=" + id, null, null);contacts+= "电话:";while(phoneCursor.moveToNext()) {phoneNumber = phoneCursor.getString(phoneCursor.getColumnIndex(___ (2-7)ContactsContract.CommonDataKinds.Phone.NUMBER______));contacts+= phoneNumber +" ";}contacts+="\n\n";phoneCursor.close();}//在界面上显示所查询到的所有手机联系人信息textView.setText(contacts);cursor.close();}}
综合题(10分)
使用TableLayout布局实现一个简单的计算器界面。
开发一个整数加法的程序,实现将计算结果显示到界面上的功能。
开发一个自定义对话框,其界面中显示标题、提示内容、确定和取消按钮。当点击回退健时,用于提示用户是否退出应用。
2025.6.5考试
第三大题 共用选项题
1.Service
2.动态添加Fragment
第四大题 简答题
1.引用图片资源方式
2.使用音频资源方式
3.Activity的生命周期
4.动态添加Fragment
背了那么多,尽考些有的没的
😄~~end~~😄