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的唤醒请求集中到某个时间点执行,减少频繁唤醒。
  • 结果: 用户省电效果可能更好,但开发者适配极其痛苦,后台任务可靠性严重依赖用户手动设置白名单

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91901.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL进阶:(第一篇) 深入解析MySQL存储引擎架构

一、MySQL的体系结构连接层&#xff1a;最上层是一些客户端和链接服务&#xff0c;主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。服务层&#xff1a;第二层架构主要完成大多数的核心服务功能&#xff0c…

京东m端 滑块 分析 t30

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;部分python代码response requests.pos…

CentOS使用命令行工具为其配置静态网络并使用VMware软件ovf配置文件快速配置多台不同ip的centos文件

目录 一、实验前准备 1.SSH远程登录工具 二、CentOS配置静态IP并实现远程ssh登录 1.VMware软件查看NAT模式下默认网段和网关 2.使用ipconfig查看当前网卡名字和动态分配的ip地址 3.使用VIM编辑网络配置文件&#xff08;此步骤可有其他编辑器替代&#xff0c;例如&#xf…

设计模式学习[17]---组合模式

文章目录前言1.引例2.一致性抽象处理3.透明组合模式与安全组合模式总结前言 在画类图的时候&#xff0c;类与类之间有组合关系&#xff0c;聚合关系&#xff0c;我本来以为这个组合模式应该是整体与部分的关系&#xff0c;其实设计模式中的组合模式和类图中的组合不是同一个东…

48Days-Day12 | 添加字符,数组变换,装箱问题

添加字符 添加字符_牛客笔试题_牛客网 算法原理 因为本题数据量都比较小&#xff0c;所以我们可以直接使用暴力解法&#xff0c;枚举B字符串的每一个位置作为与A字符串比较的起点&#xff0c;维护一个最小位数的值 代码 import java.util.*;// 注意类名必须为 Main, 不要有…

关于npm前端项目编译时栈溢出 Maximum call stack size exceeded的处理方案

背景&#xff1a;使用vueelementui的前端项目&#xff0c;使用jenkins进行自动化编译部署&#xff0c;某天在进行编译发版的时候&#xff0c;突然出现 npm ERR! Maximum call stack size exceeded 错误&#xff0c;一直都没法编译成功。原因&#xff1a;随着前端项目的不断迭代…

微信小程序组件发布为 npm 包的具体步骤

1. 准备工作 首先&#xff0c;您需要在系统上安装 Node.js 和 npm。如果尚未安装&#xff0c;请访问 Node.js — Run JavaScript Everywhere 下载并安装最新版本。 2. 创建独立的组件目录 为了更好地管理组件&#xff0c;建议将其从当前项目中独立出来&#xff1a; wechat-…

LCM中间件入门(2):LCM核心实现原理解析

文章目录一、good()函数&#xff1a;LCM实例状态检查的实现原理1. 实现逻辑2. 简化代码示例&#xff08;C语言核心逻辑&#xff09;二、publish()&#xff1a;向指定channel发送消息的原理1. 完整流程拆解2. 简化代码示例&#xff08;C核心逻辑&#xff09;三、subscribe()&…

Nginx安装及配置

一.nginx安装1.1nginx概述1.1.1 nginx介绍Nginx是一款高性能的开源HTTP和反向代理服务器&#xff0c;是免费的、开源的、高性能的HTTP和反向代理服务器、邮件代理服务器、以及TCP/UDP代理服务器解决C10K问题&#xff08;10K Connections&#xff09;。同时也支持IMAP/POP3代理服…

SelectDB数据库,新一代实时数据仓库的全面解析与应用

摘要&#xff1a;SelectDB是一款基于Apache Doris的新一代实时数据仓库解决方案&#xff0c;具备实时极速、融合统一、弹性架构和开放生态四大核心特性。它采用云原生存算分离架构&#xff0c;支持秒级数据更新、毫秒级查询响应&#xff0c;在TPC-H等基准测试中性能超越传统系统…

自动驾驶的未来:多模态传感器钻机

伦敦大学学院博士生袁方正在建造多模态传感器钻机&#xff0c;以探索自动驾驶的未来。他的最新设置汇集了一套尖端传感器&#xff1a; &#x1f4e1; 60 GHz 雷达&#xff08;用于 Raspberry Pi 的 DreamHAT&#xff09;DreamRF &#x1f4f7; RGB 深度摄像头 &#xff08;Real…

13.Redis 的级联复制

Redis 的级联复制 即实现基于Slave节点的Slave 1. 修改 Slave 节点配置文件 # 第一个slave节点 [rootubuntu2204 ~]#vim /apps/redis/etc/redis.conf(大约在533行附近) replicaof 10.0.0.100 6379 masterauth 123456# 第二个slave节点 [rootubuntu2204 ~]#vim /apps/redis/etc/…

spring-ai-alibaba 学习(二十)——graph之检查点

前面学习了graph的基本概念&#xff0c;参数设置&#xff0c;特殊节点和边&#xff0c;今天学习一下检查点检查点可能名称比较抽象&#xff0c;换个名字可能比较容易理解&#xff0c;进度保存点或者存档点&#xff0c;可以类比游戏中保存当前游戏进度的存档进度主要用于人工介入…

sqli-labs:Less-19关卡详细解析

1. 思路&#x1f680; 本关的SQL语句为&#xff1a; $insert"INSERT INTO security.referers (referer, ip_address) VALUES ($uagent, $IP)";注入类型&#xff1a;字符串型&#xff08;单引号包裹&#xff09;、INSERT操作提示&#xff1a;参数需以闭合关键参数&a…

Java小红书源码1:1还原uniapp_仿小红书源码

在内容驱动型社交平台兴起的背景下&#xff0c;小红书作为图文/视频种草社区的代表&#xff0c;其产品结构与功能体验逐渐成为众多开发者与创业团队的模仿蓝本。本项目基于Java后端uni-app前端栈&#xff0c;完整复刻小红书主要功能&#xff0c;支持多端&#xff08;小程序、H5…

USB Type-C PD协议一文通

原文&#xff1a;https://www.richtek.com/Design%20Support/Technical%20Document/AN056?sc_langzh-TW译者&#xff1a;TrustZone1、概述 USB Type-C标准的出现是为了满足不断增长的现代设备之间的连接需要&#xff0c;它在传统USB标准的基础上提供了更高的电源传输能力和资料…

AI文档比对和Word的“比较”功能有什么区别?

AI文档比对工具的核心区别在于&#xff0c;它超越了Word的纯文本“找不同”&#xff0c;能精准处理扫描件、表格及印章&#xff0c;并将文档审查从被动的文本核对&#xff0c;处理大文档也更为快速及准确。 为什么Word的“比较”功能已经不够用了&#xff1f; 对于许多专业人士…

AI驱动SEO关键词智能进化

内容概要 随着人工智能&#xff08;AI&#xff09;技术的快速演进&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;领域正迎来前所未有的变革。本文核心探讨AI如何驱动SEO关键词的智能进化&#xff0c;重点解析人工智能革新关键词研究与优化策略的机制&#xff0c;包括智能…

基于SpringBoot+MyBatis+MySQL+VUE实现的青年公寓服务平台管理系统(附源码+数据库+毕业论文+部署教程+配套软件)

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;房屋信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

12.Redis 主从复制

Redis 主从复制Redis 主从复制1. Redis 主从复制架构2. 主从复制实现2.1 主从命令配置2.1.1 启用主从同步2.1.2 查看日志观察同步状态2.1.3 修改 Slave 节点配置文件2.1.4 删除主从同步3. 主从复制故障恢复3.1 Slave 节点故障和恢复3.2 Master 节点故障和恢复3.3 常见主从复制故…