十二月 21, 2025
34 分钟阅读

初级 Android 开发工程师面试题与答案

interview
career-advice
job-search
entry-level
初级 Android 开发工程师面试题与答案
Milad Bonakdar

Milad Bonakdar

作者

通过 Kotlin、生命周期、Jetpack Compose、Room、网络和协程问题,准备初级 Android 开发面试。


引言

初级 Android 开发面试通常会考察你能否用实际场景解释 Kotlin 基础、Android 生命周期、Compose 状态、本地存储、网络请求和协程。好的回答会把概念连接到一个可靠的小应用:把 UI 状态放在合适的位置,不阻塞主线程,安全持久化数据,并清楚处理错误。

使用本指南练习简短回答,并把每个主题关联到简历或作品集中的小项目。如果你做过待办事项、天气应用或 API 驱动页面,可以用这些例子让回答更具体,而不是像背诵定义。


Kotlin 基础知识 (6 个问题)

1. Kotlin 中 valvar 有什么区别?

答案:

  • val 声明一个不可变的(只读)变量。一旦赋值,其值就不能更改。
  • var 声明一个可变的变量。其值可以在初始化后更改。
  • 最佳实践: 默认情况下使用 val 以确保安全。仅在需要重新赋值时才使用 var
val name = "John"  // 无法更改
var age = 25       // 可以更改
age = 26           // 有效
// name = "Jane"   // 错误:Val 无法重新赋值

稀有度: 非常常见 难度: 简单


2. 解释 Kotlin 中的可空类型和安全调用运算符。

答案: Kotlin 的类型系统区分可空类型和非可空类型,以防止空指针异常。

  • 可空类型: 在类型后添加 ?String?
  • 安全调用 (?.): 仅当对象不为 null 时才调用方法
  • Elvis 运算符 (?:): 如果为 null,则提供默认值
  • 非空断言 (!!): 如果为 null,则抛出异常(谨慎使用)
var email: String? = "[email protected]"

// 安全调用
val length = email?.length  // 如果 email 为 null,则返回 null

// Elvis 运算符
val displayEmail = email ?: "No email"

// Let 函数
email?.let {
    println("Email: $it")
}

// 非空断言(危险)
val len = email!!.length  // 如果 email 为 null,则抛出异常

稀有度: 非常常见 难度: 简单


3. Kotlin 中 classdata class 有什么区别?

答案:

  • 普通类: 标准类定义
  • 数据类: 自动生成用于保存数据的有用方法
    • equals()hashCode()
    • toString()
    • copy() 函数
    • 用于解构的 componentN() 函数
// 普通类
class Person(val name: String, val age: Int)

// 数据类
data class User(val name: String, val age: Int)

val user1 = User("John", 25)
val user2 = User("John", 25)

println(user1 == user2)  // true (生成了 equals())
println(user1)           // User(name=John, age=25) (生成了 toString())

// 复制并修改
val user3 = user1.copy(age = 26)

// 解构
val (name, age) = user1

稀有度: 非常常见 难度: 简单


4. Kotlin 中的 Lambda 表达式和高阶函数是什么?

答案:

  • Lambda: 可以作为值传递的匿名函数
  • 高阶函数: 将函数作为参数或返回函数的函数
// Lambda 表达式
val sum = { a: Int, b: Int -> a + b }
println(sum(5, 3))  // 8

// 高阶函数
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

val result = calculate(10, 5) { x, y -> x * y }
println(result)  // 50

// 集合操作
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }

println(doubled)  // [2, 4, 6, 8, 10]
println(evens)    // [2, 4]

稀有度: 非常常见 难度: 中等


5. 解释 Kotlin 中的扩展函数。

答案: 扩展函数允许您向现有类添加新函数,而无需修改其源代码。

// String 的扩展函数
fun String.isPalindrome(): Boolean {
    return this == this.reversed()
}

println("radar".isPalindrome())  // true
println("hello".isPalindrome())  // false

// 带有参数的扩展函数
fun Int.times(action: () -> Unit) {
    repeat(this) {
        action()
    }
}

3.times {
    println("Hello")
}
// 打印 "Hello" 三次

稀有度: 常见 难度: 简单


6. Kotlin 中 ===== 有什么区别?

答案:

  • == 结构相等(使用 equals() 比较值)
  • === 引用相等(比较内存引用)
val str1 = "Hello"
val str2 = "Hello"
val str3 = str1

println(str1 == str2)   // true (相同的值)
println(str1 === str2)  // true (字符串池优化)
println(str1 === str3)  // true (相同的引用)

val list1 = listOf(1, 2, 3)
val list2 = listOf(1, 2, 3)

println(list1 == list2)   // true (相同的内容)
println(list1 === list2)  // false (不同的对象)

稀有度: 常见 难度: 简单


Android 组件 (5 个问题)

7. 什么是 Activity 并解释其生命周期。

答案: Activity 代表具有用户界面的单个屏幕。它具有明确定义的生命周期:

Loading diagram...
  • onCreate() 创建 Activity。初始化 UI,设置内容视图。
  • onStart() Activity 变为可见。
  • onResume() Activity 位于前台并可交互。
  • onPause() Activity 失去焦点(另一个 activity 进入前台)。
  • onStop() Activity 不再可见。
  • onDestroy() Activity 被销毁。
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 初始化视图,设置数据
    }
    
    override fun onStart() {
        super.onStart()
        // Activity 变为可见
    }
    
    override fun onResume() {
        super.onResume()
        // Activity 可交互,启动动画
    }
    
    override fun onPause() {
        super.onPause()
        // Activity 失去焦点,暂停正在进行的操作
    }
}

稀有度: 非常常见 难度: 简单


8. Activity 和 Fragment 有什么区别?

答案:

  • Activity: 代表一个完整的屏幕。用户交互的入口点。有自己的生命周期。
  • Fragment: Activity 中可重用的 UI 部分。一个 activity 中可以存在多个 fragment。具有与宿主 activity 相关的自己的生命周期。
  • Fragment 的优点:
    • 跨 activity 的可重用性
    • 模块化 UI 组件
    • 对平板电脑的支持(多窗格布局)
    • 导航组件集成
class MyFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_my, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 初始化视图
    }
}

// 在 Activity 中
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        supportFragmentManager.beginTransaction()
            .replace(R.id.fragment_container, MyFragment())
            .commit()
    }
}

稀有度: 非常常见 难度: 简单


9. 什么是 Intent 以及它有哪些类型?

答案: Intent 是一个消息传递对象,用于请求另一个应用程序组件执行操作。

  • 显式 Intent: 指定要启动的确切组件(按类名)
  • 隐式 Intent: 声明一个通用操作,系统会找到相应的组件
// 显式 Intent - 启动特定的 activity
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("USER_ID", 123)
startActivity(intent)

// 隐式 Intent - 打开 URL
val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(webIntent)

// 隐式 Intent - 分享文本
val shareIntent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "Check this out!")
}
startActivity(Intent.createChooser(shareIntent, "Share via"))

// 隐式 Intent - 拨打电话
val callIntent = Intent(Intent.ACTION_DIAL).apply {
    data = Uri.parse("tel:1234567890")
}
startActivity(callIntent)

稀有度: 非常常见 难度: 简单


10. 什么是 Android 中的 Service?

答案: Service 是在后台运行以执行长时间运行的操作而无需用户界面的组件。

  • 类型:
    • 前台 Service: 执行引人注意的操作(音乐播放器)。显示通知。
    • 后台 Service: 执行用户未直接注意到的操作。
    • 绑定 Service: 允许组件绑定到它并进行交互。
class MyService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 执行后台工作
        Thread {
            // 长时间运行的操作
            Thread.sleep(5000)
            stopSelf()
        }.start()
        
        return START_STICKY
    }
    
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}

// 启动 service
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)

// 停止 service
stopService(serviceIntent)

稀有度: 常见 难度: 中等


11. 什么是 BroadcastReceiver?

答案: BroadcastReceiver 是一个响应系统范围广播公告的组件。

  • 用例: 电池电量低、网络连接变化、收到 SMS、启动完成
  • 注册:
    • 静态: 在 AndroidManifest.xml 中(在新版本的 Android 中受到限制)
    • 动态: 在代码中(首选)
class MyBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        when (intent?.action) {
            Intent.ACTION_BATTERY_LOW -> {
                // 处理电池电量低
            }
            ConnectivityManager.CONNECTIVITY_ACTION -> {
                // 处理网络变化
            }
        }
    }
}

// 动态注册
class MainActivity : AppCompatActivity() {
    private val receiver = MyBroadcastReceiver()
    
    override fun onStart() {
        super.onStart()
        val filter = IntentFilter(Intent.ACTION_BATTERY_LOW)
        registerReceiver(receiver, filter)
    }
    
    override fun onStop() {
        super.onStop()
        unregisterReceiver(receiver)
    }
}

稀有度: 常见 难度: 简单


UI 开发 (4 个问题)

12. LinearLayout、RelativeLayout 和 ConstraintLayout 有什么区别?

答案:

  • LinearLayout: 将子项排列在单行或单列中。简单但可能导致嵌套布局。
  • RelativeLayout: 相对于彼此或父项定位子项。更灵活但更复杂。
  • ConstraintLayout: 现代、灵活的布局。扁平的视图层次结构。推荐用于复杂的 UI。
<!-- ConstraintLayout 示例 -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

稀有度: 非常常见 难度: 简单


13. 什么是 RecyclerView 以及它是如何工作的?

答案: RecyclerView 是一个高效的 widget,通过回收视图来显示大型列表。

  • 组件:
    • Adapter: 将数据绑定到视图
    • ViewHolder: 保存对视图的引用(避免 findViewById 调用)
    • LayoutManager: 定位项目(线性、网格、交错)
// 数据类
data class User(val name: String, val email: String)

// ViewHolder
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val nameText: TextView = itemView.findViewById(R.id.nameText)
    private val emailText: TextView = itemView.findViewById(R.id.emailText)
    
    fun bind(user: User) {
        nameText.text = user.name
        emailText.text = user.email
    }
}

// Adapter
class UserAdapter(private val users: List<User>) : 
    RecyclerView.Adapter<UserViewHolder>() {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }
    
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(users[position])
    }
    
    override fun getItemCount() = users.size
}

// 在 Activity 中
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = UserAdapter(userList)

稀有度: 非常常见 难度: 中等


14. 什么是 Jetpack Compose?

答案: Jetpack Compose 是 Android 的现代声明式 UI 工具包。

  • 声明式: 描述 UI 应该是什么样子,而不是如何构建它
  • 优点: 更少的代码、直观、强大、加速开发
  • Composable 函数: Compose UI 的构建块
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

@Preview
@Composable
fun PreviewCounter() {
    Counter()
}

稀有度: 非常常见 难度: 简单


15. match_parentwrap_content 有什么区别?

答案: 这些是定义视图尺寸的布局参数:

  • match_parent 视图展开以填充父项的大小
  • wrap_content 视图调整自身大小以适应其内容
  • 固定大小: 特定的 dp 值(例如,100dp
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <!-- 填充父项宽度,包装内容高度 -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Title" />
    
    <!-- 包装两个维度 -->
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me" />
    
    <!-- 固定大小 -->
    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/icon" />
</LinearLayout>

稀有度: 非常常见 难度: 简单


数据和网络 (5 个问题)

16. 如何在 Android 中发出网络请求?

答案: 使用 Retrofit 或 OkHttp 等库进行联网。避免直接使用 HttpURLConnection

// Retrofit 设置
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: Int): User
    
    @POST("users")
    suspend fun createUser(@Body user: User): User
}

object RetrofitClient {
    private const val BASE_URL = "https://api.example.com/"
    
    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

// 在 ViewModel 中使用
class UserViewModel : ViewModel() {
    fun fetchUser(userId: Int) {
        viewModelScope.launch {
            try {
                val user = RetrofitClient.apiService.getUser(userId)
                // 更新 UI
            } catch (e: Exception) {
                // 处理错误
            }
        }
    }
}

稀有度: 非常常见 难度: 中等


17. 什么是 Room 以及如何使用它?

答案: Room 是 SQLite 之上的抽象层,可简化数据库访问。

  • 组件:
    • Entity: 代表一个表
    • DAO(数据访问对象): 定义数据库操作
    • Database: 数据库持有者
// Entity
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "email") val email: String
)

// DAO
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsers(): List<User>
    
    @Query("SELECT * FROM users WHERE id = :userId")
    fun getUserById(userId: Int): User?
    
    @Insert
    suspend fun insertUser(user: User)
    
    @Delete
    suspend fun deleteUser(user: User)
    
    @Update
    suspend fun updateUser(user: User)
}

// 数据库
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

稀有度: 非常常见 难度: 中等


18. 什么是 SharedPreferences?

答案: SharedPreferences 将少量原始数据存储为键值对。

  • 用例: 用户设置、首选项、简单标志
  • 不适用于: 大型数据、复杂对象(请改用 Room)
// 保存数据
val sharedPref = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
with(sharedPref.edit()) {
    putString("username", "John")
    putInt("age", 25)
    putBoolean("isLoggedIn", true)
    apply()  // 或 commit()
}

// 读取数据
val username = sharedPref.getString("username", "Guest")
val age = sharedPref.getInt("age", 0)
val isLoggedIn = sharedPref.getBoolean("isLoggedIn", false)

// 删除数据
with(sharedPref.edit()) {
    remove("username")
    apply()
}

// 清除所有
with(sharedPref.edit()) {
    clear()
    apply()
}

稀有度: 非常常见 难度: 简单


19. SharedPreferences 中 apply()commit() 有什么区别?

答案: 两者都将更改保存到 SharedPreferences,但行为有所不同:

  • apply() 异步。立即返回。更改在后台写入磁盘。没有返回值。
  • commit() 同步。阻塞直到写入更改。返回布尔值(成功/失败)。
  • 最佳实践: 除非您需要返回值,否则请使用 apply()
val editor = sharedPref.edit()

// apply() - 异步,首选
editor.putString("key", "value")
editor.apply()  // 立即返回

// commit() - 同步
editor.putString("key", "value")
val success = editor.commit()  // 等待写入,返回布尔值
if (success) {
    // 保存成功
}

稀有度: 常见 难度: 简单


20. 什么是 Kotlin 中的协程?

答案: 协程提供了一种按顺序编写异步代码的方法,使其更易于阅读和维护。

  • 优点: 轻量级、结构化并发、异常处理
  • 关键概念:
    • suspend 可以暂停和恢复的函数
    • launch 启动协程(即发即弃)
    • async 启动协程并返回结果
    • Dispatchers: 控制协程在哪个线程上运行
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            try {
                // 主线程
                showLoading()
                
                // 切换到 IO 线程进行网络调用
                val data = withContext(Dispatchers.IO) {
                    fetchDataFromNetwork()
                }
                
                // 返回主线程
                updateUI(data)
            } catch (e: Exception) {
                handleError(e)
            }
        }
    }
    
    suspend fun fetchDataFromNetwork(): String {
        delay(2000)  // 模拟网络延迟
        return "Data from server"
    }
}

// 并行执行
viewModelScope.launch {
    val deferred1 = async { fetchUser(1) }
    val deferred2 = async { fetchUser(2) }
    
    val user1 = deferred1.await()
    val user2 = deferred2.await()
}

稀有度: 非常常见 难度: 中等

Newsletter subscription

真正有效的每周职业建议

将最新见解直接发送到您的收件箱

停止申请,开始被录用

使用全球求职者信赖的AI驱动优化,将您的简历转变为面试磁铁。

免费开始

分享这篇文章

让面试回访翻倍

根据职位描述定制简历的候选人获得的面试机会是其他人的2.5倍。使用我们的AI为每一份申请即时自动定制您的简历。