Android 技能评估考点全解析:linkedin-skill-assessments-quizzes 法语题库 69 题精讲

原创2026-10-01 12:16:321,909 阅读
文章标签:文档教程知识库教育

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/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)、权限申请、 等信息。其余三个文件名均为干扰项,Android 平台不存在这些文件。

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. 哪段代码能得到下图所示的布局?

顶部居中的 Dashboard 文本

  • [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,哪条陈述为真?

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 技能评估的命题主线非常清晰:

  1. 声明与配置层:Manifest(组件、权限、SDK 级别)、Gradle(模块、变体、BuildConfig、AGP)、资源目录与限定符——约三成题目考查"文件写在哪里、声明怎么写";
  2. 运行机制层:进程优先级与回收、Activity/Fragment 生命周期、Intent 与权限模型——考查对"系统如何调度组件"的理解;
  3. 实践方法论:布局扁平化、dp/WebP/低 RAM 适配、WorkManager、测试隔离、离线兜底——考查工程经验与官方推荐做法;
  4. 陷阱辨识:DEX 65536 限制、R8 混淆破坏网络模型、Fragment 重叠、Thread.sleep() 调试——考查踩坑与排障能力。

建议读者按主题分组刷题:先合上答案自测,再逐题对照 android/android-quiz-fr.md 的 [x] 标记核对,最后回到本文解析确认机制原理。若需要英文原题或更多语言版本,可对照 android/android-quiz.md 及 README.md 中 Android 一行的翻译链接继续学习。

登录后查看全文
linkedin-skill-assessments-quizzes