Android 技能评估考点全解析:linkedin-skill-assessments-quizzes 法语题库 69 题精讲
Android 技能评估考点全解析:linkedin-skill-assessments-quizzes 法语题库 69 题精讲
本篇文章以开源仓库 linkedin-skill-assessments-quizzes 中的 法语版 Android 题库 为核心骨架,系统梳理了 Android 技能评估中反复出现的 69 个高频考点,覆盖应用清单与资源管理、Gradle 构建系统、组件与生命周期、UI 布局、数据存储与网络、后台任务、测试与性能优化等主题。读完本文,你既能逐题对照答案自查,也能理解每个考点背后的 Android 平台机制,为面试、笔试或日常开发查漏补缺提供一份可直接引用的参考资料。
题库概况:文档位置、题量与使用方式
本仓库以 Markdown 问答形式维护了 LinkedIn 技能评估(Skill Assessments)的完整参考题库。Android 方向的核心文档位于 android/android-quiz.md(共 77 题),本文重点讲解的法语翻译版位于 android/android-quiz-fr.md,收录编号 Q1~Q69 的题目(其中文档中未出现 Q39 编号)。根据 README.md 的索引表,Android 题库还提供了中文、德语、西班牙语、意大利语、印地语、乌克兰语等翻译版本,可对照阅读:
- android/android-quiz-ch.md(中文)
- android/android-quiz-de.md(德语)
- android/android-quiz-es.md(西班牙语)
- android/android-quiz-it.md(意大利语)
- android/android-quiz-hi.md(印地语)
- android/android-quiz-ua.md(乌克兰语)
文档中涉及界面截图的题目,其配图统一存放在 android/image/ 目录下(如 shape.png、00.jpeg~04.jpeg、43.jpeg、44.jpeg、Q62-*.png、Q75.png 等),可直接打开图片对照题目理解界面效果。使用本仓库时,建议先独立作答,再对照带 [x] 标记的正确答案与本文解析,最后回到 android/android-quiz-fr.md 逐题核对。
考点地图:69 道题覆盖的八大技术主题
| 主题 | 覆盖题号 | 考查方向 |
|---|---|---|
| 应用清单与资源管理 | Q1、Q2、Q22、Q26、Q27、Q31、Q38、Q45、Q46、Q53、Q54、Q59、Q60、Q69 | AndroidManifest.xml、drawable、资源目录限定符、dimens、字符串资源 |
| Gradle 构建系统与变体管理 | Q8、Q10、Q13、Q34、Q43、Q48、Q50、Q51、Q52、Q61、Q66 | settings.gradle、product flavor、build type、BuildConfig、DEX 限制、R8 |
| 组件、生命周期、Intent 与权限 | Q3、Q6、Q7、Q11、Q14、Q16、Q17、Q18、Q30、Q32、Q33、Q44、Q55、Q56、Q58 | Activity/Fragment 生命周期、进程优先级、Intent、运行时权限、Application |
| UI 布局与渲染性能 | Q5、Q20、Q29、Q37、Q42、Q47、Q57、Q62、Q67 | RecyclerView、dp 与 px、LinearLayout、ConstraintLayout、布局层级 |
| 数据存储、网络与命令行调试 | Q12、Q19、Q21、Q23、Q24、Q28、Q36 | SharedPreferences、SQLite、Retrofit、网络权限、adb |
| 后台任务、推送与即时体验 | Q4、Q63、Q64、Q65 | WorkManager、推送通知、动态功能模块、离线状态 |
| 测试策略与工具链 | Q9、Q15、Q25、Q49、Q68 | @VisibleForTesting、AndroidJUnitRunner、Espresso、androidTest |
| 性能优化与代码复用 | Q35、Q40、Q41 | WebP 图片、低 RAM 设备、模块化与 <include/> |
一、应用清单与资源管理:Manifest、drawable 与资源目录
这部分考查 Android 应用的"骨架"——清单文件声明组件与权限,资源目录按配置提供差异化内容。
Q1. 要为应用添加功能、组件和权限,需要修改哪个文件?
- [x] AndroidManifest.xml
- [ ] Composants.xml
- [ ] AppManifest.xml
- [ ] ComponentManifest.xml
解析:AndroidManifest.xml 是每个 Android 应用的必需清单文件,它声明应用包名、四大组件(Activity、Service、BroadcastReceiver、ContentProvider)、权限申请、
Q2. 要使 ImageView 无障碍可访问,应使用哪个 XML 属性?
- [ ] android:talkBack
- [ ] android:labelPour
- [ ] android:indice
- [x] android:contentDescription
解析:android:contentDescription 为视图提供文本描述,供 TalkBack 等屏幕阅读器朗读,是无障碍(Accessibility)的基本要求。android:labelFor 用于把标签关联到输入控件,android:hint 是输入框提示文本,android:talkBack 不存在。
Q59. AndroidManifest.xml 文件的作用是什么?
- [ ] 它描述应用的组件
- [ ] 它声明应用所需的最低 Android API 级别
- [ ] 它通过指定包名提供应用唯一名称
- [x] 以上全部
解析:Manifest 的作用是复合的:描述应用组件(<activity>、<service> 等)、通过 <uses-sdk>/<uses-sdk> 属性声明最低 API 级别、通过 package 属性指定唯一包名。因此四个选项中"以上全部"正确。
Q60. <uses-sdk> 元素中哪个属性用于指定应用运行所需的最低 API 级别?
- [ ] android:targetSdkVersion
- [x] android:minSdkVersion
- [ ] android:maxSdkVersion
- [ ] 以上都不是
解析:android:minSdkVersion 声明应用兼容的最低 API 级别;android:targetSdkVersion 声明应用针对测试的目标级别(影响系统行为兼容模式);android:maxSdkVersion 已不推荐使用。
Q38. 下列哪组源码在 Android Studio 创建新项目时默认不可用?
- [x]
<activity android:name=".ExampleActivity" />
- [ ]
<activity android:name=".ExampleActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
</intent-filter>
</activity>
- [ ]
<activity android:name=".ExampleActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- [ ]
<activity android:name=".ExampleActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
解析:文档原说明指出,intent-filter 用于让 Activity 能被其他应用通过 Intent 访问,因此应选择没有 intent-filter 的选项。MAIN/LAUNCHER 过滤器是应用入口 Activity 的标配,Android Studio 新建项目默认生成;其余带过滤器的声明表明该 Activity 可被隐式 Intent 唤起,属于按需声明,而非默认源码。
Q22. 哪个 drawable 定义能得到下图所示的形状?
- [ ]
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke android:width="4dp" android:color="@android:color/white" />
<solid android:color="@android:color/black" />
</shape>
- [ ]
<oval xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="4dp" android:color="@android:color/black"/>
<solid android:color="@android:color/white"/>
</oval>
- [x]
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke android:width="4dp" android:color="@android:color/black" />
<solid android:color="@android:color/white" />
</shape>
- [ ]
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke android:width="4dp" android:color="@android:color/white" />
<solid android:color="@android:color/white" />
</shape>
解析:<shape> 是 XML drawable 的根元素,android:shape="oval" 定义椭圆/圆形;<stroke> 设置描边(黑色 4dp),<solid> 设置填充(白色)。<oval> 不是合法标签,描边与填充颜色颠倒的选项也与图示不符。
Q46. 哪个 drawable 定义能得到下图所示的形状?
- [ ]
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient android:startColor="@android:color/white"
android:endColor="@android:color/black"
android:angle="45"/>
</shape>
- [ ]
<rectangle xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="@android:color/white"
android:endColor="@android:color/black"
android:angle="135"/>
</rectangle>
- [x]
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="@android:color/white"
android:endColor="@android:color/black"
android:angle="135"/>
</shape>
- [ ]
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="@android:color/white"
android:endColor="@android:color/black"
android:angle="98"/>
</shape>
解析:图示为白→黑的对角渐变矩形。<shape android:shape="rectangle"> 定义矩形,<gradient android:angle="135"/> 表示从左上到右下的对角渐变(angle 必须是 45 的倍数,135 对应白到黑、左上到右下的方向)。<rectangle> 标签不存在,angle="98" 不是合法取值。
Q26. 要在布局文件中加入一个可复用的视图组件,应使用哪个标签?
- [ ]
<merge/> - [x]
<include/> - [ ]
<layout/> - [ ]
<add/>
解析:<include> 用于在布局中复用另一个布局文件;<merge> 用于消除冗余的布局层级(通常与 <include> 配合);<layout> 仅作为 Data Binding 布局的根标签;<add> 不存在。
Q27. 想为横屏且语言为法语的设备提供不同的 drawable,哪个目录名是正确的?
- [ ] fr-land-drawable
- [x] drawable-fr-land
- [ ] french-land-drawable
- [ ] french-land-drawable
解析:Android 资源目录的限定符顺序约定为"语言-地区-方向"等,写法是 drawable-<限定符>,语言代码在前、方向在后,因此 drawable-fr-land 正确。这是资源系统按当前配置自动选择资源的基础机制。
Q31. 什么时候应该把文件存放在 /assets 目录?
- [x] 当你需要按原始文件名和目录层级访问文件时
- [ ] 当你需要通过资源 ID(如
R.assets.filename)访问文件时 - [ ] 当你有定义补间动画的 XML 文件时
- [ ] 当你需要以原始形态通过
Resources.openRawResource()访问文件时
解析:/assets 目录的文件不参与资源编译,不生成资源 ID,只能通过 AssetManager 按原始文件名/路径访问,适合存放需要保留原始层级结构的数据文件。需要资源 ID 或 openRawResource() 的场景应使用 res/raw/。
Q45. 应用启动图标应该放在哪个目录?
- [ ] /drawable
- [ ] /icon
- [x] /mipmap
- [ ] /launcher
解析:启动图标(launcher icon)按惯例放在 res/mipmap-* 系列目录(mipmap-mdpi、mipmap-hdpi、mipmap-xhdpi 等),以便系统在不同密度下选择合适尺寸,部分设备还会据此生成自适应图标。
Q53. 给定如下 dimens.xml,如何为 ImageView 设置中等大小的底部外边距?
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="spacing_medium">8dp</dimen>
<dimen name="spacing_large">12dp</dimen>
</resources>
- [x]
<ImageView
android:id="@+id/image_map_pin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_medium"
android:src="@drawable/map_pin" />
解析:引用 dimens 资源的语法是 @dimen/<name>,且外边距属性名为 android:layout_marginBottom。选项中的 @resources/spacing_medium、layout_botttom(拼写错误)等均为错误写法。把尺寸抽离到 dimens.xml 是保持 UI 间距一致性的最佳实践。
Q54. 下列哪项不是把图片、字符串等应用资源从代码中外部化的好处?
- [ ] 它允许 Android 在运行时根据当前配置自动选择合适的资源
- [x] 它能让应用性能更好,因为代码和资源被分离了
- [ ] 它允许你根据用户语言设置提供不同的 UI 体验
- [ ] 它允许你根据用户设备尺寸提供不同的 UI 体验
解析:资源外部化的核心价值是"配置驱动"——根据语言、屏幕尺寸、密度等自动切换资源,而不是提升运行性能。将代码与资源分离并不直接带来性能收益,因此该项不是好处。
Q69. 下列哪项不是为应用提供字符串的资源类型?
- [ ]
String - [x]
Text - [ ]
String-array - [ ]
Plurals
解析:Android 字符串资源类型包括 string(单个字符串)、string-array(字符串数组)和 plurals(复数形式)。Text 不是合法的资源类型标签。
二、Gradle 构建系统与变体管理
构建体系是 Android 工程化的核心,这部分题目覆盖模块声明、SDK 版本、构建变体、BuildConfig 与代码压缩。
Q8. 要在项目中加入 about 和 settings 两个模块,哪个文件的写法正确?
- [ ] build.gradle 中:
include ':app',':about' ':settings' - [x] settings.gradle 中:
include ':app',':about' ':settings' - [ ] settings.gradle 中:
include ':about',':settings' - [ ] gradle.properties 中:
include ':app',':about' ':settings'
解析:模块(子项目)的声明必须写在根目录的 settings.gradle 中,通过 include 指令列出各模块路径。gradle.properties 用于定义 Gradle 或系统属性,build.gradle 用于配置构建逻辑,两者都不负责模块收录。
Q10. 如何在 build.gradle 中指定:应用至少需要 API 21 才能运行,但可按 API 28 进行测试?
- [ ]
defaultConfig {
...
minApiVersion 21
targetApiVersion 28
}
- [ ]
defaultConfig {
...
targetSdkVersion 21
testSdkVersion 28
}
- [ ]
defaultConfig {
...
minSdkVersion 21
testApiVersion 28
}
- [x]
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 28
}
解析:minSdkVersion 声明最低运行 API 级别,targetSdkVersion 声明目标(测试/兼容)API 级别。minApiVersion、testSdkVersion、testApiVersion 都不是有效的 DSL 属性名。这是 defaultConfig 中最常用的两个配置项。
Q13. 什么时候应该使用 product flavor(产品风味)?
- [ ] 当应用字符串需要支持多种语言时
- [ ] 当需要根据设备物理尺寸提供不同版本时
- [ ] 当需要根据屏幕密度提供不同版本时
- [x] 当你想提供带有自定义配置和资源的应用版本时
解析:product flavor 用于构建同一应用的不同"变体"(如免费版/付费版、生产环境/预发环境),每个 flavor 可拥有独立的配置与资源。多语言、尺寸、密度适配属于资源限定符的职责,不属于 flavor 的典型用途。
Q48. 给定 build.gradle 片段,下列哪项不是可能的构建变体?
android {
...
defaultConfig{...}
buildTypes{
debug{...}
release{...}
}
flavorDimensions "environment"
productFlavors {
production {...}
staging {...}
}
}
- [ ] productionDebug
- [x] developmentDebug
- [ ] stagingDebug
- [ ] stagingRelease
解析:构建变体 = flavor × buildType 的笛卡尔组合。本示例的 flavor 是 production、staging,buildType 是 debug、release,因此变体为 productionDebug、productionRelease、stagingDebug、stagingRelease。developmentDebug 中不存在 development 这个 flavor,故不可能生成。
Q50. 给定名为 app-internal-debug.apk 的构建产物,下列哪个说法最可能成立?
- [ ] 该 APK 是在开发机上由 debug 产品风味构建的
- [ ] 该 APK 由 internalDebug 产品风味构建
- [ ] 该 APK 由 debug 风味和 internal 构建类型组合而成
- [x] 该 APK 由 debug 构建类型与 internal 产品风味组合而成
解析:APK 命名约定为 <module>-<flavor>-<buildType>.apk。app-internal-debug 表示模块 app、flavor 为 internal、buildType 为 debug。
Q34. 想只在 release 构建中启用分析追踪,如何在生成的 BuildConfig 类中新增字段?
- [ ]
buildTypes {
debug {
buildConfig 'boolean', 'ENABLE_ANALYTICS', 'false'
}
release {
buildConfig 'boolean', 'ENABLE_ANALYTICS', 'true'
}
}
- [ ]
buildTypes {
debug {
buildConfig 'String', 'ENABLE_ANALYTICS', 'false'
}
release {
buildConfig 'String', 'ENABLE_ANALYTICS', 'true'
}
}
- [x]
buildTypes {
debug {
buildConfigField 'boolean', 'ENABLE_ANALYTICS', 'false'
}
release {
buildConfigField 'boolean', 'ENABLE_ANALYTICS', 'true'
}
}
- [ ]
buildTypes {
debug {
buildConfigField 'boolean', 'ENABLE_ANALYTICS', 'true'
}
release {
buildConfigField 'boolean', 'ENABLE_ANALYTICS', 'false'
}
}
解析:buildConfigField 是 AGP(Android Gradle Plugin)提供的 DSL 方法,语法为 buildConfigField '类型', '名称', '值',它会把字段注入生成的 BuildConfig 类。buildConfig 不是合法方法;最后一个选项把 debug/release 的值写反了。运行时可通过 BuildConfig.ENABLE_ANALYTICS 读取该字段。
Q66. 如果代码需要读取当前版本信息,应使用哪个类?
- [x]
BuildConfig - [ ]
BuildInfo - [ ]
ConfigParams - [ ]
ConfigInfo
解析:BuildConfig 由构建系统自动生成,包含 VERSION_CODE、VERSION_NAME、APPLICATION_ID、DEBUG 及 buildConfigField 自定义字段等版本/构建信息。其余类名均为杜撰。
Q43. 要升级到最新版 Android Gradle Plugin,应修改哪个文件?
- [ ] root_project_dir/app/build.gradle
- [ ] root_project_dir/settings.gradle
- [x] root_project_dir/build.gradle
- [ ] root_project_dir/app/gradle.properties
解析:AGP 版本声明位于项目根目录的 build.gradle(顶层 buildscript 或 plugins 块中),例如 classpath "com.android.tools.build:gradle:<version>" 或 id 'com.android.application' version '<version>'。app/build.gradle 声明的是模块自身的依赖与配置,不负责插件版本。
Q52. build.gradle 中哪条语句正确表明该模块是 Android 库模块?
- [ ]
apply plugin: 'com.module.library' - [x]
apply plugin: 'com.android.library' - [ ]
apply plugin: 'com.module.library' - [ ]
include plugin: 'com.module.library'
解析:Android 库模块的插件 ID 是 com.android.library;应用模块是 com.android.application。其余写法都不是有效的插件 ID 或 DSL 语法。
Q51. 构建项目时出现以下错误,可能说明什么?
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
- [ ] build.gradle 中包含错误的格式信息
- [ ] 在 build.gradle 中添加了超过 20 个依赖
- [x] 单个 DEX 文件中可引用的方法总数超出限制
- [ ] 代码中存在 NullPointerException
解析:经典的 64K 方法数限制——单个 DEX 文件最多可引用 65536 个方法。解决方案是启用 multidex(multiDexEnabled true,API 21+ 默认支持)或精简依赖。
Q61. 为缩减 release 版本的代码体积,Android Studio 使用哪个工具?
- [x] R8
- [ ] ProGuard
- [ ] Shrinker
- [ ] D8
解析:自 Android Gradle Plugin 3.4.0 起,代码压缩(shrinking)与优化默认由 R8 完成,ProGuard 仅作为规则配置格式沿用(keep 规则等)。D8 是 DEX 编译器,负责把字节码转为 DEX,不负责缩减代码。文档中该题还附有说明:使用 3.4.0 及以上版本的 AGP 时,插件不再用 ProGuard 执行编译期代码优化,而是与 R8 编译器协同工作。
三、组件、生命周期、Intent 与权限
本主题考查 Android 应用运行的"时间线":进程何时被杀、Activity 生命周期回调、Intent 传参与启动、权限申请策略。
Q6. 系统需要释放内存时会杀死进程,进程被杀概率取决于进程状态与 Activity 状态。哪种组合最容易被杀?
- [x] 进程:后台;Activity:已停止(Stopped)
- [ ] 进程:后台;Activity:已暂停(Paused)
- [ ] 进程:前台;Activity:已启动(Started)
- [ ] 进程:前台;Activity:已暂停(Paused)
解析:Android 进程优先级从高到低依次是:前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程。后台进程中的 Activity 处于 Stopped 状态时,系统回收它的优先级最低、最先被杀;前台进程(无论 Started 还是 Paused)优先级最高,几乎不会被杀。
Q3. 启动应用后,导航到新屏幕时崩溃了,下列哪个动作不能帮你诊断问题?
- [ ] 设置断点,逐行单步调试
- [ ] 使用 Android Studio 的 Profiler 检测 CPU 与网络异常
- [x] 在启动新 Activity 前加一个
Thread.sleep()调用 - [ ] 检查 Logcat 中的日志
解析:Thread.sleep() 只会阻塞当前线程、加剧 ANR 风险,不能提供任何崩溃信息。断点调试、Profiler 与 Logcat 都是标准诊断手段。
Q18. 如果主线程被阻塞过久,系统会显示哪个对话框?
- [ ] Thread not responding(线程无响应)
- [ ] Application suspended(应用已挂起)
- [x] Application Not Responding(应用无响应)
- [ ] Application blocked(应用已阻塞)
解析:当主线程(UI 线程)超过 5 秒无法处理输入事件或广播时,系统弹出经典的 ANR(Application Not Responding) 对话框,提示"应用无响应",用户可选择等待或关闭。
Q7. 已创建依赖 Intent 内字符串数据的 NextActivity 类,哪段代码能正确启动该 Activity?
- [ ]
Intent(this, NextActivity::class.java).also { intent ->
startActivity(intent)
}
- [ ]
Intent(this, NextActivity::class.java).apply {
put(EXTRA_NEXT, "some data")
}.also { intent ->
activityStart(intent)
}
- [x]
Intent(this, NextActivity::class.java).apply {
putExtra(EXTRA_NEXT, "some data")
}.also { intent ->
startActivity(intent)
}
- [ ]
Intent(this, NextActivity::class.java).apply {
put(EXTRA_NEXT, "some data")
}.also { intent ->
activityStart(intent)
}
解析:向 Intent 附加数据的 API 是 putExtra(String, ...)(Kotlin 中 putExtra(EXTRA_NEXT, "some data")),启动 Activity 的 API 是 startActivity(intent)。put(...) 与 activityStart(...) 均为杜撰方法。正确写法:apply 块负责填充 extra,also 块负责启动。
Q58. 已创建显示应用详情的 AboutActivity,哪段代码能正确启动它?
- [x]
Intent(this, AboutActivity::class.java).also { intent ->
startActivity(intent)
}
- [ ]
Intent(this, AboutActivity::class).also { intent ->
startService(intent)
}
- [ ]
Intent(this, AboutActivity::class).also { intent ->
activity(intent)
}
- [ ]
Intent(this, AboutActivity::class).also { intent ->
startActivity(intent)
}
解析:文档原说明指出:Intent(Context packageContext, Class<?> cls) 的第二个参数是 Java 的 Class 而非 Kotlin 的 KClass,因此必须写 AboutActivity::class.java;启动 Activity 应调用 startActivity,且传 startService 会尝试启动一个不存在的服务。
Q11. Activity 的 onActivityResult() 何时会被调用?
- [ ] 在父 Activity 中调用
finish()时 - [ ] 打开其他应用将当前应用置于后台时
- [ ] 目标 Activity 中调用
onStop()时 - [x] 目标 Activity 中调用
finish()时
解析:startActivityForResult() 启动的子 Activity 调用 finish() 后,结果会回传给发起方,触发其 onActivityResult() 回调(旧 API 中用于获取子页面返回的数据;新代码建议改用 Activity Result API 或 registerForActivityResult)。
Q14. 给定以下 Fragment 代码,如何访问布局中 id 为 text_home 的 TextView?
private lateinit var textView: TextView
override fun onCreateView(...): View? {
val root = inflater.inflate(R.layout.fragment_home, container, false)
textView = ??
return root
}
- [ ]
root.getById(R.id.text_home) - [ ]
findViewById(R.id.text_home) - [x]
root.findViewById(R.id.text_home) - [ ]
root.find(R.id.text_home)
解析:在 Fragment 中,视图已膨胀(inflate)到 root 根视图上,正确写法是 root.findViewById(R.id.text_home)(或 Kotlin 的 root.text_home 扩展属性)。getById、find 均不存在;直接调用无接收者的 findViewById 需要 Activity 上下文,在 Fragment 场景不适用。
Q17. 给定上述定义,如何访问 Fragment 布局中 id 为 text_home 的 TextView?
- [ ]
root.find(R.id.text_home) - [ ]
findViewById(R.id.text_home) - [ ]
root.getById(R.id.text_home) - [x]
root.findViewById(R.id.text_home)
解析:与 Q14 同源考点,正确答案一致——root.findViewById(R.id.text_home)。两个错误选项的方法名均为杜撰。
Q16. 当 Activity 被重建时,什么能帮你正确恢复用户状态?
- [ ]
onSaveInstanceState()方法 - [x] 以上全部
- [ ] 持久化存储
- [ ] ViewModel 对象
解析:状态恢复是组合策略:onSaveInstanceState() 保存轻量 UI 状态、ViewModel 在配置变更(如旋转)中存活并保留数据、持久化存储(Room/DataStore/文件)保存需要跨进程存活的数据。三者都正确,因此选"以上全部"。
Q44. 为什么开发者常把应用初始化代码放在 Application 类中?
- [x] 进程创建时,Application 类先于任何其他类被实例化
- [ ] Application 类在进程创建时、所有权限请求之后才被实例化
- [ ] 每次启动新 Activity 时都会创建 Application 类
- [ ] 每次调用后台服务时都会创建 Application 类
解析:Application 是应用进程的全局单例,进程启动时最先实例化,早于任何 Activity/Service/Receiver,因此适合放置全局初始化(如崩溃上报、DI 容器、网络库初始化)。它不会随 Activity 或 Service 的创建而重复创建。
Q55. 这段代码第五行的主要目的是什么?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post_create)
if (savedInstanceState != null) return
val fragment = CreatePostFragment()
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit()
}
- [ ] 确保当 savedInstanceState 不为 null 时 Activity 结束
- [ ] 确保 Activity 每次从旧状态恢复时都创建新 Fragment
- [ ] 防止在 Activity 从旧状态恢复时并排显示两个 Fragment
- [x] 防止在 Activity 从旧状态恢复时创建重叠的 Fragment
解析:if (savedInstanceState != null) return 是 Fragment 编程的经典防御写法。Activity 因配置变更被系统重建时,FragmentManager 会自动恢复已存在的 Fragment;如果 onCreate 再次无条件 add 一个 Fragment,就会造成 Fragment 叠加(重叠)。该行正是为了避免这种重复创建。
Q56. 下列哪个组件不是系统或用户进入应用的入口点?
- [ ] Activity
- [ ] ContentProvider
- [x] Fragment
- [ ] Service
解析:Android 的四大"应用入口点"组件是 Activity、Service、BroadcastReceiver 和 ContentProvider(均在 Manifest 中声明)。Fragment 依附于 Activity 存在,不是独立入口组件,也无需在 Manifest 中注册。
Q30. 想打开设备默认拨号应用,这段代码哪里有问题?
val dialerIntent = Intent()
val et = findViewById(R.id.some_edit_text)
dialerIntent.action = Intent.ACTION_DIAL
dialerIntent.data = Uri.parse("tel:" + et.getText()?.toString())
startActivity(dialerIntent)
- [x] 使用
Intent.ACTION_DIAL时应改用startActivityWithResult() - [ ] 使用该
dialerIntent时,需要为Intent.ACTION_DIAL添加Intent.FLAG_ACTIVITY_NEW_TASK - [ ] 在不支持
Intent.ACTION_DIAL的设备上会抛出 ActivityNotFoundException - [ ] 使用
Intent.ACTION_DIAL前必须申请android.permission.CALL_PHONE权限
解析:ACTION_DIAL 只打开拨号界面(无需权限),返回结果是可选的,并不强制要求 startActivityForResult()。该代码段本身逻辑正确(设置 action、data、startActivity),题目问的是"哪里有问题"——按题库标准,唯一合理的"问题"是:严格意义上 ACTION_DIAL 拨号场景下使用 startActivityWithResult() 才是推荐写法(若需接收拨号结果)。CALL_PHONE 权限仅对直接拨号 ACTION_CALL 必需,ACTION_DIAL 不需要;FLAG_ACTIVITY_NEW_TASK 仅在非 Activity 上下文中需要。
Q32. 想让用户能在应用中拍照。相比直接申请相机权限,创建 intent 的非优势是什么?
- [ ] 用户可以选择他们喜欢的相机应用拍照
- [ ] 应用内无需申请拍照权限
- [x] 你对用户体验有完全控制——处理相机
intent的应用会遵循你的设计 - [ ] 无需自行设计 UI——处理相机
intent的应用会提供 UI
解析:使用 ACTION_IMAGE_CAPTURE intent 是"委托"模式:把拍照交给系统选中的相机应用,代价正是失去对 UI 与体验的控制(相机的 UI 由第三方应用决定,不会遵循你的设计)。因此"完全控制用户体验"恰恰不是这种做法的优势。
Q33. 什么时候应该使用 ActivityCompat.shouldShowRequestPermissionRationale() 函数?
- [ ] 用户首次打开应用且你想解释某个权限用途时
- [ ] 用户此前拒绝过权限申请并选择了"了解更多"时
- [ ] 用户此前拒绝过权限申请且你想解释其用途时
- [x] 用户此前拒绝了权限申请并勾选了"不再询问",但你的应用必须依赖该权限运行时
解析:shouldShowRequestPermissionRationale() 在用户**拒绝过权限且未勾选"不再询问"**时返回 true,用于决定是否展示解释性说明;当用户勾选"不再询问"后它返回 false,此时应用应引导用户去系统设置页手动授权。题干描述的正是"需要向用户说明为什么必须去设置中开启权限"的场景。
四、UI 布局与渲染性能
本主题聚焦视图层级、布局容器选择与尺寸单位,是界面正确性与渲染性能的关键。
Q5. 实现一个垂直展示 widget 列表的 RecyclerView,哪组组件类组合是正确的?
- [ ]
RecyclerView
RecyclerView.Adapter<T extends BaseAdapter>
RecyclerView.ViewHolder<T extends BaseViewHolder>
LinearLayoutManager
- [ ]
RecyclerView
RecyclerView.Adapter
RecyclerView.ViewHolder<T extends BaseViewHolder>
LinearLayoutManager
- [ ]
RecyclerView
RecyclerView.Adapter
RecyclerView.ViewHolder
LinearLayoutManager
- [x]
RecyclerView
RecyclerView.Adapter<VH extends ViewHolder>
RecyclerView.ViewHolder
LinearLayoutManager
解析:RecyclerView 的标准组合是:RecyclerView(容器)+ RecyclerView.Adapter<VH extends ViewHolder>(适配器,泛型参数必须是 ViewHolder 的子类)+ RecyclerView.ViewHolder + LayoutManager(LinearLayoutManager 实现垂直列表)。其余选项中出现了不存在的 BaseAdapter、BaseViewHolder 泛型约束。
Q57. 显示大量可滚动元素应该用什么?
- [ ] ListView
- [x] RecyclerView
- [ ] LinearLayout
- [ ] ScrollView
解析:RecyclerView 是官方推荐的列表组件,通过 ViewHolder 复用机制高效处理大数据集;ListView 是旧方案;LinearLayout + ScrollView 会把所有子视图一次性实例化,大列表下内存开销高、易卡顿。
Q20. 为什么在 Android 上用像素(px)定义尺寸会有问题?
- [ ] 虽然屏幕像素密度不同,但用像素定义尺寸不受影响
- [ ] 大设备总是有更多像素,用像素定义尺寸会影响 UI 元素
- [x] 相同数量的像素在不同设备上可能对应不同的物理尺寸,影响 UI 元素外观
- [ ] 不同设备对像素的理解不同,影响 UI 元素外观
解析:像素密度(dpi)决定了"多少像素 = 1 英寸"。同一 100px 在高密度屏上物理尺寸小、在低密度屏上物理尺寸大。因此应使用 dp(密度无关像素) 与 sp(缩放像素,用于文字),由系统按密度换算。
Q29. 下面哪个图最符合这段 LinearLayout?
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
解析:orientation="horizontal" 使两个 Button 水平排列,gravity="center" 让它们在父容器内水平垂直居中,wrap_content 使按钮贴合内容。正确答案是选项 B 对应的 01.jpeg(两个按钮水平居中)。对比其他选项图片 00.jpeg、02.jpeg、03.jpeg 可直观看到排列差异。
Q37. 哪段代码能得到下图所示的布局?
- [x]
<androidx.constraintlayout.widget.ConstraintLayout ...>
<TextView
android:id="@+id/text_dashboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:text="Dashboard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
解析:图示为顶部居中、两端留有 8dp 边距的 "Dashboard" 文本。正确实现要点:Start/End 约束到父容器 + marginStart/End = 8dp(产生左右 8dp 边距)+ Top 约束到父容器顶部。含 marginTop="16dp" 的选项顶部间距不符,缺失 marginEnd 的选项右侧无 8dp 边距,含 android:padding="8dp" 的选项把内边距混入也不符图示。
Q42. 哪种布局最适合大型复杂层级?
- [ ] LinearLayout
- [x] ConstraintLayout
- [ ] FrameLayout
- [ ] RelativeLayout
解析:ConstraintLayout 以扁平的约束关系描述视图位置,可避免嵌套多层 LinearLayout/RelativeLayout 带来的性能损耗,是大型复杂 UI 的首选。官方模板新建项目默认使用 ConstraintLayout。
Q47. 给定下面的 ConstraintLayout,哪条陈述为真?
- [ ] 视图 B 没有水平约束
- [ ] 视图 C 约束过多
- [x] 视图 B 没有垂直约束
- [ ] 视图 C 被约束到父容器
解析:约束是成对出现的(水平方向:start↔end;垂直方向:top↔bottom)。从编辑器截图看,B 缺少垂直方向的约束线,因此处于"垂直方向未约束"状态;无约束的视图默认停留在其起始位置(布局编辑器中的小旗标)。这与 Q67 的"未约束端"考点相互呼应。
Q67. 在下面的 ConstraintLayout 中,为什么按钮没有扩展到父容器宽度?
<androidx.constraintlayout.widget.ConstraintLayout ...>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- [ ] 按钮没有尺寸
- [x] 按钮没有约束到父容器末端(end)
- [ ] 按钮不能超过其默认尺寸
- [ ] 按钮的高度也应设为 0dp
解析:layout_width="0dp"(match constraints)表示宽度由约束决定,但前提是水平方向两端都有约束(start 与 end)。这里只约束了 start,end 端缺失,系统无法计算宽度,按钮就停留在 wrap 的默认宽度。补上 app:layout_constraintEnd_toEndOf="parent" 即可撑满父容器。
Q62. 哪种布局层级绘制最快?
解析:视图绘制时间与层级深度、节点数量正相关。四张层级图中,A 的树深度最浅、节点最少(如扁平化的 ConstraintLayout 结构),因此绘制/测量/布局成本最低。这是"保持布局扁平、避免深嵌套"原则的直观体现。(注:原文该题引用的是外部图片链接,本仓库以 Q62-A.jpg 等本地图片替代。)
五、数据存储、网络与命令行调试
Q19. 如何从 SharedPreferences 读取用户邮箱,并确保返回不为 null?
- [ ]
getPreferences(this).getString(EMAIL, "") - [ ]
getDefaultSharedPreferences(this).getString(EMAIL, null) - [x]
getDefaultSharedPreferences(this).getString(EMAIL, "") - [ ]
getPreferences(this).getString(EMAIL, null)
解析:文档原文说明:getString() 的第二个参数是键不存在时的默认返回值,因此必须传入空字符串 "",而不是 null,以保证返回值非空。同时应使用 getDefaultSharedPreferences()(默认偏好文件)而非 getPreferences()(仅限当前 Activity 的偏好文件)。
Q23. 要保存一小撮键值对数据,应该用什么?
- [ ] 外部文件存储
- [x] SharedPreferences
- [ ] SQLite
- [ ] 内部文件存储
解析:SharedPreferences(或其现代替代 DataStore)专为轻量键值对设计,适合保存设置项、用户偏好等小数据。SQLite/Room 面向结构化关系数据,文件存储面向大块文件。
Q12. 要根据 id 从 API 删除事件,哪段 Retrofit 代码定义了这个请求?
- [ ]
@DELETE("événements")fun deleteEvent(@Path("id") id: Long): Call<Unit> - [x]
@DELETE("events/{id}")fun deleteEvent(@Path("id") id: Long): Call<Unit> - [ ]
@REMOVE("events/{id}")fun deleteEvent(@Path("id") id: Long): Call<Unit> - [x]
@DELETE("events/{id}")fun deleteEvent(@Path("id") id: Long): Call<Unit>
解析:Retrofit 删除请求用 @DELETE 注解,路径参数用 {id} 占位符并在参数上配 @Path("id")。@REMOVE 注解不存在;不带 {id} 的写法无法按 id 删除。(注:法语文档该题第 2、4 个选项内容相同且均带 [x],属于原文笔误,正确答案应为 @DELETE("events/{id}")。)
Q24. 要从 API 拉取照片列表,哪段代码定义了 Retrofit 的 GET 请求?
- [ ]
@GET("photo/{id}")fun listPhotos(@Path("id") id: Long?): Call<Photo> - [ ]
@LIST("photo")fun listPhotos(): Call<List<Photo>> - [ ]
@GET("photo")fun listPhotos(): Call<Photo> - [x]
@GET("photo")fun listPhotos(): Call<List<Photo>>
解析:拉取列表应返回 Call<List<Photo>>,用 @GET 注解(@LIST 不存在)。返回单个 Photo 与"列表"需求不符;带 {id} 路径参数属于按 id 查单条,也不符合"列表"语义。
Q28. 为什么需要在应用中声明 android.permission.ACCESS_NETWORK_STATE 权限?
- [ ] 监控设备位置,避免用户在静止时发起网络请求
- [ ] 申请从应用发起网络调用的能力
- [ ] 监控设备网络状态,以便向用户展示应用内横幅
- [x] 监控设备网络状态,避免在网络不可用时发起网络调用
解析:ACCESS_NETWORK_STATE 是普通权限,只允许读取网络连接状态,用来判断"是否联网"以避免无效请求;真正发起网络请求需要 INTERNET 权限。展示横幅属于产品行为而非权限用途。
Q36. 已写好网络请求代码且在开发环境测试通过,但发布到 Play 商店后请求失败。下列哪项不会帮你排查此问题?
- [ ] 检查是否为网络 DTO 添加了 ProGuard keepclassmembers 规则
- [x] 使用 Android Studio Profiler 检测 CPU、内存与网络异常
- [ ] 查看服务器日志或服务端控制台
- [ ] 确认网络 DTO 属性是否带有
@SerializedName注解
解析:开发环境正常、release 异常,典型原因是构建差异:代码压缩(R8/ProGuard)混淆了 DTO 字段名、@SerializedName 缺失导致 Gson/Moshi 无法反序列化。Profiler 是运行时性能分析工具,无法发现"字段被混淆"这类静态构建问题,因此无助于排查。这也是"混淆规则要覆盖网络模型"这一工程经验的来源。
Q21. 需要列出已连接电脑且开启 USB 调试的设备,应执行哪条 adb 命令?
- [ ] list devices
- [x] adb devices
- [ ] list avd
- [ ] dir devices
解析:adb devices 列出所有已连接且被 adb 识别(授权)的设备。list avd 属于 avdmanager 的用法(列出模拟器镜像),其余写法不存在。
六、后台任务、推送与即时体验
Q63. 目前推荐用什么方式处理长时间运行的后台任务?
- [x] WorkManager
- [ ] AsyncTask
- [ ] IntentService
- [ ] Thread
解析:WorkManager 是官方推荐的后台任务调度库,支持持久化、约束条件(网络/充电状态)、重试与链式任务,能保证任务在进程重启后继续执行。AsyncTask 已被废弃,IntentService 也被 JobIntentService/WorkManager 取代,裸 Thread 不适合需要生命周期感知的长任务。
Q4. 推送通知为什么会失效?
- [x] 以上全部
- [ ] 设备令牌未正确发送给推送服务商
- [ ] 设备/模拟器未安装 Google Play Services
- [ ] 设备开启了电池优化
解析:推送失效是多因素叠加的结果:设备令牌错误会导致推送无法路由到目标设备;缺少 Google Play Services 时 FCM 无法建立通道;电池优化可能延迟或阻止后台接收。因此选"以上全部"。
Q64. 要通过 Google Play 以按需下载或即时体验的方式向用户提供部分功能,应创建哪种模块?
- [ ] Library 模块
- [x] Dynamic Feature 模块
- [ ] Android App 模块
- [ ] Google Cloud 模块
解析:Dynamic Feature(动态功能模块)允许把功能打包为独立 APK,按需/即时下载,支持"即时应用(Instant App)"体验。Library 模块是编译期复用的代码库,不具备按需分发能力。
Q65. 下列哪种做法不推荐用于在应用中提供实用的离线状态?
- [ ] 缓存数据
- [ ] 本地存储数据
- [ ] 在连接丢失时把待发送请求排队,待恢复后执行
- [x] 总是告知用户连接已丢失
解析:优秀的离线体验应"静默兜底":缓存、本地持久化、请求队列重放都是推荐做法。只反复弹出"连接已丢失"提示而不提供任何可用内容,属于打扰用户且没有实际价值,故不推荐。
七、测试策略与工具链
Q9. 使用 @VisibleForTesting 注解的好处是什么?
- [x] 表明类/方法/字段的可见性被放宽以便代码可测试
- [ ] 表明类/方法/字段仅对测试代码可见
- [ ] 表明类/方法/字段的可见性被提高以使代码更难测试
- [ ] 若类/方法/字段被错误访问,会在运行时生成错误
解析:@VisibleForTesting 是 AndroidX 的文档性注解:开发者为让测试代码能访问,把 private 放宽为 internal/public,用该注解显式标记"这是为了测试而放宽的",提醒他人不要在生产代码中依赖它。它只提供 lint 提示,不改变运行时行为。
Q15. 为什么执行 UI 测试时要使用 AndroidJUnitRunner?
- [x] 该 runner 负责把测试包与被测应用加载到设备/模拟器上,执行测试并报告结果
- [ ] 该 runner 会在测试执行期间为每个屏幕生成截图
- [ ] 该 runner 通过为每个测试类提供实例来便于并行执行测试类
- [ ] 该 runner 便于与设备上可见元素交互,无论焦点在哪个 Activity 或 Fragment
解析:AndroidJUnitRunner 是 Android 仪器化测试(instrumented test)的入口,它加载测试与目标应用、运行 JUnit3/4 风格测试并汇总结果。截图由截图测试库负责,元素交互由 Espresso/UI Automator 负责,与 runner 职责无关。
Q49. 什么时候应把测试类放在 androidTest 目录?
- [ ] 当测试只包含单元测试时
- [ ] 当要运行的测试数量很大(500+)时
- [ ] 当测试需要在本地电脑上运行时
- [x] 当测试需要在真实或虚拟设备上运行时
解析:Android 测试分两类:test/(本地单元测试,运行在 JVM 上)与 androidTest/(仪器化测试,运行在设备/模拟器上,可访问真实系统 API 与 UI)。需要真机/模拟器环境的测试必须放在 androidTest。
Q25. 给定下面的测试类,哪段代码是正确的断言?
- [ ]
assertThat(resultAdd).is(2.0) - [x]
assertNotNull(resultAdd) - [ ]
assertThat(resultAdd).isEqualTo(2.0) - [ ]
assertThat(resultAdd)
解析:题干上下文是 JUnit 风格的断言,assertNotNull() 是 JUnit 4 的标准静态断言方法。assertThat(resultAdd).is(2.0) 中的 .is() 是 Hamcrest/Truth 等库的 API 而非 JUnit 默认 API;.isEqualTo() 是 Truth 的写法;只写 assertThat(resultAdd) 缺少最终断言动作。
Q68. 下列哪项不是 Espresso 测试中使用 Idling Resource(空闲资源)的场景?
- [ ] 管理系统服务
- [x] 处理用户输入事件
- [ ] 从网络或本地数据源加载数据
- [ ] 执行位图变换
解析:Idling Resource 用于让 Espresso 等待异步耗时操作(网络加载、数据库读写、位图变换、后台任务)完成,避免测试在 UI 空闲判断上误报。用户输入事件本身就是 Espresso 要模拟的动作,不属于需要"等待"的异步工作。
八、性能优化与代码复用
Q35. 为优化 APK 体积,应使用哪种图片编码?
- [ ] JPG
- [ ] PNG
- [ ] MPEG
- [x] WebP
解析:WebP 在同等视觉质量下文件体积通常小于 JPEG 与 PNG,Android 自 API 14 起原生支持(部分编码特性需 API 18+),是官方推荐的 APK 瘦身手段之一。MPEG 是视频编码格式,不适用于静态图片资源。
Q40. 为在低内存设备上节省资源,如何判断用户设备存储能力有限?
- [x] 使用
ActivityManager.isLowRamDevice()判断设备是否被标记为"低 RAM" - [ ] 使用
Activity.isLowRam()判断 - [ ] 使用
ConnectivityManager.hasLowMemory()判断 - [ ] 发起图片下载请求并检查设备剩余存储
解析:ActivityManager.isLowRamDevice() 返回设备是否属于低 RAM 配置(系统依据设备内存划分),应用可据此禁用大内存功能(如高分辨率图片、复杂动画)。其余方法名均为杜撰;"下载后检查存储"既浪费流量也不可靠。
Q41. 下列哪项不是复用 Android 代码的好方式?
- [ ] 使用被多个 Android 项目共享的公共 Gradle 模块
- [ ] 优先创建自定义 View 或 Fragment 而非 Activity
- [x] 优先构建 Activity 而非 Fragment
- [ ] 把 UI 布局拆分为公共组件,并用
<include/>在其它布局 XML 中引用
解析:Activity 是系统管理的重型组件、不可组合,大量复用 Activity 会导致代码难以维护;Fragment/自定义 View 可嵌入、可组合,才是复用之道。共享 Gradle 模块与 <include/> 布局复用都是官方推荐做法。
小结:高频考点的内在逻辑
回顾 69 道题可以发现,Android 技能评估的命题主线非常清晰:
- 声明与配置层:Manifest(组件、权限、SDK 级别)、Gradle(模块、变体、BuildConfig、AGP)、资源目录与限定符——约三成题目考查"文件写在哪里、声明怎么写";
- 运行机制层:进程优先级与回收、Activity/Fragment 生命周期、Intent 与权限模型——考查对"系统如何调度组件"的理解;
- 实践方法论:布局扁平化、dp/WebP/低 RAM 适配、WorkManager、测试隔离、离线兜底——考查工程经验与官方推荐做法;
- 陷阱辨识:DEX 65536 限制、R8 混淆破坏网络模型、Fragment 重叠、
Thread.sleep()调试——考查踩坑与排障能力。
建议读者按主题分组刷题:先合上答案自测,再逐题对照 android/android-quiz-fr.md 的 [x] 标记核对,最后回到本文解析确认机制原理。若需要英文原题或更多语言版本,可对照 android/android-quiz.md 及 README.md 中 Android 一行的翻译链接继续学习。











