📚前言
跟着trae规划的学习开发安卓手机app的教程,一步一步学习实践,总规划见下面文档
【Trae+AI】和Trae学习搭建App_00:总项目规划_trae 封装apk-CSDN博客
下面开始规划的第四章,开始之前还有点事要说。
🌍战略调整
在进行核心API实现之前,现把前后端连起来,现了解整体运行机制,这样会更有成就感。
实践方式,尽量手搓,即Android Studio+文本编辑器+CMD+AI教程指导【豆包】
✍️AI提示语:
我要学习安卓手机app的开发,我已经完成第一章规划,第二章:开发环境配置(包括Android Studio、node.js、MongoDB、Git),以及第三章,后端基础框架 Express框架搭建 数据库模型设计(用户、任务、分类) 中间件配置(日志、跨域、认证)。请为我规划第四章的教学内容,安卓APP调用后端Express的详细教程,可以分多次输出,一定要有详细的操作过程,和对应原理,指导初学的我逐步掌握。
第二部分:后端API开发
第四章 安卓APP调用Express后端实战
一、第四章整体教学规划(语言Kotlin)
本章核心目标:基于Kotlin语言(Android官方首选开发语言),从零基础到完整调用后端所有核心接口(登录、任务CRUD、分类管理),兼顾「手把手操作步骤」和「底层原理」,整体分为6个部分,适配初学者节奏:
| 第一部分 | 安卓端网络通信基础准备(Kotlin项目创建+权限+核心依赖) | 搭建Kotlin版安卓基础项目,解决“安卓能访问后端”的前提问题(权限、依赖) |
| 第二部分 | 安卓网络请求工具封装(OkHttp3 + Kotlin协程) | 掌握Kotlin环境下的主流网络库封装,用协程替代传统子线程,简化异步逻辑 |
| 第三部分 | 登录接口调用(获取JWT令牌)+ 令牌本地存储(Kotlin版) | 实现“账号密码→后端验证→获取令牌”全流程,理解JWT在Kotlin端的使用逻辑 |
| 第四部分 | 带令牌调用认证接口(任务/分类查询) | 掌握“请求头携带JWT”的Kotlin实现,适配后端auth中间件验证逻辑 |
| 第五部分 | 业务接口CRUD(任务/分类的增、删、改、查) | 覆盖完整业务场景,掌握不同请求方式(GET/POST/PUT/DELETE)的Kotlin实现 |
| 第六部分 | 网络异常处理+调试技巧(Kotlin端+后端联调) | 解决实战中常见问题,掌握Kotlin环境下的调试方法 |
二、第四章(第一部分):安卓端网络通信基础准备(Kotlin版)
学习目标
之前教程参考
-
- 【App开发】01:Android Studio 安装配置-CSDN博客
- 【App开发】02:Android Studio项目环境设置_android studio 项目环境配置-CSDN博客
- 【App开发】03:Android Studio发布应用_android studio发布app-CSDN博客
- 【App开发】04:Android Studio第一个项目之页面开发_android studio开发-CSDN博客
前置知识回顾
- 后端已启动:http://localhost:3000(安卓模拟器访问本地后端需用 http://10.0.2.2:3000,原理后文讲);
- 后端已有核心接口:/api/login(登录)、/api/tasks(任务查询)、/test(测试接口)。
步骤1:创建Kotlin版安卓项目(详细操作)
操作流程(手把手)
- Name:AndroidExpressDemo(项目名,自定义,英文);
- Package name:com.example.androidexpressdemo(包名,默认即可,安卓唯一标识);
- Save location:选择本地存储路径(如E:\\AndroidProjects);
- Language:Kotlin(Android官方首选,初学者易上手,语法更简洁);
- Minimum SDK:选择API 24: Android 7.0 (Nougat)(覆盖95%以上设备);

- app/src/main/AndroidManifest.xml:安卓核心配置文件(权限、组件声明);
- app/src/main/java/com/example/androidexpressdemo/MainActivity.kt:主界面逻辑(Kotlin代码编写处);
- app/src/main/res/layout/activity_main.xml:主界面布局(UI控件);
- app/build.gradle:依赖配置文件(添加网络库、协程等依赖)。

说明
- Kotlin是Android官方2017年宣布的首选开发语言,相比Java:
- 语法更简洁(少样板代码,如无需写findViewById);
- 天然支持空安全(避免空指针异常,初学者踩坑少);
- 内置协程(优雅处理异步操作,替代Java的Thread);
- Minimum SDK选7.0:既保证兼容性,又能使用Kotlin协程、OkHttp3等现代API。
步骤2:配置安卓网络权限(必做)
操作流程
<uses-permission android:name="android.permission.INTERNET" />
<!– 允许访问网络状态(可选,用于判断网络是否可用) –>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidExpressDemo"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
原理说明(和Java版一致,补充Kotlin无影响)
📌Android应用中的权限声明
Android 应用清单文件(AndroidManifest.xml)中的权限声明是应用向系统申请访问设备资源 / 功能的核心方式,Android 官方将权限分为 普通权限(Normal)、危险权限(Dangerous)、特殊权限(Special) 三类,普通权限只需清单声明,危险权限需动态申请,特殊权限需引导用户到系统设置授权,且所有权限需适配对应 Android 版本的行为变更。
➡️更详细内容见附录部分。
步骤3:添加核心依赖(OkHttp3 + Gson + Kotlin协程)
操作流程
// 基础Kotlin依赖(默认已有)
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
// 新增:OkHttp3网络请求库(核心)
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
// 新增:Gson(JSON解析,后端返回JSON→Kotlin对象)
implementation 'com.google.code.gson:gson:2.10.1'
// 新增:Kotlin协程(处理异步网络请求,替代Java的Thread)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
}
🧩实际操作界面参考:
添加依赖后,同步前的界面:

同步后,成功界面如下:

📖原理说明(Kotlin专属补充)
- 避免“回调地狱”,用同步写法实现异步逻辑;
- 适配Android主线程规则,自动切换线程(网络请求在子线程,UI更新在主线程)。
📌app/build.gradle文件中依赖的两种书写格式:
| implementation(libs.androidx.core.ktx) | 基于 Gradle 版本目录(Version Catalog),依赖版本集中管理,工程级统一维护;这是 Gradle 7.0+ 引入的 Version Catalog 特性(Android Gradle Plugin 7.0+ 适配),版本号不在 build.gradle 中,而是集中写在工程根目录的 libs.versions.toml 文件里 |
| implementation 'androidx.core:core-ktx:1.9.0' | 传统直接声明,版本号硬编码在 build.gradle 中,分散管理 |
💡建议:两种写法都能正确引入依赖,推荐新建项目(AGP 7.0+)优先使用版本目录写法。
步骤4:基础测试——Kotlin版安卓访问Express后端(验证连通性)
前置准备:确保后端正常运行
app.get('/test', (req, res) => {
res.json({
code: 200,
message: '安卓(Kotlin)端连通测试成功',
data: { time: new Date().toString() }
});
});
🧩实际操作参考:
修改app.js后,通过浏览器,访问测试地址:localhost:3000/test,显示如下信息:

步骤4.1:修改布局文件(显示请求结果)
打开app/src/main/res/layout/activity_main.xml,添加TextView用于显示请求结果:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="等待请求…"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
🧩界面设置参考:

步骤4.2:编写Kotlin网络请求代码(协程版)
打开MainActivity.kt,编写核心代码(Kotlin+协程,适配Android最佳实践):
package com.example.androidexpressdemo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
class MainActivity : AppCompatActivity() {
// 日志标签(调试用)
private val TAG = "NetworkTest"
// 后端地址:安卓模拟器中localhost映射为10.0.2.2(关键!)
private val BASE_URL = "http://10.0.2.2:3000"
// 延迟初始化TextView(Kotlin空安全特性,避免空指针)
private lateinit var tvResult: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 绑定UI控件(Kotlin无需findViewById,直接用id引用,需开启视图绑定或直接引用)
tvResult = findViewById(R.id.tvResult)
// 1. 创建OkHttp客户端(全局单例更佳,此处简化)
val okHttpClient = OkHttpClient()
// 2. 构建GET请求(访问后端/test接口)
val request = Request.Builder()
.url("$BASE_URL/test") // 拼接测试接口地址
.get() // 指定GET请求(可省略,默认GET)
.build()
// 3. 启动协程处理网络请求(Kotlin核心,替代Java的Thread)
GlobalScope.launch(Dispatchers.Main) {
// 子线程执行网络请求(Dispatchers.IO:IO密集型任务)
val responseResult = withContext(Dispatchers.IO) {
try {
// 执行同步请求(OkHttp同步调用,协程中无阻塞问题)
val response = okHttpClient.newCall(request).execute()
// 获取响应体字符串
val responseBody = response.body?.string()
// 返回成功结果
"请求成功:$responseBody"
} catch (e: IOException) {
// 捕获网络异常
e.printStackTrace()
"请求失败:${e.message}"
}
}
// 4. 主线程更新UI(Dispatchers.Main:自动切换到主线程)
tvResult.text = responseResult
// 日志打印结果(调试用)
Log.d(TAG, "后端返回:$responseResult")
}
}
}
📌相关说明
- GlobalScope的提示
- 如下图

- 警告说明
-
GlobalScope的定义:它是一个全局协程作用域,不绑定任何任务,用于启动 “全应用生命周期” 的协程,但不会保持进程存活(类似守护线程)。
-
风险点:GlobalScope是 “敏感 API”,易引发资源 / 内存泄漏
-
-
解决方案
- Activity/Fragment 中:使用lifecycleScope(自动绑定页面生命周期,页面销毁时协程自动取消) lifecycleScope.launch(Dispatchers.Main) {
// 网络请求等操作
} 实际代码示例:
- ViewModel 中:使用viewModelScope(绑定 ViewModel 生命周期,ViewModel 销毁时协程自动取消) viewModelScope.launch(Dispatchers.Main) {
// 网络请求等操作
}
- Activity/Fragment 中:使用lifecycleScope(自动绑定页面生命周期,页面销毁时协程自动取消) lifecycleScope.launch(Dispatchers.Main) {
- 如下图
- 语法知识点
- val:Kotlin 关键字,声明不可变变量(类似 Java 的 final)
- OkHttpClient:OkHttp 库的核心客户端类,负责管理网络请求的所有配置(如超时时间、拦截器、连接池等)、发起请求、处理响应;
- OkHttpClient():调用 OkHttpClient 的无参构造函数,创建一个默认配置的客户端实例。
-
通过无参构造创建的 OkHttpClient 会使用 OkHttp 的默认配置,核心默认值如下:
配置项默认值作用 连接超时时间 10 秒 客户端与服务器建立连接的最大等待时间 读取超时时间 10 秒 连接建立后,读取服务器响应数据的最大等待时间 写入超时时间 10 秒 向服务器写入请求数据的最大等待时间 连接池 5 个并发连接,5 分钟空闲超时 复用 HTTP 连接(避免重复创建连接,提升性能) 拦截器 空 无自定义拦截器(可后续通过 newBuilder() 添加,如日志、请求头拦截) 重定向策略 允许自动重定向 遇到 3xx 状态码时,自动跳转至新地址
-
- OkHttpClient():调用 OkHttpClient 的无参构造函数,创建一个默认配置的客户端实例。
- Request.Builder().url("$BASE_URL/test") .get() .build()
- Request:OkHttp 中封装 HTTP 请求的核心类,包含请求的 URL、方法、头信息、请求体等所有参数;
- Builder():调用 Request 的构建器构造函数;
- .url():Request.Builder 的核心方法,用于设置请求的目标 URL;
- .get():显式指定该请求为 HTTP GET 方法;
- .build():Request.Builder 的最终方法,根据前面配置的参数(URL、请求方法等),生成不可变的 Request 实例;
- 调用后,构建器的配置会固化到 Request 对象中,无法再修改(如需调整,需重新创建 Builder)。
-
启动协程处理网络请求
-
lifecycleScope:绑定 Activity/Fragment 生命周期的协程作用域(替代危险的 GlobalScope),页面销毁(onDestroy)时,该作用域下的所有协程会自动取消;
-
launch(Dispatchers.Main):
- launch:启动一个新的协程(协程构建器);
- Dispatchers.Main:指定协程的默认调度器为 “主线程(UI 线程)”—— 即协程的代码块默认在主线程执行,且最终结果会回到主线程(方便更新 UI)
-
withContext(Dispatchers.IO)
- withContext:协程的 “挂起函数”,作用是切换到指定线程执行代码块,执行完成后切回原线程,并将代码块的返回值赋值给 responseResult。
- Dispatchers.IO:协程的 IO 调度器,专门用于处理 IO 密集型任务(网络请求、文件读写、数据库操作等),底层是线程池,自动管理线程复用,避免手动创建 Thread 的繁琐。
- 核心逻辑:把耗时的网络请求 “丢到 IO 线程执行”,执行完后自动切回 launch 指定的主线程。
-
okHttpClient.newCall(request).execute()
- okHttpClient.newCall(request):将之前构建的 Request 对象包装为 OkHttp 的 Call 对象(表示一次可执行的请求);
- execute():OkHttp 的同步请求方法(区别于 enqueue 异步回调):
- 同步请求会 “阻塞当前线程”,但这里运行在 Dispatchers.IO 线程,因此不会阻塞主线程;
- response.body?.string()
- response.body:OkHttp 的响应体对象(ResponseBody),封装服务器返回的数据;
- ?.string():安全调用响应体的 string() 方法(避免空指针),将响应体转为字符串(如 JSON 字符串);
- ⚠️ 注意:string() 只能调用一次(调用后响应体流会关闭),如需多次使用需先保存为变量。
-
- "请求成功:$responseBody",在 Kotlin 中,不需要显式写 return 就能返回结果
- 在 Kotlin 中,带有代码块的 lambda 表达式、匿名函数、普通函数,如果没有显式写 return,会默认将代码块的「最后一行语句的结果」作为返回值(前提是代码块有返回类型约束)。
- Log.d(TAG, "后端返回:$responseResult"):在Logcat窗口查看输出的日志信息
步骤4.3:运行测试(验证连通性)


第三方模拟器(如夜神、雷电),
第三方模拟器不支持10.0.2.2,需替换为模拟器的「桥接 IP」:
服务器端相应的访问信息如下:第一部分学习总结(Kotlin版)
完成Kotlin版安卓项目搭建,解决“安卓访问后端”的3个核心问题:
- 权限:配置INTERNET和明文HTTP允许;
- 依赖:引入OkHttp3(网络)、Gson(JSON)、协程(异步);
- 地址:模拟器访问电脑后端;
验证了Kotlin端与Express后端的连通性,为后续接口调用打下基础。
附录
Android权限划分
Android 官方将权限分为 普通权限(Normal)、危险权限(Dangerous)、特殊权限(Special) 三类,以下是按功能分类的常用权限及核心说明:
一、权限分类基础
| 普通权限 | 低风险,不涉及用户隐私,系统自动授予 | 仅清单声明即可 |
| 危险权限 | 高风险,涉及用户隐私 / 设备安全,需动态申请(Android 6.0+ / API 23+) | 清单声明 + 运行时动态申请 |
| 特殊权限 | 极高风险(如悬浮窗、后台定位),需通过系统设置单独申请 | 清单声明 + 引导用户到系统设置页授权 |
二、常用权限按功能分类详解
1. 网络相关权限(普通权限为主)
| android.permission.INTERNET | 普通 | 允许应用访问互联网(HTTP/HTTPS/WS 等),访问后端必备 |
| android.permission.ACCESS_NETWORK_STATE | 普通 | 获取设备当前网络状态(是否联网、Wi-Fi / 移动数据) |
| android.permission.ACCESS_WIFI_STATE | 普通 | 获取 Wi-Fi 详细信息(如 SSID、Wi-Fi 连接状态) |
| android.permission.CHANGE_NETWORK_STATE | 普通 | 修改网络状态(如开启 / 关闭移动数据,部分设备需系统级权限) |
| android.permission.CHANGE_WIFI_STATE | 普通 | 修改 Wi-Fi 状态(如开启 / 关闭 Wi-Fi、连接指定 Wi-Fi) |
| android.permission.ACCESS_NETWORK_INFO | 普通 | 兼容旧版本,等效于 ACCESS_NETWORK_STATE(API 1 后废弃,建议用后者) |
2. 存储相关权限(危险权限,Android 10+ 有特殊适配)
| android.permission.READ_EXTERNAL_STORAGE | 危险 | 读取外部存储(SD 卡 / 共享目录)文件 | Android 10+(API 29+)需声明 android:requestLegacyExternalStorage="true" 兼容旧逻辑;Android 13+ 拆分为更细粒度权限 |
| android.permission.WRITE_EXTERNAL_STORAGE | 危险 | 写入文件到外部存储 | Android 11+(API 30+)被废弃,推荐用 MediaStore/SAF 替代 |
| android.permission.MANAGE_EXTERNAL_STORAGE | 特殊 | 管理所有外部存储文件(仅系统应用 / 合规应用可申请,需谷歌审核) | 需引导用户到系统设置页授权,普通应用不建议使用 |
| android.permission.READ_MEDIA_IMAGES | 危险 | Android 13+ 读取图片 / 照片(替代 READ_EXTERNAL_STORAGE) | 仅需动态申请此权限,无需申请旧存储权限 |
| android.permission.READ_MEDIA_VIDEO | 危险 | Android 13+ 读取视频文件 | 同上 |
| android.permission.READ_MEDIA_AUDIO | 危险 | Android 13+ 读取音频文件 | 同上 |
3. 位置相关权限(危险 / 特殊权限)
| android.permission.ACCESS_FINE_LOCATION | 危险 | 获取精确位置(GPS / 北斗,精度米级) | |
| android.permission.ACCESS_COARSE_LOCATION | 危险 | 获取粗略位置(基站 / Wi-Fi,精度千米级) | |
| android.permission.ACCESS_BACKGROUND_LOCATION | 特殊 | 应用在后台(非前台)时获取位置 | Android 10+ 需单独申请,引导用户授权 |
| android.permission.ACCESS_MOCK_LOCATION | 特殊 | 模拟位置数据(仅调试 / 系统应用可用) | 需在开发者选项中开启 “允许模拟位置” |
4. 相机 / 媒体相关权限(危险权限)
| android.permission.CAMERA | 危险 | 访问相机硬件(拍照 / 录像) | |
| android.permission.RECORD_AUDIO | 危险 | 录制音频(麦克风访问) | |
| android.permission.USE_FINGERPRINT | 普通 | 访问指纹传感器(指纹识别) | Android 9+ 推荐用 USE_BIOMETRIC |
| android.permission.USE_BIOMETRIC | 普通 | 访问生物识别(指纹 / 人脸 / 虹膜) |
5. 电话 / 通讯相关权限(危险 / 普通)
| android.permission.READ_PHONE_STATE | 危险 | 读取手机状态(IMEI / 手机号 / 网络类型 / 通话状态) | Android 10+ 需申请 READ_PHONE_NUMBERS 单独读取手机号 |
| android.permission.CALL_PHONE | 危险 | 直接拨打电话(无需用户确认) | |
| android.permission.READ_CALL_LOG | 危险 | 读取通话记录 | |
| android.permission.WRITE_CALL_LOG | 危险 | 写入 / 修改通话记录 | |
| android.permission.ANSWER_PHONE_CALLS | 危险 | 接听来电(Android 8.0+) | |
| android.permission.SEND_SMS | 危险 | 发送短信 | |
| android.permission.READ_SMS | 危险 | 读取短信内容 | |
| android.permission.RECEIVE_SMS | 危险 | 接收短信(监听短信广播) |
6. 系统 / 设备控制相关权限(普通 / 特殊)
| android.permission.SYSTEM_ALERT_WINDOW | 特殊 | 显示悬浮窗(覆盖其他应用) | 需引导用户到 “设置 – 应用 – 权限 – 悬浮窗” 授权 |
| android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS | 特殊 | 忽略电池优化(应用后台不被杀死) | 需动态申请,用户确认后生效 |
| android.permission.WAKE_LOCK | 普通 | 唤醒屏幕 / 保持 CPU 运行(防止应用休眠) | 如音乐播放、后台下载时使用 |
| android.permission.VIBRATE | 普通 | 控制设备振动 | |
| android.permission.SET_WALLPAPER | 普通 | 设置系统壁纸 |
7. 日历 / 联系人 / 日程(危险权限)
| android.permission.READ_CONTACTS | 危险 | 读取联系人列表 | |
| android.permission.WRITE_CONTACTS | 危险 | 添加 / 修改 / 删除联系人 | |
| android.permission.READ_CALENDAR | 危险 | 读取日历事件 | |
| android.permission.WRITE_CALENDAR | 危险 | 添加 / 修改 / 删除日历事件 |
8. 传感器 / 硬件相关(普通权限为主)
| android.permission.BODY_SENSORS | 危险 | 访问身体传感器(心率、步数、体温等) | |
| android.permission.ACCESS_ACCELEROMETER | 普通 | 访问加速度传感器 | |
| android.permission.ACCESS_GYROSCOPE | 普通 | 访问陀螺仪传感器 | |
| android.permission.ACCESS_MAGNETIC_FIELD | 普通 | 访问磁力计传感器 |
三、特殊权限声明注意事项
- Android 6.0+(API 23):危险权限需动态申请,普通权限仍自动授予;
- Android 13+(API 33):存储权限拆分为图片 / 视频 / 音频细粒度权限,无需申请旧存储权限;
- 声明高版本权限时,可通过 android:maxSdkVersion 限制仅在指定版本生效(如 <uses-permission android:name="xxx" android:maxSdkVersion="32"/>);
四、如何快速查询所有官方权限
总结
清单文件中的权限声明需遵循 “最小权限原则”—— 仅声明应用必需的权限,避免过度申请隐私权限(否则会被应用商店审核拒绝,或降低用户信任)。普通权限只需清单声明,危险权限需动态申请,特殊权限需引导用户到系统设置授权,且所有权限需适配对应 Android 版本的行为变更。





