Android第十三次面试总结基础
Activity生命周期和四大启动模式详解
一、Activity 生命周期
Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机:
-
onCreate()
- 调用时机:Activity 首次创建时调用。
- 作用:初始化布局(
setContentView
)、绑定数据、创建后台线程等。 - 注意:在此方法中应避免耗时操作。
-
onStart()
- 调用时机:Activity 可见但未获得焦点(例如被对话框覆盖)。
- 作用:恢复UI更新或资源加载。
-
onResume()
- 调用时机:Activity 进入前台并可与用户交互。
- 作用:启动动画、传感器监听、高频率更新UI等。
- 关键点:此时 Activity 位于栈顶。
-
onPause()
- 调用时机:Activity 失去焦点(如弹出对话框或跳转到其他 Activity)。
- 作用:保存临时数据、释放资源(如摄像头)。
- 注意:需快速执行,否则会影响新 Activity 的启动。
-
onStop()
- 调用时机:Activity 完全不可见(被其他 Activity 覆盖或退出)。
- 作用:停止动画、释放非必要资源。
-
onDestroy()
- 调用时机:Activity 被销毁(用户主动退出或系统回收资源)。
- 作用:清理内存、注销广播等。
-
onRestart()
- 调用时机:Activity 从停止状态重新回到前台(如按返回键返回)。
- 流程:
onRestart()
→onStart()
→onResume()
。
场景应用
场景 1:打开新页面
流程:
- 原 Activity 执行
onPause()
(失去焦点) - 新 Activity 依次执行:
onCreate()
(初始化)onStart()
(可见)onResume()
(可交互)
- 原 Activity 执行
onStop()
(完全不可见)
典型场景:
- 从主页跳转到详情页
- 列表页打开新的商品页
场景 2:返回上一个页面
流程:
- 当前 Activity 执行
onPause()
- 上一个 Activity 依次执行:
onRestart()
(重新激活)onStart()
(可见)onResume()
(可交互)
- 当前 Activity 执行:
onStop()
onDestroy()
(被销毁)
典型场景:
- 提交订单后返回购物车
- 查看图片详情后返回相册
场景 3:屏幕旋转
流程:
onPause()
→onSaveInstanceState()
(保存数据)onStop()
→onDestroy()
(销毁实例)- 重新创建:
onCreate()
(携带保存的数据)onStart()
onRestoreInstanceState()
(恢复数据)onResume()
开发重点:
- 在
onSaveInstanceState()
保存编辑框内容/滚动位置 - 避免在旋转时中断网络请求
场景 4:被弹窗/来电中断
场景 | 生命周期变化 | 恢复顺序 |
---|---|---|
普通对话框 | onPause() | onResume() |
全屏弹窗/来电 | onPause() → onStop() | onRestart() → onResume() |
关键区别:
- 是否完全遮挡决定是否触发
onStop()
二、四大启动模式(Launch Mode)
通过 AndroidManifest.xml
或 Intent 标志(如 FLAG_ACTIVITY_NEW_TASK
)指定,控制 Activity 实例与任务栈(Task)的关系。
-
standard(默认模式)
- 行为:每次启动创建新实例,无论是否已存在。
- 示例:A → B (standard) → B (standard),栈内为 A-B-B。
- 注意:可能导致栈中重复实例。
-
singleTop(栈顶复用)
- 行为:若目标 Activity 位于栈顶,直接复用(调用
onNewIntent()
);否则创建新实例。 - 用例:避免重复通知(如点击通知栏打开同一页面)。
- 示例:栈 A-B,启动 B (singleTop) 会复用;启动 C (singleTop) 则新建。
- 行为:若目标 Activity 位于栈顶,直接复用(调用
-
singleTask(栈内单例)
- 行为:在栈中只保留一个实例。若存在则复用(清除其上所有 Activity,调用
onNewIntent()
);否则新建。 - 任务栈:默认在声明时通过
taskAffinity
指定独立栈。 - 用例:应用主页(如微信主界面,保证唯一性)。
- 示例:栈 A-B-C,启动 B (singleTask) 会清除 C,栈变为 A-B。
- 行为:在栈中只保留一个实例。若存在则复用(清除其上所有 Activity,调用
-
singleInstance(全局单例)
- 行为:独占一个任务栈,且栈内仅此一个 Activity。
- 用例:独立应用调用(如系统拨号界面)。
- 示例:栈1: A-B,启动 C (singleInstance) 会新建栈2: C。返回时先回到栈1。
onNewIntent()
是 Activity 被复用时的数据刷新入口,用于处理同一个 Activity 实例被再次启动时的新意图(Intent),而无需重建页面。
核心作用(3个关键点):
-
复用已有页面
- 避免重复创建相同 Activity(节省内存)
- 保持当前页面状态(如滚动位置、输入内容)
-
更新数据
protected void onNewIntent(Intent intent) {setIntent(intent); // 必须更新!否则getIntent()拿旧数据refreshUI(intent); // 根据新数据刷新界面 }
-
特定场景触发
- singleTop:当 Activity 位于栈顶时
- singleTask/singleInstance:当 Activity 已存在于栈中时
典型场景:
- 点击通知栏多次打开同一聊天页(直接刷新消息)
- 从支付结果页返回后再次支付(复用页面更新订单)
- 全局搜索框重复搜索(保留搜索历史记录)
记住一个原则:
只要看到 singleTop
/singleTask
,就要考虑是否需要重写 onNewIntent()
处理数据刷新!
启动模式在后台运行时的关键行为解析
一、后台启动 Activity 的通用规则
-
基本行为:
- 当应用在后台时启动新 Activity,系统会先将应用带回前台
- 新 Activity 会被添加到当前任务栈中
- 用户按返回键时会回到上一个 Activity
-
核心影响:
- 应用进程优先级提升到前台进程
- 可能触发系统回收机制(低内存时后台进程优先被杀)
二、四大启动模式在后台的表现
1. standard(默认模式)
后台启动行为:
- 每次启动都会创建新实例
- 新实例直接入栈,位于原栈顶之上
- 内存影响:可能导致栈内多个相同实例,增加内存压力
典型场景:
// 后台服务收到新消息时启动
Intent intent = new Intent(context, ChatActivity.class);
intent.putExtra("msg_id", newMsgId);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
结果:
- 栈内可能出现:Main → Chat(1) → Chat(2) → Chat(3)
- 内存不足时可能被整体回收
2. singleTop(栈顶复用)
后台启动行为:
- 若目标 Activity 已在栈顶:触发
onNewIntent()
但不创建新实例 - 若不在栈顶:创建新实例入栈
- 省内存优势:避免栈顶重复创建
典型场景:
// 后台收到多条通知时
protected void onNewIntent(Intent intent) {// 更新当前聊天页面数据showNewMessage(intent.getStringExtra("new_msg"));
}
实际效果:
- 用户正在聊天页:刷新当前页面
- 用户在其它页:新建聊天页
3. singleTask(栈内单例)
后台启动行为:
- 查找是否存在目标 Activity 的任务栈
- 若存在:
- 清除该实例之上的所有 Activity
- 触发
onNewIntent()
- 整个任务栈移到前台
- 若不存在:创建新任务栈
内存管理优势:
- 自动清理栈内多余 Activity
- 保持单实例节省内存
典型场景:
// 支付完成后跳转回主页
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
后台效果:
- 原栈:Login → Product → Checkout
- 启动后:MainActivity(清除所有支付流程页面)
4. singleInstance(全局单例)
后台启动行为:
- 独占任务栈直接带到前台
- 若已存在:直接显示现有实例(触发
onNewIntent()
) - 跨应用特性:独立于应用主任务栈
特殊内存管理:
- 独立进程空间(通过 android:process 属性)
- 系统回收时可能单独保留
典型场景:
// 从后台服务启动相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
后台效果:
- 相机应用独立运行
- 拍照完成后返回原应用
三、后台启动的注意事项
1. Android 8.0+ 限制
- 后台启动限制:应用在后台时无法随意启动 Activity
- 解决方案:
// 必须使用全屏通知 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID).setContentIntent(pendingIntent) // 用户点击才启动.setFullScreenIntent(pendingIntent, true); // 紧急通知
2. 内存回收策略
启动模式 | 回收优先级 | 恢复难度 |
---|---|---|
standard | 高 | 难(多实例) |
singleTop | 中 | 中等 |
singleTask | 低 | 易(单实例) |
singleInstance | 最低 | 最易 |
3. 最佳实践
-
后台启动 singleTask 主页:
// 清理所有历史栈 Intent intent = new Intent(context, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
避免后台 standard 启动:
- 易导致 OOM(内存溢出)
- 返回栈混乱
-
跨进程通信:
// 启动独立进程的Activity <activity android:process=":camera_process"/>
四、各模式后台表现对比表
启动模式 | 后台启动特点 | 内存效率 | 适用场景 |
---|---|---|---|
standard | 持续堆叠新实例 | 低 | 需多实例的普通页面 |
singleTop | 栈顶复用省资源 | 中 | 通知/消息更新 |
singleTask | 清理栈内多余页面 | 高 | 应用主页/核心入口 |
singleInstance | 独立进程不受主应用影响 | 最高 | 相机/电话等系统级功能 |
模式 | 实例数量 | 栈位置 | 典型场景 |
---|---|---|---|
standard | 多个 | 当前栈 | 普通页面 |
singleTop | 栈顶唯一 | 当前栈 | 防重复启动(通知栏) |
singleTask | 栈内唯一 | 可指定新栈 | 应用主界面 |
singleInstance | 全局唯一 | 独占新栈 | 独立功能(如相机) |
微信小游戏双图标背后的启动模式解析
场景重现:
-
点击微信图标启动微信主界面
-
在微信内点击进入小游戏
-
后台出现两个独立图标:
-
微信主应用图标
-
小游戏独立图标
-
核心启动模式:singleInstance
实现原理:
<!-- 小游戏Activity声明示例 -->
<activityandroid:name=".GameActivity"android:launchMode="singleInstance"android:taskAffinity="com.tencent.game"android:process=":game_process" />
三大关键机制:
-
独立进程
android:process=":game_process"
-
小游戏运行在独立的
com.tencent.mm:game_process
进程 -
与微信主进程
com.tencent.mm
完全隔离 -
效果:系统显示两个独立进程图标
-
-
独立任务栈
android:taskAffinity="com.tencent.game" android:launchMode="singleInstance"
-
创建专属任务栈(与微信主栈隔离)
-
效果:
-
最近任务显示两个独立任务项
-
游戏退出时直接回到手机桌面,不经过微信
-
-
-
跨进程通信
// 微信启动游戏的关键代码 Intent intent = new Intent(); intent.setComponent(new ComponentName("com.tencent.mm", "com.tencent.mm.plugin.game.GameActivity")); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); startActivity(intent);
-
使用
FLAG_ACTIVITY_MULTIPLE_TASK
允许多实例
-
完整启动流程:
-
用户点击小游戏入口
-
微信创建子进程:
-
通过
startActivity()
跨进程启动 -
指定
singleInstance
+NEW_TASK
标志
-
-
系统创建资源隔离区:
-
独立内存空间(分配专属内存)
-
独立渲染线程(避免微信主线程卡顿)
-
独立任务栈(系统记录为独立应用)
-
-
游戏结束后:
-
游戏进程销毁
-
微信主进程不受影响
-
返回路径:游戏 → 桌面 (不返回微信)
-
技术优势:
-
性能隔离:
-
游戏占500MB内存不影响微信聊天
-
游戏崩溃不会导致微信闪退
# 进程内存占用示例 com.tencent.mm: 300MB # 微信主进程 com.tencent.mm:game: 500MB # 游戏进程
-
-
独立生命周期:
操作
微信主进程
游戏进程
进入游戏
onPause()
onCreate()
游戏切后台
-
onPause->onStop
关闭游戏
onResume()
onDestroy()
游戏崩溃
无影响
自动重启
-
用户体验优化:
-
游戏可独立操作(微信后台保持运行)
-
小窗口模式双向互动(微信浮窗+游戏)
-
对比其他启动模式:
启动模式 | 是否分进程 | 是否独立图标 | 适用场景 |
---|---|---|---|
standard | ❌ | ❌ | 普通页面跳转 |
singleTask | ❌ | ❌ | 微信钱包 |
singleTop | ❌ | ❌ | 公众号文章 |
singleInstance | ✅ | **✅** | 小游戏/视频通话 |
典型应用场景:
-
微信/QQ内置小游戏
-
直播平台连麦功能
-
银行App的安全键盘
-
AR扫描模块
ContentProvider
一、本质:数据网关 + 跨进程通信中介
ContentProvider 的核心是 数据访问的抽象层,其本质包含双重角色:
-
数据网关 (Data Gateway)
- 提供统一的数据访问接口(CRUD)
- 隐藏底层存储实现(SQLite/文件/网络)
- 通过 URI 实现数据路由
-
跨进程通信中介 (IPC Broker)
- 基于 Binder 机制实现进程间通信
- 将数据请求转发到目标进程
- 封装跨进程数据传输细节
二、底层运作流程(源码级解析)
注册阶段:系统级路由表构建
// 系统启动时注册 Provider
ActivityThread.handleBindApplication() {// 1. 反射创建 Provider 实例ContentProvider provider = clazz.newInstance();// 2. 绑定 Context 并初始化provider.attachInfo(context, providerInfo);// 3. 加入全局路由表 (ActivityManagerService)ActivityManagerService.publishContentProviders() {mProviderMap.put(authority, provider); // 存入路由表}
}
- 关键数据结构:
mProviderMap
(系统服务中的全局哈希表) - 键:Authority(如
com.example.provider
) - 值:ContentProvider 实例的 Binder 代理对象
Service 启动方式与实战场景详解
一、两种核心启动方式
1. startService() 启动方式
本质特点:
- 启动后服务与组件完全解耦
- 生命周期独立运行
- 必须显式停止(调用 stopSelf() 或 stopService())
完整生命周期流程:
- 首次启动:
onCreate()
→onStartCommand()
- 后续启动:直接触发
onStartCommand()
- 停止服务:
onDestroy()
场景适用:
- 后台长期任务:如音乐播放、定位追踪
- 无交互任务:如日志上传、数据同步
- 跨应用操作:如推送消息处理
实战代码:
// 启动下载服务
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.putExtra("file_url", "https://example.com/file.zip");
startService(downloadIntent);// 服务内部停止
public class DownloadService extends Service {@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {new Thread(() -> {downloadFile(intent.getStringExtra("file_url"));stopSelf(); // 下载完成后自动停止}).start();return START_NOT_STICKY;}
}
2. bindService() 启动方式
本质特点:
- 建立组件与服务的绑定关系
- 通过 IBinder 接口实现双向通信
- 绑定解绑自动管理生命周期
完整生命周期流程:
- 首次绑定:
onCreate()
→onBind()
- 通信期间:通过 Binder 接口交互
- 所有绑定解除:
onUnbind()
→onDestroy()
场景适用:
- 实时交互场景:如音乐控制(播放/暂停/进度)
- 功能模块解耦:如支付模块服务
- 跨进程通信(IPC):不同应用间的数据交换
实战代码:
// 绑定音乐服务
ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder binder) {MusicService.MusicBinder musicBinder = (MusicService.MusicBinder) binder;musicBinder.play(); // 直接调用服务方法}
};
bindService(new Intent(this, MusicService.class), conn, BIND_AUTO_CREATE);// 解绑服务
unbindService(conn);
Service使用场景
音乐/视频播放服务
核心实现方案:
public class MediaPlaybackService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {private MediaPlayer mediaPlayer;private MediaSession mediaSession;private static final int NOTIFICATION_ID = 101;@Overridepublic void onCreate() {// 初始化媒体播放器mediaPlayer = new MediaPlayer();mediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build());// 创建媒体会话(支持锁屏控制)mediaSession = new MediaSession(this, "MediaPlaybackService");mediaSession.setCallback(new MediaSessionCallback());mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);// 设置通知通道(Android 8.0+必须)createNotificationChannel();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {String action = intent.getStringExtra("action");if("PLAY".equals(action)) {String mediaUrl = intent.getStringExtra("media_url");playMedia(mediaUrl);} else if("PAUSE".equals(action)) {pausePlayback();}return START_NOT_STICKY;}private void playMedia(String mediaUrl) {try {// 停止当前播放if (mediaPlayer.isPlaying()) {mediaPlayer.stop();}mediaPlayer.reset();mediaPlayer.setDataSource(mediaUrl);mediaPlayer.prepareAsync(); // 异步准备播放mediaPlayer.setOnPreparedListener(this);// 转换为前台服务Notification notification = buildMediaNotification("正在播放");startForeground(NOTIFICATION_ID, notification);} catch (IOException e) {Log.e("MediaService", "播放初始化失败", e);}}@Overridepublic void onPrepared(MediaPlayer mp) {mediaPlayer.start();mediaSession.setActive(true);// 更新通知显示播放状态Notification notification = buildMediaNotification("正在播放");NotificationManager nm = getSystemService(NotificationManager.class);nm.notify(NOTIFICATION_ID, notification);}// 构建媒体通知(含播放控制按钮)private Notification buildMediaNotification(String status) {// 构建通知内容(包含播放控制按钮)// ...}
}
关键技术与注意事项:
- 音频焦点管理:
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(focusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- 耳机事件处理:
// 注册耳机拔出广播
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(noisyReceiver, intentFilter);BroadcastReceiver noisyReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if(AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {pausePlayback(); // 耳机拔出时暂停播放}}
};
- 通知栏控制:
- 通过 MediaStyle 通知样式添加播放控制按钮
- 更新播放进度(Android 12+支持直接显示播放进度条)
- 播放状态持久化:
- 在 onDestroy() 中保存当前播放位置
- 在 onCreate() 中恢复最后播放位置