📱 Android 开发教程

IT 技术 53 阅读 更新于 2026-09-05 09:12

1. Android 开发概述

什么是 Android

Android 是由 Google 主导开发的移动操作系统,基于 Linux 内核构建。它是全球市场份额最大的移动平台,覆盖手机、平板、手表、电视、汽车等多种设备形态。

Android 版本发展

版本API Level代号重要特性
1636BaklavaAI 集成、隐私沙盒增强
1535VanillaIceCream预测性返回手势、卫星通信扩展
1434Upside Down Cake卫星通信、照片选择器增强
1333Tiramisu通知权限、主题图标
1231Snow ConeMaterial You 动态主题
1130Red Velvet Cake一次性权限、对话气泡
1029Queen Cake深色模式、手势导航

开发语言选择

  • Kotlin(推荐):Google 官方首推语言,简洁、安全、现代化,空指针安全、扩展函数、协程支持
  • Java:传统 Android 开发语言,生态成熟,资料丰富
  • Flutter / Dart:跨平台方案,一套代码同时构建 iOS 和 Android
  • React Native:使用 JavaScript/TypeScript,可复用 Web 技能
  • Kotlin Multiplatform:共享业务逻辑代码,UI 各平台原生实现

💡

建议:

新项目优先使用 Kotlin,Google 官方文档和 Jetpack 库均以 Kotlin 为主。

Android 架构层次

  1. 应用层 (Applications):系统应用与第三方应用
  2. 应用框架层 (Application Framework):Activity Manager、Window Manager、Content Provider、View System
  3. 原生库层 (Native Libraries):SQLite、OpenGL、Webkit、libc
  4. Android Runtime:ART 虚拟机(替代旧版 Dalvik),采用 AOT 编译
  5. HAL (硬件抽象层):为硬件提供标准接口
  6. Linux 内核层:驱动、内存管理、进程管理、网络协议栈
  7. Android 开发生态

    • Jetpack:Google 官方组件库,遵循最佳实践
    • Firebase:后端服务(分析、崩溃、推送、认证)
    • Google Play Services:地图、支付、登录等核心服务
    • AndroidX:替代旧版 Support Library,持续更新
    • Kotlin Coroutines:现代异步编程模型

    2. 开发环境搭建

    系统要求

    项目最低要求推荐配置
    操作系统Windows 10/11、macOS 10.14+、Linux最新版 64 位系统
    RAM8 GB16 GB+
    磁盘空间16 GB32 GB+(SSD)
    屏幕分辨率1280 × 8001920 × 1080+

    安装 Android Studio

    Android Studio 是 Google 官方的集成开发环境(IDE),基于 IntelliJ IDEA 平台。

    1. 访问官网 developer.android.com/studio 下载最新版
    2. 运行安装程序,选择标准安装(Standard)
    3. 首次启动时,会自动下载 Android SDK、Gradle 等工具
    4. 建议分配至少 4GB 内存给 IDE:Help → Change Memory Settings
    5. 配置 SDK

      打开 Settings → Languages & Frameworks → Android SDK

      • 勾选最新的稳定版 SDK Platform(如 API 34/35)
      • 在 SDK Tools 标签页中勾选:Android SDK Build-ToolsAndroid EmulatorAndroid SDK Platform-Tools
      • 安装 Google USB Driver(用于真机调试)
      • 安装 Intel HAXM 或启用 Hyper-V(模拟器加速)

      配置模拟器 (AVD)

      打开 Tools → Device Manager,创建虚拟设备:

      1. 选择设备类型(如 Pixel 7、Pixel Fold)
      2. 选择系统镜像(推荐 x86_64 版本,支持硬件加速)
      3. 设置 RAM 大小、内部存储容量
      4. 启用 HAXM 或 Hyper-V 加速以提升模拟器性能
      5. 可选启用冷启动/快照模式
      6. 真机调试配置

        1. 手机进入 设置 → 关于手机,连续点击 版本号 7 次开启开发者模式
        2. 进入 设置 → 开发者选项,开启 USB 调试
        3. 连接电脑后,在手机上确认调试授权对话框
        4. 无线调试(Android 11+):开发者选项 → 无线调试 → 使用配对码配对
        5. Gradle 基础配置

          // 项目级 build.gradle
          plugins {
              id 'com.android.application' version '8.2.0' apply false
              id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
              id 'com.google.dagger.hilt.android' version '2.50' apply false
          }
          
          // 模块级 app/build.gradle
          android {
              namespace 'com.example.myapp'
              compileSdk 35
          
              defaultConfig {
                  applicationId "com.example.myapp"
                  minSdk 24
                  targetSdk 35
                  versionCode 1
                  versionName "1.0"
                  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
              }
          
              buildTypes {
                  debug {
                      applicationIdSuffix ".debug"
                      debuggable true
                  }
                  release {
                      minifyEnabled true
                      shrinkResources true
                      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                  }
              }
          
              compileOptions {
                  sourceCompatibility JavaVersion.VERSION_17
                  targetCompatibility JavaVersion.VERSION_17
              }
          
              kotlinOptions {
                  jvmTarget = "17"
              }
          
              buildFeatures {
                  viewBinding true
                  buildConfig true
              }
          }
          
          dependencies {
              implementation 'androidx.core:core-ktx:1.12.0'
              implementation 'androidx.appcompat:appcompat:1.6.1'
              implementation 'com.google.android.material:material:1.11.0'
              implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
              implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
          
              testImplementation 'junit:junit:4.13.2'
              androidTestImplementation 'androidx.test.ext:junit:1.1.5'
              androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
          }

          提示:

          使用 Gradle Wrapper(

          gradlew

          )可保证团队成员使用相同版本的 Gradle。

          常用 Gradle 命令

          # 构建 APK
          ./gradlew assembleDebug
          ./gradlew assembleRelease
          
          # 构建 AAB
          ./gradlew bundleRelease
          
          # 运行单元测试
          ./gradlew test
          
          # 运行 UI 测试
          ./gradlew connectedAndroidTest
          
          # 清理项目
          ./gradlew clean
          
          # 查看依赖树
          ./gradlew app:dependencies
          
          # 代码静态分析
          ./gradlew lint

          3. 项目结构详解

          目录结构总览

          MyApp/
          ├── app/                          # 主模块
          │   ├── src/
          │   │   ├── main/
          │   │   │   ├── java/             # Kotlin/Java 源码
          │   │   │   ├── res/              # 资源文件
          │   │   │   │   ├── layout/       # 布局 XML
          │   │   │   │   ├── drawable/     # 图片、形状
          │   │   │   │   ├── values/       # 字符串、颜色、样式
          │   │   │   │   ├── mipmap/       # 应用图标
          │   │   │   │   ├── raw/          # 原始资源
          │   │   │   │   ├── anim/         # 动画资源
          │   │   │   │   ├── menu/         # 菜单资源
          │   │   │   │   ├── navigation/   # 导航图
          │   │   │   │   └── xml/          # XML 配置
          │   │   │   └── AndroidManifest.xml
          │   │   ├── test/                 # 单元测试
          │   │   ├── androidTest/          # UI 测试
          │   │   ├── debug/                # debug 变体资源
          │   │   └── release/              # release 变体资源
          │   └── build.gradle
          ├── gradle/
          │   └── wrapper/
          ├── build.gradle                  # 项目级配置
          ├── settings.gradle               # 模块设置
          ├── gradle.properties             # Gradle 属性
          ├── local.properties              # 本地 SDK 路径(不入版本控制)
          └── proguard-rules.pro            # 混淆规则

          AndroidManifest.xml 详解

          <?xml version="1.0" encoding="utf-8"?>
          <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools">
          
              <!-- 权限声明 -->
              <uses-permission android:name="android.permission.INTERNET"/>
              <uses-permission android:name="android.permission.CAMERA"/>
              <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
              <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
          
              <!-- 硬件特性声明 -->
              <uses-feature android:name="android.hardware.camera" android:required="false"/>
          
              <application
                  android:name=".MyApplication"
                  android:icon="@mipmap/ic_launcher"
                  android:label="@string/app_name"
                  android:theme="@style/Theme.MyApp"
                  android:allowBackup="true"
                  android:supportsRtl="true"
                  android:networkSecurityConfig="@xml/network_security_config"
                  tools:targetApi="35">
          
                  <!-- 主 Activity -->
                  <activity
                      android:name=".MainActivity"
                      android:exported="true"
                      android:launchMode="singleTop"
                      android:screenOrientation="portrait"
                      android:windowSoftInputMode="adjustResize">
                      <intent-filter>
                          <action android:name="android.intent.action.MAIN"/>
                          <category android:name="android.intent.category.LAUNCHER"/>
                      </intent-filter>
          
                      <!-- 深度链接 -->
                      <intent-filter android:autoVerify="true">
                          <action android:name="android.intent.action.VIEW"/>
                          <category android:name="android.intent.category.DEFAULT"/>
                          <category android:name="android.intent.category.BROWSABLE"/>
                          <data android:scheme="https" android:host="example.com"/>
                      </intent-filter>
                  </activity>
          
                  <!-- Service -->
                  <service
                      android:name=".service.MyService"
                      android:foregroundServiceType="dataSync"
                      android:exported="false"/>
          
                  <!-- 文件提供者(用于拍照/分享) -->
                  <provider
                      android:name="androidx.core.content.FileProvider"
                      android:authorities="${applicationId}.fileprovider"
                      android:exported="false"
                      android:grantUriPermissions="true">
                      <meta-data
                          android:name="android.support.FILE_PROVIDER_PATHS"
                          android:resource="@xml/file_paths"/>
                  </provider>
          
              </application>
          </manifest>

          资源文件命名规范

          资源类型命名规范示例
          布局文件activity_.xml / fragment_.xml / item_*.xmlactivity_main.xml, item_user.xml
          drawableic_.xml / bg_.xml / img_*ic_home.xml, bg_button_primary.xml
          字符串snake_case 加前缀title_home, btn_submit, hint_email
          颜色用途/语义命名color_primary, color_error, color_success
          尺寸用途_类型margin_normal, text_size_title
          id控件缩写+描述tv_title, btn_submit, et_email

          Build Variants 构建变体

          android {
              flavorDimensions += "environment"
              productFlavors {
                  dev {
                      dimension "environment"
                      applicationIdSuffix ".dev"
                      buildConfigField "String", "API_URL", '"https://dev-api.example.com"'
                  }
                  staging {
                      dimension "environment"
                      applicationIdSuffix ".staging"
                      buildConfigField "String", "API_URL", '"https://staging-api.example.com"'
                  }
                  prod {
                      dimension "environment"
                      buildConfigField "String", "API_URL", '"https://api.example.com"'
                  }
              }
          }

          组合后会产生:devDebug、devRelease、stagingDebug、stagingRelease、prodDebug、prodRelease 等变体。

          R 文件

          Android 会自动生成 R.java 文件,所有资源都会分配一个整型 ID。代码中通过 R.layout.activity_mainR.id.button1 等方式引用资源。

          ⚠️

          注意:

          R 文件的包名取决于

          build.gradle

          中配置的

          namespace

          4. 创建第一个 App

          Hello World 示例

          布局文件 activity_main.xml

          <?xml version="1.0" encoding="utf-8"?>
          <androidx.constraintlayout.widget.ConstraintLayout
              xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
          
              <TextView
                  android:id="@+id/tvMessage"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="Hello Android!"
                  android:textSize="24sp"
                  app:layout_constraintBottom_toBottomOf="parent"
                  app:layout_constraintEnd_toEndOf="parent"
                  app:layout_constraintStart_toStartOf="parent"
                  app:layout_constraintTop_toTopOf="parent"/>
          
              <Button
                  android:id="@+id/btnClick"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="20dp"
                  android:text="点击我"
                  app:layout_constraintTop_toBottomOf="@id/tvMessage"
                  app:layout_constraintStart_toStartOf="parent"
                  app:layout_constraintEnd_toEndOf="parent"/>
          
          </androidx.constraintlayout.widget.ConstraintLayout>

          Activity 代码 (Kotlin)

          package com.example.myapp
          
          import android.os.Bundle
          import android.widget.Button
          import android.widget.TextView
          import androidx.appcompat.app.AppCompatActivity
          
          class MainActivity : AppCompatActivity() {
          
              private lateinit var tvMessage: TextView
              private lateinit var btnClick: Button
              private var counter = 0
          
              override fun onCreate(savedInstanceState: Bundle?) {
                  super.onCreate(savedInstanceState)
                  setContentView(R.layout.activity_main)
          
                  tvMessage = findViewById(R.id.tvMessage)
                  btnClick = findViewById(R.id.btnClick)
          
                  btnClick.setOnClickListener {
                      counter++
                      tvMessage.text = "你点击了 $counter 次"
                  }
              }
          }

          ViewBinding(推荐方式)

          // build.gradle 中启用
          android {
              buildFeatures {
                  viewBinding true
              }
          }
          
          // Activity 中使用
          class MainActivity : AppCompatActivity() {
              private lateinit var binding: ActivityMainBinding
          
              override fun onCreate(savedInstanceState: Bundle?) {
                  super.onCreate(savedInstanceState)
                  binding = ActivityMainBinding.inflate(layoutInflater)
                  setContentView(binding.root)
          
                  binding.btnClick.setOnClickListener {
                      binding.tvMessage.text = "Hello ViewBinding!"
                  }
              }
          }

          运行应用

          1. 在 Android Studio 工具栏选择目标设备(模拟器或真机)
          2. 点击绿色的 ▶ Run 按钮或按 Shift + F10
          3. 首次运行需要构建 Gradle,耐心等待
          4. 应用会自动安装并启动
          5. 常用调试技巧

            • Logcat:查看应用日志(底部面板 → Logcat)
            • Layout Inspector:实时查看 UI 层级(Tools → Layout Inspector)
            • Database Inspector:查看 Room 数据库(App Inspection 面板)
            • Apply Changes:无需重启应用即可应用代码/资源改动(⚡ 图标)

            技巧:

            使用

            Log.d("TAG", "message")

            输出调试信息,配合 Logcat 过滤器可快速定位问题。

            5. UI 基础控件

            常用视图控件

            TextView - 文本显示

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="这是文本"
                android:textSize="18sp"
                android:textColor="#333333"
                android:textStyle="bold"
                android:maxLines="2"
                android:ellipsize="end"
                android:letterSpacing="0.05"
                android:lineSpacingMultiplier="1.2"/>
            
            <!-- 富文本 SpannableString -->
            val spannable = SpannableString("这是红色文字和加粗")
            spannable.setSpan(ForegroundColorSpan(Color.RED), 2, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
            spannable.setSpan(StyleSpan(Typeface.BOLD), 7, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
            textView.text = spannable

            EditText - 输入框

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="邮箱"
                app:errorEnabled="true"
                app:startIconDrawable="@drawable/ic_email">
            
                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/etEmail"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textEmailAddress"
                    android:maxLength="50"/>
            </com.google.android.material.textfield.TextInputLayout>
            
            // 密码框
            <com.google.android.material.textfield.TextInputLayout
                app:endIconMode="password_toggle"
                android:hint="密码">
                <com.google.android.material.textfield.TextInputEditText
                    android:inputType="textPassword"/>
            </com.google.android.material.textfield.TextInputLayout>
            
            // Kotlin 获取输入
            val email = binding.etEmail.text.toString()

            Button - 按钮

            <!-- 填充按钮(主操作) -->
            <com.google.android.material.button.MaterialButton
                android:id="@+id/btnAction"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="提交"
                app:icon="@drawable/ic_send"
                app:iconGravity="textStart"
                app:cornerRadius="8dp"/>
            
            <!-- 轮廓按钮 -->
            <Button
                style="@style/Widget.Material3.Button.OutlinedButton"
                android:text="取消"/>
            
            <!-- 文本按钮 -->
            <Button
                style="@style/Widget.Material3.Button.TextButton"
                android:text="了解更多"/>
            
            <!-- 图标按钮 -->
            <ImageButton
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:src="@drawable/ic_close"
                android:background="?attr/selectableItemBackgroundBorderless"
                android:contentDescription="关闭"/>

            ImageView - 图片

            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@drawable/logo"
                android:scaleType="centerCrop"
                android:contentDescription="应用logo"/>
            
            // scaleType 常用值:
            // centerCrop - 等比缩放裁剪填满
            // fitCenter - 等比缩放居中
            // centerInside - 原尺寸或缩小居中
            // fitXY - 拉伸填满(会变形)

            CheckBox / RadioButton / Switch

            <CheckBox
                android:id="@+id/cbAgree"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="我同意用户协议"/>
            
            <RadioGroup
                android:id="@+id/rgGender"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <RadioButton android:id="@+id/rbMale" android:text="男"/>
                <RadioButton android:id="@+id/rbFemale" android:text="女"/>
            </RadioGroup>
            
            <com.google.android.material.switchmaterial.SwitchMaterial
                android:id="@+id/swDarkMode"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="深色模式"/>
            
            <com.google.android.material.chip.Chip
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="标签"
                app:closeIconVisible="true"
                app:chipIcon="@drawable/ic_tag"/>
            
            // 监听
            binding.cbAgree.setOnCheckedChangeListener { _, isChecked ->
                binding.btnSubmit.isEnabled = isChecked
            }

            ProgressBar - 进度条

            <!-- 圆形加载 -->
            <ProgressBar
                android:id="@+id/pbLoading"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            
            <!-- 线性进度条 -->
            <ProgressBar
                style="?android:attr/progressBarStyleHorizontal"
                android:id="@+id/pbProgress"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:max="100"
                android:progress="60"/>
            
            <!-- Material 线性进度 -->
            <com.google.android.material.progressindicator.LinearProgressIndicator
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:indeterminate="true"/>

            常用尺寸单位

            单位说明使用场景
            dp (dip)密度无关像素布局宽高、边距
            sp缩放无关像素,跟随系统字体文字大小
            px物理像素极少使用

            💡

            提示:

            1dp 在 160dpi 屏幕上 = 1px,在 320dpi(xxhdpi)屏幕上 = 2px。

            6. 常用布局方式

            LinearLayout - 线性布局

            子元素按单行或单列排列,最基础的布局。

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:padding="16dp"
                android:gravity="center_horizontal">
            
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="标题"
                    android:textStyle="bold"/>
            
                <EditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"/>
            
                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">
                    <Button android:layout_weight="1" android:text="取消"
                        android:layout_marginEnd="8dp"/>
                    <Button android:layout_weight="1" android:text="确定"/>
                </LinearLayout>
            </LinearLayout>

            ConstraintLayout - 约束布局(推荐)

            Android 官方推荐的现代布局,通过约束关系定位子元素:

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="16dp">
            
                <!-- 居中 -->
                <TextView
                    android:id="@+id/tvTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"/>
            
                <!-- Guideline 辅助线 -->
                <androidx.constraintlayout.widget.Guideline
                    android:id="@+id/guideline"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    app:layout_constraintGuide_percent="0.5"/>
            
                <!-- Barrier 屏障(多个控件底部对齐) -->
                <androidx.constraintlayout.widget.Barrier
                    android:id="@+id/barrier"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:barrierDirection="bottom"
                    app:constraint_referenced_ids="tv1,tv2"/>
            
                <!-- 水平链 spread -->
                <Button
                    android:id="@+id/btn1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:layout_constraintHorizontal_chainStyle="spread"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toStartOf="@id/btn2"/>
            
                <!-- Bias 偏移 -->
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:layout_constraintHorizontal_bias="0.3"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"/>
            </androidx.constraintlayout.widget.ConstraintLayout>

            RelativeLayout - 相对布局

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="16dp">
            
                <ImageView
                    android:id="@+id/avatar"
                    android:layout_width="50dp"
                    android:layout_height="50dp"
                    android:layout_alignParentStart="true"
                    android:layout_centerVertical="true"/>
            
                <TextView
                    android:id="@+id/name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toEndOf="@id/avatar"
                    android:layout_marginStart="12dp"
                    android:layout_alignTop="@id/avatar"/>
            
                <TextView
                    android:id="@+id/desc"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/name"
                    android:layout_toEndOf="@id/avatar"
                    android:layout_marginStart="12dp"/>
            </RelativeLayout>

            FrameLayout - 帧布局

            子元素以堆叠方式显示,常用于 Fragment 容器:

            <FrameLayout
                android:id="@+id/fragment_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
            
            <!-- 叠加效果 -->
            <FrameLayout
                android:layout_width="200dp"
                android:layout_height="200dp">
                <ImageView android:src="@drawable/image"/>
                <TextView
                    android:layout_gravity="bottom|end"
                    android:text="NEW"
                    android:background="#FF0000"
                    android:textColor="#FFF"
                    android:padding="4dp"/>
            </FrameLayout>

            ScrollView / HorizontalScrollView

            <ScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fillViewport="true">
            
                <LinearLayout
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">
                    <!-- 这里放置可滚动内容 -->
                </LinearLayout>
            </ScrollView>
            
            <!-- 嵌套滚动 -->
            <androidx.core.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">
                <!-- RecyclerView 等内容 -->
            </androidx.core.widget.NestedScrollView>

            CoordinatorLayout + AppBar

            <androidx.coordinatorlayout.widget.CoordinatorLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">
            
                <com.google.android.material.appbar.AppBarLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">
            
                    <com.google.android.material.appbar.MaterialToolbar
                        android:layout_width="match_parent"
                        android:layout_height="?attr/actionBarSize"
                        app:title="首页"
                        app:layout_scrollFlags="scroll|enterAlways"/>
            
                    <com.google.android.material.tabs.TabLayout
                        android:id="@+id/tabs"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"/>
                </com.google.android.material.appbar.AppBarLayout>
            
                <androidx.viewpager2.widget.ViewPager2
                    android:id="@+id/viewPager"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
            
            </androidx.coordinatorlayout.widget.CoordinatorLayout>

            性能建议:

            避免多层布局嵌套(超过 3 层会显著影响性能),优先使用 ConstraintLayout 实现复杂布局。

            7. RecyclerView 列表展示

            RecyclerView 基础

            RecyclerView 是 ListView 的升级版,支持更高效的视图复用和灵活的布局管理。

            1. 添加依赖

            implementation 'androidx.recyclerview:recyclerview:1.3.2'

            2. 布局文件

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipToPadding="false"
                android:paddingBottom="80dp"/>

            3. 列表项布局 item_user.xml

            <LinearLayout
                xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:padding="16dp"
                android:background="?attr/selectableItemBackground">
            
                <ImageView
                    android:id="@+id/ivAvatar"
                    android:layout_width="50dp"
                    android:layout_height="50dp"/>
            
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical"
                    android:layout_marginStart="12dp">
            
                    <TextView
                        android:id="@+id/tvName"
                        android:textSize="16sp"
                        android:textStyle="bold"/>
                    <TextView
                        android:id="@+id/tvEmail"
                        android:textColor="#666"
                        android:textSize="14sp"/>
                </LinearLayout>
            </LinearLayout>

            4. ListAdapter(推荐,内置 DiffUtil)

            class UserAdapter : ListAdapter<User, UserAdapter.UserViewHolder>(UserDiffCallback()) {
            
                class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
                    val tvName: TextView = view.findViewById(R.id.tvName)
                    val tvEmail: TextView = view.findViewById(R.id.tvEmail)
                    val ivAvatar: ImageView = view.findViewById(R.id.ivAvatar)
                }
            
                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_user, parent, false)
                    return UserViewHolder(view)
                }
            
                override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
                    val user = getItem(position)
                    holder.tvName.text = user.name
                    holder.tvEmail.text = user.email
                    Glide.with(holder.itemView).load(user.avatarUrl).into(holder.ivAvatar)
            
                    holder.itemView.setOnClickListener {
                        onItemClickListener?.invoke(user)
                    }
                }
            
                var onItemClickListener: ((User) -> Unit)? = null
            }
            
            class UserDiffCallback : DiffUtil.ItemCallback<User>() {
                override fun areItemsTheSame(oldItem: User, newItem: User) = oldItem.id == newItem.id
                override fun areContentsTheSame(oldItem: User, newItem: User) = oldItem == newItem
            }
            
            data class User(val id: Long, val name: String, val email: String, val avatarUrl: String)

            5. 在 Activity 中使用

            val adapter = UserAdapter()
            adapter.onItemClickListener = { user ->
                // 处理点击
                Toast.makeText(this, "点击了 ${user.name}", Toast.LENGTH_SHORT).show()
            }
            
            binding.recyclerView.apply {
                layoutManager = LinearLayoutManager(this@MainActivity)
                adapter = this@MainActivity.adapter
                addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
                setHasFixedSize(true)
                // 设置 ItemAnimator
                itemAnimator = DefaultItemAnimator()
            }
            
            // 提交数据(自动计算差异并刷新)
            adapter.submitList(users)

            多种 ViewType

            override fun getItemViewType(position: Int): Int {
                return if (getItem(position).isHeader) TYPE_HEADER else TYPE_ITEM
            }
            
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                return when (viewType) {
                    TYPE_HEADER -> HeaderViewHolder(...)
                    TYPE_ITEM -> ItemViewHolder(...)
                    else -> throw IllegalArgumentException()
                }
            }

            常用 LayoutManager

            类型说明
            LinearLayoutManager纵向/横向列表
            GridLayoutManager网格布局,可设置 spanCount
            StaggeredGridLayoutManager瀑布流布局

            SwipeRefreshLayout 下拉刷新

            <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
                android:id="@+id/swipeRefresh"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
            
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/recyclerView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>
            </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
            
            // 监听
            binding.swipeRefresh.setOnRefreshListener {
                viewModel.refresh()
            }
            
            // 完成刷新
            binding.swipeRefresh.isRefreshing = false

            ItemTouchHelper 滑动删除/拖拽排序

            val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
                ItemTouchHelper.UP or ItemTouchHelper.DOWN,  // 拖拽方向
                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT  // 滑动方向
            ) {
                override fun onMove(rv: RecyclerView, vh: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
                    adapter.moveItem(vh.adapterPosition, target.adapterPosition)
                    return true
                }
            
                override fun onSwiped(vh: RecyclerView.ViewHolder, direction: Int) {
                    adapter.removeItem(vh.adapterPosition)
                }
            })
            itemTouchHelper.attachToRecyclerView(binding.recyclerView)

            💡

            推荐:

            使用

            ListAdapter

            (内置 DiffUtil)可以简化列表刷新逻辑,性能更好。

            8. Material Design 设计规范

            Material Design 3 概述

            Material Design 3(Material You)是 Google 最新的设计系统,强调个性化(动态取色)、可访问性和一致性。

            主题配置

            <!-- res/values/themes.xml -->
            <resources>
                <style name="Theme.MyApp" parent="Theme.Material3.DayNight.NoActionBar">
                    <item name="colorPrimary">@color/purple_500</item>
                    <item name="colorOnPrimary">@color/white</item>
                    <item name="colorSecondary">@color/teal_200</item>
                    <item name="colorError">@color/red_500</item>
                    <item name="android:statusBarColor">?attr/colorPrimary</item>
                </style>
            </resources>
            
            // 启用动态取色(Android 12+)
            // Application onCreate()
            DynamicColors.applyToActivitiesIfAvailable(this)

            常用 Material 组件

            AppBar / Toolbar

            <com.google.android.material.appbar.MaterialToolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:title="首页"
                app:navigationIcon="@drawable/ic_menu"
                app:menu="@menu/main_menu"/>
            
            // 代码配置
            setSupportActionBar(binding.toolbar)
            binding.toolbar.setNavigationOnClickListener { finish() }
            binding.toolbar.setOnMenuItemClickListener { menuItem ->
                when (menuItem.itemId) {
                    R.id.action_settings -> { true }
                    else -> false
                }
            }

            FloatingActionButton

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="16dp"
                app:srcCompat="@drawable/ic_add"
                android:contentDescription="新增"/>
            
            <!-- 扩展 FAB -->
            <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="新建"
                app:icon="@drawable/ic_add"/>

            CardView - 卡片

            <com.google.android.material.card.MaterialCardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                app:cardCornerRadius="12dp"
                app:cardElevation="4dp"
                app:strokeColor="@color/gray_200"
                app:strokeWidth="1dp"
                android:clickable="true"
                android:focusable="true">
            
                <LinearLayout
                    android:padding="16dp"
                    android:orientation="vertical">
                    <TextView android:text="标题" android:textSize="18sp"/>
                    <TextView android:text="描述" android:textColor="#666"/>
                </LinearLayout>
            </com.google.android.material.card.MaterialCardView>

            BottomNavigationView - 底部导航

            <com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/bottomNav"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:menu="@menu/bottom_nav_menu"
                app:labelVisibilityMode="labeled"/>
            
            // 监听选择
            binding.bottomNav.setOnItemSelectedListener { item ->
                when (item.itemId) {
                    R.id.nav_home -> { loadFragment(HomeFragment()); true }
                    R.id.nav_search -> { loadFragment(SearchFragment()); true }
                    R.id.nav_profile -> { loadFragment(ProfileFragment()); true }
                    else -> false
                }
            }

            Snackbar - 提示条

            Snackbar.make(view, "已删除 1 项", Snackbar.LENGTH_LONG)
                .setAction("撤销") {
                    // 撤销逻辑
                }
                .setAnchorView(binding.fab)  // 显示在 FAB 上方
                .show()

            Material Dialog

            MaterialAlertDialogBuilder(this)
                .setTitle("确认删除")
                .setMessage("确定要删除这条数据吗?")
                .setIcon(R.drawable.ic_warning)
                .setPositiveButton("删除") { _, _ -> deleteItem() }
                .setNegativeButton("取消", null)
                .setCancelable(false)
                .show()
            
            // 单选对话框
            val items = arrayOf("选项 A", "选项 B", "选项 C")
            MaterialAlertDialogBuilder(this)
                .setTitle("请选择")
                .setItems(items) { _, which ->
                    Toast.makeText(this, items[which], Toast.LENGTH_SHORT).show()
                }
                .show()
            
            // 多选对话框
            val checkedItems = booleanArrayOf(false, true, false)
            MaterialAlertDialogBuilder(this)
                .setTitle("多选")
                .setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
                    checkedItems[which] = isChecked
                }
                .setPositiveButton("确定", null)
                .show()

            BottomSheet - 底部弹窗

            <!-- Modal BottomSheet -->
            class MyBottomSheet : BottomSheetDialogFragment() {
                override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
                    return inflater.inflate(R.layout.dialog_bottom_sheet, container, false)
                }
            }
            
            // 显示
            MyBottomSheet().show(supportFragmentManager, "sheet")
            
            // 固定 BottomSheet(Behavior)
            val behavior = BottomSheetBehavior.from(bottomSheetView)
            behavior.state = BottomSheetBehavior.STATE_EXPANDED
            behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                override fun onStateChanged(sheet: View, newState: Int) {}
                override fun onSlide(sheet: View, slideOffset: Float) {}
            })

            9. Activity 生命周期

            生命周期回调方法

            Activity 从创建到销毁会经历一系列回调:

            class MainActivity : AppCompatActivity() {
            
                override fun onCreate(savedInstanceState: Bundle?) {
                    super.onCreate(savedInstanceState)
                    // Activity 创建时调用,初始化布局和数据
                    setContentView(R.layout.activity_main)
                    // savedInstanceState 非空表示从之前状态恢复
                }
            
                override fun onStart() {
                    super.onStart()
                    // Activity 可见时调用
                }
            
                override fun onResume() {
                    super.onResume()
                    // Activity 获得焦点、可交互时调用
                    // 适合恢复动画、注册监听
                }
            
                override fun onPause() {
                    super.onPause()
                    // Activity 失去焦点时调用
                    // 保存临时数据、释放摄像头等资源
                }
            
                override fun onStop() {
                    super.onStop()
                    // Activity 不可见时调用
                }
            
                override fun onDestroy() {
                    super.onDestroy()
                    // Activity 即将销毁,释放资源
                }
            
                override fun onRestart() {
                    super.onRestart()
                    // 从停止状态重新启动
                }
            }

            生命周期场景

            场景回调顺序
            启动 ActivityonCreate → onStart → onResume
            按 Home 键onPause → onStop
            从后台返回onRestart → onStart → onResume
            按返回键onPause → onStop → onDestroy
            屏幕旋转onPause → onStop → onDestroy → onCreate → onStart → onResume
            弹出对话框onPause(Activity 仍可见但不可交互)
            A 启动 B,B 返回到 AA.onPause → B.onCreate → B.onStart → B.onResume → A.onStop → B.onPause → A.onRestart → A.onStart → A.onResume → B.onStop → B.onDestroy

            保存与恢复状态

            override fun onSaveInstanceState(outState: Bundle) {
                super.onSaveInstanceState(outState)
                outState.putInt("counter", counter)
                outState.putString("input", binding.etInput.text.toString())
                outState.putParcelable("user", currentUser)
            }
            
            override fun onRestoreInstanceState(savedInstanceState: Bundle) {
                super.onRestoreInstanceState(savedInstanceState)
                counter = savedInstanceState.getInt("counter", 0)
                binding.etInput.setText(savedInstanceState.getString("input"))
            }
            
            // 也可以在 onCreate 中恢复
            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                savedInstanceState?.let {
                    counter = it.getInt("counter", 0)
                }
            }

            Activity 启动模式 (launchMode)

            模式说明使用场景
            standard默认,每次创建新实例大部分 Activity
            singleTop栈顶存在则复用(onNewIntent)通知打开的页面
            singleTask任务栈中唯一实例,清除上方所有主页、登录
            singleInstance独占一个任务栈通话界面
            // Manifest 中配置
            <activity
                android:name=".MainActivity"
                android:launchMode="singleTask"
                android:taskAffinity=""/>
            
            // 代码中设置 Flag
            val intent = Intent(this, MainActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
            }
            startActivity(intent)

            ⚠️

            注意:

            屏幕旋转默认会重建 Activity,导致状态丢失。可使用

            ViewModel

            保存 UI 状态,或在 Manifest 中配置

            android:configChanges="orientation|screenSize"

            10. Fragment 碎片

            什么是 Fragment

            Fragment 是 Activity 中可复用的 UI 模块,拥有自己的生命周期。一个 Activity 可以包含多个 Fragment,常用于平板、导航等场景。

            Fragment 生命周期

            onAttach() → onCreate() → onCreateView() → onViewCreated() →
            onViewCreated() → onStart() → onResume() → onPause() → onStop() →
            onDestroyView() → onDestroy() → onDetach()

            创建 Fragment

            class HomeFragment : Fragment(R.layout.fragment_home) {
            
                private var _binding: FragmentHomeBinding? = null
                private val binding get() = _binding!!
            
                override fun onCreateView(
                    inflater: LayoutInflater,
                    container: ViewGroup?,
                    savedInstanceState: Bundle?
                ): View {
                    _binding = FragmentHomeBinding.inflate(inflater, container, false)
                    return binding.root
                }
            
                override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                    super.onViewCreated(view, savedInstanceState)
                    binding.btnAction.setOnClickListener {
                        // 处理点击
                    }
            
                    // 观察 ViewModel
                    viewLifecycleOwner.lifecycleScope.launch {
                        repeatOnLifecycle(Lifecycle.State.STARTED) {
                            viewModel.data.collect { updateUI(it) }
                        }
                    }
                }
            
                override fun onDestroyView() {
                    super.onDestroyView()
                    _binding = null  // 避免内存泄漏
                }
            }

            FragmentManager 管理

            // 添加 Fragment
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, HomeFragment(), "home")
                .addToBackStack("home")
                .commit()
            
            // 替换 Fragment
            supportFragmentManager.beginTransaction()
                .replace(R.id.fragment_container, DetailFragment())
                .addToBackStack("detail")
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                .commit()
            
            // 返回上一个 Fragment
            supportFragmentManager.popBackStack()
            supportFragmentManager.popBackStack("home", FragmentManager.POP_BACK_STACK_INCLUSIVE)
            
            // 查找 Fragment
            val fragment = supportFragmentManager.findFragmentByTag("home")
            val fragmentById = supportFragmentManager.findFragmentById(R.id.fragment_container)

            Fragment 与 Activity 通信

            // 方式1:ViewModel 共享(推荐)
            class SharedViewModel : ViewModel() {
                val selected = MutableLiveData<Item>()
            }
            
            // Fragment 中获取 Activity 级别的 ViewModel
            private val model: SharedViewModel by activityViewModels()
            
            // 方式2:Fragment Result API
            // Fragment A 发送
            setFragmentResult("requestKey", bundleOf("data" to "hello"))
            
            // Fragment B 接收
            setFragmentResultListener("requestKey") { key, bundle ->
                val data = bundle.getString("data")
            }
            
            // 方式3:接口回调(旧方式,不推荐)

            Navigation Component

            implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
            implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
            
            // nav_graph.xml
            <navigation
                xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/nav_graph"
                app:startDestination="@id/homeFragment">
            
                <fragment
                    android:id="@+id/homeFragment"
                    android:name="com.example.HomeFragment"
                    android:label="首页">
                    <action
                        android:id="@+id/action_home_to_detail"
                        app:destination="@id/detailFragment"
                        app:enterAnim="@anim/slide_in_right"
                        app:exitAnim="@anim/slide_out_left"
                        app:popEnterAnim="@anim/slide_in_left"
                        app:popExitAnim="@anim/slide_out_right"/>
                </fragment>
            
                <fragment
                    android:id="@+id/detailFragment"
                    android:name="com.example.DetailFragment">
                    <argument
                        android:name="itemId"
                        app:argType="long"/>
                    <argument
                        android:name="title"
                        app:argType="string"
                        app:nullable="true"
                        android:defaultValue="@null"/>
                </fragment>
            </navigation>
            
            // 导航跳转(Safe Args 插件生成方向类)
            val action = HomeFragmentDirections.actionHomeToDetail(itemId = 123L)
            findNavController().navigate(action)
            
            // 接收参数
            val args: DetailFragmentArgs by navArgs()
            val itemId = args.itemId

            11. Intent 与页面跳转

            Intent 类型

            • 显式 Intent:明确指定目标组件(Activity/Service)
            • 隐式 Intent:描述要执行的动作,由系统匹配合适的组件

            显式跳转 Activity

            // 基础跳转
            val intent = Intent(this, DetailActivity::class.java)
            startActivity(intent)
            
            // 携带数据
            val intent = Intent(this, DetailActivity::class.java).apply {
                putExtra("user_id", 123)
                putExtra("user_name", "张三")
                putExtra("is_vip", true)
            }
            startActivity(intent)
            
            // 传递 Bundle
            val bundle = Bundle().apply {
                putLong("id", 123L)
                putString("name", "张三")
            }
            intent.putExtras(bundle)
            
            // 传递复杂对象(需实现 Parcelable)
            @Parcelize
            data class User(val id: Long, val name: String) : Parcelable
            
            val intent = Intent(this, DetailActivity::class.java).apply {
                putExtra("user", user)  // Parcelable
                putParcelableArrayListExtra("users", ArrayList(userList))
            }
            startActivity(intent)

            接收数据

            // DetailActivity 中
            val userId = intent.getLongExtra("user_id", 0)
            val userName = intent.getStringExtra("user_name")
            val user = intent.getParcelableExtra<User>("user", User::class.java)
            val users = intent.getParcelableArrayListExtra<User>("users")
            
            // Bundle 方式
            val bundle = intent.extras
            val name = bundle?.getString("name")

            隐式 Intent 常见动作

            // 打开网页
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://google.com"))
            startActivity(intent)
            
            // 拨打电话(需权限 CALL_PHONE,推荐用 DIAL)
            val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:10086"))
            startActivity(intent)
            
            // 发送短信
            val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:10086")).apply {
                putExtra("sms_body", "短信内容")
            }
            startActivity(intent)
            
            // 发送邮件
            val intent = Intent(Intent.ACTION_SENDTO).apply {
                data = Uri.parse("mailto:test@example.com")
                putExtra(Intent.EXTRA_SUBJECT, "主题")
                putExtra(Intent.EXTRA_TEXT, "正文")
                putExtra(Intent.EXTRA_STREAM, fileUri)  // 附件
            }
            startActivity(intent)
            
            // 分享内容
            val intent = Intent(Intent.ACTION_SEND).apply {
                type = "text/plain"
                putExtra(Intent.EXTRA_TEXT, "分享内容")
            }
            startActivity(Intent.createChooser(intent, "分享到"))
            
            // 打开地图
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo:39.9,116.3?q=北京"))
            startActivity(intent)
            
            // 设置闹钟
            val intent = Intent(AlarmClock.ACTION_SET_ALARM).apply {
                putExtra(AlarmClock.EXTRA_HOUR, 8)
                putExtra(AlarmClock.EXTRA_MINUTES, 30)
                putExtra(AlarmClock.EXTRA_MESSAGE, "起床")
            }
            startActivity(intent)
            
            // 选择联系人
            val intent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
            startActivity(intent)

            Activity Result API(推荐)

            // 自定义结果
            private val launcher = registerForActivityResult(
                ActivityResultContracts.StartActivityForResult()
            ) { result ->
                if (result.resultCode == Activity.RESULT_OK) {
                    val data = result.data?.getStringExtra("result")
                }
            }
            launcher.launch(Intent(this, SecondActivity::class.java))
            
            // 拍照
            private val cameraLauncher = registerForActivityResult(
                ActivityResultContracts.TakePicturePreview()
            ) { bitmap ->
                binding.imageView.setImageBitmap(bitmap)
            }
            
            // 选择图片
            private val pickImageLauncher = registerForActivityResult(
                ActivityResultContracts.PickVisualMedia()
            ) { uri ->
                uri?.let { binding.imageView.setImageURI(it) }
            }
            pickImageLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
            
            // 选择文件
            private val fileLauncher = registerForActivityResult(
                ActivityResultContracts.GetContent()
            ) { uri -> /* 处理文件 */ }
            fileLauncher.launch("application/pdf")

            权限请求

            private val permissionLauncher = registerForActivityResult(
                ActivityResultContracts.RequestPermission()
            ) { granted ->
                if (granted) {
                    openCamera()
                } else {
                    // 权限被拒绝
                    if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                        // 用户选择了"拒绝并不再询问"以外的拒绝
                        showRationaleDialog()
                    }
                }
            }
            
            // 请求
            permissionLauncher.launch(Manifest.permission.CAMERA)
            
            // 批量请求
            private val multiPermLauncher = registerForActivityResult(
                ActivityResultContracts.RequestMultiplePermissions()
            ) { result ->
                val allGranted = result.values.all { it }
            }
            multiPermLauncher.launch(arrayOf(
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
            ))

            12. Service 后台服务

            Service 类型

            • 前台服务:显示通知,用户可见(如音乐播放、下载)
            • 后台服务:不可见,Android 8.0+ 受严格限制
            • 绑定服务:提供客户端-服务器接口供组件绑定

            创建 Service

            class MyService : Service() {
                override fun onBind(intent: Intent): IBinder? = null
            
                override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
                    // 执行后台任务
                    doBackgroundWork()
                    // START_STICKY:被杀后重启
                    // START_NOT_STICKY:被杀后不重启
                    // START_REDELIVER_INTENT:被杀后重启并传递最后一个 Intent
                    return START_STICKY
                }
            
                private fun doBackgroundWork() {
                    // 在子线程中执行耗时操作
                    Thread {
                        // 任务逻辑
                    }.start()
                }
            
                override fun onDestroy() {
                    super.onDestroy()
                    // 释放资源
                }
            }

            启动与停止 Service

            // 启动
            val intent = Intent(this, MyService::class.java)
            ContextCompat.startForegroundService(this, intent)  // 前台服务(需 5 秒内调 startForeground)
            
            // Android 12+ 限制,必须声明 foregroundServiceType
            // <service android:foregroundServiceType="dataSync|location|mediaPlayback"/>
            
            // 普通启动
            startService(intent)
            
            // 停止
            stopService(Intent(this, MyService::class.java))
            // 或在 Service 内部:stopSelf()

            前台服务通知

            private fun startForeground() {
                createNotificationChannel()
            
                val pendingIntent = PendingIntent.getActivity(
                    this, 0, Intent(this, MainActivity::class.java),
                    PendingIntent.FLAG_IMMUTABLE
                )
            
                val notification = NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("正在播放")
                    .setContentText("歌曲名 - 歌手")
                    .setSmallIcon(R.drawable.ic_music)
                    .setContentIntent(pendingIntent)
                    .setOngoing(true)
                    .addAction(R.drawable.ic_pause, "暂停", pausePendingIntent)
                    .setStyle(androidx.media.app.NotificationCompat.MediaStyle()
                        .setShowActionsInCompactView(0))
                    .build()
            
                startForeground(NOTIFICATION_ID, notification)
            }
            
            private fun createNotificationChannel() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    val channel = NotificationChannel(
                        CHANNEL_ID,
                        "音乐服务",
                        NotificationManager.IMPORTANCE_LOW
                    ).apply {
                        description = "音乐播放通知"
                        setShowBadge(false)
                    }
                    val manager = getSystemService(NotificationManager::class.java)
                    manager.createNotificationChannel(channel)
                }
            }

            绑定服务

            class BoundService : Service() {
                private val binder = LocalBinder()
            
                inner class LocalBinder : Binder() {
                    fun getService(): BoundService = this@BoundService
                }
            
                override fun onBind(intent: Intent): IBinder = binder
            
                fun getData(): String = "Hello from Service"
            }
            
            // Activity 中绑定
            private var service: BoundService? = null
            private var bound = false
            
            private val connection = object : ServiceConnection {
                override fun onServiceConnected(name: ComponentName, binder: IBinder) {
                    val localBinder = binder as BoundService.LocalBinder
                    service = localBinder.getService()
                    bound = true
                }
                override fun onServiceDisconnected(name: ComponentName) {
                    bound = false
                }
            }
            
            override fun onStart() {
                super.onStart()
                Intent(this, BoundService::class.java).also {
                    bindService(it, connection, Context.BIND_AUTO_CREATE)
                }
            }
            
            override fun onStop() {
                super.onStop()
                if (bound) {
                    unbindService(connection)
                    bound = false
                }
            }

            推荐:WorkManager

            对于可延迟的后台任务(如数据同步、备份),推荐使用 WorkManager:

            implementation 'androidx.work:work-runtime-ktx:2.9.0'
            
            class SyncWorker(ctx: Context, params: WorkerParameters) :
                CoroutineWorker(ctx, params) {
            
                override suspend fun doWork(): Result {
                    return try {
                        val data = inputData.getString("url") ?: return Result.failure()
                        // 执行同步逻辑
                        val api = RetrofitClient.api
                        val response = api.syncData(data)
                        if (response.isSuccessful) {
                            val output = workDataOf("result" to "success")
                            Result.success(output)
                        } else {
                            Result.retry()  // 稍后重试
                        }
                    } catch (e: Exception) {
                        Result.failure()
                    }
                }
            }
            
            // 调度一次性任务
            val request = OneTimeWorkRequestBuilder<SyncWorker>()
                .setInputData(workDataOf("url" to "https://api.example.com"))
                .setConstraints(
                    Constraints.Builder()
                        .setRequiredNetworkType(NetworkType.CONNECTED)
                        .setRequiresBatteryNotLow(true)
                        .build()
                )
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
                .setInitialDelay(10, TimeUnit.MINUTES)
                .addTag("sync_work")
                .build()
            
            WorkManager.getInstance(context).enqueueUniqueWork(
                "sync",
                ExistingWorkPolicy.KEEP,
                request
            )
            
            // 周期性任务
            val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(
                15, TimeUnit.MINUTES  // 最小 15 分钟
            ).build()
            
            // 观察状态
            WorkManager.getInstance(context)
                .getWorkInfoByIdLiveData(request.id)
                .observe(this) { workInfo ->
                    when (workInfo?.state) {
                        WorkInfo.State.SUCCEEDED -> { /* 完成 */ }
                        WorkInfo.State.FAILED -> { /* 失败 */ }
                        else -> {}
                    }
                }
            
            // 取消任务
            WorkManager.getInstance(context).cancelAllWork()
            WorkManager.getInstance(context).cancelWorkById(request.id)
            WorkManager.getInstance(context).cancelAllWorkByTag("sync_work")

            ⚠️

            Android 12+ 限制:

            前台服务启动受限,需在 Manifest 中声明

            android:foregroundServiceType

            ,且必须从可见界面启动。

            13. BroadcastReceiver 广播

            广播类型

            • 标准广播:异步发送,所有接收器同时收到
            • 有序广播:同步依次传递,可被截断、修改
            • 本地广播:只在应用内传播,更安全高效
            • 粘性广播:已废弃(API 28+),不建议使用

            接收系统广播

            class BootReceiver : BroadcastReceiver() {
                override fun onReceive(context: Context, intent: Intent) {
                    if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
                        // 开机后执行的逻辑
                        val workRequest = OneTimeWorkRequestBuilder<StartupWorker>().build()
                        WorkManager.getInstance(context).enqueue(workRequest)
                    }
                }
            }
            
            // 注册到 Manifest
            <receiver
                android:name=".BootReceiver"
                android:exported="true"
                android:directBootAware="true">
                <intent-filter>
                    <action android:name="android.intent.action.BOOT_COMPLETED"/>
                    <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
                </intent-filter>
            </receiver>
            
            <!-- 需要权限 -->
            <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

            动态注册广播

            private lateinit var receiver: BroadcastReceiver
            
            override fun onResume() {
                super.onResume()
                receiver = object : BroadcastReceiver() {
                    override fun onReceive(context: Context?, intent: Intent?) {
                        when (intent?.action) {
                            Intent.ACTION_AIRPLANE_MODE_CHANGED -> {
                                val isAirplaneMode = intent.getBooleanExtra("state", false)
                                Log.d("TAG", "飞行模式: $isAirplaneMode")
                            }
                            Intent.ACTION_BATTERY_LOW -> {
                                // 低电量
                            }
                            ConnectivityManager.CONNECTIVITY_ACTION -> {
                                checkNetwork()
                            }
                        }
                    }
                }
            
                val filter = IntentFilter().apply {
                    addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
                    addAction(Intent.ACTION_BATTERY_LOW)
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
                } else {
                    registerReceiver(receiver, filter)
                }
            }
            
            override fun onPause() {
                super.onPause()
                unregisterReceiver(receiver)
            }

            发送自定义广播

            // 标准广播
            val intent = Intent("com.example.MY_ACTION").apply {
                putExtra("message", "hello")
                setPackage(packageName)  // Android 8.0+ 必须指定包名
            }
            sendBroadcast(intent)
            
            // 有序广播
            sendOrderedBroadcast(intent, null, object : BroadcastReceiver() {
                override fun onReceive(context: Context?, intent: Intent?) {
                    val finalResult = resultCode
                    Log.d("TAG", "最终结果: $finalResult")
                }
            }, null, Activity.RESULT_OK, null, null)
            
            // 接收器中处理有序广播
            class MyOrderedReceiver : BroadcastReceiver() {
                override fun onReceive(context: Context?, intent: Intent?) {
                    if (intent?.action == "com.example.MY_ACTION") {
                        // 可以修改结果
                        resultCode = 100
                        // 可以中断传递
                        abortBroadcast()
                    }
                }
            }

            LocalBroadcastManager(本地广播)

            // 推荐使用 Flow/EventBus 替代,更现代化
            // 旧方式:
            val manager = LocalBroadcastManager.getInstance(this)
            
            // 发送
            val intent = Intent("LOCAL_ACTION").apply {
                putExtra("data", "hello")
            }
            manager.sendBroadcast(intent)
            
            // 注册
            manager.registerReceiver(receiver, IntentFilter("LOCAL_ACTION"))
            
            // 取消注册
            manager.unregisterReceiver(receiver)

            现代替代方案:Flow / Channel

            // 单例事件总线
            object EventBus {
                private val _events = MutableSharedFlow<AppEvent>()
                val events = _events.asSharedFlow()
            
                suspend fun emit(event: AppEvent) {
                    _events.emit(event)
                }
            }
            
            sealed class AppEvent {
                data class ShowToast(val message: String) : AppEvent()
                data object RefreshList : AppEvent()
            }
            
            // 发送
            lifecycleScope.launch {
                EventBus.emit(AppEvent.ShowToast("操作成功"))
            }
            
            // 接收
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    EventBus.events.collect { event ->
                        when (event) {
                            is AppEvent.ShowToast -> Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_SHORT).show()
                            is AppEvent.RefreshList -> loadData()
                        }
                    }
                }
            }

            💡

            Android 7.0+ 限制:

            大部分隐式系统广播不再在 Manifest 中注册接收,需使用动态注册或 JobScheduler。

            14. 数据存储方案

            Android 数据存储对比

            方案适用场景特点
            SharedPreferences键值对、简单配置XML 存储、简单易用
            DataStore替代 SharedPreferences协程友好、异步、类型安全
            Room 数据库结构化数据、复杂查询SQLite ORM、类型安全
            文件存储图片、文件、缓存内部/外部存储
            ContentProvider跨应用共享数据标准化接口
            网络云端数据REST API、GraphQL

            SharedPreferences 示例

            // 写入
            val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
            prefs.edit {
                putString("username", "张三")
                putInt("age", 25)
                putBoolean("isVip", true)
                putStringSet("tags", setOf("kotlin", "android"))
                apply()  // apply() 异步,commit() 同步
            }
            
            // 读取
            val username = prefs.getString("username", "")
            val age = prefs.getInt("age", 0)
            
            // 监听变化
            prefs.registerOnSharedPreferenceChangeListener { prefs, key ->
                when (key) {
                    "username" -> { /* 用户名变化 */ }
                }
            }
            
            // Kotlin 便捷写法(推荐,使用 KTX)
            val Context.settingsPrefs by preferences("settings")
            var username by settingsPrefs.stringPreferences("username", "")
            var age by settingsPrefs.intPreferences("age", 0)

            DataStore(推荐替代 SharedPreferences)

            implementation 'androidx.datastore:datastore-preferences:1.0.0'
            
            // Preferences DataStore
            val Context.dataStore by preferencesDataStore(name = "settings")
            
            class SettingsRepository(private val context: Context) {
                private object Keys {
                    val USERNAME = stringPreferencesKey("username")
                    val AGE = intPreferencesKey("age")
                    val DARK_MODE = booleanPreferencesKey("dark_mode")
                }
            
                val settings: Flow<Settings> = context.dataStore.data.map { prefs ->
                    Settings(
                        username = prefs[Keys.USERNAME] ?: "",
                        age = prefs[Keys.AGE] ?: 0,
                        darkMode = prefs[Keys.DARK_MODE] ?: false
                    )
                }
            
                suspend fun updateUsername(name: String) {
                    context.dataStore.edit { prefs ->
                        prefs[Keys.USERNAME] = name
                    }
                }
            
                suspend fun updateSettings(settings: Settings) {
                    context.dataStore.edit { prefs ->
                        prefs[Keys.USERNAME] = settings.username
                        prefs[Keys.AGE] = settings.age
                        prefs[Keys.DARK_MODE] = settings.darkMode
                    }
                }
            }
            
            // Proto DataStore(更类型安全)
            val Context.userProtoDataStore by dataStore(
                fileName = "user.pb",
                serializer = UserSerializer
            )

            文件存储

            // 写入内部存储(私有,卸载时删除)
            val file = File(filesDir, "data.txt")
            file.writeText("Hello File")
            file.appendText("\n新内容")
            
            // 读取
            val content = file.readText()
            val lines = file.readLines()
            
            // 缓存目录(系统空间不足时自动清理)
            val cacheFile = File(cacheDir, "temp.json")
            cacheFile.writeText("""{"key":"value"}""")
            // 定期清理缓存
            cacheDir.deleteRecursively()
            
            // 外部存储(私有目录,无需权限)
            val externalFile = File(getExternalFilesDir(null), "notes.txt")
            externalFile.writeText("外部文件内容")
            
            // 下载目录
            val downloadFile = File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "file.pdf")
            
            // Assets 目录(只读资源)
            val assetContent = assets.open("config.json").bufferedReader().use { it.readText() }

            Scoped Storage(分区存储)

            Android 10+ 引入分区存储,应用只能访问自己的私有目录和公共媒体文件:

            // 保存照片到公共 Pictures 目录
            fun saveImageToGallery(context: Context, bitmap: Bitmap): Uri? {
                val values = ContentValues().apply {
                    put(MediaStore.Images.Media.DISPLAY_NAME, "photo_${System.currentTimeMillis()}.jpg")
                    put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
                    put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/MyApp")
                    put(MediaStore.Images.Media.IS_PENDING, 1)
                }
            
                val uri = context.contentResolver.insert(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    values
                )
            
                uri?.let {
                    context.contentResolver.openOutputStream(it)?.use { stream ->
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream)
                    }
                    values.clear()
                    values.put(MediaStore.Images.Media.IS_PENDING, 0)
                    context.contentResolver.update(it, values, null, null)
                }
                return uri
            }
            
            // 查询媒体文件
            fun queryImages(context: Context): List<ImageItem> {
                val projection = arrayOf(
                    MediaStore.Images.Media._ID,
                    MediaStore.Images.Media.DISPLAY_NAME,
                    MediaStore.Images.Media.DATE_ADDED
                )
                val cursor = context.contentResolver.query(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    projection,
                    null, null,
                    "${MediaStore.Images.Media.DATE_ADDED} DESC"
                )
            
                val images = mutableListOf<ImageItem>()
                cursor?.use {
                    while (it.moveToNext()) {
                        val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
                        val name = it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
                        val contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
                        images.add(ImageItem(id, name, contentUri))
                    }
                }
                return images
            }

            15. Room 数据库

            Room 简介

            Room 是 Google 提供的 SQLite 抽象层,提供编译时校验、与 LiveData/Flow 集成、协程支持。

            添加依赖

            implementation 'androidx.room:room-runtime:2.6.1'
            implementation 'androidx.room:room-ktx:2.6.1'
            ksp 'androidx.room:room-compiler:2.6.1'  // 推荐用 KSP 替代 kapt

            1. 定义 Entity(实体类)

            @Entity(
                tableName = "users",
                indices = [Index(value = ["email"], unique = true)]
            )
            data class User(
                @PrimaryKey(autoGenerate = true) val id: Long = 0,
                @ColumnInfo(name = "user_name") val name: String,
                val age: Int,
                val email: String,
                @ColumnInfo(defaultValue = "0") val isActive: Int = 1,
                val createdAt: Long = System.currentTimeMillis()
            )
            
            // 一对多关系
            @Entity(
                tableName = "orders",
                foreignKeys = [
                    ForeignKey(
                        entity = User::class,
                        parentColumns = ["id"],
                        childColumns = ["user_id"],
                        onDelete = ForeignKey.CASCADE
                    )
                ]
            )
            data class Order(
                @PrimaryKey val id: Long,
                @ColumnInfo(name = "user_id") val userId: Long,
                val amount: Double,
                @ColumnInfo(defaultValue = "pending") val status: String
            )
            
            // 嵌入关系
            @Entity
            data class UserWithAddress(
                @Embedded val user: User,
                @Embedded(prefix = "addr_") val address: Address
            )
            
            // 多对多关系
            @Entity(primaryKeys = ["userId", "groupId"])
            data class UserGroupCrossRef(
                val userId: Long,
                val groupId: Long
            )

            2. 定义 DAO(数据访问对象)

            @Dao
            interface UserDao {
                // 查询全部
                @Query("SELECT * FROM users ORDER BY name ASC")
                fun getAllUsers(): Flow<List<User>>
            
                // 按 ID 查询
                @Query("SELECT * FROM users WHERE id = :userId")
                suspend fun getUserById(userId: Long): User?
            
                // 条件查询
                @Query("SELECT * FROM users WHERE age BETWEEN :minAge AND :maxAge")
                suspend fun getUsersByAgeRange(minAge: Int, maxAge: Int): List<User>
            
                // 返回 LiveData
                @Query("SELECT * FROM users WHERE isActive = 1")
                fun getActiveUsers(): LiveData<List<User>>
            
                // 插入
                @Insert(onConflict = OnConflictStrategy.REPLACE)
                suspend fun insertUser(user: User): Long
            
                @Insert
                suspend fun insertUsers(users: List<User>): List<Long>
            
                // 更新
                @Update
                suspend fun updateUser(user: User): Int
            
                // 删除
                @Delete
                suspend fun deleteUser(user: User): Int
            
                @Query("DELETE FROM users WHERE id = :id")
                suspend fun deleteById(id: Long): Int
            
                @Query("DELETE FROM users")
                suspend fun deleteAll()
            
                // 搜索
                @Query("SELECT * FROM users WHERE user_name LIKE '%' || :keyword || '%'")
                fun searchUsers(keyword: String): Flow<List<User>>
            
                // 聚合查询
                @Query("SELECT COUNT(*) FROM users")
                suspend fun getUserCount(): Int
            
                @Query("SELECT AVG(age) FROM users")
                suspend fun getAverageAge(): Double
            
                // 关系查询
                @Transaction
                @Query("SELECT * FROM users WHERE id = :userId")
                suspend fun getUserWithOrders(userId: Long): UserWithOrders
            
                // 原生 SQL
                @RawQuery(observedEntities = [User::class])
                fun searchUsersRaw(query: SupportSQLiteQuery): Flow<List<User>>
            }
            
            // 关系数据类
            data class UserWithOrders(
                @Embedded val user: User,
                @Relation(
                    parentColumn = "id",
                    entityColumn = "user_id"
                )
                val orders: List<Order>
            )

            3. 定义 Database

            @Database(
                entities = [User::class, Order::class, UserGroupCrossRef::class],
                version = 2,
                exportSchema = true
            )
            @TypeConverters(Converters::class)
            abstract class AppDatabase : RoomDatabase() {
                abstract fun userDao(): UserDao
                abstract fun orderDao(): OrderDao
            
                companion object {
                    @Volatile
                    private var INSTANCE: AppDatabase? = null
            
                    fun getDatabase(context: Context): AppDatabase {
                        return INSTANCE ?: synchronized(this) {
                            val instance = Room.databaseBuilder(
                                context.applicationContext,
                                AppDatabase::class.java,
                                "app_database"
                            )
                            .addMigrations(MIGRATION_1_2)
                            .addCallback(object : Callback() {
                                override fun onCreate(db: SupportSQLiteDatabase) {
                                    // 数据库首次创建时执行
                                }
                                override fun onOpen(db: SupportSQLiteDatabase) {
                                    // 数据库打开时执行
                                }
                            })
                            .fallbackToDestructiveMigration()  // 开发时使用
                            .build()
                            INSTANCE = instance
                            instance
                        }
                    }
                }
            }
            
            // 类型转换器
            class Converters {
                @TypeConverter
                fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }
            
                @TypeConverter
                fun dateToTimestamp(date: Date?): Long? = date?.time
            
                @TypeConverter
                fun fromStringList(value: String?): List<String> =
                    value?.split(",") ?: emptyList()
            
                @TypeConverter
                fun toStringList(list: List<String>?): String =
                    list?.joinToString(",") ?: ""
            }

            4. 在 Repository 中使用

            class UserRepository @Inject constructor(
                private val userDao: UserDao
            ) {
                val allUsers: Flow<List<User>> = userDao.getAllUsers()
            
                suspend fun insert(user: User) = userDao.insertUser(user)
                suspend fun delete(user: User) = userDao.deleteUser(user)
                suspend fun getById(id: Long) = userDao.getUserById(id)
                fun search(keyword: String) = userDao.searchUsers(keyword)
            }
            
            // ViewModel
            @HiltViewModel
            class UserViewModel @Inject constructor(
                private val repo: UserRepository
            ) : ViewModel() {
            
                val users = repo.allUsers.stateIn(
                    scope = viewModelScope,
                    started = SharingStarted.WhileSubscribed(5000),
                    initialValue = emptyList()
                )
            
                fun addUser(name: String, age: Int) {
                    viewModelScope.launch {
                        try {
                            repo.insert(User(name = name, age = age))
                        } catch (e: Exception) {
                            // 处理错误
                        }
                    }
                }
            }

            数据库升级

            val MIGRATION_1_2 = object : Migration(1, 2) {
                override fun migrate(db: SupportSQLiteDatabase) {
                    db.execSQL("ALTER TABLE users ADD COLUMN phone TEXT")
                    db.execSQL("CREATE INDEX index_users_email ON users(email)")
                }
            }
            
            val MIGRATION_2_3 = object : Migration(2, 3) {
                override fun migrate(db: SupportSQLiteDatabase) {
                    // 复杂迁移:创建新表、迁移数据、删除旧表
                    db.execSQL("CREATE TABLE users_new (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)")
                    db.execSQL("INSERT INTO users_new (id, name, email) SELECT id, user_name, email FROM users")
                    db.execSQL("DROP TABLE users")
                    db.execSQL("ALTER TABLE users_new RENAME TO users")
                }
            }
            
            // 自动迁移(简单字段增删)
            @Database(
                entities = [User::class],
                version = 3,
                autoMigrations = [
                    AutoMigration(from = 2, to = 3)
                ]
            )
            abstract class AppDatabase : RoomDatabase() { ... }

            16. 网络请求

            常用网络库

            • Retrofit:类型安全的 REST 客户端(推荐)
            • OkHttp:底层 HTTP 客户端
            • Ktor Client:Kotlin 跨平台网络库
            • Volley:轻量级请求库(Google 出品)

            Retrofit 完整示例

            添加依赖

            implementation 'com.squareup.retrofit2:retrofit:2.9.0'
            implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
            implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
            implementation 'com.squareup.okhttp3:okhttp:4.12.0'

            定义数据类

            data class Article(
                val id: Int,
                val title: String,
                val content: String,
                @SerializedName("created_at") val createdAt: String,
                @SerializedName("image_url") val imageUrl: String? = null
            )
            
            data class ApiResponse<T>(
                val code: Int,
                val message: String,
                val data: T?
            )
            
            data class PageResponse<T>(
                val items: List<T>,
                val total: Int,
                val page: Int,
                val pageSize: Int
            )

            定义 API 接口

            interface ApiService {
                @GET("articles")
                suspend fun getArticles(
                    @Query("page") page: Int,
                    @Query("size") size: Int = 20,
                    @Query("category") category: String? = null
                ): ApiResponse<PageResponse<Article>>
            
                @GET("articles/{id}")
                suspend fun getArticleById(@Path("id") id: Int): ApiResponse<Article>
            
                @POST("articles")
                suspend fun createArticle(@Body article: Article): ApiResponse<Article>
            
                @FormUrlEncoded
                @POST("login")
                suspend fun login(
                    @Field("username") username: String,
                    @Field("password") password: String
                ): ApiResponse<Token>
            
                @Multipart
                @POST("upload")
                suspend fun uploadImage(
                    @Part file: MultipartBody.Part,
                    @Part("description") description: RequestBody
                ): ApiResponse<String>
            
                @PUT("articles/{id}")
                suspend fun updateArticle(
                    @Path("id") id: Int,
                    @Body article: Article
                ): ApiResponse<Article>
            
                @DELETE("articles/{id}")
                suspend fun deleteArticle(@Path("id") id: Int): ApiResponse<Unit>
            
                @Headers("Cache-Control: max-age=640000")
                @GET("config")
                suspend fun getConfig(): ApiResponse<AppConfig>
            
                // 流式响应(大文件下载)
                @Streaming
                @GET("download/{file}")
                suspend fun downloadFile(@Path("file") fileName: String): ResponseBody
            }

            创建 Retrofit 实例

            @Module
            @InstallIn(SingletonComponent::class)
            object NetworkModule {
            
                @Provides
                @Singleton
                fun provideOkHttpClient(
                    @ApplicationContext context: Context
                ): OkHttpClient {
                    // 缓存配置
                    val cacheSize = 10 * 1024 * 1024L  // 10MB
                    val cache = Cache(context.cacheDir, cacheSize)
            
                    // 日志拦截器
                    val loggingInterceptor = HttpLoggingInterceptor().apply {
                        level = if (BuildConfig.DEBUG)
                            HttpLoggingInterceptor.Level.BODY
                        else
                            HttpLoggingInterceptor.Level.NONE
                    }
            
                    // Token 拦截器
                    val authInterceptor = Interceptor { chain ->
                        val token = TokenManager.getToken()
                        val request = if (token != null) {
                            chain.request().newBuilder()
                                .addHeader("Authorization", "Bearer $token")
                                .addHeader("Accept", "application/json")
                                .build()
                        } else {
                            chain.request()
                        }
                        chain.proceed(request)
                    }
            
                    // Token 刷新拦截器
                    val tokenRefreshInterceptor = Interceptor { chain ->
                        val response = chain.proceed(chain.request())
                        if (response.code == 401) {
                            // 尝试刷新 Token
                            synchronized(this) {
                                val newToken = refreshToken()
                                if (newToken != null) {
                                    response.close()
                                    val newRequest = chain.request().newBuilder()
                                        .addHeader("Authorization", "Bearer $newToken")
                                        .build()
                                    return@Interceptor chain.proceed(newRequest)
                                }
                            }
                        }
                        response
                    }
            
                    return OkHttpClient.Builder()
                        .cache(cache)
                        .addInterceptor(loggingInterceptor)
                        .addInterceptor(authInterceptor)
                        .addInterceptor(tokenRefreshInterceptor)
                        .connectTimeout(30, TimeUnit.SECONDS)
                        .readTimeout(30, TimeUnit.SECONDS)
                        .writeTimeout(30, TimeUnit.SECONDS)
                        .build()
                }
            
                @Provides
                @Singleton
                fun provideRetrofit(client: OkHttpClient): Retrofit {
                    return Retrofit.Builder()
                        .baseUrl(BuildConfig.API_URL)
                        .client(client)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build()
                }
            
                @Provides
                @Singleton
                fun provideApiService(retrofit: Retrofit): ApiService {
                    return retrofit.create(ApiService::class.java)
                }
            }

            在 Repository 和 ViewModel 中调用

            class ArticleRepository @Inject constructor(
                private val api: ApiService
            ) {
                suspend fun getArticles(page: Int): Result<List<Article>> {
                    return try {
                        val response = api.getArticles(page)
                        if (response.code == 200 && response.data != null) {
                            Result.success(response.data.items)
                        } else {
                            Result.failure(Exception(response.message))
                        }
                    } catch (e: Exception) {
                        Result.failure(e)
                    }
                }
            }
            
            @HiltViewModel
            class ArticleViewModel @Inject constructor(
                private val repo: ArticleRepository
            ) : ViewModel() {
            
                private val _state = MutableStateFlow<UiState<List<Article>>>(UiState.Loading)
                val state: StateFlow<UiState<List<Article>>> = _state.asStateFlow()
            
                fun loadArticles(page: Int) {
                    viewModelScope.launch {
                        _state.value = UiState.Loading
                        val result = repo.getArticles(page)
                        _state.value = result.fold(
                            onSuccess = { UiState.Success(it) },
                            onFailure = { UiState.Error(it.message ?: "未知错误") }
                        )
                    }
                }
            }
            
            sealed class UiState<out T> {
                data object Loading : UiState<Nothing>()
                data class Success<T>(val data: T) : UiState<T>()
                data class Error(val message: String) : UiState<Nothing>()
            }

            💡

            线程:

            Retrofit 与 suspend 函数配合时自动在 IO 线程执行,ViewModelScope 在主线程接收结果。

            文件下载

            suspend fun downloadFile(url: String, saveFile: File) {
                val response = api.downloadFile(url)
                response.byteStream().use { input ->
                    saveFile.outputStream().use { output ->
                        val buffer = ByteArray(8192)
                        var bytesRead: Int
                        while (input.read(buffer).also { bytesRead = it } != -1) {
                            output.write(buffer, 0, bytesRead)
                        }
                    }
                }
            }

            17. JSON 解析

            常用 JSON 库

            特点推荐度
            GsonGoogle 出品,简单易用⭐⭐⭐⭐
            MoshiSquare 出品,性能优、Kotlin 友好⭐⭐⭐⭐⭐
            kotlinx.serializationKotlin 官方,编译时生成,跨平台⭐⭐⭐⭐⭐

            Gson 示例

            implementation 'com.google.code.gson:gson:2.10.1'
            
            data class User(
                @SerializedName("user_name") val name: String,
                val age: Int,
                @SerializedName("created_at") val createdAt: Date? = null
            )
            
            val gson = GsonBuilder()
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
                .setPrettyPrinting()
                .create()
            
            // JSON 字符串 → 对象
            val json = """{"user_name":"张三","age":25}"""
            val user = gson.fromJson(json, User::class.java)
            
            // 对象 → JSON
            val jsonStr = gson.toJson(user)
            
            // 解析数组
            val jsonArray = """[{"user_name":"张三"},{"user_name":"李四"}]"""
            val listType = object : TypeToken<List<User>>() {}.type
            val users: List<User> = gson.fromJson(jsonArray, listType)
            
            // 自定义序列化器
            class DateSerializer : JsonSerializer<Date> {
                override fun serialize(src: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
                    return JsonPrimitive(src?.time ?: 0)
                }
            }
            
            val gsonWithCustom = GsonBuilder()
                .registerTypeAdapter(Date::class.java, DateSerializer())
                .create()

            Moshi 示例

            implementation 'com.squareup.moshi:moshi:1.15.0'
            implementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
            ksp 'com.squareup.moshi:moshi-kotlin-codegen:1.15.0'
            
            @JsonClass(generateAdapter = true)
            data class User(
                @Json(name = "user_name") val name: String,
                val age: Int,
                @Json(name = "created_at") val createdAt: Long
            )
            
            val moshi = Moshi.Builder()
                .addLast(KotlinJsonAdapterFactory())
                .build()
            
            val adapter = moshi.adapter(User::class.java)
            val user = adapter.fromJson(json)
            val jsonStr = adapter.toJson(user)
            
            // 列表
            val listType = Types.newParameterizedType(List::class.java, User::class.java)
            val listAdapter = moshi.adapter<List<User>>(listType)
            
            // 自定义适配器
            class DateAdapter {
                @ToJson fun toJson(@Timestamp date: Long): String = SimpleDateFormat("yyyy-MM-dd").format(Date(date))
                @FromJson @Timestamp fun fromJson(s: String): Long = SimpleDateFormat("yyyy-MM-dd").parse(s)!!.time
            }
            
            @Retention(AnnotationRetention.RUNTIME)
            @JsonQualifier
            annotation class Timestamp

            kotlinx.serialization 示例

            plugins {
                id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.20'
            }
            
            dependencies {
                implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2'
            }
            
            @Serializable
            data class User(
                @SerialName("user_name") val name: String,
                val age: Int,
                val roles: List<String> = emptyList()
            )
            
            // 配置 JSON
            val json = Json {
                ignoreUnknownKeys = true
                isLenient = true
                encodeDefaults = true
                prettyPrint = true
            }
            
            // 解析
            val user = json.decodeFromString<User>(jsonString)
            
            // 序列化
            val jsonStr = json.encodeToString(user)
            
            // 多态序列化
            @Serializable
            sealed class Shape {
                @Serializable
                @SerialName("circle")
                data class Circle(val radius: Double) : Shape()
            
                @Serializable
                @SerialName("rectangle")
                data class Rectangle(val width: Double, val height: Double) : Shape()
            }
            
            val shape: Shape = Shape.Circle(5.0)
            val shapeJson = json.encodeToString(shape)  // 自动包含 type 字段

            18. 多线程与协程

            为什么需要多线程

            Android 主线程(UI 线程)不能执行耗时操作(网络、数据库、文件 IO),否则会触发 ANR(Application Not Responding)。

            传统方式

            // 方式1:Thread
            Thread {
                val data = fetchData()  // IO 操作
                runOnUiThread {
                    textView.text = data
                }
            }.start()
            
            // 方式2:Handler
            val handler = Handler(Looper.getMainLooper())
            Thread {
                val data = fetchData()
                handler.post { textView.text = data }
            }.start()
            
            // 方式3:ExecutorService
            val executor = Executors.newFixedThreadPool(4)
            executor.execute {
                val data = fetchData()
                handler.post { updateUI(data) }
            }
            
            // 方式4:AsyncTask(已废弃,不推荐)

            Kotlin 协程(推荐)

            implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
            implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'

            基础用法

            // ViewModel 中使用
            class MyViewModel : ViewModel() {
                fun loadData() {
                    viewModelScope.launch {  // 自动随 ViewModel 销毁取消
                        try {
                            _state.value = UiState.Loading
                            val result = withContext(Dispatchers.IO) {
                                // IO 线程执行耗时操作
                                api.getData()
                            }
                            _state.value = UiState.Success(result)
                        } catch (e: Exception) {
                            _state.value = UiState.Error(e.message ?: "Error")
                        }
                    }
                }
            }
            
            // Activity/Fragment 中使用
            lifecycleScope.launch {
                // 自动随生命周期取消
                val data = withContext(Dispatchers.IO) { fetchData() }
                updateUI(data)
            }
            
            // 全局协程(避免使用,优先用 scope 绑定生命周期)
            GlobalScope.launch {  // 不推荐
                // ...
            }

            常用 Dispatcher

            Dispatcher线程适用场景
            Dispatchers.Main主线程UI 更新
            Dispatchers.IOIO 线程池(64线程)网络、数据库、文件
            Dispatchers.DefaultCPU 密集线程池(CPU核心数)CPU 计算任务
            Dispatchers.Unconfined不限定线程特殊场景

            并发执行多个任务

            viewModelScope.launch {
                // async 并发启动,await 等待结果
                val users = async(Dispatchers.IO) { api.getUsers() }
                val posts = async(Dispatchers.IO) { api.getPosts() }
                val config = async(Dispatchers.IO) { api.getConfig() }
            
                // 等待所有任务完成
                val result = Triple(users.await(), posts.await(), config.await())
                updateUI(result)
            
                // 或使用 awaitAll
                val results = awaitAll(users, posts, config)
            }
            
            // 取消所有任务
            viewModelScope.coroutineContext.cancelChildren()

            超时与重试

            // 超时
            viewModelScope.launch {
                try {
                    val result = withTimeout(5000) {  // 5 秒超时
                        api.fetchData()
                    }
                } catch (e: TimeoutCancellationException) {
                    showError("请求超时")
                }
            }
            
            // 重试机制
            suspend fun <T> retry(
                times: Int = 3,
                initialDelay: Long = 100,
                maxDelay: Long = 1000,
                factor: Double = 2.0,
                block: suspend () -> T
            ): T {
                var currentDelay = initialDelay
                repeat(times - 1) {
                    try {
                        return block()
                    } catch (e: Exception) {
                        delay(currentDelay)
                        currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
                    }
                }
                return block()  // 最后一次尝试
            }
            
            viewModelScope.launch {
                val data = retry(times = 3) { api.fetchData() }
                updateUI(data)
            }

            Flow 数据流

            // 定义 Flow
            fun countDown(): Flow<Int> = flow {
                for (i in 10 downTo 1) {
                    emit(i)
                    delay(1000)
                }
            }
            
            // 收集 Flow
            lifecycleScope.launch {
                countDown()
                    .onStart { showLoading() }
                    .onCompletion { hideLoading() }
                    .catch { e -> showError(e.message) }
                    .flowOn(Dispatchers.IO)  // 指定上游线程
                    .collect { value ->
                        binding.tvCount.text = value.toString()
                    }
            }
            
            // 常用 Flow 操作符
            fun getDataFlow(): Flow<List<Item>> = flow {
                emit(api.getData())
            }
            .map { items -> items.filter { it.isActive } }
            .filter { it.size > 5 }
            .distinctUntilChanged()
            .debounce(300)  // 防抖
            .buffer()       // 缓冲
            .take(10)       // 取前 10 个
            .drop(1)        // 跳过第 1 个
            .combine(otherFlow) { a, b -> a + b }
            .flatMapLatest { id -> api.getDetail(id) }  // 最新优先
            
            // Room + Flow(数据变化自动通知)
            @Query("SELECT * FROM users")
            fun observeUsers(): Flow<List<User>>
            
            // StateFlow(热流,总有最新值)
            private val _uiState = MutableStateFlow(UiState())
            val uiState: StateFlow<UiState> = _uiState.asStateFlow()
            
            // SharedFlow(事件流)
            private val _events = MutableSharedFlow<UiEvent>()
            val events = _events.asSharedFlow()
            
            // 在 Compose 中收集
            val state by viewModel.uiState.collectAsState()
            
            // 传统 View 中收集
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    viewModel.uiState.collect { state ->
                        updateUI(state)
                    }
                }
            }

            19. Jetpack 组件库

            Jetpack 概览

            Jetpack 是 Google 提供的一套现代化 Android 开发库,遵循最佳实践。

            核心组件

            ViewModel

            保存 UI 数据,不受配置更改影响:

            class CounterViewModel : ViewModel() {
                private val _count = MutableLiveData(0)
                val count: LiveData<Int> = _count
            
                // StateFlow(推荐)
                private val _state = MutableStateFlow(0)
                val state: StateFlow<Int> = _state.asStateFlow()
            
                fun increment() {
                    _count.value = (_count.value ?: 0) + 1
                    _state.value++
                }
            
                // 清理资源
                override fun onCleared() {
                    super.onCleared()
                    // 取消订阅等
                }
            
                // ViewModel 工厂(带参数)
                companion object {
                    fun factory(id: Long) = object : ViewModelProvider.Factory {
                        override fun <T : ViewModel> create(modelClass: Class<T>): T {
                            return DetailViewModel(id) as T
                        }
                    }
                }
            }
            
            // Activity 中
            private val viewModel: CounterViewModel by viewModels()
            private val sharedViewModel: SharedViewModel by activityViewModels()
            
            // Fragment 中
            private val viewModel: CounterViewModel by viewModels()
            private val parentVM: SharedViewModel by activityViewModels()
            
            // 带工厂(Hilt 自动处理)
            @HiltViewModel
            class DetailViewModel @Inject constructor(
                private val repo: Repository,
                @Assisted savedStateHandle: SavedStateHandle
            ) : ViewModel() {
                val itemId: Long = savedStateHandle["id"] ?: 0
            }

            LiveData / StateFlow

            // LiveData(生命周期感知,旧方式)
            val name = MutableLiveData<String>()
            name.observe(this) { /* UI 更新 */ }
            
            // LiveData 转换
            val users: LiveData<List<User>> = Transformations.map(repo.users) { list ->
                list.filter { it.isActive }
            }
            
            // StateFlow(协程友好,推荐)
            private val _uiState = MutableStateFlow(UiState())
            val uiState: StateFlow<UiState> = _uiState.asStateFlow()
            
            // 在 Activity 中收集
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    viewModel.uiState.collect { state ->
                        updateUI(state)
                    }
                }
            }
            
            // 在 Compose 中收集
            @Composable
            fun Screen(viewModel: MyViewModel = hiltViewModel()) {
                val state by viewModel.uiState.collectAsState()
            }
            
            // Lifecycle-aware
            lifecycleScope.launch {
                lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                    launch { viewModel.state.collect { ... } }
                    launch { viewModel.events.collect { ... } }
                }
            }

            Lifecycle

            // 实现 LifecycleObserver
            class MyLocationListener(
                private val lifecycle: Lifecycle
            ) : DefaultLifecycleObserver {
            
                override fun onStart(owner: LifecycleOwner) {
                    // 开始监听位置
                }
            
                override fun onStop(owner: LifecycleOwner) {
                    // 停止监听
                }
            }
            
            // 注册
            lifecycle.addObserver(MyLocationListener(lifecycle))
            
            // 在协程中使用
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.RESUMED) {
                    // 仅在 RESUMED 时执行
                    locationFlow.collect { updateMap(it) }
                }
            }

            Paging 3 分页加载

            implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
            
            class UserPagingSource(
                private val api: ApiService
            ) : PagingSource<Int, User>() {
            
                override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
                    val page = params.key ?: 1
                    return try {
                        val response = api.getUsers(page, params.loadSize)
                        LoadResult.Page(
                            data = response.data,
                            prevKey = if (page == 1) null else page - 1,
                            nextKey = if (response.data.isEmpty()) null else page + 1
                        )
                    } catch (e: Exception) {
                        LoadResult.Error(e)
                    }
                }
            
                override fun getRefreshKey(state: PagingState<Int, User>): Int? {
                    return state.anchorPosition?.let { anchorPosition ->
                        state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                            ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
                    }
                }
            }
            
            class UserRepository @Inject constructor(private val api: ApiService) {
                fun getUsers(): Flow<PagingData<User>> {
                    return Pager(
                        config = PagingConfig(
                            pageSize = 20,
                            enablePlaceholders = false,
                            prefetchDistance = 5,
                            initialLoadSize = 40
                        ),
                        pagingSourceFactory = { UserPagingSource(api) }
                    ).flow
                }
            }
            
            @HiltViewModel
            class UserListViewModel @Inject constructor(
                repo: UserRepository
            ) : ViewModel() {
                val users = repo.getUsers().cachedIn(viewModelScope)
            }
            
            // Adapter
            class UserPagingAdapter : PagingDataAdapter<User, UserViewHolder>(DIFF_CALLBACK) {
                companion object {
                    val DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
                        override fun areItemsTheSame(oldItem: User, newItem: User) = oldItem.id == newItem.id
                        override fun areContentsTheSame(oldItem: User, newItem: User) = oldItem == newItem
                    }
                }
            }
            
            // 在 Fragment 中
            viewLifecycleOwner.lifecycleScope.launch {
                viewModel.users.collectLatest { pagingData ->
                    adapter.submitData(pagingData)
                }
            }
            
            // 加载状态监听
            adapter.addLoadStateListener { loadState ->
                binding.progressBar.isVisible = loadState.refresh is LoadState.Loading
                binding.btnRetry.isVisible = loadState.refresh is LoadState.Error
            }

            20. Jetpack Compose UI 框架

            什么是 Compose

            Jetpack Compose 是 Android 全新的声明式 UI 框架,使用 Kotlin 编写 UI,无需 XML 布局文件。

            启用 Compose

            android {
                buildFeatures {
                    compose true
                }
                composeOptions {
                    kotlinCompilerExtensionVersion "1.5.4"
                }
            }
            
            dependencies {
                implementation platform('androidx.compose:compose-bom:2024.01.00')
                implementation 'androidx.compose.ui:ui'
                implementation 'androidx.compose.ui:ui-graphics'
                implementation 'androidx.compose.ui:ui-tooling-preview'
                implementation 'androidx.compose.material3:material3'
                implementation 'androidx.activity:activity-compose:1.8.2'
                implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
                implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0'
                debugImplementation 'androidx.compose.ui:ui-tooling'
            }

            基础示例

            class MainActivity : ComponentActivity() {
                override fun onCreate(savedInstanceState: Bundle?) {
                    super.onCreate(savedInstanceState)
                    setContent {
                        MyAppTheme {
                            GreetingScreen()
                        }
                    }
                }
            }
            
            @Composable
            fun GreetingScreen() {
                var count by remember { mutableStateOf(0) }
            
                Scaffold(
                    topBar = {
                        TopAppBar(
                            title = { Text("示例") },
                            navigationIcon = {
                                IconButton(onClick = { /* 返回 */ }) {
                                    Icon(Icons.Default.ArrowBack, contentDescription = "返回")
                                }
                            }
                        )
                    },
                    floatingActionButton = {
                        FloatingActionButton(onClick = { count++ }) {
                            Icon(Icons.Default.Add, contentDescription = "新增")
                        }
                    }
                ) { paddingValues ->
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(paddingValues)
                            .padding(16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        Text(
                            text = "点击次数:$count",
                            fontSize = 24.sp,
                            fontWeight = FontWeight.Bold
                        )
                        Spacer(modifier = Modifier.height(16.dp))
                        Button(onClick = { count++ }) {
                            Text("点击 +1")
                        }
                    }
                }
            }

            常用组件

            // 文本
            Text(
                text = "Hello",
                fontSize = 16.sp,
                color = Color.Gray,
                fontWeight = FontWeight.Bold,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis
            )
            
            // 图片
            AsyncImage(  // Coil
                model = "https://example.com/image.jpg",
                contentDescription = "image",
                modifier = Modifier.size(100.dp).clip(CircleShape),
                contentScale = ContentScale.Crop
            )
            
            // 输入框
            var text by remember { mutableStateOf("") }
            OutlinedTextField(
                value = text,
                onValueChange = { text = it },
                label = { Text("用户名") },
                leadingIcon = { Icon(Icons.Default.Person, null) },
                isError = text.isEmpty(),
                supportingText = { if (text.isEmpty()) Text("不能为空") }
            )
            
            // 列表
            LazyColumn(
                contentPadding = PaddingValues(16.dp),
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(items, key = { it.id }) { item ->
                    ItemRow(item)
                }
            
                item {
                    if (isLoading) {
                        CircularProgressIndicator(modifier = Modifier.fillMaxWidth().padding(16.dp))
                    }
                }
            }
            
            // 网格
            LazyVerticalGrid(columns = GridCells.Fixed(2)) {
                items(items) { item -> GridItem(item) }
            }
            
            // 可滚动行
            LazyRow {
                items(categories) { category ->
                    CategoryChip(category)
                }
            }
            
            // 卡片
            Card(
                modifier = Modifier.fillMaxWidth().padding(8.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
                shape = RoundedCornerShape(12.dp)
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Text("标题", style = MaterialTheme.typography.titleMedium)
                    Spacer(modifier = Modifier.height(8.dp))
                    Text("描述", style = MaterialTheme.typography.bodyMedium)
                }
            }

            状态管理

            // 局部状态
            @Composable
            fun CounterScreen() {
                var count by remember { mutableStateOf(0) }
                var name by remember { mutableStateOf("") }
            
                Counter(count, onIncrement = { count++ })
            }
            
            // 保存状态(配置变化时)
            var count by rememberSaveable { mutableStateOf(0) }
            
            // ViewModel 集成
            @Composable
            fun ListScreen(viewModel: ListViewModel = hiltViewModel()) {
                val state by viewModel.state.collectAsState()
                val events by viewModel.events.collectAsState(null)
            
                LaunchedEffect(events) {
                    events?.let { event ->
                        when (event) {
                            is UiEvent.ShowToast -> {
                                // 显示 Toast
                            }
                        }
                    }
                }
            
                when (state) {
                    is UiState.Loading -> CircularProgressIndicator()
                    is UiState.Success -> UserList((state as UiState.Success).data)
                    is UiState.Error -> ErrorView((state as UiState.Error).message)
                }
            }
            
            // 副作用
            LaunchedEffect(key1 = itemId) {
                // 在组合时启动协程
                viewModel.loadDetail(itemId)
            }
            
            DisposableEffect(key1 = userId) {
                val subscription = subscribeToUser(userId)
                onDispose {
                    subscription.cancel()
                }
            }
            
            SideEffect {
                // 每次重组时执行
                analytics.trackScreen("HomeScreen")
            }

            主题系统

            @Composable
            fun MyAppTheme(content: @Composable () -> Unit) {
                val colorScheme = if (isSystemInDarkTheme()) {
                    darkColorScheme(
                        primary = Purple80,
                        secondary = PurpleGrey80,
                        tertiary = Pink80
                    )
                } else {
                    lightColorScheme(
                        primary = Purple40,
                        secondary = PurpleGrey40,
                        tertiary = Pink40
                    )
                }
            
                MaterialTheme(
                    colorScheme = colorScheme,
                    typography = Typography,
                    content = content
                )
            }
            
            // 动态取色(Android 12+)
            @Composable
            fun MyAppTheme(content: @Composable () -> Unit) {
                val context = LocalContext.current
                val colorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                    dynamicLightColorScheme(context)
                } else {
                    lightColorScheme()
                }
                MaterialTheme(colorScheme = colorScheme, content = content)
            }

            导航

            implementation 'androidx.navigation:navigation-compose:2.7.6'
            
            @Composable
            fun AppNav() {
                val navController = rememberNavController()
            
                NavHost(navController = navController, startDestination = "home") {
                    composable("home") {
                        HomeScreen(onNavigateToDetail = { id ->
                            navController.navigate("detail/$id")
                        })
                    }
            
                    composable(
                        route = "detail/{itemId}",
                        arguments = listOf(navArgument("itemId") { type = NavType.LongType })
                    ) { backStackEntry ->
                        val itemId = backStackEntry.arguments?.getLong("itemId") ?: 0
                        DetailScreen(itemId = itemId, onBack = { navController.popBackStack() })
                    }
            
                    composable(
                        route = "profile?tab={tab}",
                        arguments = listOf(
                            navArgument("tab") {
                                type = NavType.StringType
                                defaultValue = "info"
                            }
                        )
                    ) {
                        ProfileScreen()
                    }
                }
            }

            💡

            趋势:

            Google 新发布的应用和库越来越倾向于 Compose,新项目建议考虑使用。

            21. 性能优化

            启动速度优化

            • 使用 App Startup 库延迟初始化第三方 SDK
            • 避免在 Application.onCreate() 中执行耗时操作
            • 使用 Trace.beginSection() 追踪启动阶段
            • 启用 R8 代码压缩,减小 APK 体积
            • 使用 Baseline Profile 优化 ART 编译
            // App Startup
            implementation 'androidx.startup:startup-runtime:1.1.1'
            
            class AnalyticsInitializer : Initializer<Analytics> {
                override fun create(context: Context): Analytics {
                    return Analytics.init(context)
                }
                override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
            }
            
            // Manifest
            <provider
                android:name="androidx.startup.InitializationProvider"
                android:authorities="${applicationId}.androidx-startup"
                android:exported="false"
                tools:node="merge">
                <meta-data
                    android:name="com.example.AnalyticsInitializer"
                    android:value="androidx.startup"/>
            </provider>
            
            // Baseline Profile
            implementation 'androidx.profileinstaller:profileinstaller:1.3.1'
            
            // benchmark 模块
            @ExperimentalBaselineProfilesApi
            class BaselineProfileGenerator {
                @get:Rule val rule = BaselineProfileRule()
            
                @Test
                fun generateBaselineProfile() = rule.collectBaselineProfile(
                    packageName = "com.example.myapp"
                ) {
                    startActivityAndWait()
                    // 关键用户路径
                }
            }

            内存优化

            // 避免内存泄漏
            // ❌ 错误:匿名内部类持有 Activity 引用
            handler.postDelayed(object : Runnable {
                override fun run() {
                    textView.text = "hello"  // 隐式持有 Activity
                }
            }, 60000)
            
            // ✅ 正确:使用 LifecycleScope
            lifecycleScope.launch {
                delay(60000)
                binding.textView.text = "hello"
            }
            
            // 避免 Activity 泄漏
            // ❌ 静态持有 Activity 引用
            companion object { var activity: MainActivity? = null }
            
            // ✅ 使用 WeakReference 或 Application Context
            val weakActivity = WeakReference(activity)
            
            // 避免 Handler 泄漏
            // ❌ 错误
            val handler = Handler()
            
            // ✅ 正确:静态内部类 + WeakReference
            class SafeHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
                private val weakActivity = WeakReference(activity)
                override fun handleMessage(msg: Message) {
                    weakActivity.get()?.let { /* 处理消息 */ }
                }
            }
            
            // 避免 Bitmap 内存溢出
            val options = BitmapFactory.Options().apply {
                inJustDecodeBounds = true
            }
            BitmapFactory.decodeFile(path, options)
            options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
            options.inJustDecodeBounds = false
            val bitmap = BitmapFactory.decodeFile(path, options)
            
            // 大图加载
            implementation 'com.github.bumptech.glide:glide:4.16.0'
            
            Glide.with(context)
                .load(url)
                .override(800, 600)  // 限制尺寸
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(imageView)

            布局优化

            • 使用 <include> 复用布局
            • 使用 <merge> 减少根节点
            • 使用 ViewStub 延迟加载不常用视图
            • 扁平化布局,减少嵌套层数
            • 使用 Layout Inspector 工具分析布局
            • 避免过度绘制(overdraw)
            <!-- 复用布局 -->
            <include layout="@layout/layout_toolbar"/>
            
            <!-- 延迟加载 -->
            <ViewStub
                android:id="@+id/stub"
                android:layout="@layout/layout_expensive"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            
            <!-- Kotlin 中加载 -->
            binding.stub.inflate()
            
            <!-- merge 减少层级 -->
            <merge xmlns:android="...">
                <TextView ... />
                <Button ... />
            </merge>
            
            <!-- ConstraintLayout 优化 -->
            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <!-- 扁平结构,避免嵌套 LinearLayout -->
            </androidx.constraintlayout.widget.ConstraintLayout>

            列表优化

            • RecyclerView 使用 setHasFixedSize(true)
            • 使用 DiffUtilListAdapter 精准刷新
            • 图片加载使用 Glide / Coil,开启缓存
            • 避免在 onBindViewHolder 中创建对象或设置监听器
            • 设置 RecycledViewPool 跨 RecyclerView 共享
            • 使用 setItemViewCacheSize(20) 增加缓存数量
            • 预加载 Prefetch 提前准备下一个 item
            recyclerView.apply {
                setHasFixedSize(true)
                setItemViewCacheSize(20)
                recycledViewPool.setMaxRecycledViews(0, 30)
            
                // 嵌套 RecyclerView 共享 ViewPool
                setRecycledViewPool(sharedPool)
            }

            APK 瘦身

            • 启用 minifyEnabled true 和 R8 压缩
            • 启用 shrinkResources true 移除未使用资源
            • 使用 WebP 格式替代 PNG(体积减少 30%+)
            • 使用 Android App Bundle(AAB)按需下发资源
            • 删除未使用的资源:Analyze → Inspect Code → Unused Resources
            • 使用 resConfigs 限制语言资源
            • 检查并删除无用依赖
            android {
                defaultConfig {
                    // 仅保留中文、英文资源
                    resourceConfigurations += setOf("zh", "en")
                }
            
                buildTypes {
                    release {
                        minifyEnabled true
                        shrinkResources true
                        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                    }
                }
            
                // Splits:按 ABI 拆分
                splits {
                    abi {
                        enable true
                        reset()
                        include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
                        universalApk false
                    }
                }
            }
            
            // APK Analyzer 工具
            // Build → Analyze APK → 查看各部分体积

            性能分析工具

            工具用途
            Android ProfilerCPU / 内存 / 网络 / 能耗监控
            Layout Inspector实时查看 UI 层级
            Database Inspector查看 Room 数据库数据
            StrictMode检测主线程违规操作
            LeakCanary内存泄漏检测
            Android Benchmark性能基准测试
            APK Analyzer分析 APK 体积构成
            Systrace / Perfetto系统级性能追踪
            // StrictMode
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyLog()
                    .build()
            )
            StrictMode.setVmPolicy(
                StrictMode.VmPolicy.Builder()
                    .detectLeakedClosableObjects()
                    .detectLeakedSqlLiteObjects()
                    .penaltyLog()
                    .build()
            )
            
            // LeakCanary
            debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'

            22. 调试与测试

            Logcat 日志

            import android.util.Log
            
            Log.v("TAG", "Verbose")  // 详细
            Log.d("TAG", "Debug")    // 调试
            Log.i("TAG", "Info")     // 信息
            Log.w("TAG", "Warning")  // 警告
            Log.e("TAG", "Error")    // 错误
            Log.wtf("TAG", "WTF")    // 严重错误
            
            // Kotlin 封装
            object AppLog {
                private const val TAG = "MyApp"
                private val isDebug = BuildConfig.DEBUG
            
                fun d(msg: String, tag: String = TAG) {
                    if (isDebug) Log.d(tag, msg)
                }
            
                fun e(msg: String, e: Throwable? = null, tag: String = TAG) {
                    Log.e(tag, msg, e)
                }
            
                fun json(json: String, tag: String = TAG) {
                    if (isDebug) {
                        try {
                            val gson = GsonBuilder().setPrettyPrinting().create()
                            val obj = gson.fromJson(json, Any::class.java)
                            d(gson.toJson(obj), tag)
                        } catch (e: Exception) {
                            d(json, tag)
                        }
                    }
                }
            }
            
            // Timber(推荐)
            implementation 'com.jakewharton.timber:timber:5.0.1'
            
            // Application 中
            class MyApp : Application() {
                override fun onCreate() {
                    super.onCreate()
                    if (BuildConfig.DEBUG) {
                        Timber.plant(Timber.DebugTree())
                    } else {
                        Timber.plant(CrashReportingTree())
                    }
                }
            }
            
            // 使用
            Timber.d("Debug message")
            Timber.e(exception, "Error with %s", detail)
            Timber.tag("CustomTag").i("Message")

            断点调试

            • F8:单步跳过(Step Over)
            • F7:单步进入(Step Into)
            • Shift+F8:跳出(Step Out)
            • F9:运行到下一个断点
            • Alt+F9:运行到光标处
            • 条件断点:右键断点设置条件,仅在满足时触发
            • 日志断点:不暂停,仅输出日志
            • 异常断点:在抛出异常时暂停

            单元测试(JUnit)

            testImplementation 'junit:junit:4.13.2'
            testImplementation 'org.mockito:mockito-core:5.8.0'
            testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'
            testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
            testImplementation 'app.cash.turbine:turbine:1.0.0'  // Flow 测试
            
            // 测试类
            class CalculatorTest {
                private lateinit var calculator: Calculator
            
                @Before
                fun setup() {
                    calculator = Calculator()
                }
            
                @After
                fun tearDown() {
                    // 清理
                }
            
                @Test
                fun testAdd() {
                    val result = calculator.add(2, 3)
                    assertEquals(5, result)
                }
            
                @Test
                fun testDivide() {
                    assertThrows(ArithmeticException::class.java) {
                        calculator.divide(1, 0)
                    }
                }
            
                @Test
                fun testAddWithParams() {
                    // 参数化测试
                    listOf(
                        Triple(1, 2, 3),
                        Triple(0, 0, 0),
                        Triple(-1, 1, 0)
                    ).forEach { (a, b, expected) ->
                        assertEquals(expected, calculator.add(a, b))
                    }
                }
            }
            
            // Mockito 测试
            class UserRepositoryTest {
                private lateinit var api: ApiService
                private lateinit var repo: UserRepository
            
                @Before
                fun setup() {
                    api = mock()
                    repo = UserRepository(api)
                }
            
                @Test
                fun testGetUser() = runTest {
                    // Given
                    whenever(api.getUser(123)).thenReturn(ApiResponse(200, "ok", User("张三", 25)))
            
                    // When
                    val result = repo.getUser(123)
            
                    // Then
                    assertEquals("张三", result.getOrNull()?.name)
                    verify(api).getUser(123)
                }
            }
            
            // ViewModel 测试
            @OptIn(ExperimentalCoroutinesApi::class)
            class UserViewModelTest {
                private val testDispatcher = UnconfinedTestDispatcher()
                private val testScope = TestScope(testDispatcher)
            
                @Before
                fun setup() {
                    Dispatchers.setMain(testDispatcher)
                }
            
                @After
                fun tearDown() {
                    Dispatchers.resetMain()
                }
            
                @Test
                fun testLoadUsers() = testScope.runTest {
                    val repo = mock<UserRepository>()
                    val users = flowOf(listOf(User("张三", 25)))
                    whenever(repo.allUsers).thenReturn(users)
            
                    val viewModel = UserViewModel(repo)
            
                    viewModel.uiState.test {
                        assertEquals(UiState.Loading, awaitItem())
                        assertEquals(UiState.Success(listOf(User("张三", 25))), awaitItem())
                    }
                }
            }

            UI 测试(Espresso)

            androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
            androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
            androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
            androidTestImplementation 'androidx.test:runner:1.5.2'
            androidTestImplementation 'androidx.test:rules:1.5.0'
            
            @RunWith(AndroidJUnit4::class)
            class LoginActivityTest {
            
                @get:Rule
                val activityRule = ActivityScenarioRule(LoginActivity::class.java)
            
                @Before
                fun setup() {
                    Intents.init()
                }
            
                @After
                fun tearDown() {
                    Intents.release()
                }
            
                @Test
                fun testLoginSuccess() {
                    // 输入用户名
                    onView(withId(R.id.etUsername))
                        .perform(typeText("testuser"), closeSoftKeyboard())
            
                    // 输入密码
                    onView(withId(R.id.etPassword))
                        .perform(typeText("123456"), closeSoftKeyboard())
            
                    // 点击登录
                    onView(withId(R.id.btnLogin))
                        .perform(click())
            
                    // 验证跳转
                    intended(hasComponent(HomeActivity::class.java.name))
            
                    // 验证欢迎文本
                    onView(withId(R.id.tvWelcome))
                        .check(matches(withText(containsString("欢迎"))))
                }
            
                @Test
                fun testLoginEmptyUsername() {
                    onView(withId(R.id.btnLogin)).perform(click())
                    onView(withId(R.id.etUsername))
                        .check(matches(hasErrorText("用户名不能为空")))
                }
            
                @Test
                fun testRecyclerView() {
                    // 滑动到指定位置
                    onView(withId(R.id.recyclerView))
                        .perform(RecyclerViewActions.scrollToPosition<UserAdapter.UserViewHolder>(5))
            
                    // 点击某个 item
                    onView(withId(R.id.recyclerView))
                        .perform(RecyclerViewActions.actionOnItemAtPosition<UserAdapter.UserViewHolder>(
                            5, click()
                        ))
            
                    // 验证 item 文本
                    onView(withText("张三"))
                        .check(matches(isDisplayed()))
                }
            }
            
            // Compose UI 测试
            androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
            
            @RunWith(AndroidJUnit4::class)
            class MyComposeTest {
                @get:Rule
                val composeTestRule = createComposeRule()
            
                @Test
                fun testCounter() {
                    composeTestRule.setContent {
                        CounterScreen()
                    }
            
                    composeTestRule.onNodeWithText("点击 +1").performClick()
                    composeTestRule.onNodeWithText("点击次数:1").assertIsDisplayed()
                }
            }

            Robolectric(无需设备)

            testImplementation 'org.robolectric:robolectric:4.11.1'
            
            @RunWith(RobolectricTestRunner::class)
            @Config(sdk = [33], application = MyApplication::class)
            class MainActivityTest {
                @Test
                fun testButtonClick() {
                    val activity = Robolectric.buildActivity(MainActivity::class.java)
                        .create().start().resume().get()
            
                    val button = activity.findViewById<Button>(R.id.btn)
                    button.performClick()
            
                    val textView = activity.findViewById<TextView>(R.id.tvResult)
                    assertEquals("Clicked", textView.text.toString())
            
                    // 验证 Activity 跳转
                    val intent = ShadowActivity(activity).peekNextStartedActivity()
                    assertEquals(DetailActivity::class.java.name, intent.component?.className)
                }
            }

            测试覆盖率

            // build.gradle
            android {
                buildTypes {
                    debug {
                        testCoverageEnabled true
                    }
                }
            }
            
            // JaCoCo 配置
            apply plugin: 'jacoco'
            
            task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
                reports {
                    xml.required = true
                    html.required = true
                }
            
                def fileFilter = [
                    '**/R.class',
                    '**/R$*.class',
                    '**/BuildConfig.*',
                    '**/*_HiltModules*',
                    '**/Hilt_*'
                ]
                def mainSrc = "$project.projectDir/src/main/java"
            
                sourceDirectories.setFrom(files([mainSrc]))
                classDirectories.setFrom(files([
                    fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
                ]))
                executionData.setFrom(fileTree(dir: "$buildDir", includes: [
                    "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec"
                ]))
            }

            23. 应用发布上架

            签名 APK

            发布版本必须使用正式签名(不可使用 debug 签名)。

            生成签名密钥

            keytool -genkey -v -keystore my-release-key.jks -keyalg RSA \
                -keysize 2048 -validity 10000 -alias my-key-alias
            
            # 查看证书信息
            keytool -list -v -keystore my-release-key.jks
            
            # 导出证书
            keytool -export -alias my-key-alias -keystore my-release-key.jks -file cert.crt
            
            # Google Play App Signing 使用 upload key
            # 上传密钥和 App Signing 密钥分离,更安全

            配置 Gradle 签名

            // gradle.properties(不要提交到 Git)
            RELEASE_STORE_FILE=/path/to/my-release-key.jks
            RELEASE_STORE_PASSWORD=xxx
            RELEASE_KEY_ALIAS=my-key-alias
            RELEASE_KEY_PASSWORD=xxx
            
            // 或使用环境变量
            // signingConfigs.release.storePassword System.getenv("KEY_STORE_PASSWORD")
            
            // app/build.gradle
            android {
                signingConfigs {
                    release {
                        storeFile file(RELEASE_STORE_FILE)
                        storePassword RELEASE_STORE_PASSWORD
                        keyAlias RELEASE_KEY_ALIAS
                        keyPassword RELEASE_KEY_PASSWORD
                        v1SigningEnabled true
                        v2SigningEnabled true
                    }
                }
                buildTypes {
                    release {
                        signingConfig signingConfigs.release
                        minifyEnabled true
                        shrinkResources true
                        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                    }
                }
            }

            构建发布包

            # APK
            ./gradlew assembleRelease
            
            # AAB (Google Play 推荐)
            ./gradlew bundleRelease
            
            # 指定渠道
            ./gradlew assembleProdRelease
            
            # 输出位置
            # APK: app/build/outputs/apk/release/app-release.apk
            # AAB: app/build/outputs/bundle/release/app-release.aab
            
            # 映射文件(用于反混淆崩溃日志)
            # app/build/outputs/mapping/release/mapping.txt
            # 务必保存每个版本的 mapping.txt!

            Google Play 上架流程

            1. 注册 Google Play 开发者账号(一次性 25 美元)
            2. 在 Google Play Console 创建应用
            3. 填写应用信息:
              • 标题(30 字符内)、简短描述(80 字符)、详细描述(4000 字符)
              • 应用图标 512x512 PNG
              • 特色图片 1024x500 PNG
              • 截图:手机(最少 2 张)、7 寸平板、10 寸平板
            4. 设置内容分级(IARC 问卷)
            5. 填写隐私政策 URL(强制)
            6. 设置定价和分发地区
            7. 声明广告、目标受众
            8. 上传 AAB 文件
            9. 创建发布轨道:内部测试 → 封闭测试 → 开放测试 → 正式版
            10. 提交审核(一般 1-7 天)
            11. 国内应用市场

              市场开发者费用审核时间备注
              华为应用市场免费1-3 天华为手机预装
              小米应用商店免费1-3 天MIUI 生态
              OPPO 软件商店免费1-3 天ColorOS 生态
              VIVO 应用商店免费1-3 天FuntouchOS
              应用宝(腾讯)免费1-3 天微信/QQ 生态
              360 手机助手免费1-3 天老牌市场

              上架必备资料:

              • 软件著作权登记证书(中国)
              • ICP 备案(有网站)
              • 隐私政策(详细)
              • 用户协议
              • 应用截图(高清、带文案)
              • 应用图标(各尺寸)

              多渠道打包

              // 使用 productFlavors
              android {
                  flavorDimensions += "channel"
                  productFlavors {
                      huawei { dimension "channel" }
                      xiaomi { dimension "channel" }
                      oppo { dimension "channel" }
                      tencent { dimension "channel" }
                  }
              }
              
              // 读取渠道号
              val channel = try {
                  val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
                  appInfo.metaData.getString("CHANNEL")
              } catch (e: Exception) { "unknown" }
              
              // Manifest 中配置
              <meta-data android:name="CHANNEL" android:value="${CHANNEL_NAME}"/>

              版本管理最佳实践

              // build.gradle
              defaultConfig {
                  versionCode 10     // 整数,每次升级递增(Google Play 要求严格递增)
                  versionName "2.1.0"  // 用户可见的版本号
              }
              
              // 语义化版本:主版本.次版本.修订号
              // 2.1.0 = 新增功能,向下兼容
              // 2.2.0 = 新增功能
              // 2.2.1 = 修复 bug
              // 3.0.0 = 重大变更,不兼容旧版本
              
              // 强制更新机制
              @Serializable
              data class VersionInfo(
                  val latestVersion: String,
                  val minRequiredVersion: String,
                  val downloadUrl: String,
                  val forceUpdate: Boolean,
                  val changelog: String
              )
              
              // 启动时检查版本
              suspend fun checkUpdate(): VersionInfo? {
                  val current = packageManager.getPackageInfo(packageName, 0).versionName
                  val remote = api.getVersionInfo()
                  return if (compareVersion(current, remote.minRequiredVersion) < 0) {
                      remote
                  } else null
              }

              24. 架构模式

              常见架构对比

              架构特点适用场景
              MVCController 臃肿,难测试小项目,不推荐
              MVPPresenter 持有 View 接口,易测试中等项目
              MVVM数据驱动,与 Jetpack 完美配合大部分项目(推荐)
              MVI单向数据流,状态可预测复杂 UI 项目
              Clean Architecture分层清晰,高度解耦大型企业项目

              MVVM 架构详解

              // 分层:UI (Activity/Fragment/Compose) → ViewModel → Repository → Data Source
              
              // UI State
              sealed class UiState<out T> {
                  data object Loading : UiState<Nothing>()
                  data class Success<T>(val data: T) : UiState<T>()
                  data class Error(val message: String, val throwable: Throwable? = null) : UiState<Nothing>()
                  data object Empty : UiState<Nothing>()
              }
              
              // UI Event(一次性事件)
              sealed class UiEvent {
                  data class ShowToast(val message: String) : UiEvent()
                  data class Navigate(val route: String) : UiEvent()
                  data object NavigateBack : UiEvent()
              }
              
              // Repository
              class UserRepository @Inject constructor(
                  private val remoteDataSource: UserRemoteDataSource,
                  private val localDataSource: UserLocalDataSource
              ) {
                  fun getUsers(): Flow<List<User>> = flow {
                      // 缓存优先策略
                      val cached = localDataSource.getUsers()
                      if (cached.isNotEmpty()) emit(cached)
              
                      try {
                          val fresh = remoteDataSource.getUsers()
                          localDataSource.saveUsers(fresh)
                          emit(fresh)
                      } catch (e: Exception) {
                          if (cached.isEmpty()) throw e
                      }
                  }
              
                  suspend fun getUser(id: Long): User {
                      return localDataSource.getUser(id) ?: remoteDataSource.getUser(id).also {
                          localDataSource.saveUser(it)
                      }
                  }
              }
              
              // ViewModel
              @HiltViewModel
              class UserListViewModel @Inject constructor(
                  private val repo: UserRepository
              ) : ViewModel() {
              
                  private val _state = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
                  val state: StateFlow<UiState<List<User>>> = _state.asStateFlow()
              
                  private val _events = Channel<UiEvent>(Channel.BUFFERED)
                  val events = _events.receiveAsFlow()
              
                  init {
                      loadUsers()
                  }
              
                  fun loadUsers() {
                      viewModelScope.launch {
                          repo.getUsers()
                              .onStart { _state.value = UiState.Loading }
                              .catch { e ->
                                  _state.value = UiState.Error(e.message ?: "Unknown")
                              }
                              .collect { users ->
                                  _state.value = if (users.isEmpty()) UiState.Empty else UiState.Success(users)
                              }
                      }
                  }
              
                  fun onUserClick(user: User) {
                      viewModelScope.launch {
                          _events.send(UiEvent.Navigate("detail/${user.id}"))
                      }
                  }
              }
              
              // Fragment 中使用
              class UserListFragment : Fragment(R.layout.fragment_user_list) {
                  private val viewModel: UserListViewModel by viewModels()
                  private lateinit var binding: FragmentUserListBinding
              
                  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                      super.onViewCreated(view, savedInstanceState)
                      binding = FragmentUserListBinding.bind(view)
              
                      viewLifecycleOwner.lifecycleScope.launch {
                          repeatOnLifecycle(Lifecycle.State.STARTED) {
                              launch {
                                  viewModel.state.collect { state ->
                                      when (state) {
                                          UiState.Loading -> {
                                              binding.progressBar.isVisible = true
                                              binding.recyclerView.isVisible = false
                                          }
                                          is UiState.Success -> {
                                              binding.progressBar.isVisible = false
                                              binding.recyclerView.isVisible = true
                                              adapter.submitList(state.data)
                                          }
                                          is UiState.Error -> {
                                              binding.progressBar.isVisible = false
                                              binding.tvError.text = state.message
                                              binding.tvError.isVisible = true
                                          }
                                          UiState.Empty -> {
                                              binding.tvEmpty.isVisible = true
                                          }
                                      }
                                  }
                              }
              
                              launch {
                                  viewModel.events.collect { event ->
                                      when (event) {
                                          is UiEvent.ShowToast -> Toast.makeText(requireContext(), event.message, Toast.LENGTH_SHORT).show()
                                          is UiEvent.Navigate -> findNavController().navigate(event.route)
                                          is UiEvent.NavigateBack -> findNavController().popBackStack()
                                      }
                                  }
                              }
                          }
                      }
                  }
              }

              MVI 架构

              // Intent(用户意图)
              sealed class UserIntent {
                  data object LoadUsers : UserIntent()
                  data class UserClicked(val userId: Long) : UserIntent()
                  data class SearchUsers(val query: String) : UserIntent()
              }
              
              // State(状态)
              data class UserState(
                  val isLoading: Boolean = false,
                  val users: List<User> = emptyList(),
                  val error: String? = null,
                  val searchQuery: String = ""
              )
              
              // ViewModel
              @HiltViewModel
              class UserViewModel @Inject constructor(
                  private val repo: UserRepository
              ) : ViewModel() {
              
                  private val _state = MutableStateFlow(UserState())
                  val state: StateFlow<UserState> = _state.asStateFlow()
              
                  fun handleIntent(intent: UserIntent) {
                      when (intent) {
                          UserIntent.LoadUsers -> loadUsers()
                          is UserIntent.UserClicked -> navigateToDetail(intent.userId)
                          is UserIntent.SearchUsers -> searchUsers(intent.query)
                      }
                  }
              
                  private fun loadUsers() {
                      viewModelScope.launch {
                          _state.update { it.copy(isLoading = true) }
                          try {
                              val users = repo.getAllUsers()
                              _state.update { it.copy(isLoading = false, users = users) }
                          } catch (e: Exception) {
                              _state.update { it.copy(isLoading = false, error = e.message) }
                          }
                      }
                  }
              }
              
              // 在 Compose 中使用
              @Composable
              fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
                  val state by viewModel.state.collectAsState()
              
                  LaunchedEffect(Unit) {
                      viewModel.handleIntent(UserIntent.LoadUsers)
                  }
              
                  UserContent(
                      state = state,
                      onUserClick = { viewModel.handleIntent(UserIntent.UserClicked(it.id)) },
                      onSearch = { viewModel.handleIntent(UserIntent.SearchUsers(it)) }
                  )
              }

              Clean Architecture

              // 分层:Presentation → Domain → Data
              
              // Domain 层(纯 Kotlin,无 Android 依赖)
              // UseCase
              class GetUsersUseCase @Inject constructor(
                  private val repo: UserRepository
              ) {
                  operator fun invoke(): Flow<Result<List<User>>> = flow {
                      emit(Result.Loading)
                      try {
                          val users = repo.getUsers()
                          emit(Result.Success(users))
                      } catch (e: Exception) {
                          emit(Result.Error(e))
                      }
                  }
              }
              
              // Domain 接口
              interface UserRepository {
                  fun getUsers(): Flow<List<User>>
                  suspend fun getUser(id: Long): User
              }
              
              // Domain 实体
              data class User(
                  val id: Long,
                  val name: String,
                  val email: String
              )
              
              // Data 层实现
              class UserRepositoryImpl @Inject constructor(
                  private val remote: UserRemoteDataSource,
                  private val local: UserLocalDataSource,
                  private val mapper: UserMapper
              ) : UserRepository {
                  override fun getUsers(): Flow<List<User>> = flow {
                      val localUsers = local.getUsers().map(mapper::toDomain)
                      if (localUsers.isNotEmpty()) emit(localUsers)
              
                      val remoteUsers = remote.getUsers().map(mapper::toDomain)
                      local.saveUsers(remoteUsers.map(mapper::toEntity))
                      emit(remoteUsers)
                  }
              }
              
              // Presentation 层依赖 Domain UseCase
              @HiltViewModel
              class UserViewModel @Inject constructor(
                  private val getUsersUseCase: GetUsersUseCase
              ) : ViewModel() {
                  val users = getUsersUseCase().stateIn(
                      scope = viewModelScope,
                      started = SharingStarted.WhileSubscribed(5000),
                      initialValue = Result.Loading
                  )
              }

              推荐的目录结构

              app/src/main/java/com/example/myapp/
              ├── di/                        # Hilt 模块
              │   ├── NetworkModule.kt
              │   ├── DatabaseModule.kt
              │   └── RepositoryModule.kt
              ├── domain/                    # Domain 层
              │   ├── model/
              │   │   └── User.kt
              │   ├── repository/
              │   │   └── UserRepository.kt
              │   └── usecase/
              │       └── GetUsersUseCase.kt
              ├── data/                      # Data 层
              │   ├── remote/
              │   │   ├── ApiService.kt
              │   │   ├── dto/
              │   │   └── UserRemoteDataSource.kt
              │   ├── local/
              │   │   ├── AppDatabase.kt
              │   │   ├── dao/
              │   │   ├── entity/
              │   │   └── UserLocalDataSource.kt
              │   └── repository/
              │       └── UserRepositoryImpl.kt
              ├── presentation/              # Presentation 层
              │   ├── common/
              │   │   ├── UiState.kt
              │   │   └── UiEvent.kt
              │   ├── user/
              │   │   ├── UserListFragment.kt
              │   │   ├── UserListViewModel.kt
              │   │   └── UserAdapter.kt
              │   └── detail/
              │       ├── DetailFragment.kt
              │       └── DetailViewModel.kt
              └── util/                      # 工具类
                  ├── Extensions.kt
                  └── Constants.kt

              25. 依赖注入 Hilt

              什么是依赖注入

              依赖注入(DI)是一种设计模式,对象不再自己创建依赖,而是从外部注入。Hilt 是 Google 官方推荐的 Android DI 库,基于 Dagger。

              优势

              • 代码解耦,易于测试
              • 自动管理依赖的生命周期
              • 与 Jetpack 组件深度集成
              • 编译时校验,避免运行时错误

              添加依赖

              // 项目 build.gradle
              plugins {
                  id 'com.google.dagger.hilt.android' version '2.50' apply false
              }
              
              // app build.gradle
              plugins {
                  id 'com.google.dagger.hilt.android'
                  id 'com.google.devtools.ksp'  // 或 kapt
              }
              
              dependencies {
                  implementation 'com.google.dagger:hilt-android:2.50'
                  ksp 'com.google.dagger:hilt-compiler:2.50'  // 或 kapt
                  implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
              }
              
              // Application 类
              @HiltAndroidApp
              class MyApplication : Application()

              基本用法

              // @Inject 构造注入(推荐)
              class UserRepository @Inject constructor(
                  private val api: ApiService,
                  private val db: AppDatabase
              ) {
                  fun getUsers() = api.getUsers()
              }
              
              // @HiltViewModel 注入 ViewModel
              @HiltViewModel
              class UserViewModel @Inject constructor(
                  private val repo: UserRepository
              ) : ViewModel() {
                  val users = repo.getUsers()
              }
              
              // Fragment/Activity 注入
              @AndroidEntryPoint
              class UserListFragment : Fragment() {
                  private val viewModel: UserViewModel by viewModels()
                  // 也可以直接注入
                  @Inject lateinit var analytics: AnalyticsTracker
              }
              
              // Service / BroadcastReceiver 注入
              @AndroidEntryPoint
              class MyService : Service() {
                  @Inject lateinit var repo: UserRepository
              }
              
              @AndroidEntryPoint
              class MyReceiver : BroadcastReceiver() {
                  @Inject lateinit var repo: UserRepository
              }

              @Module 提供依赖

              @Module
              @InstallIn(SingletonComponent::class)  // 作用域
              object NetworkModule {
              
                  @Provides
                  @Singleton
                  fun provideOkHttpClient(): OkHttpClient {
                      return OkHttpClient.Builder()
                          .addInterceptor(HttpLoggingInterceptor())
                          .build()
                  }
              
                  @Provides
                  @Singleton
                  fun provideRetrofit(client: OkHttpClient): Retrofit {
                      return Retrofit.Builder()
                          .baseUrl("https://api.example.com/")
                          .client(client)
                          .addConverterFactory(GsonConverterFactory.create())
                          .build()
                  }
              
                  @Provides
                  @Singleton
                  fun provideApiService(retrofit: Retrofit): ApiService {
                      return retrofit.create(ApiService::class.java)
                  }
              }
              
              @Module
              @InstallIn(SingletonComponent::class)
              object DatabaseModule {
              
                  @Provides
                  @Singleton
                  fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
                      return Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
                  }
              
                  @Provides
                  fun provideUserDao(db: AppDatabase): UserDao = db.userDao()
              }
              
              // 接口绑定
              @Module
              @InstallIn(SingletonComponent::class)
              abstract class RepositoryModule {
              
                  @Binds
                  @Singleton
                  abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
              }

              Hilt 作用域(Components)

              Component生命周期对应注解
              SingletonComponentApplication@Singleton
              ActivityComponentActivity@ActivityScoped
              FragmentComponentFragment@FragmentScoped
              ViewComponentView@ViewScoped
              ServiceComponentService@ServiceScoped
              ViewModelComponentViewModel@ViewModelScoped
              @Module
              @InstallIn(ActivityComponent::class)
              object ActivityModule {
              
                  @Provides
                  @ActivityScoped
                  fun provideActivitySpecificDep(@ActivityContext context: Context): Something {
                      return Something(context)
                  }
              }

              Qualifier(限定符)

              // 多个相同类型的依赖
              @Qualifier
              @Retention(AnnotationRetention.BINARY)
              annotation class AuthInterceptorOkHttp
              
              @Qualifier
              @Retention(AnnotationRetention.BINARY)
              annotation class LoggingInterceptorOkHttp
              
              @Module
              @InstallIn(SingletonComponent::class)
              object NetworkModule {
              
                  @AuthInterceptorOkHttp
                  @Provides
                  fun provideAuthClient(): OkHttpClient { /* ... */ }
              
                  @LoggingInterceptorOkHttp
                  @Provides
                  fun provideLoggingClient(): OkHttpClient { /* ... */ }
              }
              
              // 使用
              class ApiService @Inject constructor(
                  @AuthInterceptorOkHttp private val client: OkHttpClient
              )

              多模块项目

              // 在子模块中
              @Module
              @InstallIn(SingletonComponent::class)
              @Module(includes = [CoreModule::class])  // 包含其他模块
              object FeatureModule {
                  // ...
              }
              
              // Entry Point(非 Hilt 管理的类中获取依赖)
              @EntryPoint
              @InstallIn(SingletonComponent::class)
              interface AnalyticsEntryPoint {
                  fun analytics(): Analytics
              }
              
              class NonHiltClass {
                  fun doSomething(context: Context) {
                      val entryPoint = EntryPointAccessors.fromApplication(
                          context.applicationContext,
                          AnalyticsEntryPoint::class.java
                      )
                      val analytics = entryPoint.analytics()
                  }
              }

              💡

              提示:

              Hilt 与 Compose 配合使用

              hiltViewModel()

              函数获取 ViewModel。

              26. 动画与转场

              View 动画

              属性动画 (ObjectAnimator)

              // 位移
              ObjectAnimator.ofFloat(view, "translationX", 0f, 200f).apply {
                  duration = 500
                  interpolator = AccelerateDecelerateInterpolator()
                  start()
              }
              
              // 缩放
              ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.5f).start()
              
              // 旋转
              ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).apply {
                  duration = 1000
                  repeatCount = ObjectAnimator.INFINITE
                  repeatMode = ObjectAnimator.REVERSE
                  start()
              }
              
              // 透明度
              ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).start()
              
              // 组合动画
              val animatorSet = AnimatorSet().apply {
                  playTogether(
                      ObjectAnimator.ofFloat(view, "translationX", 0f, 200f),
                      ObjectAnimator.ofFloat(view, "alpha", 1f, 0.5f),
                      ObjectAnimator.ofFloat(view, "rotation", 0f, 45f)
                  )
                  duration = 1000
                  start()
              }
              
              // 链式动画(PropertyValuesHolder)
              val scaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 1.5f, 1f)
              val scaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 1.5f, 1f)
              ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY).apply {
                  duration = 500
                  start()
              }
              
              // 使用 ViewPropertyAnimator(简化版)
              view.animate()
                  .translationX(200f)
                  .alpha(0.5f)
                  .rotation(45f)
                  .setDuration(500)
                  .setInterpolator(AccelerateDecelerateInterpolator())
                  .withEndAction { /* 动画结束 */ }
                  .start()

              XML 动画资源

              <!-- res/anim/slide_in_right.xml -->
              <set xmlns:android="http://schemas.android.com/apk/res/android">
                  <translate
                      android:fromXDelta="100%p"
                      android:toXDelta="0"
                      android:duration="300"/>
                  <alpha
                      android:fromAlpha="0"
                      android:toAlpha="1"
                      android:duration="300"/>
              </set>
              
              <!-- res/anim/fade_out.xml -->
              <alpha xmlns:android="http://schemas.android.com/apk/res/android"
                  android:fromAlpha="1"
                  android:toAlpha="0"
                  android:duration="300"/>
              
              // 代码中使用
              val anim = AnimationUtils.loadAnimation(context, R.anim.slide_in_right)
              view.startAnimation(anim)
              
              // Activity 转场动画
              overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left)

              Transition 框架(布局变化动画)

              // 布局变化时自动动画
              val transition = TransitionSet().apply {
                  addTransition(ChangeBounds())
                  addTransition(Fade(Fade.MODE_IN))
                  addTransition(Slide(Gravity.BOTTOM))
                  duration = 300
              }
              
              TransitionManager.beginDelayedTransition(container, transition)
              // 修改布局
              view.visibility = View.GONE
              textView.text = "新内容"
              
              // Scene 场景切换
              val scene1 = Scene.getSceneForLayout(container, R.layout.layout_1, context)
              val scene2 = Scene.getSceneForLayout(container, R.layout.layout_2, context)
              TransitionManager.go(scene2, AutoTransition())

              MotionLayout(高级布局动画)

              <!-- layout/activity_motion.xml -->
              <androidx.constraintlayout.motion.widget.MotionLayout
                  android:id="@+id/motionLayout"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  app:layoutDescription="@xml/motion_scene">
              
                  <ImageView
                      android:id="@+id/image"
                      android:layout_width="200dp"
                      android:layout_height="200dp"
                      android:src="@drawable/photo"/>
              </androidx.constraintlayout.motion.widget.MotionLayout>
              
              <!-- xml/motion_scene.xml -->
              <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto">
              
                  <Transition
                      app:constraintSetStart="@id/start"
                      app:constraintSetEnd="@id/end"
                      app:duration="500">
              
                      <OnSwipe
                          app:touchAnchorId="@id/image"
                          app:dragDirection="dragUp"/>
              
                      <KeyFrameSet>
                          <KeyAttribute
                              app:framePosition="50"
                              app:motionTarget="@id/image">
                              <CustomAttribute
                                  app:attributeName="rotation"
                                  app:customFloatValue="180"/>
                          </KeyAttribute>
                      </KeyFrameSet>
                  </Transition>
              
                  <ConstraintSet android:id="@+id/start">
                      <Constraint android:id="@id/image"
                          android:layout_width="200dp"
                          android:layout_height="200dp"
                          app:layout_constraintTop_toTopOf="parent"
                          app:layout_constraintStart_toStartOf="parent"/>
                  </ConstraintSet>
              
                  <ConstraintSet android:id="@+id/end">
                      <Constraint android:id="@id/image"
                          android:layout_width="match_parent"
                          android:layout_height="match_parent"/>
                  </ConstraintSet>
              </MotionScene>
              
              // 代码控制
              binding.motionLayout.transitionToStart()
              binding.motionLayout.transitionToEnd()
              binding.motionLayout.progress = 0.5f

              共享元素转场

              // Activity A 启动 Activity B
              val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                  this,
                  Pair(imageView, "shared_image"),
                  Pair(titleView, "shared_title")
              )
              startActivity(intent, options.toBundle())
              
              // 在两个 Activity 的 XML 中设置相同的 transitionName
              <ImageView
                  android:transitionName="shared_image"
                  ... />
              
              <TextView
                  android:transitionName="shared_title"
                  ... />
              
              // 启用 Activity 转场
              // values/styles.xml
              <item name="android:windowActivityTransitions">true</item>
              <item name="android:windowContentTransitions">true</item>

              Compose 动画

              // animate*AsState
              @Composable
              fun AnimatedBox() {
                  var expanded by remember { mutableStateOf(false) }
                  val size by animateDpAsState(
                      targetValue = if (expanded) 200.dp else 100.dp,
                      animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
                  )
              
                  Box(
                      modifier = Modifier
                          .size(size)
                          .background(Color.Blue)
                          .clickable { expanded = !expanded }
                  )
              }
              
              // infiniteTransition
              @Composable
              fun RotatingIcon() {
                  val infiniteTransition = rememberInfiniteTransition(label = "rotate")
                  val rotation by infiniteTransition.animateFloat(
                      initialValue = 0f,
                      targetValue = 360f,
                      animationSpec = infiniteRepeatable(
                          animation = tween(2000, easing = LinearEasing),
                          repeatMode = RepeatMode.Restart
                      ),
                      label = "rotation"
                  )
              
                  Icon(
                      imageVector = Icons.Default.Refresh,
                      contentDescription = null,
                      modifier = Modifier.rotate(rotation)
                  )
              }
              
              // AnimatedVisibility
              @Composable
              fun AnimatedContent() {
                  var visible by remember { mutableStateOf(true) }
              
                  AnimatedVisibility(
                      visible = visible,
                      enter = slideInVertically() + fadeIn(),
                      exit = slideOutVertically() + fadeOut()
                  ) {
                      Text("Hello")
                  }
              
                  Button(onClick = { visible = !visible }) {
                      Text("Toggle")
                  }
              }
              
              // updateTransition(多个属性同步动画)
              @Composable
              fun MultiPropertyAnimation() {
                  var selected by remember { mutableStateOf(false) }
                  val transition = updateTransition(selected, label = "box")
              
                  val size by transition.animateDp(label = "size") { if (it) 200.dp else 100.dp }
                  val color by transition.animateColor(label = "color") { if (it) Color.Red else Color.Blue }
              
                  Box(
                      modifier = Modifier
                          .size(size)
                          .background(color)
                          .clickable { selected = !selected }
                  )
              }

              Lottie 动画

              implementation 'com.airbnb.android:lottie:6.3.0'
              
              <com.airbnb.lottie.LottieAnimationView
                  android:id="@+id/lottieView"
                  android:layout_width="200dp"
                  android:layout_height="200dp"
                  app:lottie_rawRes="@raw/loading"
                  app:lottie_autoPlay="true"
                  app:lottie_loop="true"/>
              
              // 代码控制
              binding.lottieView.setAnimation("loading.json")  // assets 目录
              binding.lottieView.playAnimation()
              binding.lottieView.pauseAnimation()
              binding.lottieView.cancelAnimation()
              binding.lottieView.speed = 2f
              binding.lottieView.repeatCount = LottieDrawable.INFINITE
              
              // 远程 URL
              binding.lottieView.setAnimationFromUrl("https://example.com/anim.json")
              
              // Compose
              LottieAnimation(
                  composition = composition,
                  iterations = LottieConstants.IterateForever
              )

              27. 权限详解

              Android 权限分类

              类型说明示例
              普通权限安装时自动授予INTERNET, ACCESS_NETWORK_STATE
              危险权限运行时需要用户授权CAMERA, LOCATION, CONTACTS
              签名权限仅同签名应用可获得系统级权限
              特殊权限需要跳转设置页面授权SYSTEM_ALERT_WINDOW, MANAGE_EXTERNAL_STORAGE

              危险权限分组(API 33+)

              权限组权限
              CALENDARREAD_CALENDAR, WRITE_CALENDAR
              CAMERACAMERA
              CONTACTSREAD_CONTACTS, WRITE_CONTACTS
              LOCATIONACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
              MICROPHONERECORD_AUDIO
              PHONECALL_PHONE, READ_CALL_LOG
              SENSORSBODY_SENSORS
              SMSSEND_SMS, READ_SMS
              STORAGEREAD/WRITE_EXTERNAL_STORAGE(API 28+)
              NOTIFICATIONSPOST_NOTIFICATIONS(API 33+)

              完整的运行时权限请求流程

              class PermissionHelper(
                  private val activity: ComponentActivity
              ) {
                  private val requestPermissionLauncher = activity.registerForActivityResult(
                      ActivityResultContracts.RequestPermission()
                  ) { isGranted ->
                      permissionCallback?.invoke(isGranted)
                  }
              
                  private val requestMultiplePermissionsLauncher = activity.registerForActivityResult(
                      ActivityResultContracts.RequestMultiplePermissions()
                  ) { result ->
                      multiplePermissionsCallback?.invoke(result)
                  }
              
                  private var permissionCallback: ((Boolean) -> Unit)? = null
                  private var multiplePermissionsCallback: ((Map<String, Boolean>) -> Unit)? = null
              
                  fun requestPermission(
                      permission: String,
                      rationaleTitle: String = "",
                      rationaleMessage: String = "",
                      onResult: (Boolean) -> Unit
                  ) {
                      permissionCallback = onResult
              
                      when {
                          // 已授权
                          ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED -> {
                              onResult(true)
                          }
                          // 需要显示解释
                          activity.shouldShowRequestPermissionRationale(permission) -> {
                              MaterialAlertDialogBuilder(activity)
                                  .setTitle(rationaleTitle)
                                  .setMessage(rationaleMessage)
                                  .setPositiveButton("确定") { _, _ ->
                                      requestPermissionLauncher.launch(permission)
                                  }
                                  .setNegativeButton("取消") { _, _ ->
                                      onResult(false)
                                  }
                                  .show()
                          }
                          // 直接请求
                          else -> {
                              requestPermissionLauncher.launch(permission)
                          }
                      }
                  }
              
                  fun requestMultiplePermissions(
                      permissions: Array<String>,
                      onResult: (Map<String, Boolean>) -> Unit
                  ) {
                      multiplePermissionsCallback = onResult
                      requestMultiplePermissionsLauncher.launch(permissions)
                  }
              
                  fun openAppSettings() {
                      val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                          data = Uri.fromParts("package", activity.packageName, null)
                      }
                      activity.startActivity(intent)
                  }
              }
              
              // 使用
              val permissionHelper = PermissionHelper(this)
              
              permissionHelper.requestPermission(
                  permission = Manifest.permission.CAMERA,
                  rationaleTitle = "需要相机权限",
                  rationaleMessage = "应用需要使用相机拍照,请授予权限"
              ) { granted ->
                  if (granted) {
                      openCamera()
                  } else {
                      // 检查是否"不再询问"
                      if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                          showGoToSettingsDialog()
                      }
                  }
              }

              常见权限请求示例

              // 拍照权限
              Manifest.permission.CAMERA
              
              // 位置权限
              Manifest.permission.ACCESS_FINE_LOCATION  // 精确
              Manifest.permission.ACCESS_COARSE_LOCATION  // 粗略
              // API 34+ 后台位置
              Manifest.permission.ACCESS_BACKGROUND_LOCATION  // 需单独请求
              
              // 通知权限(API 33+)
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                  permissionHelper.requestPermission(Manifest.permission.POST_NOTIFICATIONS) { granted ->
                      if (granted) showNotification()
                  }
              }
              
              // 读取相册(API 33+)
              val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                  arrayOf(Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO)
              } else {
                  arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
              }
              
              // 录音权限
              Manifest.permission.RECORD_AUDIO
              
              // 悬浮窗(特殊权限)
              if (!Settings.canDrawOverlays(this)) {
                  val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                      Uri.parse("package:$packageName"))
                  startActivityForResult(intent, REQUEST_CODE_OVERLAY)
              }
              
              // 所有文件访问权限(API 30+)
              if (!Environment.isExternalStorageManager()) {
                  val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
                      Uri.parse("package:$packageName"))
                  startActivity(intent)
              }

              Android 各版本权限变化

              版本关键变化
              6.0 (23)引入运行时权限
              8.0 (26)隐式广播限制
              10 (29)分区存储(Scoped Storage)
              11 (30)一次性权限、后台位置限制
              12 (31)蓝牙权限拆分、前台服务限制
              13 (33)通知权限、媒体权限细分
              14 (34)后台活动限制增强

              ⚠️

              最佳实践:

              在用户真正需要时才请求权限,并提供清晰的理由说明。

              28. 相机与多媒体

              CameraX(推荐)

              CameraX 是 Google 提供的相机库,简化相机开发,兼容性好。

              implementation 'androidx.camera:camera-core:1.3.1'
              implementation 'androidx.camera:camera-camera2:1.3.1'
              implementation 'androidx.camera:camera-lifecycle:1.3.1'
              implementation 'androidx.camera:camera-view:1.3.1'

              布局文件

              <androidx.camera.view.PreviewView
                  android:id="@+id/previewView"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  app:scaleType="fillCenter"/>
              
              <Button
                  android:id="@+id/btnCapture"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="拍照"/>

              Activity 代码

              class CameraActivity : AppCompatActivity() {
                  private lateinit var binding: ActivityCameraBinding
                  private var imageCapture: ImageCapture? = null
                  private lateinit var cameraExecutor: ExecutorService
              
                  override fun onCreate(savedInstanceState: Bundle?) {
                      super.onCreate(savedInstanceState)
                      binding = ActivityCameraBinding.inflate(layoutInflater)
                      setContentView(binding.root)
              
                      if (allPermissionsGranted()) {
                          startCamera()
                      } else {
                          requestPermissions()
                      }
              
                      binding.btnCapture.setOnClickListener { takePhoto() }
                      cameraExecutor = Executors.newSingleThreadExecutor()
                  }
              
                  private fun startCamera() {
                      val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
              
                      cameraProviderFuture.addListener({
                          val cameraProvider = cameraProviderFuture.get()
              
                          // 预览
                          val preview = Preview.Builder()
                              .build()
                              .also {
                                  it.setSurfaceProvider(binding.previewView.surfaceProvider)
                              }
              
                          // 拍照
                          imageCapture = ImageCapture.Builder()
                              .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                              .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
                              .build()
              
                          // 图像分析
                          val imageAnalyzer = ImageAnalysis.Builder()
                              .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                              .build()
                              .also {
                                  it.setAnalyzer(cameraExecutor) { imageProxy ->
                                      processImage(imageProxy)
                                  }
                              }
              
                          // 选择后置摄像头
                          val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
              
                          try {
                              cameraProvider.unbindAll()
                              cameraProvider.bindToLifecycle(
                                  this,
                                  cameraSelector,
                                  preview,
                                  imageCapture,
                                  imageAnalyzer
                              )
                          } catch (e: Exception) {
                              Log.e("Camera", "绑定失败", e)
                          }
                      }, ContextCompat.getMainExecutor(this))
                  }
              
                  private fun takePhoto() {
                      val imageCapture = imageCapture ?: return
              
                      val photoFile = File(
                          getExternalFilesDir(Environment.DIRECTORY_PICTURES),
                          "photo_${System.currentTimeMillis()}.jpg"
                      )
              
                      val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
              
                      imageCapture.takePicture(
                          outputOptions,
                          ContextCompat.getMainExecutor(this),
                          object : ImageCapture.OnImageSavedCallback {
                              override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                                  val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
                                  // 保存到相册
                                  MediaScannerConnection.scanFile(
                                      this@CameraActivity,
                                      arrayOf(photoFile.absolutePath),
                                      null, null
                                  )
                                  Toast.makeText(this@CameraActivity, "照片已保存", Toast.LENGTH_SHORT).show()
                              }
              
                              override fun onError(exc: ImageCaptureException) {
                                  Log.e("Camera", "拍照失败: ${exc.message}", exc)
                              }
                          }
                      )
                  }
              
                  private fun processImage(imageProxy: ImageProxy) {
                      val mediaImage = imageProxy.image
                      if (mediaImage != null) {
                          val inputImage = InputImage.fromMediaImage(
                              mediaImage,
                              imageProxy.imageInfo.rotationDegrees
                          )
                          // 使用 ML Kit 进行人脸检测等
                      }
                      imageProxy.close()
                  }
              
                  override fun onDestroy() {
                      super.onDestroy()
                      cameraExecutor.shutdown()
                  }
              
                  private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
                      ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
                  }
              
                  companion object {
                      private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
                  }
              }

              ExoPlayer / Media3 视频播放

              implementation 'androidx.media3:media3-exoplayer:1.2.1'
              implementation 'androidx.media3:media3-ui:1.2.1'
              
              <androidx.media3.ui.PlayerView
                  android:id="@+id/playerView"
                  android:layout_width="match_parent"
                  android:layout_height="250dp"
                  app:show_buffering="when_playing"/>
              
              class VideoActivity : AppCompatActivity() {
                  private var player: ExoPlayer? = null
              
                  override fun onCreate(savedInstanceState: Bundle?) {
                      super.onCreate(savedInstanceState)
                      setContentView(R.layout.activity_video)
                      initializePlayer()
                  }
              
                  private fun initializePlayer() {
                      player = ExoPlayer.Builder(this).build().also { exoPlayer ->
                          binding.playerView.player = exoPlayer
              
                          val mediaItem = MediaItem.fromUri("https://example.com/video.mp4")
                          exoPlayer.setMediaItem(mediaItem)
              
                          // 监听
                          exoPlayer.addListener(object : Player.Listener {
                              override fun onPlaybackStateChanged(playbackState: Int) {
                                  when (playbackState) {
                                      Player.STATE_BUFFERING -> showLoading()
                                      Player.STATE_READY -> hideLoading()
                                      Player.STATE_ENDED -> replay()
                                  }
                              }
                          })
              
                          exoPlayer.prepare()
                          exoPlayer.playWhenReady = true
                      }
                  }
              
                  override fun onPause() {
                      super.onPause()
                      player?.pause()
                  }
              
                  override fun onDestroy() {
                      super.onDestroy()
                      player?.release()
                      player = null
                  }
              }

              音频播放 (MediaPlayer)

              val mediaPlayer = MediaPlayer().apply {
                  setAudioAttributes(
                      AudioAttributes.Builder()
                          .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                          .setUsage(AudioAttributes.USAGE_MEDIA)
                          .build()
                  )
                  setDataSource("https://example.com/music.mp3")
                  setOnPreparedListener { it.start() }
                  setOnCompletionListener { /* 播放完成 */ }
                  prepareAsync()
              }
              
              mediaPlayer.pause()
              mediaPlayer.seekTo(30000)  // 跳到 30 秒
              mediaPlayer.release()
              
              // 播放本地资源
              val mediaPlayer = MediaPlayer.create(context, R.raw.sound)
              mediaPlayer.start()

              图片选择器 (Photo Picker)

              // API 33+ 的系统图片选择器(无需权限)
              val pickImagesLauncher = registerForActivityResult(
                  ActivityResultContracts.PickMultipleVisualMedia(maxItems = 5)
              ) { uris ->
                  uris.forEach { uri ->
                      // 处理选中的图片
                      imageView.setImageURI(uri)
                  }
              }
              
              pickImagesLauncher.launch(
                  PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
              )
              
              // 单张图片
              val pickImageLauncher = registerForActivityResult(
                  ActivityResultContracts.PickVisualMedia()
              ) { uri ->
                  uri?.let { imageView.setImageURI(it) }
              }

              29. 位置与地图

              获取位置信息

              implementation 'com.google.android.gms:play-services-location:21.1.0'
              
              class LocationHelper(private val context: Context) {
              
                  private val fusedLocationClient: FusedLocationProviderClient =
                      LocationServices.getFusedLocationProviderClient(context)
              
                  // 获取当前位置(单次)
                  @SuppressLint("MissingPermission")
                  fun getCurrentLocation(onResult: (Location?) -> Unit) {
                      val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000)
                          .setMinUpdateIntervalMillis(500)
                          .build()
              
                      fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
                          .addOnSuccessListener { location ->
                              onResult(location)
                          }
                          .addOnFailureListener { e ->
                              Log.e("Location", "获取位置失败", e)
                              onResult(null)
                          }
                  }
              
                  // 持续监听位置
                  @SuppressLint("MissingPermission")
                  fun startLocationUpdates(
                      interval: Long = 5000,
                      onLocation: (Location) -> Unit
                  ) {
                      val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, interval)
                          .setMinUpdateIntervalMillis(interval / 2)
                          .build()
              
                      val locationCallback = object : LocationCallback() {
                          override fun onLocationResult(result: LocationResult) {
                              result.lastLocation?.let { onLocation(it) }
                          }
                      }
              
                      fusedLocationClient.requestLocationUpdates(
                          locationRequest,
                          locationCallback,
                          Looper.getMainLooper()
                      )
                  }
              
                  // 地理编码(地址 → 坐标)
                  suspend fun getLatLngFromAddress(address: String): LatLng? = withContext(Dispatchers.IO) {
                      val geocoder = Geocoder(context)
                      try {
                          val results = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                              var result: List<Address>? = null
                              geocoder.getFromLocationName(address, 1) { list ->
                                  result = list
                              }
                              result
                          } else {
                              @Suppress("DEPRECATION")
                              geocoder.getFromLocationName(address, 1)
                          }
                          results?.firstOrNull()?.let { LatLng(it.latitude, it.longitude) }
                      } catch (e: Exception) {
                          null
                      }
                  }
              
                  // 反向地理编码(坐标 → 地址)
                  suspend fun getAddressFromLatLng(lat: Double, lng: Double): String? = withContext(Dispatchers.IO) {
                      val geocoder = Geocoder(context)
                      try {
                          val results = geocoder.getFromLocation(lat, lng, 1)
                          results?.firstOrNull()?.getAddressLine(0)
                      } catch (e: Exception) {
                          null
                      }
                  }
              }

              Google Maps SDK

              // 在 Google Cloud Console 获取 API Key
              // AndroidManifest.xml
              <meta-data
                  android:name="com.google.android.geo.API_KEY"
                  android:value="YOUR_API_KEY"/>
              
              <!-- 布局 -->
              <androidx.fragment.app.FragmentContainerView
                  android:id="@+id/map"
                  android:name="com.google.android.gms.maps.SupportMapFragment"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"/>
              
              class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
                  private lateinit var googleMap: GoogleMap
              
                  override fun onCreate(savedInstanceState: Bundle?) {
                      super.onCreate(savedInstanceState)
                      setContentView(R.layout.activity_maps)
              
                      val mapFragment = supportFragmentManager
                          .findFragmentById(R.id.map) as SupportMapFragment
                      mapFragment.getMapAsync(this)
                  }
              
                  override fun onMapReady(map: GoogleMap) {
                      googleMap = map
              
                      // 添加标记
                      val beijing = LatLng(39.9042, 116.4074)
                      googleMap.addMarker(
                          MarkerOptions()
                              .position(beijing)
                              .title("北京")
                              .snippet("中国首都")
                              .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
                      )
              
                      // 移动相机
                      googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(beijing, 12f))
              
                      // 设置地图类型
                      googleMap.mapType = GoogleMap.MAP_TYPE_NORMAL  // SATELLITE, TERRAIN, HYBRID
              
                      // 启用我的位置按钮
                      googleMap.isMyLocationEnabled = true
                      googleMap.uiSettings.isZoomControlsEnabled = true
                      googleMap.uiSettings.isCompassEnabled = true
              
                      // 标记点击监听
                      googleMap.setOnMarkerClickListener { marker ->
                          Toast.makeText(this, marker.title, Toast.LENGTH_SHORT).show()
                          false
                      }
              
                      // 地图点击监听
                      googleMap.setOnMapClickListener { latLng ->
                          googleMap.addMarker(MarkerOptions().position(latLng).title("自定义标记"))
                      }
              
                      // 绘制多边形
                      val polygonOptions = PolygonOptions()
                          .add(LatLng(39.9, 116.3))
                          .add(LatLng(39.9, 116.5))
                          .add(LatLng(40.0, 116.5))
                          .add(LatLng(40.0, 116.3))
                          .fillColor(Color.parseColor("#40FF0000"))
                          .strokeColor(Color.RED)
                          .strokeWidth(5f)
                      googleMap.addPolygon(polygonOptions)
              
                      // 绘制路径
                      val polylineOptions = PolylineOptions()
                          .addAll(pathPoints)
                          .color(Color.BLUE)
                          .width(10f)
                      googleMap.addPolyline(polylineOptions)
                  }
              }

              高德地图(国内替代)

              implementation 'com.amap.api:3dmap:9.8.0'
              implementation 'com.amap.api:search:9.7.0'
              
              // Manifest
              <meta-data
                  android:name="com.amap.api.v2.apikey"
                  android:value="YOUR_AMAP_KEY"/>
              
              <com.amap.api.maps.MapView
                  android:id="@+id/mapView"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"/>
              
              class AMapActivity : AppCompatActivity() {
                  private lateinit var mapView: MapView
                  private lateinit var aMap: AMap
              
                  override fun onCreate(savedInstanceState: Bundle?) {
                      super.onCreate(savedInstanceState)
                      setContentView(R.layout.activity_amap)
              
                      mapView = findViewById(R.id.mapView)
                      mapView.onCreate(savedInstanceState)
                      aMap = mapView.map
              
                      // 添加标记
                      aMap.addMarker(MarkerOptions().position(LatLng(39.9, 116.4)).title("北京"))
                      aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(39.9, 116.4), 12f))
                  }
              
                  override fun onResume() { super.onResume(); mapView.onResume() }
                  override fun onPause() { super.onPause(); mapView.onPause() }
                  override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
              }

              30. 推送通知 FCM

              Firebase Cloud Messaging

              FCM 是 Google 提供的免费跨平台消息推送服务。

              接入步骤

              1. 在 Firebase Console 创建项目
              2. 添加 Android 应用,填写包名和 SHA-1
              3. 下载 google-services.json 放到 app/ 目录
              4. 添加依赖
              5. // 项目 build.gradle
                plugins {
                    id 'com.google.gms.google-services' version '4.4.0' apply false
                }
                
                // app build.gradle
                plugins {
                    id 'com.google.gms.google-services'
                }
                
                dependencies {
                    implementation platform('com.google.firebase:firebase-bom:32.7.1')
                    implementation 'com.google.firebase:firebase-messaging'
                    implementation 'com.google.firebase:firebase-analytics'
                }

                实现 FCM Service

                class MyFirebaseService : FirebaseMessagingService() {
                
                    // 收到消息
                    override fun onMessageReceived(remoteMessage: RemoteMessage) {
                        Log.d("FCM", "From: ${remoteMessage.from}")
                
                        // 数据消息
                        if (remoteMessage.data.isNotEmpty()) {
                            Log.d("FCM", "Data: ${remoteMessage.data}")
                            handleDataMessage(remoteMessage.data)
                        }
                
                        // 通知消息
                        remoteMessage.notification?.let { notification ->
                            Log.d("FCM", "Title: ${notification.title}, Body: ${notification.body}")
                            showNotification(notification.title, notification.body)
                        }
                    }
                
                    // Token 刷新
                    override fun onNewToken(token: String) {
                        Log.d("FCM", "New token: $token")
                        // 上传到服务器
                        sendTokenToServer(token)
                    }
                
                    private fun showNotification(title: String?, body: String?) {
                        createNotificationChannel()
                
                        val intent = Intent(this, MainActivity::class.java).apply {
                            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                        }
                        val pendingIntent = PendingIntent.getActivity(
                            this, 0, intent,
                            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
                        )
                
                        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
                            .setSmallIcon(R.drawable.ic_notification)
                            .setContentTitle(title ?: "新消息")
                            .setContentText(body)
                            .setPriority(NotificationCompat.PRIORITY_HIGH)
                            .setContentIntent(pendingIntent)
                            .setAutoCancel(true)
                            .setStyle(NotificationCompat.BigTextStyle().bigText(body))
                            .build()
                
                        NotificationManagerCompat.from(this).apply {
                            if (ActivityCompat.checkSelfPermission(
                                    this@MyFirebaseService,
                                    Manifest.permission.POST_NOTIFICATIONS
                                ) == PackageManager.PERMISSION_GRANTED
                            ) {
                                notify(System.currentTimeMillis().toInt(), notification)
                            }
                        }
                    }
                
                    private fun createNotificationChannel() {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            val channel = NotificationChannel(
                                CHANNEL_ID,
                                "消息通知",
                                NotificationManager.IMPORTANCE_HIGH
                            )
                            val manager = getSystemService(NotificationManager::class.java)
                            manager.createNotificationChannel(channel)
                        }
                    }
                
                    companion object {
                        private const val CHANNEL_ID = "fcm_channel"
                    }
                }
                
                // Manifest
                <service
                    android:name=".MyFirebaseService"
                    android:exported="false">
                    <intent-filter>
                        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
                    </intent-filter>
                </service>
                
                <!-- 默认通知图标 -->
                <meta-data
                    android:name="com.google.firebase.messaging.default_notification_icon"
                    android:resource="@drawable/ic_notification"/>
                <meta-data
                    android:name="com.google.firebase.messaging.default_notification_color"
                    android:resource="@color/colorAccent"/>
                <meta-data
                    android:name="com.google.firebase.messaging.default_notification_channel_id"
                    android:value="fcm_channel"/>

                获取 FCM Token

                // 在 MainActivity 或 Application 中
                FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
                    if (!task.isSuccessful) {
                        Log.w("FCM", "获取 token 失败", task.exception)
                        return@addOnCompleteListener
                    }
                
                    val token = task.result
                    Log.d("FCM", "Token: $token")
                    // 发送到服务器
                    sendTokenToServer(token)
                }
                
                // 订阅主题
                FirebaseMessaging.getInstance().subscribeToTopic("news")
                    .addOnCompleteListener { task ->
                        if (task.isSuccessful) {
                            Log.d("FCM", "已订阅 news 主题")
                        }
                    }
                
                // 取消订阅
                FirebaseMessaging.getInstance().unsubscribeFromTopic("news")

                本地通知

                class NotificationHelper(private val context: Context) {
                
                    private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                
                    init {
                        createNotificationChannels()
                    }
                
                    private fun createNotificationChannels() {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            val highChannel = NotificationChannel(
                                "high_priority",
                                "重要通知",
                                NotificationManager.IMPORTANCE_HIGH
                            ).apply {
                                description = "需要及时查看的通知"
                                enableLights(true)
                                lightColor = Color.RED
                                enableVibration(true)
                            }
                
                            val lowChannel = NotificationChannel(
                                "low_priority",
                                "一般通知",
                                NotificationManager.IMPORTANCE_LOW
                            ).apply {
                                description = "不重要的通知"
                            }
                
                            notificationManager.createNotificationChannels(listOf(highChannel, lowChannel))
                        }
                    }
                
                    fun showNotification(
                        title: String,
                        content: String,
                        channelId: String = "high_priority",
                        bigText: Boolean = false
                    ) {
                        val notification = NotificationCompat.Builder(context, channelId)
                            .setSmallIcon(R.drawable.ic_notification)
                            .setContentTitle(title)
                            .setContentText(content)
                            .setAutoCancel(true)
                            .apply {
                                if (bigText) {
                                    setStyle(NotificationCompat.BigTextStyle().bigText(content))
                                }
                            }
                            .build()
                
                        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
                            == PackageManager.PERMISSION_GRANTED) {
                            notificationManager.notify(System.currentTimeMillis().toInt(), notification)
                        }
                    }
                
                    // 定时通知
                    fun scheduleNotification(title: String, content: String, delayMillis: Long) {
                        val intent = Intent(context, NotificationReceiver::class.java).apply {
                            putExtra("title", title)
                            putExtra("content", content)
                        }
                        val pendingIntent = PendingIntent.getBroadcast(
                            context, 0, intent,
                            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
                        )
                
                        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
                        alarmManager.set(
                            AlarmManager.RTC_WAKEUP,
                            System.currentTimeMillis() + delayMillis,
                            pendingIntent
                        )
                    }
                }

                国内推送替代方案

                • 华为 Push:HMS Core Push Kit
                • 小米 Push:MiPush
                • 极光推送:JPush(统一集成多家)
                • 个推:Getui
                • 阿里云推送:EMAS Push

                31. 深度链接

                深度链接类型

                • Deep Links:自定义 scheme(如 myapp://path
                • Web Links:http/https 链接
                • Android App Links:验证的 https 链接(自动打开,无需选择)

                配置深度链接

                <!-- Manifest -->
                <activity android:name=".DetailActivity" android:exported="true">
                
                    <!-- 自定义 scheme 深度链接 -->
                    <intent-filter>
                        <action android:name="android.intent.action.VIEW"/>
                        <category android:name="android.intent.category.DEFAULT"/>
                        <category android:name="android.intent.category.BROWSABLE"/>
                        <data
                            android:scheme="myapp"
                            android:host="product"
                            android:pathPrefix="/detail"/>
                    </intent-filter>
                
                    <!-- Android App Links -->
                    <intent-filter android:autoVerify="true">
                        <action android:name="android.intent.action.VIEW"/>
                        <category android:name="android.intent.category.DEFAULT"/>
                        <category android:name="android.intent.category.BROWSABLE"/>
                        <data
                            android:scheme="https"
                            android:host="www.example.com"
                            android:pathPrefix="/product"/>
                    </intent-filter>
                
                    <!-- 分享文本 -->
                    <intent-filter>
                        <action android:name="android.intent.action.SEND"/>
                        <category android:name="android.intent.category.DEFAULT"/>
                        <data android:mimeType="text/plain"/>
                    </intent-filter>
                </activity>

                处理深度链接

                class DetailActivity : AppCompatActivity() {
                    override fun onCreate(savedInstanceState: Bundle?) {
                        super.onCreate(savedInstanceState)
                        setContentView(R.layout.activity_detail)
                
                        handleIntent(intent)
                    }
                
                    override fun onNewIntent(intent: Intent) {
                        super.onNewIntent(intent)
                        handleIntent(intent)
                    }
                
                    private fun handleIntent(intent: Intent) {
                        val action = intent.action
                        val data = intent.data
                
                        when {
                            // 深度链接
                            Intent.ACTION_VIEW == action && data != null -> {
                                when {
                                    data.scheme == "myapp" && data.host == "product" -> {
                                        val productId = data.lastPathSegment
                                        val ref = data.getQueryParameter("ref")
                                        loadProduct(productId, ref)
                                    }
                                    data.scheme == "https" && data.host == "www.example.com" -> {
                                        val path = data.path
                                        if (path?.startsWith("/product/") == true) {
                                            val productId = path.substringAfter("/product/")
                                            loadProduct(productId)
                                        }
                                    }
                                }
                            }
                            // 分享文本
                            Intent.ACTION_SEND == action && intent.type == "text/plain" -> {
                                val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
                                handleSharedText(sharedText)
                            }
                        }
                    }
                }
                
                // 使用示例
                // 自定义 scheme: myapp://product/detail/123?ref=wechat
                // App Links: https://www.example.com/product/123

                验证 Android App Links

                // 1. 在服务器配置 Digital Asset Links
                // https://www.example.com/.well-known/assetlinks.json
                [
                    {
                        "relation": ["delegate_permission/common"],
                        "target": {
                            "namespace": "android_app",
                            "package_name": "com.example.myapp",
                            "sha256_cert_fingerprints": [
                                "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
                            ]
                        }
                    }
                ]
                
                // 2. 获取 SHA-256 指纹
                // Android Studio: Gradle → signingReport
                // 或 keytool -list -v -keystore my-release-key.jks
                
                // 3. 验证链接
                adb shell pm verify-app-links --re-verify com.example.myapp
                adb shell pm get-app-links com.example.myapp
                
                // 4. 测试
                adb shell am start -a android.intent.action.VIEW \
                    -d "https://www.example.com/product/123" \
                    com.example.myapp

                Navigation Component 与深度链接

                <!-- nav_graph.xml -->
                <fragment
                    android:id="@+id/detailFragment"
                    android:name="com.example.DetailFragment">
                
                    <deepLink
                        android:id="@+id/deepLink"
                        app:uri="myapp://product/{id}"
                        app:uri="https://www.example.com/product/{id}"/>
                
                    <argument
                        android:name="id"
                        app:argType="string"/>
                </fragment>
                
                // 编程式导航到深度链接
                val deepLinkIntent = Intent(
                    Intent.ACTION_VIEW,
                    "myapp://product/123".toUri(),
                    this,
                    MainActivity::class.java
                )
                startActivity(deepLinkIntent)
                
                // 创建 PendingIntent(用于通知)
                val pendingIntent = NavDeepLinkBuilder(context)
                    .setComponentName(MainActivity::class.java)
                    .setGraph(R.navigation.nav_graph)
                    .setDestination(R.id.detailFragment)
                    .setArguments(bundleOf("id" to "123"))
                    .createPendingIntent()

                32. 图片加载库

                常用图片库对比

                语言特点
                GlideJava/KotlinGoogle 推荐,功能全面
                CoilKotlinKotlin 优先,Compose 友好
                PicassoJava简单易用,Square 出品

                Glide

                implementation 'com.github.bumptech.glide:glide:4.16.0'
                ksp 'com.github.bumptech.glide:ksp:4.16.0'
                
                // 基础使用
                Glide.with(context)
                    .load("https://example.com/image.jpg")
                    .into(imageView)
                
                // 常用配置
                Glide.with(context)
                    .load(url)
                    .placeholder(R.drawable.placeholder)  // 占位图
                    .error(R.drawable.error)              // 错误图
                    .fallback(R.drawable.fallback)        // null URL 时显示
                    .centerCrop()                          // 裁剪方式
                    .circleCrop()                          // 圆形
                    .override(800, 600)                    // 指定尺寸
                    .diskCacheStrategy(DiskCacheStrategy.ALL)
                    .skipMemoryCache(false)
                    .priority(Priority.HIGH)
                    .thumbnail(0.1f)                       // 缩略图
                    .transition(DrawableTransitionOptions.withCrossFade(300))
                    .into(imageView)
                
                // 监听加载状态
                Glide.with(context)
                    .load(url)
                    .listener(object : RequestListener<Drawable> {
                        override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
                            Log.e("Glide", "加载失败: ${e?.message}")
                            return false  // 返回 true 阻止 error 图显示
                        }
                
                        override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
                            return false
                        }
                    })
                    .into(imageView)
                
                // 预加载
                Glide.with(context)
                    .load(url)
                    .preload()  // 预加载到缓存
                
                // 清除缓存
                Glide.get(context).clearMemory()  // 主线程
                Thread { Glide.get(context).clearDiskCache() }.start()  // 子线程
                
                // 自定义 GlideModule
                @GlideModule
                class MyAppGlideModule : AppGlideModule() {
                    override fun applyOptions(context: Context, builder: GlideBuilder) {
                        builder.setDefaultRequestOptions(
                            RequestOptions()
                                .diskCacheStrategy(DiskCacheStrategy.ALL)
                                .encodeQuality(90)
                        )
                    }
                
                    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
                        // 注册自定义 ModelLoader
                    }
                }
                
                // 生成 API
                val glideUrl = GlideApp.with(context)
                    .load(url)
                    .placeholder(R.drawable.placeholder)
                    .into(imageView)

                Coil(Kotlin 推荐)

                implementation 'io.coil-kt:coil:2.5.0'
                implementation 'io.coil-kt:coil-compose:2.5.0'  // Compose
                implementation 'io.coil-kt:coil-gif:2.5.0'       // GIF
                implementation 'io.coil-kt:coil-svg:2.5.0'       // SVG
                implementation 'io.coil-kt:coil-video:2.5.0'     // 视频帧
                
                // 基础使用
                imageView.load("https://example.com/image.jpg") {
                    placeholder(R.drawable.placeholder)
                    error(R.drawable.error)
                    crossfade(true)
                    transformations(CircleCropTransformation())
                    size(800, 600)
                }
                
                // 链式调用
                imageView.load(url) {
                    crossfade(true)
                    placeholder(R.drawable.placeholder)
                    transformations(
                        RoundedCornersTransformation(12f),
                        BlurTransformation(context, 25f, 4f)
                    )
                }
                
                // Compose 中使用
                @Composable
                fun ImageItem(url: String) {
                    AsyncImage(
                        model = ImageRequest.Builder(LocalContext.current)
                            .data(url)
                            .crossfade(true)
                            .build(),
                        placeholder = painterResource(R.drawable.placeholder),
                        error = painterResource(R.drawable.error),
                        contentDescription = "image",
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.size(100.dp).clip(RoundedCornerShape(8.dp))
                    )
                
                    // 简化版
                    AsyncImage(
                        model = url,
                        contentDescription = null
                    )
                }
                
                // 图片加载器配置
                val imageLoader = ImageLoader.Builder(context)
                    .memoryCachePolicy(CachePolicy.ENABLED)
                    .diskCachePolicy(CachePolicy.ENABLED)
                    .networkCachePolicy(CachePolicy.ENABLED)
                    .crossfade(true)
                    .build()
                
                // 设置全局
                Coil.setImageLoader(imageLoader)
                
                // 下载图片
                val request = ImageRequest.Builder(context)
                    .data(url)
                    .target(
                        onStart = { /* 开始 */ },
                        onSuccess = { drawable -> /* 成功 */ },
                        onError = { /* 失败 */ }
                    )
                    .build()
                
                context.imageLoader.enqueue(request)

                SubsamplingScaleImageView(大图加载)

                implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
                
                <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
                    android:id="@+id/imageView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>
                
                binding.imageView.setImage(ImageSource.Uri(uri))
                binding.imageView.setImage(ImageSource.Resource(R.drawable.large_map))
                binding.imageView.setDoubleTapZoomScale(2f)
                binding.imageView.setMaxScale(5f)

                33. 安全最佳实践

                数据安全

                EncryptedSharedPreferences(加密存储)

                implementation 'androidx.security:security-crypto:1.1.0-alpha06'
                
                val masterKey = MasterKey.Builder(context)
                    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                    .build()
                
                val securePrefs = EncryptedSharedPreferences.create(
                    context,
                    "secure_prefs",
                    masterKey,
                    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
                )
                
                securePrefs.edit {
                    putString("token", accessToken)
                    putString("password", password)
                    apply()
                }

                EncryptedFile(加密文件)

                val encryptedFile = EncryptedFile.Builder(
                    context,
                    File(context.filesDir, "secret_data.txt"),
                    masterKey,
                    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
                ).build()
                
                // 写入
                encryptedFile.openFileOutput().use { outputStream ->
                    outputStream.write("sensitive data".toByteArray())
                }
                
                // 读取
                encryptedFile.openFileInput().use { inputStream ->
                    val content = inputStream.bufferedReader().readText()
                }

                网络安全

                网络安全配置

                <!-- res/xml/network_security_config.xml -->
                <?xml version="1.0" encoding="utf-8"?>
                <network-security-config>
                    <!-- 默认只允许 HTTPS -->
                    <base-config cleartextTrafficPermitted="false">
                        <trust-anchors>
                            <certificates src="system"/>
                        </trust-anchors>
                    </base-config>
                
                    <!-- 开发环境允许 HTTP -->
                    <debug-overrides>
                        <trust-anchors>
                            <certificates src="user"/>  <!-- 允许 Charles 等代理证书 -->
                        </trust-anchors>
                    </debug-overrides>
                
                    <!-- 特定域名配置 -->
                    <domain-config cleartextTrafficPermitted="false">
                        <domain includeSubdomains="true">api.example.com</domain>
                        <pin-set expiration="2025-12-31">
                            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
                            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKsO/04cY1bkg=</pin>
                        </pin-set>
                    </domain-config>
                </network-security-config>
                
                <!-- Manifest -->
                <application
                    android:networkSecurityConfig="@xml/network_security_config"
                    ... />

                证书固定 (Certificate Pinning)

                // OkHttp 证书固定
                val certificatePinner = CertificatePinner.Builder()
                    .add("api.example.com", "sha256/7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=")
                    .add("api.example.com", "sha256/fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKsO/04cY1bkg=")
                    .build()
                
                val client = OkHttpClient.Builder()
                    .certificatePinner(certificatePinner)
                    .build()

                代码安全

                ProGuard / R8 混淆规则

                # proguard-rules.pro
                
                # 保留 Application
                -keep public class * extends android.app.Application
                
                # 保留 Activity
                -keep public class * extends android.app.Activity
                -keep public class * extends android.app.Service
                
                # 保留实体类(JSON 解析需要)
                -keep class com.example.model.** { *; }
                
                # 保留注解
                -keepattributes *Annotation*
                -keepattributes Signature
                
                # Kotlin
                -keep class kotlin.Metadata { *; }
                -dontwarn kotlin.**
                -keepclassmembers class **$WhenMappings {
                    <fields>;
                }
                
                # Retrofit
                -keep,allowobfuscation,allowshrinking interface retrofit2.Call
                -keep,allowobfuscation,allowshrinking class retrofit2.Response
                -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
                
                # Gson
                -keepattributes Signature
                -keepattributes *Annotation*
                -keep class com.google.gson.** { *; }
                -keep class * implements com.google.gson.TypeAdapterFactory
                -keep class * implements com.google.gson.JsonSerializer
                -keep class * implements com.google.gson.JsonDeserializer
                
                # Room
                -keep class * extends androidx.room.RoomDatabase
                -keep @androidx.room.Entity class *
                -dontwarn androidx.room.paging.**
                
                # 保留 Parcelable
                -keepclassmembers class * implements android.os.Parcelable {
                    public static final ** CREATOR;
                }
                
                # 保留 Serializable
                -keepclassmembers class * implements java.io.Serializable {
                    static final long serialVersionUID;
                    private static final java.io.ObjectStreamField[] serialPersistentFields;
                    private void writeObject(java.io.ObjectOutputStream);
                    private void readObject(java.io.ObjectInputStream);
                }
                
                # 保留枚举
                -keepclassmembers enum * {
                    public static **[] values();
                    public static ** valueOf(java.lang.String);
                }
                
                # WebView JavaScript 接口
                -keepclassmembers class * {
                    @android.webkit.JavascriptInterface <methods>;
                }

                隐藏敏感信息

                // ❌ 不要硬编码
                const val API_KEY = "sk_live_xxxxxxxxxxxx"
                
                // ✅ 使用 local.properties
                // local.properties(不入库)
                API_KEY=sk_live_xxxxxxxxxxxx
                
                // build.gradle
                android {
                    defaultConfig {
                        buildConfigField "String", "API_KEY", "\"${getProperty("API_KEY")}\""
                    }
                }
                
                def getProperty(String name) {
                    def properties = new Properties()
                    def localProperties = rootProject.file("local.properties")
                    if (localProperties.exists()) {
                        localProperties.withInputStream { properties.load(it) }
                    }
                    return properties.getProperty(name) ?: ""
                }
                
                // 使用
                val apiKey = BuildConfig.API_KEY
                
                // ✅ 或使用 Secrets Gradle Plugin
                plugins {
                    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
                }
                
                // secrets.properties
                MAPS_API_KEY=your_key
                
                // 自动生成 BuildConfig
                // BuildConfig.MAPS_API_KEY

                安全组件使用

                // 防止截屏
                window.setFlags(
                    WindowManager.LayoutParams.FLAG_SECURE,
                    WindowManager.LayoutParams.FLAG_SECURE
                )
                
                // 隐藏最近任务中的截图
                // 同上 FLAG_SECURE
                
                // Root 检测(基础)
                fun isDeviceRooted(): Boolean {
                    return checkRootMethod1() || checkRootMethod2() || checkRootMethod3()
                }
                
                private fun checkRootMethod1(): Boolean {
                    val paths = arrayOf(
                        "/system/app/Superuser.apk",
                        "/sbin/su",
                        "/system/bin/su",
                        "/system/xbin/su"
                    )
                    return paths.any { File(it).exists() }
                }
                
                // 模拟器检测
                fun isEmulator(): Boolean {
                    return (Build.FINGERPRINT.startsWith("generic")
                            || Build.FINGERPRINT.startsWith("unknown")
                            || Build.MODEL.contains("google_sdk")
                            || Build.MODEL.contains("Emulator")
                            || Build.MODEL.contains("Android SDK built for x86")
                            || Build.MANUFACTURER.contains("Genymotion")
                            || Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
                            || "google_sdk" == Build.PRODUCT)
                }
                
                // 防 Hook (基础)
                fun detectHook(): Boolean {
                    try {
                        throw Exception()
                    } catch (e: Exception) {
                        for (stackTraceElement in e.stackTrace) {
                            if (stackTraceElement.className.contains("Xposed") ||
                                stackTraceElement.className.contains("de.robv.android.xposed")) {
                                return true
                            }
                        }
                    }
                    return false
                }

                34. 国际化 i18n

                多语言支持

                // 目录结构
                res/
                ├── values/                   # 默认(英文)
                │   └── strings.xml
                ├── values-zh/                # 中文
                │   └── strings.xml
                ├── values-zh-rCN/            # 简体中文
                │   └── strings.xml
                ├── values-zh-rTW/            # 繁体中文
                │   └── strings.xml
                ├── values-ja/                # 日文
                │   └── strings.xml
                ├── values-es/                # 西班牙语
                │   └── strings.xml
                └── values-ar/                # 阿拉伯语(RTL)
                    └── strings.xml

                strings.xml 示例

                <!-- values/strings.xml -->
                <resources>
                    <string name="app_name">MyApp</string>
                    <string name="welcome">Welcome, %1$s!</string>
                    <string name="item_count">%d items</string>
                    <string name="html_text"><![CDATA[<b>Bold</b> and <i>italic</i>]]></string>
                
                    <!-- 带数量 -->
                    <plurals name="message_count">
                        <item quantity="zero">No messages</item>
                        <item quantity="one">One message</item>
                        <item quantity="other">%d messages</item>
                    </plurals>
                
                    <!-- 字符串数组 -->
                    <string-array name="countries">
                        <item>China</item>
                        <item>USA</item>
                        <item>Japan</item>
                    </string-array>
                </resources>
                
                <!-- values-zh/strings.xml -->
                <resources>
                    <string name="app_name">我的应用</string>
                    <string name="welcome">欢迎,%1$s!</string>
                    <string name="item_count">%d 项</string>
                
                    <plurals name="message_count">
                        <item quantity="other">%d 条消息</item>
                    </plurals>
                </resources>

                代码中使用

                // 字符串
                val welcome = getString(R.string.welcome, "张三")  // "欢迎,张三!"
                textView.text = welcome
                
                // 带数量
                val count = 5
                val msg = resources.getQuantityString(R.plurals.message_count, count, count)
                textView.text = msg  // "5 条消息"
                
                // HTML
                textView.text = HtmlCompat.fromHtml(
                    getString(R.string.html_text),
                    HtmlCompat.FROM_HTML_MODE_LEGACY
                )
                
                // 字符串数组
                val countries = resources.getStringArray(R.array.countries)

                日期时间本地化

                // 日期格式化
                val formatter = DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault())
                val dateStr = formatter.format(Date())  // "2024年1月11日"
                
                // 时间格式化
                val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
                val timeStr = timeFormatter.format(Date())  // "下午3:45"
                
                // 数字格式化
                val numberFormat = NumberFormat.getInstance(Locale.getDefault())
                val numStr = numberFormat.format(1234567.89)  // "1,234,567.89" 或 "1.234.567,89"
                
                // 货币
                val currencyFormat = NumberFormat.getCurrencyInstance()
                val moneyStr = currencyFormat.format(99.99)  // "¥99.99" 或 "$99.99"
                
                // Kotlin 现代写法
                val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
                    .withLocale(Locale.getDefault())
                val now = LocalDateTime.now().format(dateTimeFormatter)

                RTL(从右到左)支持

                <!-- Manifest -->
                <application
                    android:supportsRtl="true"
                    ... />
                
                <!-- 使用 start/end 替代 left/right -->
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingStart="16dp"
                    android:paddingEnd="16dp"
                    android:layout_marginStart="8dp"
                    android:layout_marginEnd="8dp"
                    android:drawableStart="@drawable/ic_icon"
                    android:drawablePadding="8dp"
                    android:textAlignment="viewStart"/>
                
                <!-- 布局方向 -->
                <LinearLayout
                    android:layoutDirection="locale"
                    android:orientation="horizontal">
                    <!-- 子元素自动跟随语言方向 -->
                </LinearLayout>

                应用内切换语言

                // Android 13+ 使用系统 API
                val appLocale = LocaleListCompat.forLanguageTags("zh-CN")
                AppCompatDelegate.setApplicationLocales(appLocale)
                
                // 保存到 preferences
                val locales = AppCompatDelegate.getApplicationLocales()
                
                // Android 13+ 配置
                // res/xml/locales_config.xml
                <?xml version="1.0" encoding="utf-8"?>
                <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
                    <locale android:name="en"/>
                    <locale android:name="zh-Hans"/>
                    <locale android:name="zh-Hant"/>
                    <locale android:name="ja"/>
                </locale-config>
                
                <!-- Manifest -->
                <application
                    android:localeConfig="@xml/locales_config"
                    ... />

                其他资源本地化

                // 不同语言的图标
                res/
                ├── drawable-en/   # 英文图标
                ├── drawable-zh/   # 中文图标
                ├── drawable-ar/   # 阿拉伯文图标
                
                // 不同语言的布局
                res/
                ├── layout/        # 默认
                ├── layout-land/   # 横屏
                ├── layout-ar/     # 阿拉伯语(RTL 布局调整)

                35. 无障碍适配

                为什么需要无障碍

                • 服务视障、听障、认知障碍、运动障碍用户
                • 法律要求(美国 ADA、欧盟 EAA)
                • 提升所有用户体验
                • Google Play 推荐

                核心原则

                原则实践
                可感知提供文字替代、高对比度、字幕
                可操作支持键盘、TalkBack、手势
                可理解清晰的标签、错误提示
                稳健兼容辅助技术

                TalkBack 适配

                <!-- 为图片提供描述 -->
                <ImageView
                    android:src="@drawable/photo"
                    android:contentDescription="一张海滩日落的照片"/>
                
                <!-- 装饰性图片设为空 -->
                <ImageView
                    android:src="@drawable/divider"
                    android:contentDescription="@null"
                    android:importantForAccessibility="no"/>
                
                <!-- 为图标按钮提供描述 -->
                <ImageButton
                    android:src="@drawable/ic_delete"
                    android:contentDescription="删除此文章"/>
                
                <!-- 为状态变化提供反馈 -->
                <Button
                    android:stateListAnimator="@animator/button_state"
                    android:contentDescription="提交表单"/>
                
                <!-- 为自定义 View 提供描述 -->
                <com.example.RatingBar
                    android:contentDescription="评分 4 星,共 5 星"/>
                
                <!-- 标签关联 -->
                <TextView
                    android:id="@+id/label_email"
                    android:text="邮箱"
                    android:labelFor="@+id/et_email"/>
                
                <EditText
                    android:id="@+id/et_email"
                    android:hint="请输入邮箱"/>
                
                <!-- 分组 -->
                <LinearLayout
                    android:importantForAccessibility="yes"
                    android:contentDescription="用户卡片:张三,25 岁,北京">
                    <!-- 子元素被合并描述 -->
                </LinearLayout>

                自定义无障碍操作

                ViewCompat.setAccessibilityDelegate(view, object : AccessibilityDelegateCompat() {
                    override fun onInitializeAccessibilityNodeInfo(v: View, info: AccessibilityNodeInfoCompat) {
                        super.onInitializeAccessibilityNodeInfo(v, info)
                
                        // 添加自定义操作
                        val customAction = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
                            R.id.action_custom,
                            "收藏此文章"
                        )
                        info.addAction(customAction)
                
                        // 设置角色描述
                        info.roleDescription = "滑动卡片"
                    }
                
                    override fun performAccessibilityAction(v: View, action: Int, arguments: Bundle?): Boolean {
                        if (action == R.id.action_custom) {
                            favoriteArticle()
                            return true
                        }
                        return super.performAccessibilityAction(v, action, arguments)
                    }
                })
                
                // 发送无障碍事件
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
                view.announceForAccessibility("已添加到购物车")

                焦点顺序管理

                <!-- 自定义焦点顺序 -->
                <TextView
                    android:id="@+id/tv1"
                    android:nextFocusDown="@+id/et1"/>
                
                <EditText
                    android:id="@+id/et1"
                    android:nextFocusDown="@+id/btn1"/>
                
                <!-- 焦点组 -->
                <LinearLayout
                    android:focusable="true"
                    android:focusableInTouchMode="true">
                    <!-- 子元素作为一个整体获得焦点 -->
                </LinearLayout>
                
                <!-- 禁止获得焦点 -->
                <TextView
                    android:focusable="false"
                    android:importantForAccessibility="noHideDescendants"/>

                颜色和对比度

                // 检查对比度
                val contrastRatio = ContrastColorUtil.getInstance(context)
                    .calculateContrast(textColor, backgroundColor)
                
                // 建议:
                // 正文文字:至少 4.5:1
                // 大文字(18sp+ 或 14sp+ 加粗):至少 3:1
                // UI 组件和图形:至少 3:1
                
                // 避免仅用颜色传达信息
                // ❌ 错误:红色表示必填字段
                // ✅ 正确:红色 + 星号 * 表示必填
                
                <TextView
                    android:text="姓名 *"
                    android:textColor="@color/text_primary"/>
                
                // 高对比度主题支持
                val uiModeManager = getSystemService(Context.UI_SERVICE) as UiModeManager
                if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_WATCH) {
                    // 手表高对比度模式
                }

                动画与减弱动效

                // 检测用户是否偏好减弱动效
                val reduceMotion = context.resources.configuration
                    .isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_NORMAL)
                
                // API 34+ 直接获取
                val animationScale = Settings.Global.getFloat(
                    context.contentResolver,
                    Settings.Global.ANIMATOR_DURATION_SCALE,
                    1f
                )
                
                if (animationScale == 0f) {
                    // 用户关闭了动画,使用淡入淡出替代复杂动画
                    view.animate().alpha(1f).setDuration(100).start()
                } else {
                    // 正常播放动画
                }
                
                // Jetpack Compose
                @Composable
                fun shouldReduceMotion(): Boolean {
                    val context = LocalContext.current
                    return remember {
                        Settings.Global.getFloat(
                            context.contentResolver,
                            Settings.Global.ANIMATOR_DURATION_SCALE,
                            1f
                        ) == 0f
                    }
                }

                无障碍测试工具

                工具用途
                TalkBack屏幕阅读器测试
                Accessibility Scanner自动检测问题
                Lint静态分析
                Color Contrast Analyzer对比度检查
                Espresso AccessibilityChecksUI 测试集成
                // Espresso 无障碍检查
                AccessibilityChecks.enable().apply {
                    setRunChecksFromRootView(true)
                    setSuppressingResultMatcher(
                        Matchers.allOf(
                            Matchers.hasMatchedView(withId(R.id.ignored_view)),
                            Matchers.matchesAccessibilityCheckResult(...)
                        )
                    )
                }

                36. WebView 混合开发

                WebView 基础使用

                <WebView
                    android:id="@+id/webView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>
                
                class WebViewActivity : AppCompatActivity() {
                    private lateinit var webView: WebView
                
                    override fun onCreate(savedInstanceState: Bundle?) {
                        super.onCreate(savedInstanceState)
                        setContentView(R.layout.activity_webview)
                
                        webView = findViewById(R.id.webView)
                
                        // 配置 WebView
                        webView.settings.apply {
                            javaScriptEnabled = true
                            domStorageEnabled = true  // 启用 localStorage
                            allowFileAccess = false   // 安全:禁用文件访问
                            allowContentAccess = false
                            setSupportZoom(true)
                            builtInZoomControls = true
                            displayZoomControls = false
                            useWideViewPort = true    // 自适应屏幕
                            loadWithOverviewMode = true
                            mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW  // HTTPS only
                            cacheMode = WebSettings.LOAD_DEFAULT
                            mediaPlaybackRequiresUserGesture = true
                        }
                
                        // 设置 WebViewClient(处理页面内跳转)
                        webView.webViewClient = object : WebViewClient() {
                            override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
                                val url = request.url.toString()
                                // 处理自定义 scheme
                                if (url.startsWith("myapp://")) {
                                    handleCustomScheme(url)
                                    return true
                                }
                                return false  // 在 WebView 内加载
                            }
                
                            override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                                super.onPageStarted(view, url, favicon)
                                showLoading()
                            }
                
                            override fun onPageFinished(view: WebView?, url: String?) {
                                super.onPageFinished(view, url)
                                hideLoading()
                            }
                
                            override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
                                super.onReceivedError(view, request, error)
                                if (request?.isForMainFrame == true) {
                                    showErrorPage()
                                }
                            }
                
                            override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
                                // 生产环境不要这样处理!
                                // handler?.proceed()  // 不安全
                                handler?.cancel()
                            }
                        }
                
                        // 设置 WebChromeClient(处理 JS 对话框、进度条)
                        webView.webChromeClient = object : WebChromeClient() {
                            override fun onProgressChanged(view: WebView?, newProgress: Int) {
                                binding.progressBar.progress = newProgress
                                if (newProgress == 100) binding.progressBar.visibility = View.GONE
                            }
                
                            override fun onReceivedTitle(view: WebView?, title: String?) {
                                supportActionBar?.title = title
                            }
                
                            override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
                                MaterialAlertDialogBuilder(this@WebViewActivity)
                                    .setTitle("提示")
                                    .setMessage(message)
                                    .setPositiveButton("确定") { _, _ -> result?.confirm() }
                                    .show()
                                return true
                            }
                
                            // 文件上传
                            override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
                                val intent = fileChooserParams?.createIntent()
                                try {
                                    fileUploadLauncher.launch(intent)
                                    uploadCallback = filePathCallback
                                } catch (e: Exception) {
                                    return false
                                }
                                return true
                            }
                        }
                
                        // 注入 JS 接口
                        webView.addJavascriptInterface(JsBridge(this), "AndroidBridge")
                
                        // 加载页面
                        val url = intent.getStringExtra("url") ?: "https://example.com"
                        webView.loadUrl(url)
                    }
                
                    override fun onBackPressed() {
                        if (webView.canGoBack()) {
                            webView.goBack()
                        } else {
                            super.onBackPressed()
                        }
                    }
                
                    override fun onDestroy() {
                        webView.apply {
                            stopLoading()
                            webChromeClient = null
                            webViewClient = null
                            clearHistory()
                            clearCache(true)
                            loadUrl("about:blank")
                            onPause()
                            removeAllViews()
                            destroyDrawingCache()
                            destroy()
                        }
                        super.onDestroy()
                    }
                }

                JavaScript 与 Native 交互

                // Native 提供给 JS 的接口
                class JsBridge(private val context: Context) {
                
                    @JavascriptInterface
                    fun showToast(message: String) {
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
                        }
                    }
                
                    @JavascriptInterface
                    fun getUserInfo(): String {
                        val user = User("张三", 25)
                        return Gson().toJson(user)
                    }
                
                    @JavascriptInterface
                    fun openActivity(activityName: String) {
                        try {
                            val intent = Intent(context, Class.forName(activityName))
                            context.startActivity(intent)
                        } catch (e: Exception) {
                            Log.e("JsBridge", "打开 Activity 失败", e)
                        }
                    }
                
                    @JavascriptInterface
                    fun logEvent(eventName: String, params: String) {
                        Analytics.logEvent(eventName, parseParams(params))
                    }
                
                    @JavascriptInterface
                    fun share(title: String, content: String, url: String) {
                        Handler(Looper.getMainLooper()).post {
                            val intent = Intent(Intent.ACTION_SEND).apply {
                                type = "text/plain"
                                putExtra(Intent.EXTRA_SUBJECT, title)
                                putExtra(Intent.EXTRA_TEXT, "$content\n$url")
                            }
                            context.startActivity(Intent.createChooser(intent, "分享到"))
                        }
                    }
                }
                
                // JS 中调用
                <script>
                    // 调用 Native
                    AndroidBridge.showToast('Hello from JS!');
                    const userInfo = JSON.parse(AndroidBridge.getUserInfo());
                    AndroidBridge.logEvent('page_view', '{"page":"home"}');
                
                    // Native 调用 JS
                    function onReceiveFromNative(data) {
                        console.log('Received:', data);
                    }
                </script>
                
                // Native 调用 JS
                webView.evaluateJavascript("onReceiveFromNative('${json}')") { result ->
                    Log.d("WebView", "JS 返回: $result")
                }
                
                // 异步获取 JS 返回值
                webView.evaluateJavascript("getUserAge()") { result ->
                    val age = result.toIntOrNull() ?: 0
                }

                安全注意事项

                // ✅ 安全设置
                settings.apply {
                    javaScriptEnabled = true  // 仅在必要时开启
                    allowFileAccess = false
                    allowContentAccess = false
                    allowFileAccessFromFileURLs = false
                    allowUniversalAccessFromFileURLs = false
                }
                
                // ❌ 避免:addJavascriptInterface 在 API 17 以下有漏洞
                // ✅ 解决:使用 @JavascriptInterface 注解(API 17+ 自动防护)
                
                // ✅ 验证 URL 来源
                override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
                    val url = request.url.toString()
                    if (!url.startsWith("https://trusted-domain.com")) {
                        // 拒绝或提示用户
                        return true
                    }
                    return false
                }
                
                // ✅ 处理 SSL 错误
                override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
                    handler?.cancel()  // 取消加载
                    // 不要 proceed(),除非你有充分理由
                }
                
                // ✅ 限制 JavaScript 访问的接口
                // 只暴露必要的方法,使用 @JavascriptInterface 注解

                37. 传感器与硬件

                常用传感器

                传感器用途常量
                加速度计运动检测、摇一摇TYPE_ACCELEROMETER
                陀螺仪旋转检测、ARTYPE_GYROSCOPE
                磁力计指南针TYPE_MAGNETIC_FIELD
                光线自动亮度TYPE_LIGHT
                接近通话时灭屏TYPE_PROXIMITY
                气压计海拔高度TYPE_PRESSURE
                心率健康应用TYPE_HEART_RATE
                步数计运动健康TYPE_STEP_COUNTER

                加速度传感器(摇一摇检测)

                class ShakeDetector(context: Context) : SensorEventListener {
                
                    private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
                    private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
                    private var shakeListener: (() -> Unit)? = null
                
                    private var lastShakeTime = 0L
                    private var lastX = 0f
                    private var lastY = 0f
                    private var lastZ = 0f
                    private var lastUpdate = 0L
                
                    fun start(listener: () -> Unit) {
                        shakeListener = listener
                        sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)
                    }
                
                    fun stop() {
                        sensorManager.unregisterListener(this)
                    }
                
                    override fun onSensorChanged(event: SensorEvent) {
                        val currentTime = System.currentTimeMillis()
                        if (currentTime - lastUpdate < 100) return
                
                        val timeDiff = currentTime - lastUpdate
                        lastUpdate = currentTime
                
                        val x = event.values[0]
                        val y = event.values[1]
                        val z = event.values[2]
                
                        val speed = Math.sqrt(((x - lastX) * (x - lastX) + (y - lastY) * (y - lastY) + (z - lastZ) * (z - lastZ)).toDouble()) / timeDiff * 10000
                
                        if (speed > SHAKE_THRESHOLD) {
                            if (currentTime - lastShakeTime > 1000) {
                                lastShakeTime = currentTime
                                shakeListener?.invoke()
                            }
                        }
                
                        lastX = x
                        lastY = y
                        lastZ = z
                    }
                
                    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
                
                    companion object {
                        private const val SHAKE_THRESHOLD = 800
                    }
                }
                
                // 使用
                val shakeDetector = ShakeDetector(this)
                shakeDetector.start {
                    Toast.makeText(this, "检测到摇一摇!", Toast.LENGTH_SHORT).show()
                }

                光线传感器

                class LightSensorHelper(private val context: Context) : SensorEventListener {
                
                    private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
                    private val lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
                    private var listener: ((Float) -> Unit)? = null
                
                    fun start(onLightChange: (Float) -> Unit) {
                        listener = onLightChange
                        sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL)
                    }
                
                    fun stop() {
                        sensorManager.unregisterListener(this)
                    }
                
                    override fun onSensorChanged(event: SensorEvent) {
                        val lux = event.values[0]
                        listener?.invoke(lux)
                        // lux: 0 = 黑暗, 50 = 室内, 10000 = 日光
                    }
                
                    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
                }

                指纹识别 (Biometric)

                implementation 'androidx.biometric:biometric:1.2.0-alpha05'
                
                class BiometricHelper(private val activity: FragmentActivity) {
                
                    private val executor = ContextCompat.getMainExecutor(activity)
                    private val biometricPrompt = BiometricPrompt(activity, executor,
                        object : BiometricPrompt.AuthenticationCallback() {
                            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                                super.onAuthenticationError(errorCode, errString)
                                Log.e("Biometric", "错误: $errString")
                                authCallback?.onError(errString.toString())
                            }
                
                            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                                super.onAuthenticationSucceeded(result)
                                authCallback?.onSuccess(result.cryptoObject)
                            }
                
                            override fun onAuthenticationFailed() {
                                super.onAuthenticationFailed()
                                authCallback?.onFailed()
                            }
                        })
                
                    private var authCallback: AuthCallback? = null
                
                    fun authenticate(
                        title: String = "身份验证",
                        subtitle: String = "请使用指纹验证身份",
                        negativeButtonText: String = "取消",
                        callback: AuthCallback
                    ) {
                        authCallback = callback
                
                        // 检查设备是否支持
                        val biometricManager = BiometricManager.from(activity)
                        when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
                            BiometricManager.BIOMETRIC_SUCCESS -> { /* 支持 */ }
                            BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
                                callback.onError("设备不支持生物识别")
                                return
                            }
                            BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
                                // 引导用户设置
                                val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL)
                                activity.startActivity(intent)
                                return
                            }
                        }
                
                        val promptInfo = BiometricPrompt.PromptInfo.Builder()
                            .setTitle(title)
                            .setSubtitle(subtitle)
                            .setNegativeButtonText(negativeButtonText)
                            .setAllowedAuthenticators(
                                BiometricManager.Authenticators.BIOMETRIC_STRONG
                                        or BiometricManager.Authenticators.DEVICE_CREDENTIAL
                            )
                            .build()
                
                        biometricPrompt.authenticate(promptInfo)
                    }
                
                    // 带加密的生物识别
                    fun authenticateWithCrypto(callback: AuthCallback) {
                        val keyStore = KeyStore.getInstance("AndroidKeyStore")
                        keyStore.load(null)
                
                        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
                        val keyGenParameterSpec = KeyGenParameterSpec.Builder(
                            "my_key",
                            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                        )
                            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                            .setUserAuthenticationRequired(true)
                            .build()
                
                        keyGenerator.init(keyGenParameterSpec)
                        keyGenerator.generateKey()
                
                        val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/GCM/NoPadding")
                        val key = keyStore.getKey("my_key", null) as SecretKey
                        cipher.init(Cipher.ENCRYPT_MODE, key)
                
                        val cryptoObject = BiometricPrompt.CryptoObject(cipher)
                        biometricPrompt.authenticate(promptInfo, cryptoObject)
                    }
                
                    interface AuthCallback {
                        fun onSuccess(cryptoObject: BiometricPrompt.CryptoObject?)
                        fun onError(message: String)
                        fun onFailed()
                    }
                }
                
                // 使用
                val biometricHelper = BiometricHelper(this)
                biometricHelper.authenticate(
                    title = "身份验证",
                    subtitle = "请使用指纹登录"
                    callback = object : BiometricHelper.AuthCallback {
                        override fun onSuccess(cryptoObject: BiometricPrompt.CryptoObject?) {
                            navigateToHome()
                        }
                        override fun onError(message: String) {
                            Toast.makeText(this@LoginActivity, message, Toast.LENGTH_SHORT).show()
                        }
                        override fun onFailed() {
                            Toast.makeText(this@LoginActivity, "验证失败", Toast.LENGTH_SHORT).show()
                        }
                    }
                )

                蓝牙 (BLE)

                // 权限
                <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
                <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
                
                class BleHelper(private val context: Context) {
                    private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
                    private val bluetoothAdapter = bluetoothManager.adapter
                    private var scanCallback: ((BluetoothDevice) -> Unit)? = null
                
                    private val leScanCallback = object : ScanCallback() {
                        override fun onScanResult(callbackType: Int, result: ScanResult) {
                            super.onScanResult(callbackType, result)
                            scanCallback?.invoke(result.device)
                        }
                    }
                
                    fun startScan(onDeviceFound: (BluetoothDevice) -> Unit) {
                        scanCallback = onDeviceFound
                        val scanner = bluetoothAdapter.bluetoothLeScanner
                        scanner.startScan(leScanCallback)
                
                        // 10 秒后停止扫描
                        Handler(Looper.getMainLooper()).postDelayed({
                            scanner.stopScan(leScanCallback)
                        }, 10000)
                    }
                
                    fun stopScan() {
                        bluetoothAdapter.bluetoothLeScanner.stopScan(leScanCallback)
                    }
                
                    fun connect(device: BluetoothDevice, callback: GattCallback) {
                        val gatt = device.connectGatt(context, false, object : BluetoothGattCallback() {
                            override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
                                if (newState == BluetoothProfile.STATE_CONNECTED) {
                                    gatt.discoverServices()
                                }
                            }
                
                            override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
                                if (status == BluetoothGatt.GATT_SUCCESS) {
                                    callback.onConnected(gatt)
                                }
                            }
                
                            override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
                                callback.onDataReceived(characteristic.value)
                            }
                        })
                    }
                
                    interface GattCallback {
                        fun onConnected(gatt: BluetoothGatt)
                        fun onDataReceived(data: ByteArray)
                    }
                }

                38. Kotlin 多平台

                Kotlin Multiplatform Mobile (KMM)

                KMM 允许你在 Android 和 iOS 之间共享业务逻辑代码,UI 各自原生实现。

                共享什么

                适合共享不适合共享
                业务逻辑UI 层
                数据层(Repository)平台特定 UI
                网络请求平台特定功能
                数据库操作动画
                状态管理导航

                项目结构

                MyApp/
                ├── shared/                    # 共享模块
                │   └── src/
                │       ├── commonMain/        # 跨平台代码
                │       │   └── kotlin/
                │       │       ├── data/
                │       │       ├── domain/
                │       │       └── presentation/
                │       ├── androidMain/       # Android 特定代码
                │       └── iosMain/           # iOS 特定代码
                ├── androidApp/                # Android 应用
                └── iosApp/                    # iOS 应用 (Swift/Xcode)

                Expect/Actual 模式

                // commonMain - 声明期望
                expect class Platform() {
                    val name: String
                }
                
                expect fun getUuid(): String
                
                expect class DatabaseDriverFactory {
                    fun createDriver(): SqlDriver
                }
                
                // androidMain - Android 实现
                actual class Platform actual constructor() {
                    actual val name: String = "Android ${Build.VERSION.RELEASE}"
                }
                
                actual fun getUuid(): String = UUID.randomUUID().toString()
                
                actual class DatabaseDriverFactory(private val context: Context) {
                    actual fun createDriver(): SqlDriver {
                        return AndroidSqliteDriver(AppDatabase.Schema, context, "app.db")
                    }
                }
                
                // iosMain - iOS 实现
                actual class Platform actual constructor() {
                    actual val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
                }
                
                actual fun getUuid(): String = NSUUID().UUIDString()
                
                actual class DatabaseDriverFactory {
                    actual fun createDriver(): SqlDriver {
                        return NativeSqliteDriver(AppDatabase.Schema, "app.db")
                    }
                }

                共享 ViewModel

                // commonMain
                import kotlinx.coroutines.flow.StateFlow
                import kotlinx.coroutines.flow.MutableStateFlow
                
                class SharedViewModel(private val repo: UserRepository) {
                    private val _users = MutableStateFlow<List<User>>(emptyList())
                    val users: StateFlow<List<User>> = _users
                
                    private val _loading = MutableStateFlow(false)
                    val loading: StateFlow<Boolean> = _loading
                
                    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
                
                    fun loadUsers() {
                        scope.launch {
                            _loading.value = true
                            try {
                                val users = repo.getUsers()
                                _users.value = users
                            } finally {
                                _loading.value = false
                            }
                        }
                    }
                
                    fun clear() {
                        scope.cancel()
                    }
                }
                
                // Android 使用
                class UserListFragment : Fragment() {
                    private val sharedViewModel = SharedViewModel(UserRepositoryImpl())
                
                    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                        super.onViewCreated(view, savedInstanceState)
                        viewLifecycleOwner.lifecycleScope.launch {
                            repeatOnLifecycle(Lifecycle.State.STARTED) {
                                sharedViewModel.users.collect { users ->
                                    adapter.submitList(users)
                                }
                            }
                        }
                        sharedViewModel.loadUsers()
                    }
                }
                
                // iOS 使用 (Swift)
                class UserListViewModel: ObservableObject {
                    @Published var users: [User] = []
                    @Published var loading = false
                
                    private let shared = SharedViewModel(repo: UserRepositoryImpl())
                
                    func loadUsers() {
                        shared.loadUsers()
                        // 观察 shared.users
                    }
                }

                Ktor 客户端(跨平台网络)

                // commonMain
                implementation 'io.ktor:ktor-client-core:2.3.7'
                implementation 'io.ktor:ktor-client-content-negotiation:2.3.7'
                implementation 'io.ktor:ktor-serialization-kotlinx-json:2.3.7'
                
                // androidMain
                implementation 'io.ktor:ktor-client-okhttp:2.3.7'
                
                // iosMain
                implementation 'io.ktor:ktor-client-darwin:2.3.7'
                
                // 共享网络层
                class HttpClientFactory {
                    fun create(): HttpClient {
                        return HttpClient {
                            install(ContentNegotiation) {
                                json(Json {
                                    ignoreUnknownKeys = true
                                })
                            }
                            install(Logging) {
                                level = LogLevel.BODY
                            }
                        }
                    }
                }
                
                class ApiClient(private val client: HttpClient) {
                    suspend fun getUsers(): List<User> {
                        return client.get("https://api.example.com/users").body()
                    }
                
                    suspend fun createUser(user: User): User {
                        return client.post("https://api.example.com/users") {
                            contentType(ContentType.Application.Json)
                            setBody(user)
                        }.body()
                    }
                }

                SQLDelight(跨平台数据库)

                // build.gradle
                plugins {
                    id 'app.cash.sqldelight' version '2.0.1'
                }
                
                sqldelight {
                    databases {
                        create("AppDatabase") {
                            packageName.set("com.example.db")
                        }
                    }
                }
                
                // src/commonMain/sqldelight/com/example/db/User.sq
                CREATE TABLE user (
                    id INTEGER NOT NULL PRIMARY KEY,
                    name TEXT NOT NULL,
                    email TEXT NOT NULL
                );
                
                selectAll:
                SELECT * FROM user;
                
                insert:
                INSERT INTO user(name, email) VALUES (?, ?);
                
                selectById:
                SELECT * FROM user WHERE id = ?;
                
                // Kotlin 生成代码
                class UserRepository(driver: SqlDriver) {
                    private val database = AppDatabase(driver)
                
                    fun getAll(): List<User> = database.userQueries.selectAll().executeAsList()
                    fun insert(name: String, email: String) = database.userQueries.insert(name, email)
                
                    // Flow 响应式查询
                    fun observeAll(): Flow<List<User>> = database.userQueries.selectAll()
                        .asFlow()
                        .mapToList(Dispatchers.IO)
                }

                💡

                适用场景:

                当你的 Android 和 iOS 应用有相似的业务逻辑,但希望 UI 保持原生体验时,KMM 是最佳选择。

                39. CI/CD 流水线

                常用 CI/CD 平台

                平台特点适用场景
                GitHub ActionsGitHub 集成、免费额度大GitHub 托管项目
                GitLab CI私有部署、强大GitLab 托管项目
                Jenkins自托管、插件丰富企业内部
                Bitrise移动专用、可视化移动团队
                Firebase App Distribution免费、易分享内测分发

                GitHub Actions 完整配置

                # .github/workflows/android.yml
                name: Android CI
                
                on:
                  push:
                    branches: [ main, develop ]
                  pull_request:
                    branches: [ main ]
                
                env:
                  JAVA_VERSION: '17'
                  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
                
                jobs:
                  # 单元测试
                  test:
                    name: Unit Tests
                    runs-on: ubuntu-latest
                    steps:
                      - uses: actions/checkout@v4
                
                      - name: Set up JDK
                        uses: actions/setup-java@v4
                        with:
                          java-version: ${{ env.JAVA_VERSION }}
                          distribution: 'temurin'
                          cache: 'gradle'
                
                      - name: Setup Android SDK
                        uses: android-actions/setup-android@v3
                
                      - name: Grant execute permission for gradlew
                        run: chmod +x gradlew
                
                      - name: Run unit tests
                        run: ./gradlew testDebugUnitTest
                
                      - name: Generate test report
                        uses: dorny/test-reporter@v1
                        if: success() || failure()
                        with:
                          name: Unit Tests
                          path: '**/build/test-results/test*/TEST-*.xml'
                          reporter: java-junit
                
                      - name: Upload coverage report
                        uses: actions/upload-artifact@v4
                        with:
                          name: coverage-report
                          path: app/build/reports/jacoco/
                
                  # Lint 检查
                  lint:
                    name: Lint Check
                    runs-on: ubuntu-latest
                    steps:
                      - uses: actions/checkout@v4
                      - uses: actions/setup-java@v4
                        with:
                          java-version: ${{ env.JAVA_VERSION }}
                          distribution: 'temurin'
                          cache: 'gradle'
                      - run: chmod +x gradlew
                      - run: ./gradlew lintDebug
                      - uses: actions/upload-artifact@v4
                        if: always()
                        with:
                          name: lint-report
                          path: app/build/reports/lint-results-*.html
                
                  # 构建 APK
                  build:
                    name: Build APK
                    runs-on: ubuntu-latest
                    needs: [test, lint]
                    if: github.event_name == 'push'
                    steps:
                      - uses: actions/checkout@v4
                      - uses: actions/setup-java@v4
                        with:
                          java-version: ${{ env.JAVA_VERSION }}
                          distribution: 'temurin'
                          cache: 'gradle'
                
                      - name: Decode keystore
                        run: |
                          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > release-key.jks
                
                      - name: Build Release APK
                        env:
                          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
                          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
                          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
                        run: ./gradlew assembleRelease
                
                      - name: Upload APK
                        uses: actions/upload-artifact@v4
                        with:
                          name: app-release-apk
                          path: app/build/outputs/apk/release/*.apk
                
                      - name: Upload mapping
                        uses: actions/upload-artifact@v4
                        with:
                          name: mapping
                          path: app/build/outputs/mapping/release/mapping.txt
                
                  # 发布到 Firebase App Distribution
                  distribute:
                    name: Distribute to Firebase
                    runs-on: ubuntu-latest
                    needs: build
                    if: github.ref == 'refs/heads/develop'
                    steps:
                      - uses: actions/checkout@v4
                      - uses: actions/download-artifact@v4
                        with:
                          name: app-release-apk
                
                      - name: Upload to Firebase App Distribution
                        uses: wzieba/firebase-distribution-github-action@v1
                        with:
                          appId: ${{ secrets.FIREBASE_APP_ID }}
                          serviceCredentialsFileContent: ${{ secrets.FIREBASE_CREDENTIAL }}
                          groups: testers
                          file: app-release.apk
                          releaseNotes: |
                            Commit: ${{ github.event.head_commit.message }}
                            Branch: ${{ github.ref_name }}
                            Author: ${{ github.actor }}
                
                  # 发布到 Google Play
                  deploy:
                    name: Deploy to Google Play
                    runs-on: ubuntu-latest
                    needs: build
                    if: startsWith(github.ref, 'refs/tags/v')
                    steps:
                      - uses: actions/checkout@v4
                      - uses: actions/download-artifact@v4
                        with:
                          name: app-release-apk
                
                      - name: Upload to Play Store
                        uses: r0adkll/upload-google-play@v1
                        with:
                          serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT }}
                          packageName: com.example.myapp
                          releaseFiles: app-release.apk
                          track: production
                          status: completed
                          whatsNewDirectory: distribution/whatsnew
                
                  # 构建 AAB
                  build-aab:
                    name: Build AAB
                    runs-on: ubuntu-latest
                    needs: [test, lint]
                    if: startsWith(github.ref, 'refs/tags/v')
                    steps:
                      - uses: actions/checkout@v4
                      - uses: actions/setup-java@v4
                        with:
                          java-version: ${{ env.JAVA_VERSION }}
                          distribution: 'temurin'
                          cache: 'gradle'
                
                      - name: Build AAB
                        run: ./gradlew bundleRelease
                
                      - name: Upload AAB
                        uses: actions/upload-artifact@v4
                        with:
                          name: app-release-aab
                          path: app/build/outputs/bundle/release/*.aab

                Fastlane 自动化

                # 安装
                sudo gem install fastlane
                
                # 初始化
                fastlane init
                
                # Fastfile
                default_platform(:android)
                
                platform :android do
                  desc "Run tests"
                  lane :test do
                    gradle(task: "testDebugUnitTest")
                  end
                
                  desc "Build release APK"
                  lane :build do
                    gradle(
                      task: "assemble",
                      build_type: "Release",
                      properties: {
                        "android.injected.signing.store.file" => "release-key.jks",
                        "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
                        "android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
                        "android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
                      }
                    )
                  end
                
                  desc "Submit to Play Store"
                  lane :deploy do
                    upload_to_play_store(
                      track: "production",
                      json_key: "play-store-key.json",
                      apk: "app/build/outputs/apk/release/app-release.apk"
                    )
                  end
                
                  desc "Distribute to Firebase"
                  lane :beta do
                    firebase_app_distribution(
                      app: "1:123456789:android:abcdef",
                      service_credentials_file: "firebase-key.json",
                      groups: "testers",
                      release_notes: "New features and bug fixes"
                    )
                  end
                
                  desc "Full pipeline"
                  lane :release do
                    test
                    build
                    deploy
                  end
                end
                
                # 运行
                fastlane android test
                fastlane android build
                fastlane android deploy

                Danger 代码审查

                # Gemfile
                gem 'danger'
                gem 'danger-android_lint'
                gem 'danger-jacoco'
                
                # Dangerfile
                danger.import_dangerfile(path: 'danger/commit')
                
                # 检查 PR 大小
                warn("PR 太大,请拆分") if git.lines_of_code > 500
                
                # 检查未解决的 TODO
                github.dismiss_out_of_range_messages
                warn("有未解决的 TODO") if git.modified_files.any? { |f| File.read(f).include?("TODO") }
                
                # Android Lint
                android_lint.report_file = 'app/build/reports/lint-results-debug.xml'
                android_lint.lint
                
                # Jacoco 覆盖率
                jacoco.report_path = 'app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'
                jacoco.warn_if_coverage_less_than 80

                40. 崩溃监控与分析

                Firebase Crashlytics

                // 添加依赖
                implementation platform('com.google.firebase:firebase-bom:32.7.1')
                implementation 'com.google.firebase:firebase-crashlytics'
                implementation 'com.google.firebase:firebase-analytics'
                
                // 初始化(自动)
                class MyApplication : Application() {
                    override fun onCreate() {
                        super.onCreate()
                
                        // 启用 Crashlytics(发布版)
                        FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
                    }
                }
                
                // 记录非致命异常
                try {
                    // 可能出错的代码
                } catch (e: Exception) {
                    FirebaseCrashlytics.getInstance().recordException(e)
                
                    // 添加自定义日志
                    FirebaseCrashlytics.getInstance().log("User clicked button X")
                
                    // 添加自定义键值
                    FirebaseCrashlytics.getInstance().setCustomKey("user_id", userId)
                    FirebaseCrashlytics.getInstance().setCustomKey("screen", "home")
                    FirebaseCrashlytics.getInstance().setUserId(userId)
                }
                
                // 强制崩溃(测试)
                FirebaseCrashlytics.getInstance().crash()
                
                // 检查上次崩溃
                val crashlytics = FirebaseCrashlytics.getInstance()
                if (crashlytics.didCrashOnPreviousExecution()) {
                    // 处理上次崩溃后的逻辑
                }
                
                // 启用 Analytics 集成
                FirebaseAnalytics.getInstance(this).setUserId(userId)
                FirebaseAnalytics.getInstance(this).logEvent("purchase", bundle)

                自定义异常处理器

                class CrashHandler private constructor(
                    private val context: Context,
                    private val defaultHandler: Thread.UncaughtExceptionHandler?
                ) : Thread.UncaughtExceptionHandler {
                
                    override fun uncaughtException(thread: Thread, throwable: Throwable) {
                        try {
                            // 收集设备信息
                            val deviceInfo = collectDeviceInfo()
                            val stackTrace = Log.getStackTraceString(throwable)
                
                            // 保存到本地
                            saveCrashLog(stackTrace, deviceInfo)
                
                            // 上传到服务器
                            uploadCrashLog(stackTrace, deviceInfo)
                
                            // 发送通知
                            sendCrashNotification()
                
                        } catch (e: Exception) {
                            e.printStackTrace()
                        } finally {
                            // 交给系统默认处理(杀死进程)
                            defaultHandler?.uncaughtException(thread, throwable)
                            // 或退出应用
                            android.os.Process.killProcess(android.os.Process.myPid())
                            exitProcess(1)
                        }
                    }
                
                    private fun collectDeviceInfo(): String {
                        return """
                            App Version: ${BuildConfig.VERSION_NAME}
                            Android Version: ${Build.VERSION.RELEASE}
                            SDK: ${Build.VERSION.SDK_INT}
                            Device: ${Build.MANUFACTURER} ${Build.MODEL}
                            Memory: ${getAvailableMemory()}MB
                            Disk: ${getAvailableDisk()}MB
                        """.trimIndent()
                    }
                
                    private fun saveCrashLog(stackTrace: String, deviceInfo: String) {
                        val file = File(context.filesDir, "crash_${System.currentTimeMillis()}.log")
                        file.writeText("$deviceInfo\n\n$stackTrace")
                    }
                
                    companion object {
                        @Volatile
                        private var instance: CrashHandler? = null
                
                        fun init(context: Context) {
                            instance ?: synchronized(this) {
                                instance ?: CrashHandler(
                                    context.applicationContext,
                                    Thread.getDefaultUncaughtExceptionHandler()
                                ).also {
                                    Thread.setDefaultUncaughtExceptionHandler(it)
                                }
                            }
                        }
                    }
                }
                
                // Application 中初始化
                class MyApplication : Application() {
                    override fun onCreate() {
                        super.onCreate()
                        CrashHandler.init(this)
                    }
                }

                ANR 监控

                // StrictMode 检测主线程违规
                StrictMode.setThreadPolicy(
                    StrictMode.ThreadPolicy.Builder()
                        .detectAll()
                        .penaltyLog()
                        .penaltyDeath()  // 或 penaltyDialog()
                        .build()
                )
                
                // 检测 ANR
                class ANRDetector {
                    private val handler = Handler(Looper.getMainLooper())
                    private var isResponded = false
                
                    fun startDetection(timeoutMs: Long = 5000) {
                        isResponded = false
                        handler.postDelayed({
                            if (!isResponded) {
                                // 发生 ANR
                                val stackTrace = Thread.getAllStackTraces()
                                logANR(stackTrace)
                            }
                        }, timeoutMs)
                    }
                
                    fun onResponse() {
                        isResponded = true
                    }
                }
                
                // 使用 Application.ActivityLifecycleCallbacks 监控
                registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
                    override fun onActivityResumed(activity: Activity) {
                        anrDetector.startDetection()
                    }
                    override fun onActivityPaused(activity: Activity) {
                        anrDetector.onResponse()
                    }
                    // ... 其他方法
                })

                性能监控

                // Firebase Performance
                implementation 'com.google.firebase:firebase-perf'
                
                // 自动监控
                // 应用启动时间、屏幕渲染、网络请求等
                
                // 自定义追踪
                val trace = FirebasePerformance.startTrace("custom_trace")
                // 执行操作
                trace.putMetric("items_processed", items.size.toLong())
                trace.stop()
                
                // HTTP 网络监控(自动)
                // OkHttp 集成
                val client = OkHttpClient.Builder()
                    .addNetworkInterceptor(FirebasePerformance.getOkHttpInterceptor())
                    .build()
                
                // App Startup 时间
                class AppStartupLogger : Application.ActivityLifecycleCallbacks {
                    private var startupStartTime = 0L
                    private var firstActivityResumed = false
                
                    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                        if (startupStartTime == 0L) {
                            startupStartTime = System.currentTimeMillis()
                        }
                    }
                
                    override fun onActivityResumed(activity: Activity) {
                        if (!firstActivityResumed) {
                            firstActivityResumed = true
                            val startupTime = System.currentTimeMillis() - startupStartTime
                            FirebaseAnalytics.getInstance(activity)
                                .logEvent("app_startup_time", bundleOf("time_ms" to startupTime))
                        }
                    }
                }

                日志收集与分析

                // 自定义日志系统
                class AppLogger(private val context: Context) {
                
                    private val logFile = File(context.cacheDir, "app.log")
                    private val maxFileSize = 5 * 1024 * 1024L  // 5MB
                
                    fun log(level: LogLevel, tag: String, message: String, throwable: Throwable? = null) {
                        val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
                            .format(Date())
                
                        val logEntry = buildString {
                            append("[$timestamp] ")
                            append("[${level.name}] ")
                            append("[$tag] ")
                            append(message)
                            throwable?.let { append("\n${Log.getStackTraceString(it)}") }
                            append("\n")
                        }
                
                        logFile.appendText(logEntry)
                
                        // 检查文件大小
                        if (logFile.length() > maxFileSize) {
                            rotateLogFile()
                        }
                
                        // 同时输出到 Logcat
                        when (level) {
                            LogLevel.DEBUG -> Log.d(tag, message)
                            LogLevel.INFO -> Log.i(tag, message)
                            LogLevel.WARN -> Log.w(tag, message, throwable)
                            LogLevel.ERROR -> Log.e(tag, message, throwable)
                        }
                    }
                
                    private fun rotateLogFile() {
                        val oldFile = File(context.cacheDir, "app.log.old")
                        if (oldFile.exists()) oldFile.delete()
                        logFile.renameTo(oldFile)
                        logFile.createNewFile()
                    }
                
                    fun uploadLogs() {
                        // 压缩并上传到服务器
                        val zipFile = File(context.cacheDir, "logs.zip")
                        ZipOutputStream(zipFile.outputStream()).use { zos ->
                            zos.putNextEntry(ZipEntry("app.log"))
                            logFile.inputStream().copyTo(zos)
                            zos.closeEntry()
                        }
                        // 上传 zipFile
                    }
                }
                
                enum class LogLevel { DEBUG, INFO, WARN, ERROR }

                用户反馈收集

                // Shake to Feedback(摇一摇反馈)
                class FeedbackHelper(private val activity: Activity) {
                    private val shakeDetector = ShakeDetector(activity)
                
                    fun enableShakeFeedback() {
                        shakeDetector.start {
                            showFeedbackDialog()
                        }
                    }
                
                    private fun showFeedbackDialog() {
                        val dialogView = LayoutInflater.from(activity)
                            .inflate(R.layout.dialog_feedback, null)
                
                        MaterialAlertDialogBuilder(activity)
                            .setTitle("反馈问题")
                            .setView(dialogView)
                            .setPositiveButton("提交") { _, _ ->
                                val feedback = dialogView.findViewById<EditText>(R.id.etFeedback).text.toString()
                                val screenshot = takeScreenshot()
                                submitFeedback(feedback, screenshot)
                            }
                            .setNegativeButton("取消", null)
                            .show()
                    }
                
                    private fun takeScreenshot(): Bitmap? {
                        val view = activity.window.decorView.rootView
                        view.isDrawingCacheEnabled = true
                        val bitmap = Bitmap.createBitmap(view.drawingCache)
                        view.isDrawingCacheEnabled = false
                        return bitmap
                    }
                
                    private fun submitFeedback(feedback: String, screenshot: Bitmap?) {
                        // 上传到服务器
                        val crashLog = getLastCrashLog()
                        val deviceInfo = collectDeviceInfo()
                
                        viewModelScope.launch {
                            api.submitFeedback(FeedbackRequest(
                                content = feedback,
                                screenshot = bitmapToBase64(screenshot),
                                crashLog = crashLog,
                                deviceInfo = deviceInfo,
                                appVersion = BuildConfig.VERSION_NAME
                            ))
                        }
                    }
                }

                最佳实践:

                建立完整的监控体系:崩溃监控 + 性能监控 + 业务指标监控 + 用户反馈。

                😔 没有找到相关内容,请尝试其他关键词

← 返回IT 技术 yicool 百科 · 📱 Android 开发教程

评论 0