当前位置: 首页 > news >正文

Android四大组件学习总结

1. Activity 启动模式问题

面试官​:
“我看你项目里用了 SingleTask 模式,能具体说说为什么用它吗?如果从 Activity A(SingleTask)跳转到 B(Standard),再返回 A,任务栈会怎么变化?”

候选人​:
技巧​:先明确问题,再分场景解释)
“好的。在我们的项目里,主页 Activity 被设置为 SingleTask 模式,主要是为了防止重复创建主页。比如用户从通知栏跳转到主页时,如果已经有主页实例在后台,就直接复用,而不是新建一个,这样可以避免用户按返回键时多次退出。

您提到的从 A 跳转到 B 再返回 A 的情况,假设 A 是 SingleTask,B 是 Standard:

  1. 当第一次启动 A 时,系统会为 A 创建一个独立的任务栈,假设叫 Task1。
  2. 从 A 跳转 B,因为 B 是 Standard 模式,B 会被压入 Task1 的栈顶。
  3. 当从 B 返回 A 时,由于 A 是 SingleTask,系统会先检查任务栈。发现 Task1 中已经存在 A 实例,于是会把 A 之上的所有 Activity(也就是 B)弹出栈,让 A 回到栈顶。这时候用户再按返回键,就直接退出应用了。”

加分话术​:
“这里有个实际踩过的坑:如果 A 的启动模式设置不当,可能会导致返回栈混乱。我们当时用 adb shell dumpsys activity 命令查看任务栈,验证逻辑是否符合预期。”


2. Service 保活与 ANR 问题

面试官​:
“Service 里做耗时操作为什么会 ANR?你们项目里怎么解决的?”

候选人​:
技巧​:承认问题 + 解决方案 + 实际案例)
“是的,Service 默认运行在主线程,如果直接在里面做网络请求或大量计算,肯定会阻塞主线程导致 ANR。我们在项目里是这样处理的:

  1. 异步处理​:比如用 IntentService,它内部自己开了工作线程,处理完自动停止。
  2. 结合 HandlerThread​:如果是长期运行的后台任务,会创建一个 HandlerThread,再通过 Handler 发送任务到子线程执行。
  3. 前台服务保活​:像音乐播放功能,我们用了前台服务,显示通知栏避免被系统回收。不过保活很难完全做到,Android 8.0 之后限制更多,我们最终改用 JobScheduler 在合适时机重启服务。”

加分话术​:
“其实现在更推荐用 WorkManager 处理后台任务,它能根据系统版本自动选择底层实现,比如用 JobScheduler 或 AlarmManager,这样兼容性更好。”

Service 扩展​

场景一:Service 混合启动模式

面试官​:
“假设我先用 startService() 启动了一个 Service,接着又用 bindService() 绑定它。这时候 Service 的生命周期会怎么走?销毁时要注意什么?”

候选人​:
自然思考状
“嗯,这个问题其实涉及到 Service 的两种启动方式混合使用的情况。我举个例子吧:比如我们做一个音乐播放器,先用 startService() 启动播放服务,保证音乐在后台持续播放;然后在 Activity 里调用 bindService() 绑定它,用来调节音量或者切歌。这时候 Service 的生命周期大概是这样的——”

  1. 第一步​:startService() 触发后,Service 会先走 onCreate(),然后 onStartCommand(),这时候服务就算正式启动了。
  2. 第二步​:再调用 bindService(),这时候不会重新创建 Service,而是直接调用 onBind(),返回一个 IBinder 对象给客户端。
  3. 销毁时​:得同时解绑和停止服务。比如用户退出 Activity 时调用 unbindService(),然后还得主动调用 stopService(),否则 Service 会一直活着,直到系统资源不足被干掉。”

加分话术​:
“在源码中,Service 的生命周期由 ActivityManagerService 管理。每次 startService() 会增加一个 ‘started’ 状态计数,而 bindService() 会增加一个 ‘bound’ 计数。只有当两个计数都归零时,才会调用 onDestroy()。我们可以通过 adb shell dumpsys activity services 查看当前 Service 的状态。” 

补充踩坑经验
“对了,我之前遇到过这种情况:只调了 unbindService() 没调 stopService(),结果发现通知栏的音乐控件还在,用户点了之后又恢复播放。后来用 adb shell dumpsys activity services 一查,发现 Service 的 ‘started’ 状态还没归零,这才恍然大悟。”


场景二:IntentService 的线程模型

面试官​:
“IntentService 是怎么在子线程处理任务的?如果我连续发 10 个 Intent,它们是排队一个一个执行,还是能并发?”

候选人​:
        这个问题我当初也好奇,还特意写代码验证过。IntentService 内部其实藏了个 HandlerThread,这个类特别有意思,它自己带了一个 Looper,专门用来在子线程处理消息队列。
        当您调用 startService() 发送 Intent 时,IntentService 会把每个 Intent 包装成一个 Message,扔进 HandlerThread 的消息队列里。这个队列是先进先出的,所以哪怕同时发 10 个 Intent,也得老老实实排队。比如第一个任务是下载文件,耗时 5 秒,后面 9 个任务都得等它完了才能执行。

举反例
“不过有一次,我手贱在 onHandleIntent() 里开了个新线程,结果任务全并行跑起来了,日志乱成一团。这才明白:IntentService 的串行特性全靠 HandlerThread 的消息队列,如果自己开线程反而会打破这个机制。”


场景三:Service 的 ANR 避坑

面试官​:
“听说在 Service 里直接做网络请求会 ANR?你们项目里是怎么处理的?”

候选人​:
“这事儿我们还真踩过坑!早期图省事,在 onStartCommand() 里直接写了个网络请求,结果线上 ANR 率飙升。后来复盘发现,Service 默认在主线程跑,一个慢请求就能卡死整个 APP。”

分步骤解释
“我们的解决方案分了三步走:

  1. 紧急修复​:先在 onStartCommand() 里手动 new Thread(),把请求扔到子线程。虽然土,但能快速止血。
  2. 中期优化​:换成 IntentService,但很快就发现它只能串行执行,下个版本需求要支持多文件同时上传,只好再改。
  3. 最终方案​:上线程池!用 Executors.newFixedThreadPool(3) 控制并发数,搭配 LiveData 把结果抛回主线程更新 UI。代码大概是这样的——”

模拟写代码

public class UploadService extends Service {private ExecutorService pool = Executors.newFixedThreadPool(3);@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {pool.execute(() -> {String url = intent.getStringExtra("url");boolean success = doUpload(url); // 模拟上传LiveDataBus.getInstance().post(new UploadEvent(success));});return START_NOT_STICKY;}
}

场景四:IntentService 的淘汰原因

面试官​:
“听说 IntentService 过时了?你们现在用什么替代?”

候选人​:
“确实,Android 8.0 之后 IntentService 有点力不从心了。比如我们之前用它做日志上报,结果在后台经常被系统干掉,查文档才发现:8.0 开始限制后台 Service,startService() 得配合前台通知才能用。”

对比分析
“后来我们全面转向 WorkManager,这玩意儿聪明在哪呢?它底层自动适配系统版本——在 Android 6.0 用 AlarmManager,7.0 用 JobScheduler,甚至还能在国产 ROM 上兼容。比如上传失败的任务会自动重试,还能设置网络条件,比 IntentService 手动重试省心多了。”

举例说明
“比如一个夜间日志上传的需求,用 WorkManager 可以这么搞:”

// 定义任务
class LogUploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {override fun doWork(): Result {return if (uploadLogs()) Result.success() else Result.retry()}
}// 设置约束:充电状态 + 夜间时段
val constraints = Constraints.Builder().setRequiresCharging(true).build()val request = OneTimeWorkRequestBuilder<LogUploadWorker>().setConstraints(constraints).setInitialDelay(6, TimeUnit.HOURS) // 延迟到凌晨.build()WorkManager.getInstance(context).enqueue(request)

“现在除非兼容老系统,否则新项目基本不用 IntentService 了。不过它的设计思想——子线程干活、干完自毁,在 WorkManager 的 Worker 里还能看到影子。”


3. BroadcastReceiver 使用场景

面试官​:
“你提到用广播实现登录状态同步,能具体说说吗?静态注册和动态注册怎么选?”

候选人​:
技巧​:场景化描述 + 安全提醒)
“比如用户登录成功后,我们需要更新多个页面的 UI。这时候发送一个自定义广播,各个页面注册 Receiver 监听。登录页发送广播后,主页、个人中心页收到通知,主动刷新数据。

关于注册方式的选择:

  • 静态注册​:适合监听系统广播,比如开机启动。但要注意 Android 8.0 之后对静态广播的限制。
  • 动态注册​:灵活且优先级高,但要在 Activity 或 Fragment 的 onResume 注册,onPause 注销,防止内存泄漏。

不过现在更推荐用 LiveData 或 EventBus 替代广播,减少系统开销。只有跨进程通信时才会用 ContentProvider 或广播。”

加分话术​:
“我们之前遇到过一个坑:动态注册的 Receiver 没及时注销,导致 Activity 泄漏。后来用 LeakCanary 检测出来,加了个 try-catch 确保 unregisterReceiver 一定执行。”


4. ContentProvider 安全与多进程

面试官​:
“如果想让其他应用访问我们的数据,用 ContentProvider 怎么保证安全?”

候选人​:
技巧​:分层回答 + 权限细节)
“我们分了三层防护:

  1. 权限声明​:在 AndroidManifest.xml 里定义读写权限,比如 com.example.READ_DATA,并设置 protectionLevel="signature",只允许相同签名的应用访问。
  2. Uri 权限控制​:在 Provider 的 query 方法里校验调用方的包名,通过 Binder.getCallingUid() 获取 UID,对比白名单。
  3. 数据加密​:敏感数据(如用户手机号)在存储时用 AES 加密,即使被恶意读取也无法解密。”

加分话术​:
“其实 Android 的 FileProvider 也是类似的思路,通过 grantUriPermission 动态授权临时权限,避免长期暴露数据。”


基础知识讲解

Activity 启动模式(LaunchMode)及场景
  • Standard​:默认模式,每次启动创建新实例(可能产生重复登录页面的问题)。
  • SingleTop​:栈顶复用,若已在栈顶则不创建新实例(适用于通知跳转页)。
  • SingleTask​:栈内复用,在任务栈中只存在一个实例(主页面常用)。
  • SingleInstance​:独立任务栈,全局唯一(如系统来电页面)。

真题​:
Q​:从 Activity A(SingleTask)跳转到 B(Standard),再从 B 跳转回 A,描述任务栈变化。
A​:A 启动时创建新任务栈,B 在默认栈中。当 B 跳转回 A 时,由于 A 的 SingleTask 特性,系统会清空 A 所在栈顶之上的所有 Activity,直接复用 A 实例。


Q1:Activity A 跳转 B,B 跳转 C,按返回键时的生命周期回调顺序? (假设 A, B, C 都是 Standard 模式)

A: (这是一个经典问题,关键在于理解 Activity 栈和生命周期)

  1. 当前状态: 栈顶是 C,其下是 B,再下是 A。 [A, B, C]

  2. 在 C 按返回键:

    • C: onPause()
    • B: onRestart() (如果 B 之前 onStop() 了) -> onStart() -> onResume() (B 变为可见并获得焦点)
    • C: onStop() -> onDestroy() (C 被销毁并出栈)
    • 此时栈: [A, B]
  3. 在 B 按返回键:

    • B: onPause()
    • A: onRestart() (如果 A 之前 onStop() 了) -> onStart() -> onResume()
    • B: onStop() -> onDestroy()
    • 此时栈: [A]

Activity 与 Fragment 通信方式
  • Bundle + setArguments​:传递初始参数。
  • 接口回调​:Fragment 定义接口,Activity 实现。
  • ViewModel​:通过共享 ViewModel 实现数据监听。
  • EventBus​:事件总线解耦通信(需注意内存泄漏)。

真题​:
Q​:Fragment 如何回传数据给 Activity?
A​:在 Fragment 中定义接口,在 onAttach() 中绑定 Activity 实例,调用接口方法传值。代码示例:

// Fragment 中
public interface OnDataCallback {void onDataReceived(String data);
}@Override
public void onAttach(Context context) {super.onAttach(context);if (context instanceof OnDataCallback) {callback = (OnDataCallback) context;}
}// 传值时调用
callback.onDataReceived("data");

Q1​:Activity A 跳转 B,B 跳转 C,按返回键的生命周期回调顺序?
A​:

  • C → onPause() → B → onRestart() → onStart() → onResume() → C → onStop() → onDestroy()

Q2​:Service 中执行耗时操作为何可能引发 ANR?如何解决?
A​:Service 默认运行在主线程,直接执行耗时操作会阻塞 UI 线程。应使用 IntentService 或 HandlerThread 在子线程处理。

Q3​:BroadcastReceiver 的 onReceive() 中能否启动弹窗?
A​:可以,但需通过 Intent 跳转 Activity 并添加 FLAG_ACTIVITY_NEW_TASK(因为 Receiver 无任务栈)。

相关文章:

  • 【Python】Python 装饰器的用法总结
  • 氢气传感器维护常见问题及解决方法
  • Q网络(Q-Network)简介
  • 49页 @《人工智能生命体 新启点》中國龍 原创连载
  • 今日学习:AOP数据脱敏|线程池|方法引用的实例|背包(0-1)及子集
  • linux_cmake的笔记
  • 2025年——ComfyUI_连接HuggingFace及更改缓存路径
  • rosbridge_suit、roslibpy 源码阅读与简单测试 —— 图片编解码与传输
  • linux初识--基础指令
  • 并发编程艺术--底层原理
  • 中科驭数携DPU全栈产品亮相福州数博会,赋能智算时代算力基建
  • 关于 Web 风险点原理与利用:6. 逻辑风险点
  • SpringBoot3整合WebSocket
  • AI大模型和SpringAI简介
  • 各类Agent技术的发展现状和核心痛点
  • 银基固态电池硬件解析
  • 暗黑科技感风格智慧工地监管系统
  • C++ 结构体封装模式与 Promise 链式调用:设计思想的异曲同工
  • Python实例题:使用Python实现深度神经网络
  • SQL解析工具JSQLParser
  • 网站建设 cn/怎么注册网址
  • 微信朋友圈做网站推广赚钱吗/百度一下你就知道下载
  • 古董交易网站怎么做/百度人工服务
  • 做网站背景全覆盖的代码/在线网页制作网站
  • 网站结构怎么做适合优化/云南网站建设百度
  • 织梦做的网站首页打不开/广州seo