第一章:Android 系统架构(20 题)
1.1 Android系统架构分为哪几层?
答案:
Android系统架构采用分层架构设计,从下到上分为五层,每层都有明确的职责。
架构层次:
-
Linux内核层(Linux Kernel)
- 作用:提供底层系统服务
- 内容:进程管理、内存管理、设备驱动、网络协议栈
- 特点:基于Linux内核,提供硬件抽象层
-
硬件抽象层(HAL)
- 作用:为上层提供统一的硬件接口
- 内容:音频、相机、传感器等硬件驱动接口
- 特点:屏蔽硬件差异,便于移植
-
系统运行库层(Native Libraries)
- 作用:提供核心系统功能
- 内容:
- C/C++库:SQLite、OpenGL ES、WebKit等
- Android运行时(ART):虚拟机、核心库
- 特点:使用C/C++实现,性能高效
-
应用框架层(Application Framework)
- 作用:为应用开发提供API
- 内容:
- 四大组件:Activity、Service、BroadcastReceiver、ContentProvider
- 系统服务:WindowManager、ActivityManager、PackageManager等
- 资源管理:资源访问、主题样式等
- 特点:Java/Kotlin API,面向应用开发
-
应用层(Applications)
- 作用:用户可见的应用
- 内容:系统应用(电话、短信、设置等)和第三方应用
- 特点:基于应用框架层开发
架构图:
┌─────────────────────────────────┐
│ 应用层(Applications) │
├─────────────────────────────────┤
│ 应用框架层(Framework) │
├─────────────────────────────────┤
│ 系统运行库层(Libraries) │
├─────────────────────────────────┤
│ 硬件抽象层(HAL) │
├─────────────────────────────────┤
│ Linux内核层(Kernel) │
└─────────────────────────────────┘
各层关系:
- 上层依赖下层:应用层依赖框架层,框架层依赖运行库层
- 接口抽象:每层通过接口与下层交互,降低耦合
- 功能封装:下层功能被上层封装,提供更高级的API
1.2 Linux内核层的作用是什么?
答案:
Linux内核层是Android系统的基础层,提供底层系统服务和硬件抽象。
主要作用:
-
进程管理
- 进程创建、调度、销毁
- 进程间通信(IPC)
- 内存管理
-
设备驱动
- 硬件设备驱动(显示、音频、传感器等)
- 设备文件系统管理
- 硬件资源分配
-
网络协议栈
- TCP/IP协议实现
- 网络数据包处理
- 网络接口管理
-
文件系统
- 文件系统管理(ext4、F2FS等)
- 文件读写操作
- 存储设备管理
-
安全机制
- 权限管理(基于Linux权限模型)
- SELinux安全策略
- 进程隔离
关键特性:
- 基于Linux:使用Linux内核,继承Linux的稳定性和安全性
- 硬件抽象:屏蔽硬件差异,便于在不同设备上运行
- 性能基础:为上层提供高效的底层服务
1.3 系统运行库层包含哪些内容?
答案:
系统运行库层包含C/C++库和Android运行时(ART),为应用框架层提供核心功能支持。
主要内容:
1. C/C++核心库
- SQLite:轻量级数据库引擎
- OpenGL ES:3D图形渲染库
- WebKit:Web浏览器引擎
- Media Framework:音视频编解码库
- Surface Manager:窗口管理
- Libc:C标准库(Bionic)
2. Android运行时(ART)
- ART虚拟机:执行DEX字节码
- 核心库(Core Libraries):Java核心API
- JNI支持:Java Native Interface
3. 运行时特性
- AOT编译:应用安装时编译为机器码
- JIT编译:运行时即时编译
- 垃圾回收:自动内存管理
- 多线程支持:并发执行
作用:
- 为应用框架层提供底层功能支持
- 提供高性能的系统服务
- 实现Java/Kotlin代码的执行环境
1.4 应用框架层的作用是什么?
答案:
应用框架层为应用开发提供API,是Android开发的核心层。
主要作用:
-
四大组件管理
- Activity:界面管理
- Service:后台服务
- BroadcastReceiver:广播接收
- ContentProvider:数据共享
-
系统服务
- ActivityManager:Activity生命周期管理
- WindowManager:窗口管理
- PackageManager:应用包管理
- LocationManager:位置服务
- NotificationManager:通知管理
-
资源管理
- 资源访问(drawable、layout、string等)
- 主题样式管理
- 多语言支持
-
UI框架
- View系统
- 布局管理
- 事件处理
特点:
- Java/Kotlin API:面向应用开发
- 功能丰富:提供完整的应用开发能力
- 易于使用:封装底层复杂性
1.5 应用层的特点是什么?
答案:
应用层是用户直接交互的应用软件层。
特点:
-
用户可见
- 用户直接使用的应用
- 提供用户界面和交互
-
基于框架层
- 使用应用框架层提供的API
- 遵循Android开发规范
-
应用类型
- 系统应用:电话、短信、设置、相机等
- 第三方应用:开发者开发的应用
-
独立运行
- 每个应用运行在独立的进程中
- 应用间通过系统机制通信
开发特点:
- 使用Java或Kotlin开发
- 基于Android SDK
- 遵循Android设计规范
1.6 各层之间的关系是什么?
答案:
Android系统各层之间是依赖关系,上层依赖下层提供的服务。
关系说明:
-
依赖关系
应用层 → 应用框架层 → 系统运行库层 → Linux内核层- 上层调用下层提供的接口
- 下层为上层提供服务
-
接口抽象
- 每层通过接口与下层交互
- 降低层间耦合
- 便于维护和扩展
-
功能封装
- 下层功能被上层封装
- 提供更高级、更易用的API
- 隐藏实现细节
示例:
- 应用层调用应用框架层的Activity API
- 应用框架层调用系统运行库层的SQLite库
- 系统运行库层调用Linux内核层的文件系统
1.7 系统架构的设计思想是什么?
答案:
Android系统架构的设计思想包括分层设计、模块化、接口抽象等。
设计思想:
-
分层设计
- 每层职责明确
- 层间通过接口交互
- 便于维护和扩展
-
模块化
- 功能模块化
- 组件可替换
- 降低耦合
-
接口抽象
- 定义清晰的接口
- 隐藏实现细节
- 便于测试
-
安全性
- 进程隔离
- 权限管理
- 沙箱机制
-
性能优化
- 底层使用C/C++实现
- 虚拟机优化
- 资源管理
1.8 Dalvik虚拟机和ART虚拟机的区别是什么?
答案:
Dalvik和ART是Android的两种虚拟机实现,ART是Dalvik的改进版本。
主要区别:
| 特性 | Dalvik | ART |
|---|---|---|
| 编译方式 | JIT(运行时编译) | AOT(安装时编译)+ JIT |
| 启动速度 | 较快 | 较慢(首次) |
| 运行速度 | 较慢 | 较快 |
| 存储占用 | 较小 | 较大 |
| 安装时间 | 快 | 慢(首次安装) |
| 内存占用 | 较小 | 较大 |
Dalvik特点:
- JIT编译:运行时将DEX字节码编译为机器码
- 启动快:安装时不需要编译
- 运行慢:每次运行都需要编译
ART特点:
- AOT编译:安装时将DEX字节码编译为机器码
- 启动慢:首次安装需要编译
- 运行快:直接执行机器码,无需运行时编译
- 混合模式:Android 7.0+支持AOT+JIT混合编译
演进历史:
- Android 4.4及以前:使用Dalvik
- Android 5.0+:使用ART(AOT编译)
- Android 7.0+:ART支持混合编译(AOT+JIT)
1.9 ART的AOT编译和JIT编译是什么?
答案:
AOT(Ahead-Of-Time)和JIT(Just-In-Time)是两种不同的编译方式。
AOT编译(提前编译):
- 时机:应用安装时
- 过程:将DEX字节码编译为机器码
- 存储:机器码存储在设备上
- 优点:
- 运行时性能好(直接执行机器码)
- 减少运行时编译开销
- 缺点:
- 安装时间长
- 存储占用大
- 首次启动可能较慢
JIT编译(即时编译):
- 时机:应用运行时
- 过程:运行时将热点代码编译为机器码
- 存储:不存储机器码
- 优点:
- 安装快
- 存储占用小
- 启动快
- 缺点:
- 运行时需要编译,影响性能
- 首次执行较慢
混合编译(Android 7.0+):
- 策略:结合AOT和JIT的优势
- 机制:
- 安装时:不编译,快速安装
- 运行时:JIT编译热点代码
- 后台:AOT编译常用代码
- 优势:平衡安装速度、启动速度和运行性能
1.10 为什么Android使用虚拟机而不是直接编译为机器码?
答案:
Android使用虚拟机主要出于跨平台兼容性、安全性和开发效率的考虑。
主要原因:
-
跨平台兼容性
- Android运行在不同架构的硬件上(ARM、x86等)
- 虚拟机可以屏蔽硬件差异
- 一份DEX字节码可以在不同设备上运行
-
安全性
- 虚拟机提供沙箱环境
- 应用隔离,提高安全性
- 权限控制和资源访问限制
-
开发效率
- 使用Java/Kotlin开发,开发效率高
- 不需要为不同架构编译不同版本
- 代码复用性好
-
动态特性
- 支持动态加载
- 支持热更新(通过技术手段)
- 运行时优化
对比:
- 直接编译为机器码:性能最好,但需要为不同架构编译,开发复杂
- 使用虚拟机:性能稍差,但跨平台、安全性好、开发效率高
1.11 ART相比Dalvik的优势是什么?
答案:
ART相比Dalvik的主要优势是性能提升和更好的内存管理。
主要优势:
-
性能提升
- AOT编译后直接执行机器码,运行速度快
- 减少运行时编译开销
- 更好的代码优化
-
内存管理
- 改进的垃圾回收器
- 更高效的内存分配
- 减少GC暂停时间
-
启动优化
- 混合编译模式优化启动速度
- 预编译常用代码
- 减少首次启动时间
-
64位支持
- 更好的64位支持
- 提升大内存应用性能
-
调试支持
- 更好的调试工具支持
- 更详细的性能分析
性能对比:
- 运行速度:ART比Dalvik快约2-3倍
- 启动速度:混合模式下接近Dalvik
- 内存占用:ART略高,但可接受
1.12 AOT编译和JIT编译的优缺点是什么?
答案:
AOT和JIT各有优缺点,Android采用混合模式平衡两者。
AOT编译优缺点:
优点:
- 运行时性能好(直接执行机器码)
- 减少运行时编译开销
- 更好的代码优化机会
缺点:
- 安装时间长
- 存储占用大(机器码比字节码大)
- 首次启动可能较慢
JIT编译优缺点:
优点:
- 安装快(不需要编译)
- 存储占用小
- 启动快
缺点:
- 运行时需要编译,影响性能
- 首次执行较慢
- 编译开销在运行时
混合编译的优势:
- 平衡安装速度、启动速度和运行性能
- 根据使用情况动态优化
- 兼顾用户体验和性能
1.13 混合编译模式是什么?
答案:
混合编译模式是Android 7.0+引入的AOT+JIT混合编译策略,结合两者的优势。
工作机制:
-
安装阶段
- 不进行AOT编译,快速安装
- 保持DEX字节码格式
-
运行阶段
- JIT编译热点代码(频繁执行的代码)
- 将编译结果缓存
-
后台优化
- 设备空闲时,AOT编译常用代码
- 提升后续运行性能
-
自适应优化
- 根据应用使用情况调整编译策略
- 优化常用代码路径
优势:
- 快速安装:安装时不需要编译
- 快速启动:启动时使用JIT,启动快
- 高性能运行:常用代码已AOT编译,运行快
- 智能优化:根据使用情况自动优化
1.14 Android的四大组件是什么?
答案:
Android的四大组件是Activity、Service、BroadcastReceiver和ContentProvider,是Android应用开发的核心。
四大组件:
-
Activity(活动)
- 作用:用户界面组件
- 特点:每个Activity代表一个屏幕
- 用途:展示UI,处理用户交互
-
Service(服务)
- 作用:后台服务组件
- 特点:无界面,在后台运行
- 用途:执行长时间运行的任务
-
BroadcastReceiver(广播接收器)
- 作用:接收系统或应用广播
- 特点:响应系统事件
- 用途:监听系统事件,执行相应操作
-
ContentProvider(内容提供者)
- 作用:数据共享组件
- 特点:提供统一的数据访问接口
- 用途:应用间数据共享
共同特点:
- 都需要在AndroidManifest.xml中注册
- 都有生命周期
- 都通过Intent启动或通信
1.15 系统服务(System Services)的作用是什么?
答案:
系统服务是Android系统提供的核心服务,为应用提供系统级功能。
主要系统服务:
-
ActivityManager
- 管理Activity生命周期
- 任务栈管理
- 进程管理
-
WindowManager
- 窗口管理
- 窗口层级管理
- 输入事件分发
-
PackageManager
- 应用包管理
- 权限管理
- 应用信息查询
-
LocationManager
- 位置服务
- GPS定位
- 网络定位
-
NotificationManager
- 通知管理
- 通知显示
- 通知权限
-
AlarmManager
- 定时任务
- 系统唤醒
- 后台任务调度
特点:
- 系统级服务,应用通过API访问
- 单例模式,全局唯一
- 提供系统核心功能
1.16 四大组件的特点是什么?
答案:
四大组件各有特点,共同构成Android应用的基础架构。
Activity特点:
- 有用户界面
- 生命周期管理
- 任务栈管理
- 通过Intent启动
Service特点:
- 无用户界面
- 后台运行
- 支持绑定和启动两种模式
- 可以跨进程
BroadcastReceiver特点:
- 无界面
- 响应广播事件
- 生命周期短暂
- 可以静态或动态注册
ContentProvider特点:
- 数据共享接口
- 统一数据访问
- 支持跨进程访问
- 基于URI访问
共同特点:
- 都需要注册
- 都有生命周期
- 都通过Intent通信
- 都运行在主进程
1.17 四大组件的生命周期如何管理?
答案:
四大组件的生命周期由系统服务管理,每个组件都有特定的生命周期方法。
生命周期管理:
-
Activity生命周期
- 由ActivityManager管理
- 生命周期方法:onCreate、onStart、onResume、onPause、onStop、onDestroy
- 受用户操作和系统资源影响
-
Service生命周期
- 由ActivityManager管理
- 启动模式:onCreate、onStartCommand、onDestroy
- 绑定模式:onCreate、onBind、onUnbind、onDestroy
-
BroadcastReceiver生命周期
- 由ActivityManager管理
- 生命周期短暂:onReceive执行完即结束
- 静态注册:系统管理
- 动态注册:需要手动注销
-
ContentProvider生命周期
- 由ActivityManager管理
- 生命周期方法:onCreate(应用启动时创建)
- 生命周期与应用进程绑定
管理机制:
- 系统服务统一管理
- 根据系统状态调整
- 资源不足时可能被回收
1.18 Android应用进程的启动流程是什么?
答案:
Android应用进程的启动流程涉及Zygote进程、ActivityManagerService等多个系统组件。
启动流程:
-
用户启动应用
- 点击应用图标
- 或通过Intent启动
-
ActivityManagerService处理
- 检查应用是否已运行
- 如果未运行,创建新进程
-
Zygote进程fork
- 从Zygote进程fork新进程
- 继承Zygote的预加载资源
-
进程初始化
- 创建应用进程
- 加载应用代码
- 初始化运行时环境
-
Application创建
- 创建Application实例
- 调用onCreate()
-
Activity创建
- 创建Activity实例
- 调用生命周期方法
- 显示界面
关键点:
- Zygote预加载常用类,加快启动速度
- 进程隔离,每个应用独立进程
- 系统统一管理进程生命周期
1.19 Zygote进程的作用是什么?
答案:
Zygote进程是Android系统的进程孵化器,负责创建应用进程。
主要作用:
-
进程创建
- 通过fork()创建新进程
- 新进程继承Zygote的预加载资源
-
预加载资源
- 预加载常用类库
- 预加载系统资源
- 减少应用启动时间
-
资源共享
- 多个应用进程共享Zygote的代码段
- 节省内存占用
-
快速启动
- 避免每个应用都重新加载类库
- 提升应用启动速度
工作流程:
Zygote进程启动
↓
预加载常用类库和资源
↓
等待fork请求
↓
收到请求后fork新进程
↓
新进程继承预加载资源
↓
加载应用特定代码
↓
应用进程启动完成
优势:
- 快速启动应用
- 节省内存(代码段共享)
- 统一管理进程创建
1.20 应用进程和系统进程的区别是什么?
答案:
应用进程和系统进程在权限、优先级和生命周期上有重要区别。
主要区别:
| 特性 | 应用进程 | 系统进程 |
|---|---|---|
| 权限 | 受限权限 | 系统权限 |
| 优先级 | 较低 | 较高 |
| 生命周期 | 可被回收 | 常驻内存 |
| 资源访问 | 受限 | 完整访问 |
| 进程名 | 包名 | system_server等 |
应用进程特点:
- 运行第三方应用
- 权限受限
- 系统资源不足时可能被回收
- 进程名通常是应用包名
系统进程特点:
- 运行系统服务
- 拥有系统权限
- 常驻内存,不易被回收
- 进程名如system_server、zygote等
进程优先级:
- 前台进程(Foreground)
- 可见进程(Visible)
- 服务进程(Service)
- 后台进程(Background)
- 空进程(Empty)
系统进程通常具有更高的优先级,确保系统稳定运行。
第二章:Activity(35 题)
2.1 Activity是什么?它的生命周期是什么?
答案:
Activity是Android的用户界面组件,代表应用中的一个屏幕,负责展示UI和处理用户交互。
Activity的定义:
- 一个Activity代表一个用户界面
- 每个Activity都是独立的组件
- 通过Activity栈管理多个Activity
生命周期方法:
-
onCreate()
- Activity创建时调用
- 初始化工作
- 设置布局
-
onStart()
- Activity可见但不可交互
- 在onCreate()之后调用
-
onResume()
- Activity可见且可交互
- 用户可以与Activity交互
-
onPause()
- Activity失去焦点
- 部分可见或不可见
-
onStop()
- Activity完全不可见
- 在onPause()之后调用
-
onDestroy()
- Activity销毁时调用
- 清理资源
-
onRestart()
- Activity从停止状态重新启动
- 在onStop()之后,onStart()之前
生命周期流程图:
启动:onCreate() → onStart() → onResume()
暂停:onPause() → onStop()
恢复:onRestart() → onStart() → onResume()
销毁:onPause() → onStop() → onDestroy()
2.2 Activity的生命周期方法有哪些?分别在什么时候调用?
答案:
Activity有7个生命周期方法,在不同场景下按顺序调用。
生命周期方法:
-
onCreate(Bundle savedInstanceState)
- 调用时机:Activity首次创建时
- 作用:初始化Activity,设置布局
- 特点:只会调用一次(除非Activity被销毁重建)
-
onStart()
- 调用时机:Activity变为可见时
- 作用:准备显示Activity
- 特点:可能在onResume()之前或onRestart()之后调用
-
onResume()
- 调用时机:Activity获得焦点,可以交互时
- 作用:恢复Activity的交互功能
- 特点:Activity处于前台,用户可见可交互
-
onPause()
- 调用时机:Activity失去焦点时
- 作用:暂停Activity,释放资源
- 特点:另一个Activity显示时调用
-
onStop()
- 调用时机:Activity完全不可见时
- 作用:停止Activity
- 特点:Activity被其他Activity完全覆盖时调用
-
onRestart()
- 调用时机:Activity从停止状态重新启动时
- 作用:重新启动Activity
- 特点:在onStop()之后,onStart()之前调用
-
onDestroy()
- 调用时机:Activity被销毁时
- 作用:清理资源,释放内存
- 特点:Activity生命周期结束
调用场景:
正常启动:
onCreate() → onStart() → onResume()
返回后台:
onPause() → onStop()
重新显示:
onRestart() → onStart() → onResume()
销毁:
onPause() → onStop() → onDestroy()
2.3 onCreate()、onStart()、onResume()的区别是什么?
答案:
这三个方法在Activity启动时依次调用,但职责不同。
onCreate():
- 调用时机:Activity首次创建时
- 职责:初始化工作,设置布局
- 特点:只调用一次
- 使用场景:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initData(); }
onStart():
- 调用时机:Activity变为可见时
- 职责:准备显示Activity
- 特点:可能多次调用(onRestart()后会再次调用)
- 使用场景:
@Override protected void onStart() { super.onStart(); // 注册广播接收器 // 启动位置服务等 }
onResume():
- 调用时机:Activity获得焦点,可以交互时
- 职责:恢复交互功能
- 特点:Activity处于前台
- 使用场景:
@Override protected void onResume() { super.onResume(); // 恢复动画 // 恢复数据刷新等 }
区别总结:
- onCreate():初始化,只调用一次
- onStart():可见,可能多次调用
- onResume():可交互,Activity在前台
2.4 onPause()、onStop()、onDestroy()的区别是什么?
答案:
这三个方法在Activity退出时依次调用,但时机和职责不同。
onPause():
- 调用时机:Activity失去焦点时
- 职责:暂停Activity,快速释放资源
- 特点:另一个Activity显示时立即调用
- 使用场景:
@Override protected void onPause() { super.onPause(); // 暂停动画 // 保存临时数据 // 释放相机等资源 }
onStop():
- 调用时机:Activity完全不可见时
- 职责:停止Activity
- 特点:在onPause()之后调用
- 使用场景:
@Override protected void onStop() { super.onStop(); // 停止网络请求 // 注销广播接收器 }
onDestroy():
- 调用时机:Activity被销毁时
- 职责:清理所有资源
- 特点:Activity生命周期结束
- 使用场景:
@Override protected void onDestroy() { super.onDestroy(); // 清理资源 // 取消所有任务 }
区别总结:
- onPause():失去焦点,快速处理
- onStop():完全不可见,停止操作
- onDestroy():销毁,清理资源
2.5 Activity的生命周期流程图是什么?
答案:
Activity的生命周期流程可以通过状态图表示。
完整生命周期流程:
启动流程:
┌─────────┐
│ 不存在 │
└────┬────┘
│ onCreate()
↓
┌─────────┐
│ Created │
└────┬────┘
│ onStart()
↓
┌─────────┐
│ Started │
└────┬────┘
│ onResume()
↓
┌──────────┐
│ Resumed │ ← Activity运行中
└────┬─────┘
│ onPause()
↓
┌─────────┐
│ Paused │
└────┬────┘
│ onStop()
↓
┌─────────┐
│ Stopped │
└────┬────┘
│ onRestart() 或 onDestroy()
↓
┌──────────┐ ┌──────────┐
│ Restart │ │ Destroyed│
└────┬─────┘ └──────────┘
│ onStart()
↓
┌─────────┐
│ Started │
└─────────┘
关键状态:
- Created:Activity已创建,但不可见
- Started:Activity可见,但不可交互
- Resumed:Activity可见且可交互(运行中)
- Paused:Activity部分可见或失去焦点
- Stopped:Activity完全不可见
- Destroyed:Activity已销毁
2.6 Activity的生命周期和进程生命周期的关系是什么?
答案:
Activity的生命周期受进程生命周期影响,但两者不完全一致。
关系说明:
-
进程存活时
- Activity可以正常经历完整生命周期
- 系统资源充足时,Activity生命周期正常
-
进程被回收时
- Activity可能被系统回收
- 系统会调用onSaveInstanceState()保存状态
- 进程重启后,Activity会重建并恢复状态
-
内存不足时
- 系统优先回收后台Activity
- 前台Activity通常不会被回收
- 回收顺序:空进程 → 后台进程 → 服务进程
进程优先级影响:
- 前台进程:Activity生命周期正常
- 可见进程:可能调用onPause(),但不会onDestroy()
- 后台进程:可能被回收,调用onSaveInstanceState()后onDestroy()
状态保存:
- 系统会在Activity可能被回收前调用onSaveInstanceState()
- 保存的数据在onCreate()或onRestoreInstanceState()中恢复
2.7 Activity的启动模式有哪些?它们的区别是什么?
答案:
Activity有四种启动模式,控制Activity在任务栈中的行为。
启动模式:
-
standard(标准模式)
- 特点:每次启动都创建新实例
- 任务栈:在启动它的Activity所在栈中
- 使用场景:大多数情况
-
singleTop(栈顶复用)
- 特点:如果Activity在栈顶,复用该实例
- 任务栈:在启动它的Activity所在栈中
- 使用场景:防止重复启动(如通知点击)
-
singleTask(栈内复用)
- 特点:如果Activity在栈中存在,复用并清除其上所有Activity
- 任务栈:在指定的任务栈中(通过taskAffinity)
- 使用场景:主界面、登录页
-
singleInstance(单例模式)
- 特点:Activity独占一个任务栈
- 任务栈:独立的任务栈
- 使用场景:系统Launcher、来电界面
区别对比:
| 启动模式 | 实例数量 | 任务栈 | 使用场景 |
|---|---|---|---|
| standard | 多个 | 当前栈 | 默认 |
| singleTop | 栈顶复用 | 当前栈 | 防止重复 |
| singleTask | 栈内复用 | 指定栈 | 主界面 |
| singleInstance | 单例 | 独立栈 | 系统应用 |
2.8 standard启动模式的特点是什么?
答案:
standard是默认启动模式,每次启动都创建新的Activity实例。
特点:
-
每次创建新实例
- 每次调用startActivity()都创建新实例
- 不检查是否已存在
-
任务栈行为
- 在启动它的Activity所在任务栈中
- 遵循后进先出(LIFO)原则
-
生命周期
- 每次启动都调用onCreate()
- 正常经历完整生命周期
示例:
// Activity A启动Activity B(standard模式)
// 任务栈:A → B
// 再次启动B
// 任务栈:A → B → B(新实例)
使用场景:
- 大多数Activity使用此模式
- 需要多个实例的场景
- 不需要特殊栈管理的场景
注意事项:
- 可能创建多个实例,占用内存
- 返回键会依次关闭,可能体验不佳
2.9 singleTop启动模式的特点是什么?
答案:
singleTop是栈顶复用模式,如果Activity在栈顶则复用,否则创建新实例。
特点:
-
栈顶复用
- 如果Activity在栈顶,复用该实例
- 调用onNewIntent()而不是onCreate()
- 如果不在栈顶,创建新实例
-
任务栈行为
- 在启动它的Activity所在任务栈中
- 复用时不改变栈结构
-
生命周期
- 复用:onNewIntent() → onResume()
- 新建:正常生命周期
示例:
// 任务栈:A → B(B是singleTop)
// 在B中启动B
// 结果:A → B(复用B,调用onNewIntent())
// 任务栈:A → B → C
// 在C中启动B(B是singleTop)
// 结果:A → B → C → B(创建新实例)
使用场景:
- 防止重复启动(如通知点击)
- 搜索页面
- 详情页面
2.10 singleTask启动模式的特点是什么?
答案:
singleTask是栈内复用模式,如果Activity在栈中存在,复用并清除其上所有Activity。
特点:
-
栈内复用
- 检查任务栈中是否存在该Activity
- 如果存在,复用并清除其上所有Activity
- 如果不存在,创建新实例
-
任务栈行为
- 在指定的任务栈中(通过taskAffinity)
- 如果没有指定,在启动它的Activity所在栈中
-
生命周期
- 复用:onNewIntent() → onResume()
- 新建:正常生命周期
- 清除:其上Activity调用onDestroy()
示例:
// 任务栈:A → B → C(B是singleTask)
// 在C中启动B
// 结果:A → B(复用B,清除C,调用onNewIntent())
使用场景:
- 主界面(MainActivity)
- 登录页面
- 需要作为栈底Activity的场景
2.11 singleInstance启动模式的特点是什么?
答案:
singleInstance是单例模式,Activity独占一个任务栈,系统中只有一个实例。
特点:
-
独占任务栈
- Activity独占一个任务栈
- 该栈中只有这一个Activity
-
单例
- 系统中只有一个实例
- 其他Activity启动它时,复用该实例
-
任务栈行为
- 独立的任务栈
- 不受其他Activity影响
示例:
// 任务栈1:A → B
// 启动C(singleInstance)
// 任务栈1:A → B
// 任务栈2:C(独立栈)
// 从任务栈1启动C
// 结果:切换到任务栈2,复用C
使用场景:
- 系统Launcher
- 来电界面
- 需要完全独立的Activity
注意事项:
- 使用较少
- 可能影响用户体验(切换任务栈)
2.12 如何选择合适的启动模式?
答案:
根据Activity的使用场景和业务需求选择合适的启动模式。
选择标准:
-
standard(默认)
- 大多数Activity
- 需要多个实例的场景
- 不需要特殊管理的场景
-
singleTop
- 防止重复启动
- 通知点击跳转
- 搜索、详情页面
-
singleTask
- 主界面(MainActivity)
- 登录页面
- 需要作为栈底的Activity
-
singleInstance
- 系统级应用
- 需要完全独立的Activity
- 使用较少
选择建议:
- 默认使用standard
- 防止重复启动用singleTop
- 主界面用singleTask
- 谨慎使用singleInstance
2.13 启动模式的Task栈管理是什么?
答案:
Task(任务)是Activity的集合,按照后进先出(LIFO)原则管理。
Task栈管理:
-
Task概念
- Task是Activity的集合
- 用户完成一个任务的所有Activity
- 通过返回键可以返回上一个Activity
-
栈管理规则
- 后进先出(LIFO)
- 新Activity压入栈顶
- 返回键弹出栈顶Activity
-
不同启动模式的影响
- standard:正常入栈
- singleTop:栈顶复用,不入栈
- singleTask:清除其上Activity,可能切换Task
- singleInstance:独立Task
Task切换:
- 通过taskAffinity指定Task
- singleTask可以指定不同的Task
- 系统Launcher是一个特殊的Task
2.14 singleTask的TaskAffinity是什么?
答案:
TaskAffinity是Activity的任务 affinity,用于指定Activity所属的任务栈。
TaskAffinity说明:
-
默认值
- 默认是应用的包名
- 同一应用的Activity默认在同一Task
-
自定义TaskAffinity
<activity android:name=".MainActivity" android:launchMode="singleTask" android:taskAffinity="com.example.main" /> -
singleTask的行为
- 如果指定了taskAffinity,会在指定的Task中查找
- 如果Task不存在,创建新Task
- 如果Task存在且Activity存在,复用
使用场景:
- 需要将Activity放在特定Task中
- 跨应用的Activity共享Task
- 复杂的导航场景
2.15 如何通过代码设置启动模式?
答案:
可以通过Intent标志在代码中设置启动模式。
设置方法:
-
FLAG_ACTIVITY_NEW_TASK
- 在新Task中启动Activity
- 类似singleTask
-
FLAG_ACTIVITY_SINGLE_TOP
- 栈顶复用
- 类似singleTop
-
FLAG_ACTIVITY_CLEAR_TOP
- 清除其上Activity
- 类似singleTask
代码示例:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
注意事项:
- 代码设置会覆盖AndroidManifest中的设置
- 标志可以组合使用
- 某些标志组合有特定含义
2.16 Activity的onSaveInstanceState()和onRestoreInstanceState()的作用是什么?
答案:
这两个方法用于保存和恢复Activity的状态,防止Activity被系统回收后数据丢失。
onSaveInstanceState():
- 作用:保存Activity状态
- 调用时机:Activity可能被系统回收前
- 保存内容:临时数据、UI状态等
- 特点:系统自动调用,也可以手动调用
onRestoreInstanceState():
- 作用:恢复Activity状态
- 调用时机:Activity重建后,onStart()之后
- 恢复内容:从Bundle中恢复数据
- 特点:只在Activity被系统回收重建时调用
使用示例:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("key", "value");
outState.putInt("count", count);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String value = savedInstanceState.getString("key");
int count = savedInstanceState.getInt("count");
}
注意事项:
- 只保存临时数据,持久数据应使用其他方式
- Bundle有大小限制(约1MB)
- 系统会自动保存View的状态(有id的View)
2.17 onSaveInstanceState()什么时候调用?
答案:
onSaveInstanceState()在Activity可能被系统回收时调用。
调用时机:
-
系统回收前
- 内存不足时
- 配置变更时(如横竖屏切换)
- 系统需要回收Activity时
-
不调用的情况
- 用户主动退出(按返回键)
- 调用finish()
- 正常销毁不会调用
-
调用顺序
- 在onPause()之后
- 可能在onStop()之前或之后
- 不保证调用顺序
调用场景:
- 横竖屏切换
- 多窗口模式切换
- 系统内存不足
- 应用进入后台
2.18 onRestoreInstanceState()什么时候调用?
答案:
onRestoreInstanceState()在Activity被系统回收后重建时调用。
调用时机:
-
Activity重建后
- 系统回收Activity后重建
- 在onStart()之后调用
- 在onResume()之前调用
-
调用条件
- 必须有保存的状态(Bundle不为null)
- 只在系统回收重建时调用
- 用户主动退出不会调用
-
调用顺序
onCreate() → onStart() → onRestoreInstanceState() → onResume()
使用建议:
- 可以在onCreate()中恢复(Bundle可能为null)
- 也可以在onRestoreInstanceState()中恢复(Bundle一定不为null)
- 推荐在onRestoreInstanceState()中恢复,逻辑更清晰
2.19 哪些情况下Activity会被销毁并重建?
答案:
Activity在以下情况下会被销毁并重建。
销毁重建场景:
-
配置变更
- 横竖屏切换
- 语言切换
- 字体大小改变
- 多窗口模式切换
-
系统回收
- 内存不足
- 应用进入后台
- 系统需要释放资源
-
进程被杀死
- 应用进程被系统杀死
- 进程重启后Activity重建
不重建的情况:
- 用户主动退出
- 调用finish()
- 正常销毁
处理方式:
- 使用onSaveInstanceState()保存状态
- 使用ViewModel保存数据
- 配置android:configChanges避免重建
2.20 如何保存和恢复Activity的状态?
答案:
有多种方式保存和恢复Activity状态。
保存方式:
-
onSaveInstanceState()
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("key", "value"); } -
ViewModel
ViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); viewModel.setData(data); -
SharedPreferences
SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE); prefs.edit().putString("key", "value").apply();
恢复方式:
-
onCreate()或onRestoreInstanceState()
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { String value = savedInstanceState.getString("key"); } } -
ViewModel
ViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); String data = viewModel.getData();
选择建议:
- 临时数据:onSaveInstanceState()
- 配置变更数据:ViewModel
- 持久数据:SharedPreferences、数据库
2.21 ViewModel和onSaveInstanceState()的区别是什么?
答案:
ViewModel和onSaveInstanceState()都可以保存数据,但使用场景不同。
区别对比:
| 特性 | ViewModel | onSaveInstanceState() |
|---|---|---|
| 保存时机 | 配置变更时 | 系统回收时 |
| 数据大小 | 无限制 | 约1MB限制 |
| 数据类型 | 任意对象 | 基本类型、Parcelable |
| 生命周期 | 配置变更后仍存在 | Activity重建后恢复 |
| 使用场景 | 配置变更 | 系统回收 |
ViewModel特点:
- 配置变更(如横竖屏)时数据保留
- 可以保存复杂对象
- 与Activity生命周期关联
onSaveInstanceState()特点:
- 系统回收时保存
- 只能保存基本类型和Parcelable
- Bundle有大小限制
使用建议:
- 配置变更数据:使用ViewModel
- 系统回收数据:使用onSaveInstanceState()
- 两者结合:ViewModel + onSaveInstanceState()
2.22 Activity之间如何传递数据?
答案:
Activity之间通过Intent传递数据。
传递方式:
-
基本数据类型
Intent intent = new Intent(this, SecondActivity.class); intent.putExtra("name", "张三"); intent.putExtra("age", 25); startActivity(intent); -
对象数据(Parcelable)
// 实现Parcelable接口 public class User implements Parcelable { private String name; private int age; // ... Parcelable实现 } Intent intent = new Intent(this, SecondActivity.class); intent.putExtra("user", user); startActivity(intent); -
接收数据
Intent intent = getIntent(); String name = intent.getStringExtra("name"); int age = intent.getIntExtra("age", 0); User user = intent.getParcelableExtra("user");
注意事项:
- 数据大小有限制(约1MB)
- 复杂对象需要实现Parcelable
- 大量数据考虑使用其他方式(数据库、文件等)
2.23 Intent的作用是什么?有哪些类型?
答案:
Intent是Android的消息传递机制,用于组件间通信。
Intent的作用:
-
启动组件
- 启动Activity
- 启动Service
- 发送广播
-
传递数据
- 组件间数据传递
- 携带参数
-
隐式调用
- 通过Action启动组件
- 系统选择合适的组件
Intent类型:
-
显式Intent
Intent intent = new Intent(this, SecondActivity.class); startActivity(intent);- 明确指定目标组件
- 用于应用内调用
-
隐式Intent
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("https://www.example.com")); startActivity(intent);- 通过Action、Category、Data匹配
- 系统选择合适的组件
Intent组成:
- Component:目标组件
- Action:动作
- Category:类别
- Data:数据URI
- Extras:额外数据
2.24 startActivityForResult()的使用场景是什么?
答案:
startActivityForResult()用于从目标Activity获取返回结果。
使用场景:
-
选择数据
- 选择图片
- 选择联系人
- 选择文件
-
输入数据
- 输入用户名
- 填写表单
- 确认操作
使用示例:
// 启动Activity并等待结果
Intent intent = new Intent(this, SecondActivity.class);
startActivityForResult(intent, REQUEST_CODE);
// 在目标Activity中返回结果
Intent resultIntent = new Intent();
resultIntent.putExtra("result", "data");
setResult(RESULT_OK, resultIntent);
finish();
// 在源Activity中接收结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
String result = data.getStringExtra("result");
}
}
注意事项:
- 已废弃,推荐使用ActivityResult API
- requestCode用于区分不同的请求
- resultCode表示结果状态
2.25 Activity的onActivityResult()如何处理?
答案:
onActivityResult()用于接收从目标Activity返回的结果。
处理方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 检查请求码
if (requestCode == REQUEST_CODE) {
// 检查结果码
if (resultCode == RESULT_OK) {
// 处理成功结果
String result = data.getStringExtra("result");
} else if (resultCode == RESULT_CANCELED) {
// 处理取消
}
}
}
参数说明:
- requestCode:启动Activity时传入的请求码
- resultCode:结果码(RESULT_OK、RESULT_CANCELED等)
- data:返回的Intent,包含数据
注意事项:
- 已废弃,推荐使用ActivityResult API
- 需要检查requestCode和resultCode
- 可能为null,需要判空
2.26 ActivityResult API的使用方法是什么?
答案:
ActivityResult API是新的结果回调机制,替代startActivityForResult()。
使用方法:
-
注册ActivityResultLauncher
private ActivityResultLauncher<Intent> launcher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == RESULT_OK) { Intent data = result.getData(); String result = data.getStringExtra("result"); } } } ); -
启动Activity
Intent intent = new Intent(this, SecondActivity.class); launcher.launch(intent); -
Lambda简化
private ActivityResultLauncher<Intent> launcher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { // 处理结果 } } );
优势:
- 类型安全
- 更好的生命周期管理
- 支持多种Contract(拍照、选择文件等)
2.27 ActivityResult API和startActivityForResult()的区别是什么?
答案:
ActivityResult API是新的推荐方式,相比startActivityForResult()有诸多优势。
主要区别:
| 特性 | ActivityResult API | startActivityForResult() |
|---|---|---|
| 状态 | 推荐使用 | 已废弃 |
| 类型安全 | 是 | 否 |
| 生命周期 | 自动管理 | 手动处理 |
| 使用方式 | 注册Launcher | 重写方法 |
| 灵活性 | 高 | 低 |
ActivityResult API优势:
- 类型安全:编译时检查
- 自动管理:生命周期自动处理
- 更灵活:支持多种Contract
- 代码清晰:逻辑更清晰
迁移建议:
- 新项目使用ActivityResult API
- 旧项目逐步迁移
- 两者可以共存
2.28 Activity的启动流程是什么?
答案:
Activity的启动流程涉及多个系统组件的协作。
启动流程:
-
应用层调用
startActivity(intent); -
ActivityManagerService处理
- 检查目标Activity
- 检查权限
- 创建或复用进程
-
进程创建(如需要)
- 从Zygote fork进程
- 加载应用代码
- 创建Application
-
Activity创建
- 创建Activity实例
- 调用onCreate()
- 创建Window
-
界面显示
- 调用onStart()
- 调用onResume()
- 显示界面
关键步骤:
- ActivityManagerService统一管理
- 进程隔离,每个应用独立进程
- 通过Binder IPC通信
2.29 Activity的onCreate()之前做了什么?
答案:
在onCreate()之前,系统进行了大量的初始化工作。
onCreate()之前的工作:
-
进程创建(如需要)
- 从Zygote fork进程
- 加载应用代码
-
Application创建
- 创建Application实例
- 调用Application.onCreate()
-
Activity实例创建
- 通过反射创建Activity实例
- 初始化Activity对象
-
Context创建
- 创建Activity的Context
- 关联Application Context
-
Window创建
- 创建PhoneWindow
- 关联WindowManager
-
主题应用
- 应用主题样式
- 设置窗口属性
性能影响:
- 这些操作影响启动速度
- 优化启动需要减少这些开销
2.30 Activity的onResume()之后做了什么?
答案:
在onResume()之后,Activity进入可交互状态,系统进行界面渲染。
onResume()之后的工作:
-
界面渲染
- View的测量、布局、绘制
- 显示Activity界面
-
输入事件处理
- 可以接收触摸事件
- 可以接收键盘输入
-
动画开始
- 启动动画
- 恢复动画状态
-
数据刷新
- 恢复数据加载
- 刷新UI
关键点:
- onResume()之后Activity完全可见可交互
- 此时可以安全地进行UI操作
- 界面渲染在onResume()之后进行
2.31 Activity的启动性能如何优化?
答案:
Activity启动性能优化可以从多个方面入手。
优化策略:
-
减少onCreate()工作量
- 延迟初始化
- 异步加载数据
- 减少布局复杂度
-
优化布局
- 减少布局层级
- 使用ViewStub延迟加载
- 避免过度绘制
-
预加载优化
- 使用启动画面
- 预加载数据
- 使用缓存
-
代码优化
- 避免主线程阻塞
- 减少反射调用
- 优化资源加载
最佳实践:
- 使用启动画面提升体验
- 延迟非关键初始化
- 使用异步加载数据
- 优化布局性能
2.32 Activity的内存泄漏如何避免?
答案:
Activity内存泄漏是常见问题,需要注意生命周期和引用关系。
常见泄漏场景:
-
静态引用
// 错误:静态引用Activity static Activity activity; // 正确:使用WeakReference static WeakReference<Activity> activityRef; -
Handler泄漏
// 错误:非静态内部类持有Activity引用 private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // 持有Activity引用 } }; // 正确:使用静态内部类+WeakReference private static class MyHandler extends Handler { private WeakReference<Activity> activityRef; } -
异步任务泄漏
// 错误:AsyncTask持有Activity引用 new AsyncTask() { // 持有Activity引用 }.execute(); // 正确:在onDestroy()中取消 @Override protected void onDestroy() { super.onDestroy(); asyncTask.cancel(true); }
避免方法:
- 避免静态引用Activity
- 使用WeakReference
- 及时取消异步任务
- 使用LeakCanary检测
2.33 Activity的异常退出如何处理?
答案:
Activity异常退出需要通过异常捕获和状态恢复处理。
处理方式:
-
全局异常捕获
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { // 记录异常 // 保存状态 // 重启应用或显示错误页面 } }); -
try-catch保护
try { // 可能崩溃的代码 } catch (Exception e) { // 处理异常 e.printStackTrace(); } -
状态恢复
- 使用onSaveInstanceState()保存状态
- 异常退出后可以恢复
最佳实践:
- 关键操作使用try-catch
- 记录异常日志
- 提供友好的错误提示
- 使用崩溃监控工具(如Firebase Crashlytics)
2.34 Activity的横竖屏切换如何处理?
答案:
横竖屏切换会导致Activity销毁重建,需要保存和恢复状态。
处理方式:
-
保存状态
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("key", "value"); } -
恢复状态
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { String value = savedInstanceState.getString("key"); } } -
避免重建(不推荐)
<activity android:name=".MainActivity" android:configChanges="orientation|screenSize" />@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 手动处理配置变更 }
推荐方式:
- 使用ViewModel保存数据
- 使用onSaveInstanceState()保存UI状态
- 避免使用configChanges(除非必要)
2.35 Activity的最佳实践有哪些?
答案:
Activity最佳实践包括生命周期管理、内存管理、性能优化等。
最佳实践:
-
生命周期管理
- 在正确的生命周期方法中执行操作
- 及时释放资源
- 避免在onCreate()中执行耗时操作
-
内存管理
- 避免内存泄漏
- 使用WeakReference
- 及时取消异步任务
-
性能优化
- 优化启动速度
- 减少布局复杂度
- 使用异步加载
-
状态管理
- 使用ViewModel保存数据
- 使用onSaveInstanceState()保存UI状态
- 合理使用启动模式
-
代码规范
- 单一职责
- 避免在Activity中写业务逻辑
- 使用MVP/MVVM架构
总结:
- 遵循生命周期
- 注意内存管理
- 优化性能
- 合理架构设计
第三章:Fragment(30 题)
3.1 Fragment是什么?它的生命周期是什么?
答案:
Fragment是Android的UI片段组件,可以嵌入到Activity中,提供模块化的UI和逻辑复用。
Fragment的定义:
- 一个Fragment代表Activity中的一部分UI
- 可以在多个Activity中复用
- 有自己的生命周期,但受Activity生命周期影响
生命周期方法:
-
onAttach()
- Fragment与Activity关联时调用
- 获取Activity引用
-
onCreate()
- Fragment创建时调用
- 初始化Fragment
-
onCreateView()
- 创建Fragment的视图
- 返回根视图
-
onActivityCreated()
- Activity的onCreate()完成后调用
- 可以安全访问Activity
-
onStart()
- Fragment可见时调用
-
onResume()
- Fragment可交互时调用
-
onPause()
- Fragment失去焦点时调用
-
onStop()
- Fragment不可见时调用
-
onDestroyView()
- 销毁Fragment的视图
-
onDestroy()
- Fragment销毁时调用
-
onDetach()
- Fragment与Activity分离时调用
生命周期流程图:
onAttach() → onCreate() → onCreateView() → onActivityCreated()
→ onStart() → onResume() → onPause() → onStop()
→ onDestroyView() → onDestroy() → onDetach()
3.2 Fragment的生命周期和Activity的生命周期有什么关系?
答案:
Fragment的生命周期受Activity生命周期影响,两者紧密关联。
关系说明:
-
Fragment生命周期包含在Activity生命周期中
- Fragment不能独立存在
- 必须依附于Activity
-
生命周期对应关系
Activity.onCreate() → Fragment.onAttach() → Fragment.onCreate() → Fragment.onCreateView() → Fragment.onActivityCreated() Activity.onStart() → Fragment.onStart() Activity.onResume() → Fragment.onResume() Activity.onPause() → Fragment.onPause() Activity.onStop() → Fragment.onStop() Activity.onDestroy() → Fragment.onDestroyView() → Fragment.onDestroy() → Fragment.onDetach() -
关键点
- Fragment的onAttach()在Activity.onCreate()之前
- Fragment的onActivityCreated()在Activity.onCreate()之后
- Fragment的onDestroy()在Activity.onDestroy()之前
注意事项:
- Fragment的生命周期方法在对应的Activity生命周期方法中调用
- Activity销毁时,所有Fragment也会销毁
- Fragment可以感知Activity的生命周期变化
3.3 Fragment的生命周期方法有哪些?
答案:
Fragment有11个生命周期方法,按顺序调用。
生命周期方法:
-
onAttach(Context context)
- Fragment与Activity关联
- 获取Activity引用
-
onCreate(Bundle savedInstanceState)
- Fragment创建
- 初始化Fragment
-
onCreateView(LayoutInflater, ViewGroup, Bundle)
- 创建Fragment的视图
- 返回根视图
-
onActivityCreated(Bundle savedInstanceState)
- Activity的onCreate()完成后调用
- 可以安全访问Activity
-
onStart()
- Fragment可见
-
onResume()
- Fragment可交互
-
onPause()
- Fragment失去焦点
-
onStop()
- Fragment不可见
-
onDestroyView()
- 销毁Fragment的视图
-
onDestroy()
- Fragment销毁
-
onDetach()
- Fragment与Activity分离
调用顺序:
创建:onAttach() → onCreate() → onCreateView() → onActivityCreated()
→ onStart() → onResume()
销毁:onPause() → onStop() → onDestroyView() → onDestroy() → onDetach()
3.4 Fragment的创建方式有哪些?
答案:
Fragment有两种创建方式:静态创建和动态创建。
1. 静态创建(XML布局)
在Activity的布局文件中直接声明Fragment:
<fragment
android:id="@+id/fragment"
android:name="com.example.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
特点:
- 在XML中声明
- 不能动态替换
- 简单直接
2. 动态创建(代码)
通过代码动态添加Fragment:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.container, new MyFragment());
transaction.commit();
特点:
- 灵活,可以动态添加、替换、移除
- 可以添加到回退栈
- 更常用
使用场景:
- 静态创建:固定不变的Fragment
- 动态创建:需要动态管理的Fragment
3.5 Fragment的静态创建和动态创建的区别是什么?
答案:
静态创建和动态创建在使用方式、灵活性和适用场景上有区别。
主要区别:
| 特性 | 静态创建 | 动态创建 |
|---|---|---|
| 声明方式 | XML布局 | 代码 |
| 灵活性 | 低 | 高 |
| 替换 | 不能 | 可以 |
| 回退栈 | 不支持 | 支持 |
| 使用场景 | 固定Fragment | 动态Fragment |
静态创建:
- 在XML中声明
- 简单直接
- 不能动态替换
- 适合固定不变的Fragment
动态创建:
- 通过代码添加
- 灵活,可以动态管理
- 可以添加到回退栈
- 适合需要动态切换的Fragment
推荐:
- 大多数情况使用动态创建
- 静态创建仅用于固定不变的Fragment
3.6 Fragment的add()和replace()的区别是什么?
答案:
add()和replace()是添加Fragment的两种方式,行为不同。
add()方法:
- 行为:在容器中添加Fragment,不移除现有Fragment
- 结果:多个Fragment可能重叠
- 使用场景:需要同时显示多个Fragment
transaction.add(R.id.container, fragment1);
transaction.add(R.id.container, fragment2);
// 结果:fragment1和fragment2都存在于容器中(可能重叠)
replace()方法:
- 行为:替换容器中的Fragment,移除现有Fragment
- 结果:容器中只有一个Fragment
- 使用场景:切换Fragment
transaction.replace(R.id.container, fragment1);
transaction.replace(R.id.container, fragment2);
// 结果:fragment1被移除,只有fragment2存在
区别总结:
- add():添加,不移除现有Fragment
- replace():替换,移除现有Fragment
选择建议:
- 需要同时显示多个Fragment:使用add()
- 需要切换Fragment:使用replace()
3.7 Fragment的show()和hide()的作用是什么?
答案:
show()和hide()用于显示和隐藏Fragment,不销毁Fragment。
show()方法:
- 作用:显示Fragment
- 特点:Fragment已存在,只是显示
- 生命周期:不触发生命周期方法
transaction.show(fragment);
hide()方法:
- 作用:隐藏Fragment
- 特点:Fragment仍存在,只是隐藏
- 生命周期:不触发生命周期方法
transaction.hide(fragment);
使用场景:
- 需要频繁切换Fragment
- 需要保持Fragment状态
- 避免重复创建和销毁
优势:
- 性能好(不创建销毁)
- 保持Fragment状态
- 切换快速
注意事项:
- 使用show/hide时,Fragment的onHiddenChanged()会被调用
- 需要手动管理Fragment的显示状态
3.8 Fragment的attach()和detach()的作用是什么?
答案:
attach()和detach()用于关联和分离Fragment,会触发部分生命周期方法。
attach()方法:
- 作用:重新关联Fragment
- 生命周期:调用onAttach()、onCreateView()、onActivityCreated()等
- 使用场景:重新显示之前detach的Fragment
transaction.attach(fragment);
detach()方法:
- 作用:分离Fragment,但保留Fragment实例
- 生命周期:调用onDestroyView(),但不会调用onDestroy()
- 使用场景:暂时移除Fragment,但保留状态
transaction.detach(fragment);
与remove()的区别:
- detach():分离,保留Fragment实例
- remove():移除,销毁Fragment实例
使用场景:
- 需要暂时移除Fragment但保留状态:使用detach()
- 需要完全移除Fragment:使用remove()
3.9 什么时候使用add(),什么时候使用replace()?
答案:
根据业务需求和Fragment关系选择使用add()或replace()。
使用add()的场景:
- 需要同时显示多个Fragment
- Fragment之间需要叠加显示
- 使用show/hide切换Fragment
使用replace()的场景:
- 需要切换Fragment(一个容器只显示一个)
- 不需要保留之前的Fragment
- 简单的Fragment切换
示例:
使用add() + show/hide:
// 添加多个Fragment
transaction.add(R.id.container, fragment1);
transaction.add(R.id.container, fragment2);
transaction.commit();
// 切换显示
transaction.hide(fragment1);
transaction.show(fragment2);
transaction.commit();
使用replace():
// 切换Fragment
transaction.replace(R.id.container, fragment1);
transaction.commit();
// 再次切换
transaction.replace(R.id.container, fragment2);
transaction.commit();
推荐:
- 需要频繁切换:使用add() + show/hide
- 简单切换:使用replace()
3.10 Fragment和Activity如何通信?
答案:
Fragment和Activity有多种通信方式。
1. 通过接口回调
Fragment定义接口,Activity实现:
// Fragment中
public interface OnFragmentListener {
void onFragmentAction(String data);
}
private OnFragmentListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentListener) {
listener = (OnFragmentListener) context;
}
}
// 调用
listener.onFragmentAction("data");
// Activity中实现接口
public class MainActivity extends AppCompatActivity implements OnFragmentListener {
@Override
public void onFragmentAction(String data) {
// 处理数据
}
}
2. 通过getActivity()
Fragment直接访问Activity:
// Fragment中
Activity activity = getActivity();
if (activity instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) activity;
mainActivity.doSomething();
}
3. 通过ViewModel
共享ViewModel:
// Fragment和Activity共享同一个ViewModel
ViewModelProvider viewModelProvider = new ViewModelProvider(getActivity());
MyViewModel viewModel = viewModelProvider.get(MyViewModel.class);
4. 通过EventBus等事件总线
使用第三方库进行通信。
推荐:
- 简单通信:接口回调
- 数据共享:ViewModel
- 复杂通信:EventBus
3.11 Fragment之间如何通信?
答案:
Fragment之间可以通过Activity中转、ViewModel共享、事件总线等方式通信。
1. 通过Activity中转
Fragment A → Activity → Fragment B:
// Fragment A
((MainActivity) getActivity()).sendDataToFragmentB("data");
// Activity中
public void sendDataToFragmentB(String data) {
FragmentB fragmentB = (FragmentB) getSupportFragmentManager()
.findFragmentById(R.id.fragment_b);
if (fragmentB != null) {
fragmentB.receiveData(data);
}
}
2. 通过ViewModel共享
Fragment A和Fragment B共享ViewModel:
// Fragment A
ViewModelProvider viewModelProvider = new ViewModelProvider(getActivity());
MyViewModel viewModel = viewModelProvider.get(MyViewModel.class);
viewModel.setData("data");
// Fragment B
ViewModelProvider viewModelProvider = new ViewModelProvider(getActivity());
MyViewModel viewModel = viewModelProvider.get(MyViewModel.class);
String data = viewModel.getData();
3. 通过事件总线
使用EventBus等:
// Fragment A
EventBus.getDefault().post(new MessageEvent("data"));
// Fragment B
@Subscribe
public void onMessageEvent(MessageEvent event) {
// 处理事件
}
推荐:
- 数据共享:ViewModel
- 事件通信:EventBus
- 简单通信:Activity中转
3.12 Fragment的setArguments()和getArguments()的作用是什么?
答案:
setArguments()和getArguments()用于传递参数给Fragment,是推荐的方式。
setArguments()方法:
- 作用:设置Fragment的参数
- 时机:在Fragment创建后、onCreate()之前
- 特点:参数保存在Bundle中,Fragment重建时会保留
// 创建Fragment并设置参数
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString("key", "value");
fragment.setArguments(args);
getArguments()方法:
- 作用:获取Fragment的参数
- 时机:在onCreate()及之后可以获取
- 特点:返回Bundle,可能为null
// Fragment中获取参数
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
String value = getArguments().getString("key");
}
}
优势:
- Fragment重建时参数会保留
- 推荐的方式
- 避免使用构造参数(系统可能重建Fragment)
注意事项:
- 必须在Fragment创建后、添加到Activity之前调用setArguments()
- getArguments()可能为null,需要判空
3.13 Fragment的onAttach()和onDetach()的作用是什么?
答案:
onAttach()和onDetach()用于关联和分离Fragment与Activity。
onAttach()方法:
- 调用时机:Fragment与Activity关联时
- 作用:获取Activity引用,初始化与Activity的关联
- 参数:Context(通常是Activity)
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentListener) {
listener = (OnFragmentListener) context;
}
}
onDetach()方法:
- 调用时机:Fragment与Activity分离时
- 作用:清理与Activity的关联,避免内存泄漏
- 特点:Fragment生命周期最后调用
@Override
public void onDetach() {
super.onDetach();
listener = null; // 清理引用,避免内存泄漏
}
注意事项:
- onAttach()中获取Activity引用
- onDetach()中清理引用,避免内存泄漏
- onDetach()后不能再访问Activity
3.14 Fragment的接口回调如何实现?
答案:
接口回调是Fragment与Activity通信的标准方式。
实现步骤:
- 定义接口
public interface OnFragmentListener {
void onFragmentAction(String data);
void onFragmentResult(int result);
}
- Fragment中定义接口变量
public class MyFragment extends Fragment {
private OnFragmentListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentListener) {
listener = (OnFragmentListener) context;
} else {
throw new RuntimeException("Activity must implement OnFragmentListener");
}
}
private void doSomething() {
if (listener != null) {
listener.onFragmentAction("data");
}
}
@Override
public void onDetach() {
super.onDetach();
listener = null; // 清理引用
}
}
- Activity实现接口
public class MainActivity extends AppCompatActivity implements OnFragmentListener {
@Override
public void onFragmentAction(String data) {
// 处理Fragment的回调
}
@Override
public void onFragmentResult(int result) {
// 处理结果
}
}
优势:
- 类型安全
- 解耦Fragment和Activity
- 标准做法
3.15 Fragment的ViewModel如何共享?
答案:
Fragment可以通过ViewModelProvider共享ViewModel,作用域可以是Activity或Fragment。
共享方式:
- Activity作用域的ViewModel(Fragment间共享)
// Fragment A和Fragment B共享同一个ViewModel
ViewModelProvider viewModelProvider = new ViewModelProvider(getActivity());
MyViewModel viewModel = viewModelProvider.get(MyViewModel.class);
// Fragment A设置数据
viewModel.setData("data");
// Fragment B获取数据
String data = viewModel.getData();
- Fragment作用域的ViewModel(Fragment独享)
// Fragment独享的ViewModel
ViewModelProvider viewModelProvider = new ViewModelProvider(this);
MyViewModel viewModel = viewModelProvider.get(MyViewModel.class);
- 使用ViewModelFactory传递参数
ViewModelProvider.Factory factory = new ViewModelFactory("param");
ViewModelProvider viewModelProvider = new ViewModelProvider(getActivity(), factory);
MyViewModel viewModel = viewModelProvider.get(MyViewModel.class);
作用域说明:
- getActivity():Activity作用域,Fragment间共享
- this:Fragment作用域,Fragment独享
使用场景:
- Fragment间共享数据:使用Activity作用域
- Fragment独享数据:使用Fragment作用域
3.16 FragmentManager和FragmentTransaction的作用是什么?
答案:
FragmentManager和FragmentTransaction用于管理Fragment。
FragmentManager:
- 作用:管理Fragment的添加、移除、查找等
- 获取方式:getSupportFragmentManager()(v4包)或getFragmentManager()
FragmentManager fragmentManager = getSupportFragmentManager();
主要方法:
- findFragmentById():通过ID查找Fragment
- findFragmentByTag():通过Tag查找Fragment
- popBackStack():弹出回退栈
FragmentTransaction:
- 作用:执行Fragment的添加、移除、替换等操作
- 获取方式:fragmentManager.beginTransaction()
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.container, fragment);
transaction.commit();
主要方法:
- add():添加Fragment
- remove():移除Fragment
- replace():替换Fragment
- show()/hide():显示/隐藏Fragment
- attach()/detach():关联/分离Fragment
- commit():提交事务
注意事项:
- 所有操作必须在commit()之前
- commit()是异步的,commitNow()是同步的
- 不能在onSaveInstanceState()之后调用commit()
3.17 Fragment的回退栈(BackStack)是什么?
答案:
回退栈用于管理Fragment的返回操作,类似Activity的任务栈。
回退栈的作用:
- 记录Fragment的添加顺序
- 支持返回键返回上一个Fragment
- 管理Fragment的导航
使用方式:
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, fragment1);
transaction.addToBackStack(null); // 添加到回退栈
transaction.commit();
// 再次添加
transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, fragment2);
transaction.addToBackStack(null);
transaction.commit();
// 按返回键会依次返回:fragment2 → fragment1
addToBackStack()方法:
- 参数:名称(可以为null),用于标识
- 作用:将当前事务添加到回退栈
回退栈管理:
// 弹出回退栈
fragmentManager.popBackStack();
// 弹出到指定名称
fragmentManager.popBackStack("name", FragmentManager.POP_BACK_STACK_INCLUSIVE);
// 清空回退栈
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
注意事项:
- 只有添加到回退栈的Fragment才能通过返回键返回
- 返回时会触发Fragment的生命周期方法
3.18 FragmentTransaction的commit()和commitAllowingStateLoss()的区别是什么?
答案:
commit()和commitAllowingStateLoss()用于提交Fragment事务,但在异常处理上有区别。
commit()方法:
- 行为:提交事务
- 异常处理:如果Activity状态已保存,会抛出异常
- 安全性:更安全,避免状态丢失
transaction.commit();
// 如果Activity状态已保存,可能抛出异常
commitAllowingStateLoss()方法:
- 行为:提交事务,允许状态丢失
- 异常处理:即使Activity状态已保存,也不会抛出异常
- 安全性:可能丢失状态,不推荐使用
transaction.commitAllowingStateLoss();
// 即使Activity状态已保存,也不会抛出异常
使用场景:
- commit():正常情况使用
- commitAllowingStateLoss():特殊情况(如onSaveInstanceState()之后),但不推荐
注意事项:
- 推荐使用commit()
- commitAllowingStateLoss()可能导致状态丢失
- 不能在onSaveInstanceState()之后调用commit()
3.19 Fragment的懒加载如何实现?
答案:
懒加载用于延迟加载Fragment的数据,提升性能。
实现方式:
- 使用setUserVisibleHint()(已废弃)
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()) {
loadData();
}
}
- 使用onHiddenChanged()(show/hide方式)
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden && isResumed()) {
loadData();
}
}
- 使用FragmentPagerAdapter + ViewPager(推荐)
public class LazyFragment extends Fragment {
private boolean isDataLoaded = false;
private boolean isViewCreated = false;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && !isDataLoaded && isViewCreated) {
loadData();
isDataLoaded = true;
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewCreated = true;
if (getUserVisibleHint() && !isDataLoaded) {
loadData();
isDataLoaded = true;
}
}
private void loadData() {
// 加载数据
}
}
推荐:
- ViewPager中使用setUserVisibleHint()
- show/hide方式使用onHiddenChanged()
3.20 ViewPager中Fragment的生命周期特点是什么?
答案:
ViewPager中Fragment的生命周期有特殊行为,受ViewPager的预加载机制影响。
生命周期特点:
-
预加载机制
- ViewPager会预加载相邻的Fragment
- 默认预加载左右各1个Fragment
- 可以通过setOffscreenPageLimit()设置
-
生命周期调用
- 预加载的Fragment会调用onCreate()、onCreateView()等
- 但不会调用onStart()、onResume()
- 只有可见的Fragment才会调用onStart()、onResume()
-
生命周期顺序
预加载:onAttach() → onCreate() → onCreateView() → onActivityCreated() 可见时:onStart() → onResume() 不可见:onPause() → onStop()
注意事项:
- 预加载的Fragment已经创建,但不可见
- 使用setUserVisibleHint()判断是否可见
- 注意内存占用(预加载会占用内存)
3.21 Fragment的setRetainInstance()的作用是什么?
答案:
setRetainInstance()用于在配置变更时保留Fragment实例(已废弃,Android 3.0+推荐使用ViewModel)。
作用:
- 配置变更(如横竖屏切换)时,Fragment实例不会被销毁重建
- 保留Fragment的状态和数据
使用方式:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); // 保留实例
}
注意事项:
- 已废弃,不推荐使用
- 推荐使用ViewModel保存数据
- 只能用于没有UI的Fragment(setRetainInstance(true)的Fragment不能有View)
替代方案:
- 使用ViewModel保存数据
- 使用onSaveInstanceState()保存状态
3.22 Fragment的状态保存如何实现?
答案:
Fragment的状态保存通过**onSaveInstanceState()**实现。
保存状态:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("key", "value");
outState.putInt("count", count);
}
恢复状态:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
String value = savedInstanceState.getString("key");
int count = savedInstanceState.getInt("count");
}
}
调用时机:
- 系统回收Fragment时
- 配置变更时
- Activity的onSaveInstanceState()中自动调用
注意事项:
- 只保存临时数据
- 持久数据使用其他方式(SharedPreferences、数据库等)
- Bundle有大小限制
3.23 Fragment的onSaveInstanceState()什么时候调用?
答案:
onSaveInstanceState()在Fragment可能被系统回收时调用。
调用时机:
- Activity的onSaveInstanceState()被调用时
- 配置变更时(如横竖屏切换)
- 系统需要回收Fragment时
调用顺序:
Activity.onSaveInstanceState()
→ Fragment.onSaveInstanceState()
注意事项:
- 系统自动调用
- 不能保证一定调用(用户主动退出不会调用)
- 保存的数据在onCreate()的Bundle中恢复
3.24 Fragment的状态恢复如何实现?
答案:
Fragment的状态恢复在**onCreate()或onActivityCreated()**中实现。
恢复方式:
- 在onCreate()中恢复
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
String value = savedInstanceState.getString("key");
}
}
- 在onActivityCreated()中恢复
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
String value = savedInstanceState.getString("key");
}
}
推荐:
- 在onCreate()中恢复数据
- 在onActivityCreated()中恢复UI状态
3.25 Fragment的内存泄漏如何避免?
答案:
Fragment内存泄漏的常见原因和避免方法。
常见泄漏场景:
- 静态引用
// 错误
static Fragment fragment;
// 正确:使用WeakReference
static WeakReference<Fragment> fragmentRef;
- 异步任务持有引用
// 错误:AsyncTask持有Fragment引用
new AsyncTask() {
// 持有Fragment引用
}.execute();
// 正确:在onDestroy()中取消
@Override
public void onDestroy() {
super.onDestroy();
asyncTask.cancel(true);
}
- 监听器未注销
// 正确:在onDestroy()中注销
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
- Handler泄漏
// 正确:使用静态内部类+WeakReference
private static class MyHandler extends Handler {
private WeakReference<Fragment> fragmentRef;
}
避免方法:
- 避免静态引用Fragment
- 及时取消异步任务
- 及时注销监听器
- 使用WeakReference
3.26 Fragment的异常退出如何处理?
答案:
Fragment异常退出需要通过异常捕获和状态恢复处理。
处理方式:
- try-catch保护
try {
// 可能崩溃的代码
} catch (Exception e) {
e.printStackTrace();
}
- 状态恢复
- 使用onSaveInstanceState()保存状态
- 异常退出后可以恢复
- 全局异常处理
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 处理异常
}
});
最佳实践:
- 关键操作使用try-catch
- 记录异常日志
- 使用崩溃监控工具
3.27 Fragment的嵌套如何处理?
答案:
Fragment嵌套(Fragment中包含Fragment)需要使用getChildFragmentManager()。
嵌套Fragment:
public class ParentFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 使用getChildFragmentManager()而不是getFragmentManager()
FragmentManager childFragmentManager = getChildFragmentManager();
FragmentTransaction transaction = childFragmentManager.beginTransaction();
transaction.add(R.id.child_container, new ChildFragment());
transaction.commit();
}
}
关键点:
- 使用**getChildFragmentManager()**管理子Fragment
- 子Fragment的生命周期受父Fragment影响
- 子Fragment的回退栈独立管理
注意事项:
- 嵌套层级不宜过深
- 注意性能影响
- 正确管理FragmentManager
3.28 Fragment的最佳实践有哪些?
答案:
Fragment最佳实践包括生命周期管理、通信方式、状态管理等。
最佳实践:
-
生命周期管理
- 在正确的生命周期方法中执行操作
- 及时释放资源
- 避免内存泄漏
-
通信方式
- 使用接口回调与Activity通信
- 使用ViewModel共享数据
- 避免直接访问Activity
-
状态管理
- 使用onSaveInstanceState()保存状态
- 使用ViewModel保存数据
- 合理使用setArguments()
-
Fragment管理
- 使用动态创建,灵活管理
- 合理使用回退栈
- 注意Fragment的生命周期
-
性能优化
- 实现懒加载
- 避免过度嵌套
- 优化布局
总结:
- 遵循生命周期
- 注意内存管理
- 合理通信
- 优化性能
3.29 Fragment和Activity的选择标准是什么?
答案:
根据使用场景和需求选择Fragment或Activity。
使用Activity的场景:
- 独立的屏幕
- 需要独立的生命周期
- 系统级功能(如设置)
使用Fragment的场景:
- 可复用的UI组件
- 需要在多个Activity中使用
- 灵活的UI组合
- 适配不同屏幕(手机/平板)
选择标准:
- 复用性:需要复用 → Fragment
- 独立性:完全独立 → Activity
- 灵活性:需要灵活组合 → Fragment
- 简单性:简单场景 → Activity
推荐:
- 大多数情况使用Fragment
- 系统级功能使用Activity
- 根据实际需求选择
3.30 Fragment的测试如何进行?
答案:
Fragment测试可以使用AndroidX Test框架。
测试方式:
- 单元测试
@Test
public void testFragmentCreation() {
MyFragment fragment = new MyFragment();
assertNotNull(fragment);
}
- UI测试(使用FragmentScenario)
@RunWith(AndroidJUnit4.class)
public class MyFragmentTest {
@Test
public void testFragment() {
FragmentScenario<MyFragment> scenario =
FragmentScenario.launchInContainer(MyFragment.class);
scenario.onFragment(fragment -> {
// 测试Fragment
});
}
}
- 集成测试
- 测试Fragment与Activity的交互
- 测试Fragment的生命周期
- 测试Fragment的状态保存和恢复
测试内容:
- Fragment的创建和销毁
- 生命周期方法
- 状态保存和恢复
- 与Activity的通信
第四章:Service(25 题)
4.1 Service是什么?它的生命周期是什么?
答案:
Service是Android的后台服务组件,用于执行长时间运行的任务,没有用户界面。
Service的定义:
- 后台运行,无用户界面
- 可以执行长时间运行的任务
- 可以跨进程运行
生命周期方法:
启动模式(startService):
- onCreate():Service创建时调用
- onStartCommand():每次启动时调用
- onDestroy():Service销毁时调用
绑定模式(bindService):
- onCreate():Service创建时调用
- onBind():绑定Service时调用
- onUnbind():解绑Service时调用
- onDestroy():Service销毁时调用
生命周期流程图:
启动模式:onCreate() → onStartCommand() → onDestroy()
绑定模式:onCreate() → onBind() → onUnbind() → onDestroy()
4.2 Service和Activity的区别是什么?
答案:
Service和Activity在用途、生命周期和用户交互上有重要区别。
主要区别:
| 特性 | Activity | Service |
|---|---|---|
| 用户界面 | 有 | 无 |
| 用途 | 用户交互 | 后台任务 |
| 生命周期 | 受用户操作影响 | 受启动方式影响 |
| 可见性 | 可见 | 不可见 |
| 启动方式 | startActivity() | startService()/bindService() |
Activity特点:
- 有用户界面
- 用户可见
- 用于用户交互
- 生命周期受用户操作影响
Service特点:
- 无用户界面
- 后台运行
- 用于执行长时间任务
- 生命周期受启动方式影响
使用场景:
- Activity:需要用户界面的场景
- Service:后台任务、音乐播放、文件下载等
4.3 Service的启动方式有哪些?
答案:
Service有两种启动方式:startService()和bindService()。
1. startService()(启动模式)
Intent intent = new Intent(this, MyService.class);
startService(intent);
- 特点:Service独立运行,不依赖调用者
- 生命周期:onCreate() → onStartCommand() → onDestroy()
- 使用场景:后台任务、音乐播放等
2. bindService()(绑定模式)
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
- 特点:Service与调用者绑定,可以通信
- 生命周期:onCreate() → onBind() → onUnbind() → onDestroy()
- 使用场景:需要与Service通信的场景
混合模式:
- 可以同时使用startService()和bindService()
- Service只有在既没有启动也没有绑定时才会销毁
4.4 startService()和bindService()的区别是什么?
答案:
startService()和bindService()在运行方式、生命周期和通信方式上有区别。
主要区别:
| 特性 | startService() | bindService() |
|---|---|---|
| 运行方式 | 独立运行 | 与调用者绑定 |
| 生命周期 | 调用者销毁后仍运行 | 调用者销毁后可能销毁 |
| 通信方式 | 不能直接通信 | 可以通过Binder通信 |
| 使用场景 | 后台任务 | 需要通信 |
startService()特点:
- Service独立运行
- 调用者销毁后Service仍运行
- 需要调用stopService()或stopSelf()停止
- 不能直接通信
bindService()特点:
- Service与调用者绑定
- 调用者销毁后Service可能销毁(如果没有startService)
- 可以通过Binder通信
- 调用unbindService()解绑
选择建议:
- 需要后台运行:使用startService()
- 需要通信:使用bindService()
- 两者结合:同时使用
4.5 Service的生命周期方法有哪些?
答案:
Service的生命周期方法根据启动方式不同而不同。
启动模式(startService)的生命周期:
-
onCreate()
- Service创建时调用
- 只调用一次
- 初始化工作
-
onStartCommand(Intent, int, int)
- 每次startService()时调用
- 可以多次调用
- 处理启动请求
-
onDestroy()
- Service销毁时调用
- 清理资源
绑定模式(bindService)的生命周期:
-
onCreate()
- Service创建时调用
- 只调用一次
-
onBind(Intent)
- 绑定Service时调用
- 返回IBinder用于通信
-
onUnbind(Intent)
- 解绑Service时调用
- 返回true表示可以重新绑定
-
onDestroy()
- Service销毁时调用
混合模式:
- 如果同时使用startService()和bindService()
- 需要同时调用stopService()和unbindService()才会销毁
4.6 Service的生命周期和启动方式的关系是什么?
答案:
Service的生命周期完全由启动方式决定。
关系说明:
-
startService()方式
- onCreate() → onStartCommand() → onDestroy()
- Service独立运行
- 调用者销毁不影响Service
-
bindService()方式
- onCreate() → onBind() → onUnbind() → onDestroy()
- Service与调用者绑定
- 所有调用者解绑后可能销毁
-
混合方式
- 同时使用startService()和bindService()
- 需要同时停止和解绑才会销毁
- 生命周期方法都会调用
关键点:
- 启动方式决定生命周期
- 可以同时使用两种方式
- Service只有在既没有启动也没有绑定时才会销毁
4.7 前台Service和后台Service的区别是什么?
答案:
前台Service和后台Service在优先级和通知显示上有区别。
后台Service:
- 特点:普通后台服务
- 优先级:较低,系统可能回收
- 通知:不需要显示通知
- 使用场景:一般后台任务
前台Service:
- 特点:高优先级服务
- 优先级:较高,不易被系统回收
- 通知:必须显示通知
- 使用场景:音乐播放、文件下载等
创建前台Service:
@Override
public void onCreate() {
super.onCreate();
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("前台服务")
.setContentText("服务正在运行")
.setSmallIcon(R.drawable.icon)
.build();
startForeground(1, notification);
}
区别总结:
- 后台Service:低优先级,不需要通知
- 前台Service:高优先级,必须显示通知
4.8 如何创建前台Service?
答案:
创建前台Service需要显示通知并调用startForeground()。
创建步骤:
- 创建通知渠道(Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, "服务渠道", NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
- 创建通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("前台服务")
.setContentText("服务正在运行")
.setSmallIcon(R.drawable.icon)
.build();
- 启动前台服务
@Override
public void onCreate() {
super.onCreate();
startForeground(1, notification);
}
- 在AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
注意事项:
- Android 8.0+需要创建通知渠道
- 必须在onCreate()或onStartCommand()中调用startForeground()
- 必须在5秒内调用,否则会ANR
4.9 前台Service的通知如何管理?
答案:
前台Service的通知需要持续显示,可以更新和停止。
通知管理:
- 更新通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("更新标题")
.setContentText("更新内容")
.setProgress(100, progress, false)
.build();
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(1, notification);
- 停止前台服务
stopForeground(true); // true表示移除通知
// 或
stopForeground(false); // false表示保留通知
- 取消通知
NotificationManager manager = getSystemService(NotificationManager.class);
manager.cancel(1);
注意事项:
- 通知必须持续显示
- 可以更新通知内容
- 停止前台服务时可以移除或保留通知
4.10 IntentService的特点是什么?
答案:
IntentService是自带工作线程的Service,自动处理异步任务。
特点:
-
自动创建工作线程
- 在后台线程执行任务
- 不阻塞主线程
-
串行执行
- 任务按顺序执行
- 一次只执行一个任务
-
自动停止
- 任务执行完成后自动停止
- 不需要手动调用stopSelf()
-
简单易用
- 只需实现onHandleIntent()方法
- 自动处理线程和停止
使用示例:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// 在后台线程执行任务
String data = intent.getStringExtra("data");
// 处理任务
}
}
注意事项:
- 已废弃(Android 8.0+),推荐使用JobIntentService或WorkManager
- 适合简单的后台任务
- 任务串行执行,不适合并发
4.11 IntentService的实现原理是什么?
答案:
IntentService通过HandlerThread和Handler实现后台线程执行任务。
实现原理:
- 创建HandlerThread
private HandlerThread handlerThread;
private Handler handler;
@Override
public void onCreate() {
super.onCreate();
handlerThread = new HandlerThread("IntentService");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
- 在onStartCommand()中发送消息
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handler.post(() -> {
onHandleIntent(intent);
stopSelf(startId);
});
return START_NOT_STICKY;
}
- 在onHandleIntent()中处理任务
protected abstract void onHandleIntent(Intent intent);
关键点:
- 使用HandlerThread创建工作线程
- 使用Handler发送任务到工作线程
- 任务执行完成后自动停止
优势:
- 自动管理线程
- 串行执行,线程安全
- 自动停止,无需手动管理
4.12 JobService的作用是什么?
答案:
JobService是系统调度的后台服务,用于执行定时任务和条件任务。
作用:
- 在满足条件时执行任务(如网络可用、充电时等)
- 系统统一调度,节省电量
- 替代后台Service(Android 8.0+限制后台服务)
使用方式:
- 继承JobService
public class MyJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// 在后台线程执行任务
new Thread(() -> {
// 执行任务
jobFinished(params, false);
}).start();
return true; // 返回true表示任务在后台线程执行
}
@Override
public boolean onStopJob(JobParameters params) {
// 任务被停止时调用
return false; // 返回false表示任务不需要重新调度
}
}
- 调度任务
JobScheduler jobScheduler = getSystemService(JobScheduler.class);
JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(this, MyJobService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setRequiresCharging(true)
.build();
jobScheduler.schedule(jobInfo);
注意事项:
- Android 5.0+支持
- 系统统一调度,执行时间不确定
- 适合不紧急的后台任务
4.13 WorkManager和Service的区别是什么?
答案:
WorkManager是新的后台任务调度框架,相比Service有诸多优势。
主要区别:
| 特性 | Service | WorkManager |
|---|---|---|
| 调度方式 | 立即执行 | 系统调度 |
| 执行条件 | 无 | 支持条件(网络、充电等) |
| 电量优化 | 较差 | 较好 |
| 适用场景 | 立即执行的任务 | 可延迟的任务 |
| API级别 | 所有版本 | Android 4.0+ |
Service特点:
- 立即执行
- 适合需要立即执行的任务
- 电量消耗较大
WorkManager特点:
- 系统统一调度
- 支持执行条件
- 电量优化好
- 适合可延迟的任务
使用建议:
- 立即执行的任务:使用Service
- 可延迟的任务:使用WorkManager
- 定时任务:使用WorkManager
4.14 Service和Activity如何通信?
答案:
Service和Activity可以通过Binder、BroadcastReceiver、Messenger等方式通信。
1. 通过Binder通信(绑定模式)
// Service中
public class MyService extends Service {
private MyBinder binder = new MyBinder();
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void doSomething() {
// Service的方法
}
}
// Activity中
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyService.MyBinder binder = (MyService.MyBinder) service;
MyService myService = binder.getService();
myService.doSomething();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
bindService(intent, connection, Context.BIND_AUTO_CREATE);
2. 通过BroadcastReceiver通信
// Service中发送广播
Intent broadcast = new Intent("ACTION");
broadcast.putExtra("data", "value");
sendBroadcast(broadcast);
// Activity中接收广播
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String data = intent.getStringExtra("data");
}
};
registerReceiver(receiver, new IntentFilter("ACTION"));
3. 通过Messenger通信
// Service中
Messenger messenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
});
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
// Activity中
Messenger serviceMessenger = new Messenger(service);
Message msg = Message.obtain();
serviceMessenger.send(msg);
推荐:
- 简单通信:Binder
- 跨进程通信:AIDL或Messenger
- 事件通知:BroadcastReceiver
4.15 Binder机制是什么?
答案:
Binder是Android的进程间通信(IPC)机制,用于跨进程通信。
Binder的作用:
- 进程间通信
- 跨应用通信
- 系统服务通信
Binder的特点:
- 高性能(比Socket快)
- 安全性好(基于Linux内核)
- Android特有机制
Binder的工作原理:
- Client进程通过Binder驱动发送请求
- Binder驱动将请求转发到Server进程
- Server进程处理请求并返回结果
- Binder驱动将结果返回给Client进程
使用方式:
- 本地通信:直接使用Binder
- 跨进程通信:使用AIDL生成Binder接口
优势:
- 性能好
- 安全性高
- 系统统一管理
4.16 Binder的通信原理是什么?
答案:
Binder通信基于Linux内核的Binder驱动,实现进程间通信。
通信流程:
-
Client进程
- 调用Service的方法
- 通过Binder驱动发送请求
-
Binder驱动
- 接收Client的请求
- 转发到Server进程
- 管理进程间通信
-
Server进程
- 接收Binder驱动的请求
- 处理请求
- 返回结果
-
返回结果
- Server通过Binder驱动返回结果
- Binder驱动转发给Client
- Client接收结果
关键组件:
- Binder驱动:Linux内核模块,管理IPC
- ServiceManager:系统服务管理器
- Binder对象:进程间通信的代理
优势:
- 一次拷贝(比Socket的两次拷贝快)
- 安全性好(基于Linux内核)
- 系统统一管理
4.17 AIDL的作用是什么?如何使用?
答案:
AIDL(Android Interface Definition Language)用于定义跨进程通信接口。
AIDL的作用:
- 定义跨进程通信接口
- 自动生成Binder代码
- 简化跨进程通信
使用步骤:
- 定义AIDL接口
// IMyService.aidl
interface IMyService {
void doSomething(String data);
String getResult();
}
- 实现Service
public class MyService extends Service {
private IMyService.Stub binder = new IMyService.Stub() {
@Override
public void doSomething(String data) {
// 实现方法
}
@Override
public String getResult() {
return "result";
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
- 在Activity中绑定Service
private IMyService myService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myService = IMyService.Stub.asInterface(service);
myService.doSomething("data");
}
@Override
public void onServiceDisconnected(ComponentName name) {
myService = null;
}
};
bindService(intent, connection, Context.BIND_AUTO_CREATE);
注意事项:
- AIDL文件需要放在aidl目录下
- 支持的数据类型有限(基本类型、String、Parcelable等)
- 跨进程调用是异步的
4.18 AIDL的接口如何定义?
答案:
AIDL接口定义需要遵循特定语法。
定义规则:
- 基本语法
// IMyService.aidl
package com.example;
interface IMyService {
void method1();
int method2(String param);
}
- 支持的数据类型
- 基本类型:int、long、char、boolean等
- String、CharSequence
- List(元素必须是支持的类型)
- Map(key和value必须是支持的类型)
- Parcelable对象
- 定义Parcelable对象
// User.aidl
package com.example;
parcelable User;
// User.java实现Parcelable
public class User implements Parcelable {
// Parcelable实现
}
- 方向标识(in/out/inout)
interface IMyService {
void method(in String input, out String output, inout String inout);
}
注意事项:
- 包名必须与Java包名一致
- 支持的类型有限
- 跨进程调用是异步的
4.19 Messenger的使用方法是什么?
答案:
Messenger是基于Message的跨进程通信方式,比AIDL更简单。
使用方法:
- Service中创建Messenger
public class MyService extends Service {
private Messenger messenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
String data = (String) msg.obj;
// 回复消息
Message reply = Message.obtain();
reply.obj = "result";
try {
msg.replyTo.send(reply);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}
- Activity中绑定Service
private Messenger serviceMessenger;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceMessenger = new Messenger(service);
// 发送消息
Message msg = Message.obtain();
msg.obj = "data";
msg.replyTo = clientMessenger; // 设置回复Messenger
try {
serviceMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
serviceMessenger = null;
}
};
// 创建客户端Messenger接收回复
private Messenger clientMessenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
String result = (String) msg.obj;
}
});
特点:
- 基于Message通信
- 比AIDL简单
- 适合简单的跨进程通信
4.20 Messenger和AIDL的区别是什么?
答案:
Messenger和AIDL都可以用于跨进程通信,但使用方式和适用场景不同。
主要区别:
| 特性 | Messenger | AIDL |
|---|---|---|
| 通信方式 | 基于Message | 基于接口方法 |
| 使用复杂度 | 简单 | 较复杂 |
| 适用场景 | 简单通信 | 复杂通信 |
| 性能 | 稍慢 | 稍快 |
| 双向通信 | 需要两个Messenger | 支持 |
Messenger特点:
- 基于Message通信
- 使用简单
- 适合简单的跨进程通信
- 双向通信需要两个Messenger
AIDL特点:
- 基于接口方法调用
- 功能强大
- 适合复杂的跨进程通信
- 支持双向通信
选择建议:
- 简单通信:使用Messenger
- 复杂通信:使用AIDL
- 性能要求高:使用AIDL
4.21 Service的最佳实践有哪些?
答案:
Service最佳实践包括生命周期管理、内存管理、性能优化等。
最佳实践:
-
选择合适的启动方式
- 需要立即执行:使用startService()
- 需要通信:使用bindService()
- 可延迟执行:使用WorkManager
-
及时释放资源
- 在onDestroy()中清理资源
- 取消异步任务
- 注销监听器
-
避免内存泄漏
- 避免静态引用Context
- 及时取消异步任务
- 使用WeakReference
-
使用前台Service
- 重要任务使用前台Service
- 必须显示通知
- 提升优先级
-
合理使用WorkManager
- 可延迟的任务使用WorkManager
- 利用系统调度优化电量
总结:
- 选择合适的启动方式
- 注意内存管理
- 及时释放资源
- 合理使用前台Service
4.22 Service的内存泄漏如何避免?
答案:
Service内存泄漏的常见原因和避免方法。
常见泄漏场景:
- 静态引用Context
// 错误
static Context context;
// 正确:使用Application Context
static Context context = getApplicationContext();
- 异步任务持有引用
// 错误:AsyncTask持有Service引用
new AsyncTask() {
// 持有Service引用
}.execute();
// 正确:在onDestroy()中取消
@Override
public void onDestroy() {
super.onDestroy();
asyncTask.cancel(true);
}
- 监听器未注销
// 正确:在onDestroy()中注销
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
- Handler泄漏
// 正确:使用静态内部类+WeakReference
private static class MyHandler extends Handler {
private WeakReference<Service> serviceRef;
}
避免方法:
- 避免静态引用Context
- 及时取消异步任务
- 及时注销监听器
- 使用WeakReference
4.23 Service的保活机制有哪些?
答案:
Service保活机制用于防止Service被系统回收,但需要注意合规性。
保活机制:
-
前台Service
- 提升优先级
- 不易被系统回收
- 必须显示通知
-
START_STICKY
- Service被系统杀死后自动重启
- 在onStartCommand()中返回START_STICKY
-
双进程守护
- 两个进程互相守护
- 一个进程被杀死,另一个进程重启它
- 不推荐,可能被系统限制
-
JobScheduler/WorkManager
- 系统统一调度
- 在满足条件时执行
- 推荐方式
注意事项:
- Android 8.0+限制后台服务
- 过度保活可能影响用户体验
- 推荐使用WorkManager等系统推荐方式
合规建议:
- 使用前台Service
- 使用WorkManager
- 避免过度保活
4.24 Service的异常退出如何处理?
答案:
Service异常退出需要通过异常捕获和重启机制处理。
处理方式:
- try-catch保护
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
// 可能崩溃的代码
} catch (Exception e) {
e.printStackTrace();
// 记录异常
}
return START_STICKY; // 异常后重启
}
- START_STICKY重启
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 处理任务
return START_STICKY; // Service被杀死后自动重启
}
- 全局异常处理
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 处理异常
// 重启Service
}
});
最佳实践:
- 关键操作使用try-catch
- 记录异常日志
- 使用START_STICKY自动重启
- 使用崩溃监控工具
4.25 Service的测试如何进行?
答案:
Service测试可以使用AndroidX Test框架。
测试方式:
- 单元测试
@Test
public void testServiceCreation() {
MyService service = new MyService();
assertNotNull(service);
}
- 集成测试(使用ServiceTestRule)
@RunWith(AndroidJUnit4.class)
public class MyServiceTest {
@Rule
public final ServiceTestRule serviceRule = new ServiceTestRule();
@Test
public void testService() throws TimeoutException {
Intent intent = new Intent(ApplicationProvider.getApplicationContext(), MyService.class);
IBinder binder = serviceRule.bindService(intent);
MyService.MyBinder myBinder = (MyService.MyBinder) binder;
MyService service = myBinder.getService();
assertNotNull(service);
}
}
- 功能测试
- 测试Service的启动和停止
- 测试Service的生命周期
- 测试Service与Activity的通信
测试内容:
- Service的创建和销毁
- 生命周期方法
- 与Activity的通信
- 异常处理
第五章:BroadcastReceiver(20 题)
5.1 BroadcastReceiver是什么?它的作用是什么?
答案:
BroadcastReceiver是Android的广播接收器组件,用于接收系统或应用发送的广播消息。
BroadcastReceiver的定义:
- 用于接收广播消息
- 无用户界面
- 生命周期短暂
主要作用:
-
接收系统广播
- 系统事件(开机、网络变化、电量变化等)
- 系统状态变化通知
-
接收应用广播
- 应用间通信
- 组件间通信
-
响应事件
- 根据广播执行相应操作
- 更新UI、启动Service等
使用场景:
- 监听系统事件
- 应用间通信
- 组件间解耦通信
5.2 BroadcastReceiver的注册方式有哪些?
答案:
BroadcastReceiver有两种注册方式:静态注册和动态注册。
1. 静态注册(AndroidManifest.xml)
在AndroidManifest.xml中声明:
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
特点:
- 应用安装时注册
- 应用未运行也能接收广播
- 适合系统广播
2. 动态注册(代码)
在代码中注册:
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 处理广播
}
};
IntentFilter filter = new IntentFilter();
filter.addAction("ACTION");
registerReceiver(receiver, filter);
特点:
- 运行时注册
- 应用运行才能接收
- 需要手动注销
注销:
unregisterReceiver(receiver);
5.3 静态注册和动态注册的区别是什么?
答案:
静态注册和动态注册在注册时机、生命周期和使用场景上有区别。
主要区别:
| 特性 | 静态注册 | 动态注册 |
|---|---|---|
| 注册时机 | 应用安装时 | 运行时 |
| 生命周期 | 应用生命周期 | 注册到注销 |
| 接收时机 | 应用未运行也能接收 | 应用运行才能接收 |
| 注销 | 系统管理 | 手动注销 |
| 使用场景 | 系统广播 | 应用广播 |
静态注册:
- 在AndroidManifest.xml中声明
- 应用安装时注册
- 应用未运行也能接收
- 适合系统广播(如开机广播)
动态注册:
- 在代码中注册
- 运行时注册
- 应用运行才能接收
- 需要手动注销,避免内存泄漏
- 适合应用广播
注意事项:
- 动态注册必须在合适的时机注销
- Android 8.0+限制静态注册(部分系统广播除外)
5.4 有序广播和普通广播的区别是什么?
答案:
有序广播和普通广播在接收顺序和传播机制上有区别。
普通广播(sendBroadcast):
- 特点:所有接收者同时接收,无序
- 传播:不能中断,所有接收者都会收到
- 使用场景:不需要控制接收顺序的场景
Intent intent = new Intent("ACTION");
sendBroadcast(intent);
有序广播(sendOrderedBroadcast):
- 特点:按优先级顺序接收
- 传播:可以中断(abortBroadcast()),后续接收者收不到
- 使用场景:需要控制接收顺序的场景
Intent intent = new Intent("ACTION");
sendOrderedBroadcast(intent, null);
优先级设置:
<intent-filter android:priority="1000">
<action android:name="ACTION" />
</intent-filter>
区别总结:
- 普通广播:无序,不能中断
- 有序广播:有序,可以中断
5.5 粘性广播(Sticky Broadcast)是什么?
答案:
粘性广播是会保留的广播,即使没有接收者,广播也会保留,后续注册的接收者可以收到。
特点:
- 广播会保留在系统中
- 后续注册的接收者可以收到之前的广播
- 需要权限:BROADCAST_STICKY
使用方式:
Intent intent = new Intent("ACTION");
sendStickyBroadcast(intent);
接收粘性广播:
Intent intent = registerReceiver(receiver, filter);
if (intent != null) {
// 处理粘性广播
}
注意事项:
- 已废弃(Android 5.0+),不推荐使用
- 推荐使用其他方式(如SharedPreferences、数据库等)保存状态
5.6 广播的优先级如何设置?
答案:
广播优先级通过IntentFilter的priority属性设置,数值越大优先级越高。
设置方式:
- 静态注册中设置
<receiver android:name=".MyReceiver">
<intent-filter android:priority="1000">
<action android:name="ACTION" />
</intent-filter>
</receiver>
- 动态注册中设置
IntentFilter filter = new IntentFilter("ACTION");
filter.setPriority(1000);
registerReceiver(receiver, filter);
优先级说明:
- 数值范围:-1000 到 1000
- 数值越大,优先级越高
- 相同优先级按注册顺序接收
注意事项:
- 只对有序广播有效
- 系统广播的优先级通常较高
- 不建议设置过高的优先级
5.7 系统广播有哪些?
答案:
系统广播是Android系统发送的广播,用于通知系统事件。
常见系统广播:
-
开机广播
android.intent.action.BOOT_COMPLETED- 系统启动完成
-
网络变化广播
android.net.conn.CONNECTIVITY_CHANGE- 网络连接状态变化
-
电量变化广播
android.intent.action.BATTERY_CHANGED- 电池电量变化
-
时间变化广播
android.intent.action.TIME_TICK- 每分钟触发一次
-
屏幕开关广播
android.intent.action.SCREEN_ONandroid.intent.action.SCREEN_OFF- 屏幕开关
-
应用安装/卸载广播
android.intent.action.PACKAGE_ADDEDandroid.intent.action.PACKAGE_REMOVED
注意事项:
- Android 8.0+限制部分系统广播的静态注册
- 需要使用动态注册或替代方案
5.8 自定义广播如何实现?
答案:
自定义广播用于应用内或应用间通信。
实现步骤:
- 定义广播接收者
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String data = intent.getStringExtra("data");
// 处理广播
}
}
- 注册广播接收者
// 静态注册(AndroidManifest.xml)
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="com.example.ACTION" />
</intent-filter>
</receiver>
// 或动态注册
BroadcastReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter("com.example.ACTION");
registerReceiver(receiver, filter);
- 发送广播
Intent intent = new Intent("com.example.ACTION");
intent.putExtra("data", "value");
sendBroadcast(intent);
注意事项:
- 使用包名作为Action前缀,避免冲突
- 动态注册需要及时注销
- 跨应用广播需要设置权限
5.9 本地广播(LocalBroadcastManager)的作用是什么?
答案:
LocalBroadcastManager用于应用内广播,比全局广播更安全、高效。
作用:
- 只在应用内传播,不跨进程
- 更安全(其他应用无法接收)
- 更高效(不需要跨进程通信)
使用方式:
// 注册接收者
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver,
new IntentFilter("ACTION")
);
// 发送广播
Intent intent = new Intent("ACTION");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
// 注销接收者
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
优势:
- 安全性好(应用内)
- 性能好(不跨进程)
- 简单易用
注意事项:
- 已废弃(AndroidX),推荐使用其他方式(如LiveData、EventBus等)
- 只适用于应用内通信
5.10 本地广播和全局广播的区别是什么?
答案:
本地广播和全局广播在传播范围、安全性和性能上有区别。
主要区别:
| 特性 | 本地广播 | 全局广播 |
|---|---|---|
| 传播范围 | 应用内 | 系统范围 |
| 安全性 | 高 | 低 |
| 性能 | 高 | 低 |
| 跨进程 | 不支持 | 支持 |
| 使用场景 | 应用内通信 | 跨应用通信 |
本地广播:
- 只在应用内传播
- 更安全
- 更高效
- 适合应用内通信
全局广播:
- 系统范围传播
- 可以跨应用
- 需要权限控制
- 适合跨应用通信
推荐:
- 应用内通信:使用本地广播或其他方式(LiveData、EventBus)
- 跨应用通信:使用全局广播(需要权限)
5.11 有序广播的传播机制是什么?
答案:
有序广播按优先级顺序传播,可以中断传播。
传播机制:
-
按优先级排序
- 系统根据优先级对接收者排序
- 优先级高的先接收
-
顺序传播
- 按顺序依次发送给接收者
- 前一个接收者处理完后,才发送给下一个
-
可以中断
- 接收者可以调用
abortBroadcast()中断 - 后续接收者收不到广播
- 接收者可以调用
-
可以修改结果
- 接收者可以修改广播结果
- 通过
setResultData()等方法
示例:
@Override
public void onReceive(Context context, Intent intent) {
// 处理广播
if (shouldAbort) {
abortBroadcast(); // 中断传播
}
setResultData("result"); // 设置结果
}
使用场景:
- 需要控制接收顺序
- 需要中断传播
- 需要传递结果
5.12 BroadcastReceiver的最佳实践有哪些?
答案:
BroadcastReceiver最佳实践包括注册管理、性能优化、安全性等。
最佳实践:
-
及时注销动态注册
- 在onDestroy()中注销
- 避免内存泄漏
-
避免耗时操作
- onReceive()在主线程执行
- 耗时操作使用Service或异步任务
-
使用本地广播
- 应用内通信使用本地广播
- 更安全、高效
-
权限控制
- 跨应用广播设置权限
- 保护广播安全
-
适配Android 8.0+
- 使用动态注册或替代方案
- 避免使用已废弃的API
总结:
- 及时注销
- 避免耗时操作
- 注意安全性
- 适配新版本
5.13 BroadcastReceiver的内存泄漏如何避免?
答案:
BroadcastReceiver内存泄漏的常见原因和避免方法。
常见泄漏场景:
- 动态注册未注销
// 错误:注册后未注销
registerReceiver(receiver, filter);
// 正确:在onDestroy()中注销
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
- 内部类持有外部类引用
// 错误:内部类持有Activity引用
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 持有Activity引用
}
};
// 正确:使用静态内部类+WeakReference
private static class MyReceiver extends BroadcastReceiver {
private WeakReference<Activity> activityRef;
}
- Context泄漏
// 错误:持有Activity Context
Context context = getActivity();
// 正确:使用Application Context
Context context = getApplicationContext();
避免方法:
- 及时注销动态注册
- 使用静态内部类
- 使用Application Context
5.14 Android 8.0对广播的限制是什么?
答案:
Android 8.0(API 26)对静态注册的隐式广播进行了限制。
限制内容:
- 大部分隐式广播不能静态注册
- 只能动态注册或使用替代方案
- 例外:部分系统广播仍可静态注册
受影响的广播:
- 自定义广播
- 大部分系统广播(如网络变化广播)
仍可静态注册的广播:
- 开机广播(BOOT_COMPLETED)
- 应用安装/卸载广播
- 部分系统关键广播
原因:
- 提升系统性能
- 减少后台应用唤醒
- 优化电量消耗
解决方案:
- 使用动态注册
- 使用JobScheduler/WorkManager
- 使用其他通信方式
5.15 如何适配Android 8.0的广播限制?
答案:
适配Android 8.0的广播限制需要使用动态注册或替代方案。
适配方案:
- 使用动态注册
// 在Activity或Service中动态注册
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 处理广播
}
};
IntentFilter filter = new IntentFilter();
filter.addAction("ACTION");
registerReceiver(receiver, filter);
// 在onDestroy()中注销
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
- 使用JobScheduler/WorkManager
// 替代网络变化广播
JobScheduler jobScheduler = getSystemService(JobScheduler.class);
JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(this, MyJobService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
jobScheduler.schedule(jobInfo);
- 使用其他通信方式
- LiveData
- EventBus
- 直接调用
推荐:
- 优先使用动态注册
- 系统事件使用JobScheduler/WorkManager
- 应用内通信使用其他方式
5.16 广播的安全问题如何避免?
答案:
广播安全问题主要包括未授权接收和数据泄露。
安全问题:
-
未授权接收
- 其他应用可能接收广播
- 敏感信息泄露
-
数据泄露
- 广播数据可能被拦截
- 需要加密敏感数据
解决方案:
- 使用权限
<!-- 发送广播时设置权限 -->
<uses-permission android:name="com.example.PERMISSION" />
<!-- 接收者声明权限 -->
<receiver android:name=".MyReceiver"
android:permission="com.example.PERMISSION">
<intent-filter>
<action android:name="ACTION" />
</intent-filter>
</receiver>
- 使用本地广播
// 应用内通信使用本地广播
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
- 加密敏感数据
// 加密广播数据
String encryptedData = encrypt(data);
intent.putExtra("data", encryptedData);
- 使用显式Intent
// 指定接收者
Intent intent = new Intent(this, MyReceiver.class);
sendBroadcast(intent);
推荐:
- 设置权限保护
- 应用内通信使用本地广播
- 加密敏感数据
5.17 广播的发送方式有哪些?
答案:
广播有三种发送方式:普通广播、有序广播和粘性广播。
1. 普通广播(sendBroadcast)
Intent intent = new Intent("ACTION");
sendBroadcast(intent);
- 所有接收者同时接收
- 无序
- 不能中断
2. 有序广播(sendOrderedBroadcast)
Intent intent = new Intent("ACTION");
sendOrderedBroadcast(intent, null);
- 按优先级顺序接收
- 可以中断
- 可以传递结果
3. 粘性广播(sendStickyBroadcast)
Intent intent = new Intent("ACTION");
sendStickyBroadcast(intent);
- 广播会保留
- 后续注册的接收者可以收到
- 已废弃,不推荐使用
选择建议:
- 大多数情况:使用普通广播
- 需要控制顺序:使用有序广播
- 避免使用粘性广播
5.18 广播的接收顺序如何控制?
答案:
广播接收顺序通过优先级和广播类型控制。
控制方式:
- 设置优先级(有序广播)
<intent-filter android:priority="1000">
<action android:name="ACTION" />
</intent-filter>
IntentFilter filter = new IntentFilter("ACTION");
filter.setPriority(1000);
registerReceiver(receiver, filter);
- 使用有序广播
Intent intent = new Intent("ACTION");
sendOrderedBroadcast(intent, null);
- 中断传播
@Override
public void onReceive(Context context, Intent intent) {
if (shouldAbort) {
abortBroadcast(); // 中断,后续接收者收不到
}
}
优先级说明:
- 数值范围:-1000 到 1000
- 数值越大,优先级越高
- 相同优先级按注册顺序
注意事项:
- 只对有序广播有效
- 系统广播优先级通常较高
- 不建议设置过高的优先级
5.19 广播的权限如何设置?
答案:
广播权限用于控制谁可以发送或接收广播。
设置方式:
- 发送广播时设置权限
Intent intent = new Intent("ACTION");
sendBroadcast(intent, "com.example.PERMISSION");
- 接收者声明权限
<receiver android:name=".MyReceiver"
android:permission="com.example.PERMISSION">
<intent-filter>
<action android:name="ACTION" />
</intent-filter>
</receiver>
- 定义权限
<permission
android:name="com.example.PERMISSION"
android:protectionLevel="normal" />
- 声明使用权限
<uses-permission android:name="com.example.PERMISSION" />
权限级别:
- normal:自动授予
- dangerous:需要用户授权
- signature:相同签名自动授予
使用场景:
- 保护敏感广播
- 控制广播接收者
- 提升安全性
5.20 广播的性能优化有哪些?
答案:
广播性能优化可以从注册方式、处理逻辑和使用场景等方面优化。
优化策略:
-
使用本地广播
- 应用内通信使用本地广播
- 避免跨进程开销
-
避免耗时操作
- onReceive()在主线程执行
- 耗时操作使用Service或异步任务
-
及时注销
- 动态注册及时注销
- 避免不必要的接收
-
减少广播频率
- 避免频繁发送广播
- 合并多个广播
-
使用替代方案
- 应用内通信使用LiveData、EventBus等
- 系统事件使用JobScheduler/WorkManager
最佳实践:
- 应用内通信:使用本地广播或其他方式
- 避免耗时操作
- 及时注销
- 减少广播频率