Android 底层实现基础

Activity 生命周期

在这里插入图片描述

应用内 Activity 跳转流程(A → B)

从 Activity A 打开新的 Activity B(如点击按钮跳转详情页)

  1. A.onCreate()A.onStart()A.onResume() (A 已在前台)
  2. 点击跳转按钮 → A.onPause() (A 暂停但仍可见)
  3. B.onCreate()B.onStart()B.onResume() (B 进入前台)
  4. A.onStop() (A 完全不可见,但未被销毁)

在这里插入图片描述


返回键关闭当前 Activity(B → A)

在 Activity B 中按返回键,回到 Activity A

  1. 按返回键 → B.onPause()
  2. A.onRestart()A.onStart()A.onResume() (A 重新可见)
  3. B.onStop()B.onDestroy() (B 被销毁)

在这里插入图片描述


Home 键切到后台(应用存活)

在 Activity A 运行时按 Home 键回到桌面

  1. 按 Home 键 → A.onPause()A.onStop()
    注意:此时 A 未被销毁,进程存活)

切换到其他应用(如从微信跳转到支付宝)

从当前应用 Activity A 打开另一个应用(如点击链接跳转支付宝)

  1. 点击跳转 → A.onPause()
  2. 支付宝冷/温启动 → 支付宝页面显示
  3. A.onStop() (A 完全不可见,但进程存活)

后台被系统回收后恢复(温启动场景)

应用在后台时,因内存不足被系统回收 Activity(非杀进程),用户再次点击图标进入

  1. 系统回收 Activity → 调用 A.onSaveInstanceState() 保存数据
  2. 用户点击图标 → 重建 Activity A
    A.onCreate(savedInstanceState)A.onStart()A.onResume()

返回键退出应用(销毁所有 Activity)

在首页 Activity A 按返回键退出应用

  1. 按返回键 → A.onPause()A.onStop()A.onDestroy()
  2. 进程仍存活(系统缓存),但任务栈清空

任务(Task)和返回栈(Back Stack)

一、核心概念

  1. 任务(Task)

    • 本质:用户为完成特定目标(如“写邮件”、“购物”)而交互的 Activity 集合
    • 表现形式:一个按打开顺序排列的 Activity 栈(即返回栈)
    • 系统级标识:每个任务有独立 任务 ID,系统通过它管理任务切换。
    • 用户视角:在“最近任务列表”(Recents Screen)中显示为独立卡片。
  2. 返回栈(Back Stack)

    • 本质:属于同一任务的 Activity 实例的有序栈(后进先出)。
    • 关键规则:用户按返回键时,栈顶 Activity 出栈并销毁,前一个 Activity 恢复显示。
    • 跨进程支持:栈内 Activity 可来自不同应用(如从浏览器打开地图应用)。

二、底层工作原理

1. Activity 启动与入栈
  • 默认行为:新启动的 Activity 被压入当前任务的栈顶(standard 启动模式)。
  • 任务亲和性(Task Affinity)
    • 每个 Activity 通过 android:taskAffinity 属性声明“归属偏好”。
    • 默认亲和性 = 应用包名(同一应用 Activity 通常属于同一任务)。
  • Intent Flags 控制栈行为(代码动态控制):
    • FLAG_ACTIVITY_NEW_TASK:在新任务中启动 Activity(若任务不存在则创建)。
    • FLAG_ACTIVITY_CLEAR_TOP:若目标 Activity 已在栈中,则清除其上的所有 Activity。
    • FLAG_ACTIVITY_SINGLE_TOP:若目标 Activity 已在栈顶,则复用实例(触发 onNewIntent())。
2. 启动模式(Launch Modes)
模式行为描述测试关注点
standard (默认)每次启动创建新实例,压入当前栈。多实例场景下的状态一致性(如填写表单)。
singleTop若目标 Activity 在栈顶,则复用实例(触发 onNewIntent());否则创建新实例。通知栏点击打开已存在的页面时是否刷新数据。
singleTask系统创建新任务或将 Activity 移至现有任务根部。同一任务只存在一个实例。多任务边界、深度链接跳转后的返回路径是否异常。
singleInstance独占整个任务,该任务仅容纳此一个 Activity。与其他应用的交互(如相机调用),返回栈隔离性。
3. 任务管理机制
  • 最近任务列表(Recents)
    • 系统维护任务快照(缩略图 + 描述)。
    • 移除任务卡片会清除整个返回栈(所有 Activity 销毁)。
  • 任务重用(Re-parenting)
    • 当从应用 A 启动应用 B 的 Activity 时:
      • 若 B 已有任务在后台,该 Activity 会移入 B 的任务栈
      • 返回键会先回退到 B 的前一个 Activity,而非回到 A。
  • 后台任务回收
    • 系统内存不足时,按 LRU 规则销毁后台任务栈(保留状态 Bundle 以便重建)。

进程间通信规则

核心思想: 应用运行在独立的进程(沙盒)中,无法直接访问彼此的内存。IPC 提供一种安全的“邮递”机制,让应用可以发送请求(消息、数据、方法调用)并接收响应。

底层核心机制:Binder

  1. 建立邮箱(Binder 驱动): 操作系统内核提供了一个中央“邮局”(Binder 驱动)。所有需要通信的应用(进程)都向这个邮局注册自己的“邮箱地址”(Binder 引用)。
  2. 写信(序列化): 发送方应用(客户端)将想要传递的数据或方法调用请求(包括方法名、参数)序列化成一个线性格式(通常使用 Parcel)。想象成把信息写在纸上。
  3. 投递到邮局(系统调用): 客户端通过系统调用(ioctl)将打包好的 Parcel 发送给 Binder 驱动。这个调用会指定目标“邮箱地址”(目标服务的 Binder 引用)。
  4. 邮局分拣(内核处理): Binder 驱动在内核空间接收到数据包。它根据目标引用找到接收方应用(服务端)对应的进程和线程信息。
  5. 派送信件(唤醒目标线程): Binder 驱动将数据包放入接收方进程的一个专属接收队列中,并唤醒服务端进程中负责处理 IPC 的线程(通常是主线程或 Binder 线程池中的一个线程)。
  6. 拆信(反序列化): 服务端线程被唤醒,从队列中取出 Parcel,将数据反序列化回原始格式(方法名、参数)。
  7. 处理请求(执行方法): 服务端根据方法名找到对应的实现代码,使用反序列化得到的参数执行该方法。
  8. 写回信(序列化结果): 服务端将方法执行的结果(或异常)再次序列化Parcel
  9. 回信投递(系统调用): 服务端通过另一个系统调用将结果 Parcel 发送回 Binder 驱动
  10. 邮局送回(内核处理): Binder 驱动将结果包放入客户端进程的接收队列,并唤醒等待结果的客户端线程。
  11. 客户端收信(反序列化结果): 客户端线程被唤醒,取出结果 Parcel反序列化得到最终结果或异常。
  12. 客户端处理结果: 客户端继续执行,使用收到的结果。

隐式/显式 Intent

  1. 显式 Intent (点名道姓):

    • 明确知道要启动哪个“人”(组件)干活。
    • 直接告诉系统:“启动 包名 com.example.app 里 类名 com.example.app.MyActivity 这个 Activity!”
    • 用在: 启动自己 App 内部的界面 (Activity)、服务 (Service) 等,或者明确知道另一个 App 里具体哪个组件(需要知道包名和类名)。
    • 优点: 精准、高效。
    • 缺点: 必须知道具体目标,跨 App 启动需要对方暴露组件信息(有时不推荐)。
  2. 隐式 Intent (发广播招人):

    • 只知道要干什么“活”(操作),但不知道谁干。
    • 告诉系统:“我要 查看一张图片 (Action=VIEW, Data=图片URI, Type=image/*)!” 或者 “我要 发送一封邮件 (Action=SEND, Type=text/plain)!”
    • 系统怎么做: 系统拿着你的“招聘要求”(Action, Data, Type, Category等),去查所有 App 的“简历”(在 AndroidManifest.xml 中声明的 <intent-filter>)。找到所有符合条件的组件。
    • 结果:
      • 如果只有一个组件符合:直接启动它。
      • 如果有多个符合:弹出选择器 (Chooser) 让用户选一个。
      • 如果没找到:启动失败。
    • 用在: 启动系统功能(拍照、打电话、选择联系人)、分享内容、打开特定类型文件、让其他 App 提供特定服务等。跨 App 协作的主要方式。
    • 优点: 灵活、解耦。你的 App 不需要知道具体谁来处理。
    • 缺点: 控制权较低(用户可能选错 App),性能略低(需要系统匹配)。
特征显式 Intent (Explicit Intent)隐式 Intent (Implicit Intent)
目标指定点名道姓! setComponent(), setClass()new Intent(Context, Class) 明确指定要启动哪个 App 的哪个 Activity/Service 等。只提要求! 通过 action (动作,如打电话、发邮件、查看)、data (数据,如网址、电话号码) 和 category (类别) 描述你想做什么
定位方式精准定位。 就像你知道朋友的具体门牌号去找他。广播找人。 就像你在广场喊“谁会修电脑?”,会修的人(组件)自己响应。
作用范围通常用于启动自己 App 内部的组件。 因为你知道组件的具体名字。用于启动自己 App 内部或其他 App 的组件。 是实现不同 App 之间协作的关键。
系统处理系统直接启动你指定的那个组件。系统查找所有声明了能处理该 Intent 要求的 (action + data + category) 的组件,如果有多个,会让用户选择(选择器)。
典型用途App 内部页面跳转、启动自己 App 的后台 Service。打开网页、打电话、发邮件、分享内容、选择图片、使用地图等跨 App 或系统级功能
关键优势精准、高效、安全(不易被劫持)。灵活、解耦、支持跨应用。
关键风险只能启动已知组件,灵活性差。可能找不到匹配组件导致崩溃(需用 resolveActivity() 检查),或有多个匹配时用户需要选择

一句话总结:

  • 显式 Intent:张三,你去把这事办了!” (指定具体组件)
  • 隐式 Intent:谁能办这事? 来个人把它办了!” (声明需求,系统找匹配者)

关键底层点简化:

  • 显式 Intent 直接调用目标组件,不经过系统匹配。
  • 隐式 Intent 依赖系统在安装时收集所有 App 的 <intent-filter> 信息(存储在 PackageManager 数据库里)。启动时,系统根据 Intent 里的信息(主要是 Action + Data/Type)去数据库里快速查找匹配的组件。

View系统与事件分发机制

一、 View 系统:UI 的构建基石

  1. 树形结构:

    • 所有 UI 元素 (Button, TextView, ImageView, 甚至 LinearLayout, RelativeLayout) 都是 View 或其子类 (ViewGroup)。
    • ViewGroup 是特殊的 View,可以包含其他 View (子 View) 或 ViewGroup (子 ViewGroup)。
    • 整个界面是一棵由 ViewViewGroup 组成的树状结构,最顶层通常是 DecorView (包含状态栏、标题栏、内容区域),根部是 ActivityWindow
  2. 核心流程:

    • 测量 (Measure): 父 View (ViewGroup) 询问每个子 View:“你需要多大空间?” (考虑自身尺寸要求 wrap_content/match_parent/固定值 和父 View 的约束)。这是一个递归过程,从根 View 开始向下遍历整棵树。
    • 布局 (Layout): 父 View (ViewGroup) 根据测量结果,告诉每个子 View:“你被放在哪里 (左上右下坐标)”。这也是递归过程。
    • 绘制 (Draw): 每个 View 负责绘制自己到屏幕上指定的矩形区域。流程是从根 View 开始,先绘制背景,再绘制自己内容 (onDraw),然后递归绘制它的所有子 View。遵循顺序:父 View 在底层 -> 子 View 在上层
  3. 关键角色:

    • View UI 基本单元,负责自身绘制和响应触摸事件
    • ViewGroup 特殊的 View,核心职责是容纳和管理子 View
      • 测量子 View (询问大小)。
      • 摆放子 View (决定位置)。
      • 管理事件分发 (决定哪个子 View 能处理触摸事件)。

二、 事件分发机制:触摸事件的旅程

  1. 事件源头: 用户触摸屏幕产生一个 MotionEvent 对象 (包含触摸坐标、动作类型如 ACTION_DOWN/MOVE/UP 等)。

  2. 分发目标: 事件需要找到能“消费” (处理) 它的 View

  3. 传递路径: 事件从根 View (通常是 DecorView) 开始,沿着 View 树自上而下传递。

    • 事件首先到达最顶层的 ViewGroup (Activity 的根布局)。
    • 然后层层向下传递到可能的子 ViewGroup 或最终的子 View
  4. 核心方法 (决策点): 事件在 ViewViewGroup 之间传递时,关键由三个方法决定去向:

    • dispatchTouchEvent(MotionEvent event) 事件分发入口View/ViewGroup 收到事件后首先调用此方法。

      • View: 检查自身是否可点击/可处理事件,是则尝试 onTouchEvent
      • ViewGroup: 核心逻辑所在地! 它决定:
        • 是否拦截 (onInterceptTouchEvent) 事件,不让子 View 处理。
        • 如果不拦截,则遍历子 View (通常按 Z 序或添加顺序反向遍历,后添加/上层 View 优先),询问子 View 是否愿意处理 (dispatchTouchEvent)。
    • onInterceptTouchEvent(MotionEvent event) ViewGroup 独有!dispatchTouchEvent 内部调用。用于判断当前 ViewGroup 是否要“截胡” 这个事件序列 (从 DOWNUP/CANCEL)。如果返回 true,后续事件不再分发给子 View,直接交给自身的 onTouchEvent 处理。默认返回 false (不拦截)

    • onTouchEvent(MotionEvent event) 事件处理终点View 或拦截了事件的 ViewGroup 在这里真正尝试消费 (处理) 事件。如果成功处理 (如点击了按钮),返回 true;如果处理不了或不关心,返回 false,事件会向上回溯给父 View 的 onTouchEvent 尝试处理。

  5. 分发逻辑 (核心流程):

    1. 事件从根 ViewGroupdispatchTouchEvent 开始。
    2. ViewGroup 先调用自己的 onInterceptTouchEvent 看是否拦截。
    3. 如果不拦截
      • 遍历子 View (通常从最上层的子 View 开始)。
      • 判断触摸点是否落在子 View 区域内且子 View 能接收事件。
      • 如果满足,调用子 View 的 dispatchTouchEvent (递归开始)。
    4. 如果拦截所有子 View 都不处理
      • 调用自身的 onTouchEvent 尝试处理。
    5. 如果自身的 onTouchEvent 也不处理,事件回传给父 ViewGrouponTouchEvent (向上回溯)。
    6. 如果某个 View 的 onTouchEventACTION_DOWN 时返回 true,表示它消费了这个事件序列,后续的 MOVE/UP 等事件会直接分发给它 (不再询问 onInterceptTouchEvent,可能跳过中间 ViewGroup 的 dispatch 部分逻辑,但流程更高效),直到序列结束 (UP/CANCEL)。

资源管理与适配机制

核心目标: 让同一份 App 代码能优雅地适配不同设备(屏幕尺寸、分辨率、语言、系统版本、横竖屏、夜间模式等)和用户配置(字体大小)。

一、 资源管理:组织与访问

  1. 资源是什么?

    • App 中非代码的一切:图片 (drawable)、布局 (layout)、字符串 (string)、颜色 (color)、尺寸 (dimen)、样式 (style)、菜单 (menu)、动画 (anim)、原始文件 (raw)、XML 等。
    • 目的: 将 UI 内容、文本、样式等与 Java/Kotlin 代码逻辑分离,便于修改、复用和适配。
  2. 资源存放 (res/ 目录):

    • 按类型分目录: res/drawable/, res/layout/, res/values/, res/menu/ 等。这是基本组织方式。
    • 关键:资源限定符 (Qualifiers): 核心适配机制!
      • 在目录名后添加后缀来指定资源适用的特定条件
      • 格式: 资源类型-限定符1-限定符2-... (例如:drawable-hdpi, layout-sw600dp-land, values-en-rUS)。
      • 系统自动选择: 运行时,Android 系统根据设备的当前配置(语言、屏幕尺寸、横竖屏、夜间模式等),自动选择最匹配限定符目录下的资源。如果没有完全匹配,会寻找最接近的或默认目录 (drawable/, values/ 等) 的资源。
      • 优先级: 系统按预定义规则评估多个限定符的优先级(如屏幕尺寸优先级高于语言)。
  3. 资源编译与访问:

    • 编译: aapt2 (Android Asset Packaging Tool) 将 res/ 下资源编译打包进 APK,并生成 R.java (或 R.kt) 文件。
    • 访问 (代码中): 通过自动生成的 R 类访问资源 (如 R.drawable.icon, R.string.app_name, R.layout.activity_main)。
    • 访问 (XML 中): 使用 @ 符号引用 (如 @drawable/icon, @string/hello, @dimen/padding_medium)。

二、 适配机制:应对多样性

  1. 屏幕适配:

    • 核心理念:密度无关 (Density-Independent)
      • dp (Density-independent Pixels): 长度/尺寸单位。 1dp 在屏幕密度为 160dpi (基准密度) 的设备上等于 1px。系统会根据实际屏幕密度自动缩放。应始终用于指定 View 尺寸和边距!
      • sp (Scale-independent Pixels): 字体大小单位。 类似 dp,但会额外尊重用户系统的字体大小设置应始终用于字体大小!
      • 避免 px (Pixels): 直接对应屏幕物理像素,在不同密度屏幕上显示大小不一致。
    • 布局适配:
      • 限定符: 使用 smallestWidth (sw<N>dp,如 sw600dp 用于 7 寸平板)、screen size (small, normal, large, xlarge - 已弃用,推荐 sw)、screen orientation (land 横屏, port 竖屏) 为不同屏幕尺寸/方向提供不同的布局文件。
      • 响应式布局设计: 使用 ConstraintLayoutLinearLayout (权重 weight)、RelativeLayout 等构建能弹性伸缩和重新排列的布局。优先考虑 match_parent, wrap_content 和约束关系。
      • 使用 dimens.xml 为不同屏幕尺寸定义不同的尺寸值 (使用限定符目录)。
  2. 语言/区域适配:

    • 限定符: 使用语言代码 (en, zh)、区域代码 (rUS, rCN) 创建不同的 values-<qualifier> 目录 (如 values-en/, values-zh-rCN/)。
    • 存放内容: 在对应的 values-<qualifier>/strings.xml 等文件中放置翻译好的字符串、本地化的图片引用、日期/货币格式等。
    • 自动切换: 系统根据用户设备的语言/区域设置,自动加载匹配的字符串资源。
  3. 夜间模式/主题适配:

    • 限定符: 使用 night (values-night/, drawable-night/)。
    • 主题属性:styles.xml 中定义主题,使用主题属性 (?attr/colorPrimary) 引用颜色等资源,而非硬编码。在日间/夜间主题中为同一属性指定不同的颜色值。
    • 动态切换: AppCompatDelegate.setDefaultNightMode() 允许 App 内动态切换日/夜模式。
  4. API 版本适配:

    • 限定符: 使用 v<N> (如 drawable-v21/) 提供只在特定 API 级别及以上可用的资源(如 Vector Drawables, 特定主题属性)。
    • 代码检查: 在 Java/Kotlin 代码中使用 Build.VERSION.SDK_INT 判断系统版本,决定是否使用新 API 或提供兼容方案。

权限机制

核心目标: 保护用户隐私和设备安全,防止 App 随意访问敏感数据(如位置、通讯录、短信)或执行危险操作(如打电话、录音、访问外部存储)。

核心原则: 最小权限原则 - App 只能获取其明确声明且用户明确授权的权限。

一、 权限分类(按获取时机与方式):

  1. 安装时权限 (Install-Time Permissions / Normal Permissions):

    • 特点: 涉及低风险操作,对用户隐私或设备操作影响极小。
    • 获取方式: 在 App 安装时,系统自动授予(用户无需额外操作)。用户无法在安装后单独撤销这些权限。
    • 例子: 设置时区 (android.permission.SET_TIME_ZONE)、访问网络 (android.permission.INTERNET)、蓝牙 (android.permission.BLUETOOTH)、振动 (android.permission.VIBRATE)。
  2. 运行时权限 (Runtime Permissions / Dangerous Permissions):

    • 特点: 涉及高风险操作,直接访问用户隐私数据或影响设备安全/其他 App 操作这是权限机制的核心和重点!
    • 获取方式 (关键流程):
      1. 声明:AndroidManifest.xml 中声明需要的权限 (如 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>)。
      2. 检查: 在代码中执行需要该权限的操作之前,使用 ContextCompat.checkSelfPermission(Context, permissionString) 检查该权限是否已被授予。
      3. 请求:
        • 如果未授予,调用 ActivityCompat.requestPermissions(Activity, new String[]{permissionString}, requestCode) 向用户弹出系统对话框请求授权
        • 用户可以选择 允许拒绝
      4. 处理结果: 在 Activity/Fragment 中重写 onRequestPermissionsResult(requestCode, permissions[], grantResults[]) 方法,处理用户的授权选择结果。
    • 关键点:
      • 用户控制: 用户可以在系统 设置 > 应用 > 权限 中随时授予或撤销这些权限。
      • 临时拒绝 (Ask Every Time): 用户首次拒绝时,系统可能会提供“仅此一次”或“使用时允许”的选项(取决于权限类型和系统版本)。如果用户选择了 拒绝 并且 勾选了 不再询问 (或等效选项),后续请求将直接失败。
      • 权限组: 运行时权限被分组管理(如 位置 组包含 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION)。一旦用户授予了组内某个权限,再次请求组内其他权限时系统会自动授予(不会弹窗)。 但最佳实践仍是显式请求所需的所有权限。
    • 例子: 相机 (CAMERA)、位置 (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)、通讯录 (READ_CONTACTS)、麦克风 (RECORD_AUDIO)、短信 (SEND_SMS)、日历 (READ_CALENDAR)、存储 (READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE - 注意 Scoped Storage 限制)。
  3. 特殊权限 (Special Permissions):

    • 特点: 权限行为非常特殊,不在标准运行时权限流程内。通常涉及系统级设置或深度集成
    • 获取方式: 无法通过 requestPermissions() 获取! 需要引导用户跳转到特定的系统设置页面 (Settings.ACTION_APPLICATION_DETAILS_SETTINGS 或其他特定 ACTION_..._SETTINGS) 去手动开启。
    • 例子: 悬浮窗 (SYSTEM_ALERT_WINDOW)、修改系统设置 (WRITE_SETTINGS)、精确闹钟 (SCHEDULE_EXACT_ALARM - Android 12+)、电池优化忽略 (REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)。
  4. 签名权限 (Signature Permissions):

    • 特点: 主要用于系统 App 或由同一开发者签名的 App 之间进行受保护的交互。
    • 获取方式: 如果 App 的签名证书与声明该权限的 App/系统的签名证书匹配,则系统会在安装时自动授予。
    • 开发者控制: 普通开发者一般无法定义或使用新的签名权限,主要用于平台或预装应用。

二、 关键机制与最佳实践:

  1. AndroidManifest.xml 声明是必须的: 任何权限(尤其是运行时权限)都必须先在清单文件中声明,否则系统不会授予(即使代码请求了)。
  2. 按需请求: 只在真正需要执行相关操作时才请求权限。避免在启动时请求一堆权限(“权限轰炸”),这会让用户反感并卸载 App。
  3. 解释为什么需要权限: 在请求权限前(尤其是用户可能不理解为什么需要时),使用 ActivityCompat.shouldShowRequestPermissionRationale(Activity, permissionString) 检查是否需要向用户解释。如果需要,先弹出自定义对话框解释清楚、简洁的原因,解释完后再调用 requestPermissions()
  4. 优雅处理拒绝:
    • 如果用户拒绝(未勾选“不再询问”),可以在后续合适时机再次请求(并附带解释)。
    • 如果用户永久拒绝(勾选“不再询问”),应引导用户到 App 的设置页面 (Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 手动开启权限,并禁用依赖该权限的功能(而不是崩溃或反复弹窗)。
  5. 权限组意识: 了解权限分组,但不要依赖自动授予行为作为不请求权限的理由。始终请求你需要的具体权限。
  6. 适配新版本: 关注新 Android 版本(如 11, 12, 13, 14)对权限模型的更新(如后台位置访问限制、照片选择器、邻近 Wi-Fi 权限、通知权限等),及时调整 App 逻辑。
  7. 存储权限 (READ/WRITE_EXTERNAL_STORAGE) 的演变:
    • Android 10 (API 29) 引入 Scoped Storage: 限制 App 随意访问外部存储其他 App 的私有文件。强调使用 MediaStore API 访问媒体文件和 SAF (Storage Access Framework) 访问特定文档/目录。
    • Android 11 (API 30) 及以后: 进一步收紧,MANAGE_EXTERNAL_STORAGE 成为特殊权限(需跳转设置),普通 App 应尽量避免使用。优先使用 App 专属目录 (Context.getExternalFilesDir()) 和共享存储 API (MediaStore, SAF)。

存储机制

核心目标: 在保护用户隐私和数据安全的前提下,为 App 提供可靠的文件存储能力,并实现不同 App 之间的安全数据共享。

核心挑战: 平衡 App 功能需求与用户数据安全/隐私,尤其在设备文件系统日益复杂和恶意软件威胁下。

一、 关键演变:从自由到严格(Scoped Storage 为核心)

  1. Android 10 (API 29) 之前:相对自由

    • WRITE_EXTERNAL_STORAGE 权限 = 万能钥匙: 一旦用户授予,App 几乎可以读写整个外部存储(SD卡和内置存储的公共部分)的任何文件,包括其他 App 的私有文件。隐私泄露风险高!
  2. Android 10 (API 29) 引入 Scoped Storage (分区存储):重大变革!

    • 核心理念: 限制 App 随意扫描整个存储空间,保护用户隐私和其他 App 的数据。
    • 关键变化:
      • 默认作用域: App 默认只能无需权限访问:
        • 自身专属的外部存储目录 (Context.getExternalFilesDir(), Context.getExternalCacheDir()):存放 App 私有文件,卸载时会被删除。这是首选存放位置。
        • 特定类型的媒体文件 (图片、视频、音频):但必须通过 MediaStore API 访问(需要运行时权限 READ_EXTERNAL_STORAGE 来读取其他 App 创建的媒体文件)。
      • WRITE_EXTERNAL_STORAGE 权限作用大幅缩减: 在 Android 10 上,它主要允许写入 MediaStore不再能随意写任何地方!
      • 访问其他 App 的私有目录或非媒体文件: 必须使用 Storage Access Framework (SAF) (系统文件选择器)。
  3. Android 11 (API 30) 及以后:强化与完善

    • 进一步限制: READ_EXTERNAL_STORAGE 权限也受到更严格限制。
    • MANAGE_EXTERNAL_STORAGE 成为特殊权限: 提供给文件管理器、备份恢复等需要广泛文件访问的特定类型 App。普通 App 强烈不建议申请,上架应用商店审核严格且用户授权率极低。需要引导用户跳转到系统设置手动开启。
    • 文件访问意图更明确:
      • 媒体文件: 优先且主要使用 MediaStore
      • 文档/其他文件: 优先使用 Storage Access Framework (SAF)
      • App 自身文件: 使用 App 专属目录

通知机制

📣 核心流程(简单版)

  1. APP想通知你: 某个应用(比如微信、邮箱、游戏)发生了需要你注意的事情(新消息、下载完成、系统提醒)。
  2. APP打包“通知”: APP按照安卓系统的规定,创建一个通知对象 (Notification)。这个对象包含:
    • 小图标 (Small Icon): 在状态栏显示的小图(必须)。
    • 标题 (Title): 通知的主题(比如“新消息”、“下载完成”)。
    • 内容文本 (Content Text): 通知的详细内容(比如“张三:晚上吃饭吗?”)。
    • 大图标 (Large Icon - 可选): 展开通知后显示的大图(比如发信人头像)。
    • 优先级 (Priority): 告诉系统这个通知有多紧急(高、中、低等,影响显示位置和是否响铃)。
    • 点击动作 (PendingIntent): 最关键!你点击通知后要做什么?(比如打开聊天窗口、跳转到邮件详情、播放音乐)。
    • 渠道 (Channel - Android 8.0+ 必须): 通知的分类(比如微信可以有“新消息”、“群通知”、“公众号更新”等不同渠道)。用户可以根据渠道单独设置开关和提醒方式!
    • 其他花活 (可选): 进度条、按钮(快速回复、标记已读)、图片、媒体控制等。
  3. APP把通知“递”给系统: APP调用 NotificationManager.notify(id, notification) 方法,把这个打包好的通知对象交给安卓系统的 通知管理器 (Notification Manager)
  4. 系统“展示”通知:
    • 状态栏图标: 通知的小图标会出现在屏幕顶部的状态栏。
    • 通知抽屉: 下拉状态栏,你会看到通知的详细列表(标题、内容、图标等)。
    • 提醒方式 (根据用户设置):
      • 声音 (Sound): 播放提示音。
      • 震动 (Vibrate): 手机震动。
      • 呼吸灯 (Lights - 如果手机有): 闪烁指示灯。
      • 浮动通知/弹窗 (Heads-up - 高优先级): 在屏幕顶部短暂弹出(不影响当前操作)。
    • 锁屏显示 (根据用户设置): 通知内容可能显示在锁屏上(注意隐私)。

🔑 关键机制和规则

  1. 通知渠道 (Android 8.0 Oreo 引入):

    • 核心思想: 让用户精细控制通知! 不再是“整个APP的通知要么全开要么全关”。
    • APP的责任: APP必须为不同类型的通知创建不同的渠道 (Channel) (比如“交易提醒”、“营销推送”、“聊天消息”)。
    • 用户的权力: 用户可以单独为每个渠道设置:
      • 开关: 是否允许显示。
      • 提醒方式: 是否响铃、震动、浮动显示、在锁屏显示。
      • 重要性 (Importance Level): 决定通知的干扰程度(紧急、高、中、低)。
    • 好处: 用户能屏蔽烦人的广告推送,但保留重要的聊天消息提醒。
  2. 通知权限:

    • Android 13 (Tiramisu) 之前: APP安装后默认可以发通知。
    • Android 13 及以后: 新增运行时权限 POST_NOTIFICATIONS
      • 当APP第一次尝试发通知时,系统会弹窗询问用户**“是否允许 [APP名称] 发送通知?”**。
      • 用户可以选择 “允许”“不允许”
      • 开发者注意: 必须适配!用户拒绝后,调用 notify() 会失效。
  3. 勿扰模式 (Do Not Disturb):

    • 用户可以开启“勿扰模式”(手动或按计划)。
    • 在该模式下,只有被用户标记为“允许打扰” 的APP或联系人的通知(通常是最高优先级或特殊渠道)才会发出声音/震动,其他通知会静默进入通知抽屉。
  4. 后台限制 (省电优化):

    • 安卓系统(尤其国产定制系统)对APP在后台运行有严格限制,防止耗电。
    • 影响: 如果APP被系统“杀掉”或在后台被严格限制,它可能无法及时触发后台服务来发送通知
    • 解决方案 (给开发者):
      • 使用 WorkManager 安排可靠的后台任务(系统会找合适时机运行)。
      • 使用厂商推送服务 (如小米推送、华为推送、FCM) 替代APP自己维持长连接(更省电,推送更可靠)。
      • 引导用户将APP加入“电池优化白名单”或“允许后台运行”(效果因厂商而异)。
  5. 通知分组和摘要 (Android 7.0+):

    • 分组 (Grouping): 同一个APP的多个通知(比如多封未读邮件)可以被折叠成一个“组”显示,点击组再展开详情。避免通知栏被刷屏。
    • 摘要 (Bundling/Summary): 可以为分组提供一个摘要通知(比如“5条新消息”)。
  6. 长连接与推送服务:

    • APP主动拉取 (Polling): APP定期去服务器检查新消息(耗电、不实时)。
    • 长连接 (Persistent Connection): APP在后台和服务器保持一个连接,服务器有新消息可以立刻推给APP,APP再发通知(更实时,但APP需后台保活,可能被系统限制)。
    • 统一推送服务 (FCM/厂商推送): 最佳实践!
      • APP不需要自己维持长连接。
      • 服务器把通知消息发给 Google 的 Firebase Cloud Messaging (FCM)手机厂商的推送服务器 (如小米推送、华为推送)
      • FCM/厂商服务器利用系统级的、更省电的长连接通道,将消息推送到用户设备
      • 设备系统收到后,直接唤醒目标APP或代表APP弹出通知(无需APP后台运行)。
      • 好处: 省电、推送可靠、及时。

后台执行限制

核心就是 “系统如何管住APP在后台偷偷搞事情” 的规则,目的是 省电、省流量、保流畅、护隐私


🛑 核心目标:限制APP在后台干啥?

系统想阻止APP在你不用它的时候:

  • 狂耗电: 后台不断联网、定位、计算。
  • 偷跑流量: 后台疯狂上传下载。
  • 拖慢手机: 后台占用CPU和内存,让你用前台APP时卡顿。
  • 偷偷收集数据: 后台扫描位置、读取文件、监听传感器。

🔒 主要限制手段(不同安卓版本不断加码)

1. 后台服务限制 (Android 8.0 Oreo 起关键变化)
  • 以前: APP可以轻松在后台启动一个Service(服务)长期运行(比如放音乐、下载文件、定时同步)。
  • 现在 (Android 8.0+):
    • 前台服务 (Foreground Service): 如果APP需要在后台做用户可感知需要持续运行的任务(如音乐播放、导航、文件下载),必须启动一个前台服务!
      • 特点: 必须在状态栏显示一个常驻通知(告诉用户“我正在后台工作呢!”)。
      • 好处: 用户知道谁在耗电,也能手动划掉通知停止它。
    • 后台服务 (Background Service):
      • APP在前台或刚退到后台: 可以正常启动和使用后台服务(有短暂宽限期)。
      • APP在后台一段时间后: 系统会强制停止APP的所有后台服务! APP想再启动新服务?门都没有!
  • 开发者应对: 需要长时间后台任务?用前台服务(配通知)!或者用更智能的调度方式(如WorkManager)。
2. 广播接收器限制 (Android 8.0+)
  • 广播 (Broadcast): 系统或APP发出的全局事件(比如开机完成、网络变化、充电中)。
  • 以前: APP可以注册监听很多广播(即使没在运行),一收到广播就能被唤醒干活。
  • 现在 (Android 8.0+):
    • 显式广播 (Explicit Broadcast): 发给特定APP的广播,基本不受限。
    • 隐式广播 (Implicit Broadcast): 发给所有APP的全局广播(如 ACTION_BOOT_COMPLETED 开机完成、CONNECTIVITY_CHANGE 网络变化)受到严格限制
      • 静态注册 (Manifest 里声明): 大部分隐式广播收不到了!只有少数系统白名单广播例外(如开机完成,但应用首次启动后也收不到了)。
      • 动态注册 (代码里注册): APP在前台时能收到,退到后台后就收不到了
  • 目的: 防止一堆APP被无关紧要的全局广播频繁唤醒。
  • 开发者应对: 避免依赖隐式广播唤醒后台任务。用JobScheduler/WorkManager替代。
3. 后台位置访问限制 (Android 10+ 大幅收紧)
  • 以前: APP在后台可以相对容易地获取用户位置。
  • 现在 (Android 10+):
    • 新增权限: ACCESS_BACKGROUND_LOCATION (后台位置权限)。
    • 用户授权更严格: 用户必须在设置页里单独授予这个权限(不像前台位置权限那样在运行时弹窗就能给)。
    • 前台服务要求: 即使有后台位置权限,APP在后台持续获取位置信息时,也必须启动一个前台服务(并显示通知告知用户)。
  • 目的: 防止APP在后台偷偷追踪用户位置,严重侵犯隐私。
  • 开发者应对: 非导航/运动类APP,强烈建议避免在后台获取位置。如必须,请求后台权限并配前台服务+通知。
4. 后台网络访问限制 (Android 7.0+ Doze & App Standby)
  • Doze 模式 (打盹模式 - Android 6.0+):
    • 触发: 手机灭屏、静置、未充电一段时间后。
    • 限制:
      • 暂停所有后台网络访问(WiFi和移动数据)。
      • 延迟所有后台JobScheduler任务、SyncAdapter同步、AlarmManager闹钟(非精确闹钟)。
      • 禁止后台服务启动。
    • 维护窗口 (Maintenance Window): 系统会周期性地短暂退出Doze(例如每小时一次),让被延迟的任务有机会执行。执行完又进入Doze。
  • App Standby (应用待机桶 - Android 6.0+):
    • 触发: 用户长时间没用某个APP。
    • 限制: 将该APP放入限制桶 (Restricted Bucket)
      • 大幅限制后台网络访问
      • 延迟后台任务(JobScheduler/SyncAdapter)。
      • 禁止后台服务启动。
    • 用户唤醒: 只要用户手动启动了该APP,它立刻跳出限制桶,恢复所有能力。
  • 目的: 限制不常用APP在后台偷跑网络和资源。
  • 开发者应对: 使用WorkManager调度网络任务(它知道如何应对Doze和待机桶)。避免在后台做不必要的网络请求。
5. 厂商定制系统的“魔改” (尤其国内 ROM)
  • 更激进! 小米、华为、OPPO、vivo 等国产手机的系统,后台限制往往比原生安卓更狠
  • 常见手段:
    • 自动启动管理: 默认禁止APP开机自启、被其他APP唤醒(链式启动)。
    • 后台运行管理: 锁屏后几分钟就清理后台APP进程和服务(即使你设置了前台服务通知也可能被清!)。
    • 省电优化/电池管理: 用户必须手动将APP加入“白名单”、“允许后台运行”、“允许关联启动”、“忽略电池优化”,否则后台任务几乎无法运行。
    • 对齐唤醒: 强制所有APP的唤醒请求集中到某个时间点执行,减少频繁唤醒。
  • 结果: 用户省电效果可能更好,但开发者适配极其痛苦,后台任务可靠性严重依赖用户手动设置白名单
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值