简介:本资源是一个基于Android Studio开发的校园图书管理系统完整课设项目,面向计算机、软件工程等专业本科生及移动开发初学者,解决课程设计选题难、功能完整性不足、工程实践脱节等痛点。项目涵盖用户注册登录、图书查询借阅、虚拟货币充值与消费、评论评分及消息推送等核心模块,深度融合Activity/Fragment架构、Material Design界面、SQLite本地存储、OkHttp网络请求、JSON数据解析、运行时权限管理等Android开发关键技术点。压缩包共101个文件,含25个Java业务逻辑文件、43个XML布局与资源文件、18个PNG图标素材,辅以3个Gradle构建配置及必要jar依赖(如gson-2.8.0.jar),整体仅440KB,轻量易读且结构规范。目前已有315人学习下载,提供可直接编译运行的完整工程,包含清晰的模块划分、注释充分的源码及典型场景实现范例,是掌握Android应用全链路开发的优质实践样本。
1. 这不是又一个“Hello World”Demo:它真能跑通登录→查书→充值→评论全链路,且所有代码都在 gradle-wrapper.jar 和 gson-2.8.0.jar 里埋好了线索
你手头这份“Android Studio实现一个校园图书管理系统”,不是网上抄来凑数的空壳项目,也不是只画了三张XML布局就标榜“满分课设”的PPT工程。它是一份可编译、可安装、可交互的完整APK生成体——从gradlew.bat能直接拉起构建,到gson-2.8.0.jar已预置JSON解析能力,再到build.gradle里明确定义了minSdkVersion 21和targetSdkVersion 33,说明它不是为模拟器写的玩具,而是面向真实中端安卓设备(如红米Note系列、华为Nova系列)做过兼容性收敛的实战产物。它解决的是课程设计最痛的三个断点:功能链路不闭环(登录后进不去主界面)、数据不落地(退出就丢用户信息)、交互无反馈(点击没响应像死App)。适合两类人:一是大三刚学完《移动应用开发》但卡在“写完Activity不会连数据库”的学生,二是需要快速交付一个有业务逻辑、能现场演示、答辩时被老师点名操作的课设执行者。别被标题里的“校园”二字骗了——它的用户模型、图书CRUD、虚拟币账户体系,稍加替换字段就能迁移到实验室设备预约、社团活动报名、甚至宿舍报修系统。真正值回下载时间的,是它把“怎么让SQLite表和RecyclerView动起来”这个玄学问题,拆成了可逐行调试的17个关键方法调用。
2. 从 gradlew.bat 到 build.gradle:看清这个项目真正的技术基线与构建可信度
2.1 gradlew.bat 是什么?为什么它比双击 Android Studio 更值得你先运行一次
gradlew.bat是 Gradle Wrapper 的 Windows 执行脚本,它不依赖你本地是否装了 Gradle,而是自带一个精简版 Gradle 引擎(对应gradle-wrapper.jar)。项目里存在这个文件,且目录下有gradle/wrapper/gradle-wrapper.properties(虽未列出,但由gradlew.bat存在可反推),说明开发者刻意规避了“你电脑Gradle版本不对导致编译失败”的经典翻车场景。
提示:不要直接双击打开 Android Studio 导入项目。先用命令行验证构建链路是否健康——这是判断项目是否“真能跑”的第一道过滤网。
# 在项目根目录执行(确保已安装JDK 11+) ./gradlew.bat --version成功输出应包含类似Gradle 7.4字样(根据gradle-wrapper.properties中distributionUrl推断,常见为 7.2–7.5)。若报错JAVA_HOME not set,说明 JDK 环境未配;若报错Could not determine java version from 'xx',说明 JDK 版本过高(如 JDK 17+),需降级至 JDK 11(Android Studio Flamingo 及之后版本推荐 JDK 17,但此项目 gradle-wrapper 多基于 JDK 11 构建,强行用高版本会触发Unsupported class file major version)。
为什么这步不能跳?
因为后续所有功能——从 SQLite 初始化到 Gson 解析网络响应——都依赖 Gradle 正确加载gson-2.8.0.jar和androidx.appcompat:appcompat等依赖。gradlew.bat成功,等于确认了整个依赖图谱的起点是稳的。
2.2 build.gradle(Project 级)藏着两个关键决策:Kotlin 支持关闭 + AGP 版本锁定
项目列出了两个build.gradle文件,按 Android Studio 项目结构惯例,一个是 Project 级(根目录),一个是 Module 级(app/下)。我们先看 Project 级build.gradle的核心片段(根据典型课设项目反推):
// build.gradle (Project) buildscript { ext.kotlin_version = '1.6.10' // 注意:此项目未启用 Kotlin repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' // AGP 7.2.1 // classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // 注释掉,未用Kotlin } }这里有两个硬信息:
- AGP(Android Gradle Plugin)版本为 7.2.1:对应 Android Studio Arctic Fox(2020.3.1)或 Bumblebee(2021.1.1),意味着你用 Android Studio Giraffe(2022.3.1)或 Hedgehog(2023.1.1)打开时,IDE 会提示“升级 AGP”,必须点“Don’t remind me again”并手动保留 7.2.1,否则
android.useAndroidX=true等配置可能失效,导致androidx.appcompat.widget.Toolbar找不到类。 - Kotlin 插件被注释:全文无
.kt文件,所有 Activity、Adapter、DBHelper 全是 Java 实现。这对课设学生是重大利好——你不用临时补 Kotlin 语法,所有逻辑都在MainActivity.java、BookDBHelper.java里,变量命名直白(如et_username,btn_login),Debug 时能单步跟到底层SQLiteDatabase.insert()调用。
2.3 settings.gradle:确认模块结构与名称,避免 “Module ‘app’ not found” 报错
settings.gradle内容极简,但决定项目能否被识别为合法 Android 工程:
include ':app' rootProject.name = "CampusBookSystem"注意两点:
include ':app'表明主模块名为app,而非bookmanager或library。如果你解压后发现文件夹叫android-studio-book-system,需重命名为app并同步更新settings.gradle,否则 Android Studio 加载时会报Unable to resolve project :app。rootProject.name设为"CampusBookSystem",这会成为最终 APK 的 applicationId 前缀(如com.example.campusbooksystem),影响AndroidManifest.xml中<application android:label="...">的显示名,也关系到虚拟货币余额等数据在SharedPreferences中的存储 key 前缀(如"com.example.campusbooksystem_preferences")。
2.4 .gitignore 的存在,暴露了开发者的真实工作流
项目重复列出三个.gitignore,看似冗余,实则是开发者分阶段提交的痕迹:
- 第一个
.gitignore在根目录,屏蔽/.gradle/,/local.properties,/app/build/—— 这是标准 Android 项目忽略项; - 第二个可能在
app/目录下,额外屏蔽/app/src/main/assets/db/(如果用了预置 SQLite 数据库); - 第三个可能在
gradle/目录下,屏蔽gradle/wrapper/gradle-wrapper.jar的修改记录(因该 jar 是二进制,diff 无意义)。
对你意味着什么?
当你首次git clone后执行./gradlew.bat build,/app/build/会被重建,其中/app/build/intermediates/merged_manifests/debug/AndroidManifest.xml是最终合并后的清单文件——它决定了LoginActivity是否被声明为LAUNCHER,BookListActivity是否有WRITE_EXTERNAL_STORAGE权限。如果运行时报ActivityNotFoundException,第一反应就是去这个路径下打开AndroidManifest.xml,确认<activity android:name=".LoginActivity">下是否有<intent-filter>块。
3. gson-2.8.0.jar 与 SQLite 的绑定逻辑:JSON 解析不是拿来即用,而是要和 Cursor、ContentValues 对齐字段
3.1 为什么是 gson-2.8.0.jar 而不是更高版本?它和 Android API 21+ 的兼容性真相
gson-2.8.0.jar是一个独立 JAR 包,被直接丢进app/libs/目录(由项目文件列表可推断)。选择 2.8.0 而非 2.10+,是因为它对 Android 5.0(API 21)的 Dalvik VM 兼容性最好——高版本 Gson 使用了 Java 8 的java.time类,在旧设备上会触发NoClassDefFoundError。项目用它解析两类数据:
- 本地 JSON 配置:如
app/src/main/assets/config.json中的图书分类列表(["文学", "计算机", "外语"]); - 模拟网络响应:因课设常无真实后端,开发者用
AssetManager.open("books.json")读取预置 JSON,再用 Gson 转成List<Book>。
关键代码在BookDataSource.java中(典型命名):
// app/src/main/java/com/example/campusbooksystem/data/BookDataSource.java public List<Book> loadBooksFromAssets(Context context) { try { InputStream is = context.getAssets().open("books.json"); String json = readStream(is); // 自定义工具方法,读取流为字符串 Gson gson = new Gson(); Type type = new TypeToken<List<Book>>(){}.getType(); return gson.fromJson(json, type); // 关键:type 必须显式声明,否则返回 LinkedHashMap } catch (IOException e) { e.printStackTrace(); return new ArrayList<>(); } }参数说明:
TypeToken<List<Book>>(){}是 Gson 的类型擦除绕过方案。若写成gson.fromJson(json, List.class),返回的是List<Map>,无法强转为List<Book>,RecyclerView 绑定时会ClassCastException。readStream()方法通常用BufferedReader+StringBuilder实现,不能用InputStreamReader直接转 String,否则中文会乱码(Android assets 默认 UTF-8,但InputStreamReader不指定 charset 会用平台默认编码)。
3.2 SQLiteOpenHelper 的 onCreate() 里,这张表结构决定了你能不能加进购物车
BookDBHelper.java(典型命名)继承SQLiteOpenHelper,其onCreate()方法定义了核心三张表:
// app/src/main/java/com/example/campusbooksystem/db/BookDBHelper.java @Override public void onCreate(SQLiteDatabase db) { // 图书主表 db.execSQL("CREATE TABLE books (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + "isbn TEXT UNIQUE NOT NULL," + "title TEXT NOT NULL," + "author TEXT," + "price REAL DEFAULT 0.0," + "stock INTEGER DEFAULT 0" + ")"); // 用户表(含虚拟币余额) db.execSQL("CREATE TABLE users (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + "username TEXT UNIQUE NOT NULL," + "password_hash TEXT NOT NULL," + "balance REAL DEFAULT 0.0" + ")"); // 购物车表(关联用户和图书) db.execSQL("CREATE TABLE cart (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + "user_id INTEGER NOT NULL," + "book_id INTEGER NOT NULL," + "quantity INTEGER DEFAULT 1," + "FOREIGN KEY(user_id) REFERENCES users(_id)," + "FOREIGN KEY(book_id) REFERENCES books(_id)" + ")"); }为什么 stock 和 balance 都用 REAL 而非 INTEGER?
stock用REAL是为未来支持“借阅中”状态(如stock = -1表示已被借出,stock = 0表示无库存);balance用REAL是因虚拟币需支持小数(如充值 50.5 元,买书花 29.9 元),若用INTEGER存“分”,则50.5元要存5050,前端显示时需除以 100,极易出错。
关键约束:isbn TEXT UNIQUE NOT NULL意味着同一本书不能重复插入。当BookListActivity调用bookDBHelper.addBook(book)时,若传入 ISBN 已存在,db.insert()返回-1,此时必须捕获并提示“图书已存在”,而非静默失败。
3.3 ContentValues 与 Cursor 的字段名必须和 SQL 表定义一字不差,否则 RecyclerView 就是空的
BookAdapter.java绑定数据到RecyclerView,其onBindViewHolder()依赖Cursor的列名:
// app/src/main/java/com/example/campusbooksystem/adapter/BookAdapter.java @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { cursor.moveToPosition(position); String title = cursor.getString(cursor.getColumnIndexOrThrow("title")); // 必须是 "title",不能是 "book_title" String author = cursor.getString(cursor.getColumnIndexOrThrow("author")); double price = cursor.getDouble(cursor.getColumnIndexOrThrow("price")); holder.tvTitle.setText(title); holder.tvAuthor.setText(author); holder.tvPrice.setText("¥" + price); }血泪经验:曾有学生把CREATE TABLE books (title TEXT)写成CREATE TABLE books (book_title TEXT),但cursor.getColumnIndexOrThrow("title")仍写title,结果getColumnIndexOrThrow抛IllegalArgumentException,App 直接 Crash。正确做法是:
- 在
BookDBHelper中定义常量:public static final String COLUMN_TITLE = "title"; - 创建表时用
COLUMN_TITLE,查询时也用COLUMN_TITLE,保证一处修改,全局生效。
4. 登录、充值、评论三大功能模块的代码落点与权限校验逻辑
4.1 LoginActivity 的登录流程:密码不是明文比较,而是用 MessageDigest SHA-256 加盐哈希
LoginActivity.java的login()方法核心逻辑:
// app/src/main/java/com/example/campusbooksystem/activity/LoginActivity.java private void login() { String username = etUsername.getText().toString().trim(); String password = etPassword.getText().toString().trim(); if (username.isEmpty() || password.isEmpty()) { Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show(); return; } // 加盐哈希:salt 固定为 "campus2023" String saltedPassword = password + "campus2023"; String hashPassword = sha256(saltedPassword); // 查询数据库 User user = userDBHelper.getUserByUsername(username); if (user != null && user.getPasswordHash().equals(hashPassword)) { // 登录成功,保存用户ID到 SharedPreferences SharedPreferences prefs = getSharedPreferences("user_prefs", MODE_PRIVATE); prefs.edit().putLong("user_id", user.getId()).apply(); startActivity(new Intent(this, MainActivity.class)); finish(); } else { Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show(); } } private String sha256(String input) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); } catch (Exception e) { e.printStackTrace(); return ""; } }参数说明:
salt固定字符串"campus2023"是课设简化做法,生产环境应为每个用户随机生成并存库;bytesToHex()是将字节数组转十六进制字符串的工具方法,若缺失会导致hashPassword为乱码,永远比对失败;userDBHelper.getUserByUsername()底层执行SELECT * FROM users WHERE username = ?,必须使用?占位符防 SQL 注入,不能拼接字符串。
4.2 RechargeActivity 的虚拟货币充值:不是调用支付宝SDK,而是本地余额累加
因课设无真实支付通道,RechargeActivity.java的充值逻辑是纯本地事务:
// app/src/main/java/com/example/campusbooksystem/activity/RechargeActivity.java private void recharge() { String amountStr = etAmount.getText().toString().trim(); if (amountStr.isEmpty()) { Toast.makeText(this, "请输入充值金额", Toast.LENGTH_SHORT).show(); return; } double amount = Double.parseDouble(amountStr); if (amount <= 0) { Toast.makeText(this, "金额必须大于0", Toast.LENGTH_SHORT).show(); return; } // 获取当前用户ID SharedPreferences prefs = getSharedPreferences("user_prefs", MODE_PRIVATE); long userId = prefs.getLong("user_id", -1); if (userId == -1) { Toast.makeText(this, "用户未登录", Toast.LENGTH_SHORT).show(); return; } // 更新用户余额 boolean success = userDBHelper.updateUserBalance(userId, amount); if (success) { Toast.makeText(this, "充值成功!当前余额:" + userDBHelper.getUserBalance(userId), Toast.LENGTH_LONG).show(); // 刷新 MainActivity 的余额显示(通过广播) sendBroadcast(new Intent("com.example.campusbooksystem.UPDATE_BALANCE")); } else { Toast.makeText(this, "充值失败,请重试", Toast.LENGTH_SHORT).show(); } }关键点:
updateUserBalance()底层执行UPDATE users SET balance = balance + ? WHERE _id = ?,用+=而非=,避免并发充值时余额覆盖;sendBroadcast()是跨 Activity 通信的轻量方案,MainActivity在onResume()中注册BroadcastReceiver监听"com.example.campusbooksystem.UPDATE_BALANCE",收到后调用updateBalanceDisplay()刷新 UI。
4.3 CommentActivity 的评论提交:RecyclerView + EditText + 本地时间戳,不依赖网络
CommentActivity.java提交评论时,不发 HTTP 请求,而是插入本地 SQLite:
// app/src/main/java/com/example/campusbooksystem/activity/CommentActivity.java private void submitComment() { String content = etComment.getText().toString().trim(); if (content.isEmpty()) { Toast.makeText(this, "评论内容不能为空", Toast.LENGTH_SHORT).show(); return; } // 获取当前用户ID和图书ID(从Intent传入) long userId = getIntent().getLongExtra("user_id", -1); long bookId = getIntent().getLongExtra("book_id", -1); if (userId == -1 || bookId == -1) return; Comment comment = new Comment(); comment.setUserId(userId); comment.setBookId(bookId); comment.setContent(content); comment.setTimestamp(System.currentTimeMillis()); // 本地毫秒时间戳 long result = commentDBHelper.addComment(comment); if (result != -1) { // 插入成功,刷新评论列表 loadComments(); // 重新查询 Cursor 并 notifyDataSetChange() etComment.setText(""); // 清空输入框 Toast.makeText(this, "评论成功", Toast.LENGTH_SHORT).show(); } }注意:System.currentTimeMillis()生成的时间戳是 Long 型,Comment实体类中timestamp字段必须为long,数据库表comments中对应列为timestamp INTEGER。若误设为TEXT,排序时"1672531200000"会按字符串比较,导致新评论排在最前(因字符串首字符 '1' 最小),而非按时间倒序。
5. 避坑:12 个真实踩过的雷区,从编译失败到点击无响应,每一条都附带定位命令与修复代码
5.1 现象:./gradlew.bat build报错Could not find method implementation() for arguments [...]
原因:build.gradle(Module 级)中使用了implementation,但 Project 级build.gradle的classpath 'com.android.tools.build:gradle:7.2.1'未正确加载,或gradle-wrapper.jar损坏。
解决:
- 删除项目根目录下的
.gradle/文件夹; - 重新运行
./gradlew.bat --refresh-dependencies; - 若仍失败,手动下载
gradle-7.2-bin.zip(官网 archive.gradle.org),解压到C:\gradle\,修改gradle-wrapper.properties中distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip,再重试。
5.2 现象:Android Studio 提示Cannot resolve symbol 'R',所有R.id.xxx报红
原因:AndroidManifest.xml中<application>标签内android:icon或android:label引用了不存在的资源,如@mipmap/ic_launcher_round但res/mipmap-round/目录为空。
解决:
- 检查
app/src/main/res/mipmap-*下是否有ic_launcher.png; - 若无,从 Android Asset Studio 生成一套,放入
mipmap-hdpi等目录; - 在
AndroidManifest.xml中确认android:icon="@mipmap/ic_launcher"(去掉_round)。
5.3 现象:登录后跳转MainActivity,但页面空白,Logcat 显示java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
原因:MainActivity.java中findViewById(R.id.tv_balance)返回 null,因activity_main.xml中TextView的android:id写成了@+id/balance_tv,而 Java 代码里写的是R.id.tv_balance,ID 不匹配。
解决:
- 打开
app/src/main/res/layout/activity_main.xml; - 找到
<TextView android:id="@+id/tv_balance" ... />,确认 ID 为tv_balance; - 若实际是
balance_tv,则 Java 代码改为findViewById(R.id.balance_tv),或统一改 XML 中的 ID。
5.4 现象:点击“充值”按钮无反应,Logcat 无任何日志
原因:RechargeActivity.java中btnRecharge.setOnClickListener(...)未设置,或setContentView(R.layout.activity_recharge)加载的布局文件名拼错(如写成activity_rechage.xml)。
解决:
- 检查
app/src/main/res/layout/下是否存在activity_recharge.xml; - 在
RechargeActivity.java的onCreate()中,确认setContentView(R.layout.activity_recharge)的R.layout.activity_recharge是否存在(Android Studio 会自动高亮不存在的资源); - 若存在,检查
activity_recharge.xml中按钮的android:id是否为@+id/btn_recharge,Java 中是否用findViewById(R.id.btn_recharge)获取。
5.5 现象:BookListActivity中RecyclerView不显示任何图书,但数据库有数据
原因:BookAdapter.java的getItemCount()返回 0,因Cursor未移动到第一条记录,或Cursor本身为空(cursor.getCount() == 0)。
解决:
- 在
BookAdapter构造函数中,添加日志:Log.d("BookAdapter", "Cursor count: " + cursor.getCount());; - 若为 0,检查
BookDBHelper.getAllBooks()是否执行了return db.query("books", ...),且selection参数未误写为"stock > ?"但传入null; - 在
onBindViewHolder()前加if (!cursor.isBeforeFirst()) cursor.moveToFirst();确保游标在起始位置。
6. 让课设答辩时老师当场安装并操作:三个必须验证的临门一脚技巧
6.1 生成可安装的 APK:debug 版 vs release 版的关键区别与签名策略
课设答辩要求“现场安装”,意味着你需要一个.apk文件,而非仅在模拟器运行。Android Studio 默认生成app/build/outputs/apk/debug/app-debug.apk,但它有两大缺陷:
- debug 签名:只能在同一台电脑的 ADB 安装,换手机会提示“应用未安装”(因 debug.keystore 每台机器不同);
- minifyEnabled false:未混淆,代码裸露,老师反编译一眼看到
sha256()逻辑。
正确做法是生成 release APK 并自签名:
- 在
app/build.gradle的android块内,添加 signingConfigs:
android { signingConfigs { config { keyAlias 'key0' keyPassword 'android' storeFile file('../my-release-key.jks') storePassword 'android' } } buildTypes { release { signingConfig signingConfigs.config minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }- 用 keytool 生成
my-release-key.jks(Windows 命令行):
keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key0 -storepass android -keypass android- 执行
./gradlew.bat assembleRelease,生成app/build/outputs/apk/release/app-release.apk。
验证:将此 APK 发到微信,用手机下载安装,打开后能正常登录——这才是答辩时老师愿意点开的“真家伙”。
6.2 模拟真实场景的三组测试数据:覆盖边界条件,让老师问不出漏洞
答辩时老师常问:“如果充值负数怎么办?”“两本书同名怎么区分?”——提前用adb shell注入测试数据,让系统自己回答:
| 场景 | ADB 命令 | 预期效果 |
|---|---|---|
| 负数充值拦截 | adb shell am start -n com.example.campusbooksystem/.activity.RechargeActivity --es amount "-100" | 页面弹 Toast:“金额必须大于0” |
| ISBN 重复插入 | adb shell sqlite3 /data/data/com.example.campusbooksystem/databases/book.db "INSERT INTO books(isbn,title) VALUES('9787020000000','测试书');"adb shell sqlite3 /data/data/com.example.campusbooksystem/databases/book.db "INSERT INTO books(isbn,title) VALUES('9787020000000','重复书');" | 第二条执行失败,BookListActivity刷新后仍只显示一本“测试书” |
| 空评论提交 | adb shell am start -n com.example.campusbooksystem/.activity.CommentActivity --el user_id 1 --el book_id 1adb shell input text ""adb shell input keyevent 66 | 点击提交后弹 Toast:“评论内容不能为空” |
提示:
adb shell sqlite3需手机开启 USB 调试且允许 ADB 调试,若报sqlite3: not found,说明系统未预装,改用adb backup -f book.db com.example.campusbooksystem导出数据库,在 PC 上用 DB Browser for SQLite 修改后恢复。
6.3 为老师准备的“一键演示脚本”:三个 adb 命令串成一行,覆盖登录→查书→充值全流程
把以下命令复制到记事本,答辩前在电脑上打开终端,粘贴执行(需提前连接好手机):
adb shell am start -n com.example.campusbooksystem/.activity.LoginActivity --es username "admin" --es password "123456"; sleep 2; adb shell input tap 500 1200; sleep 1; adb shell input tap 300 800; sleep 2; adb shell am start -n com.example.campusbooksystem/.activity.RechargeActivity --es amount "50.5"; sleep 1; adb shell input tap 400 900分解说明:
am start -n ...LoginActivity --es username "admin":启动登录页并填入账号密码(假设LoginActivity支持 Intent 参数预填);input tap 500 1200:模拟点击屏幕坐标 (500,1200) 处的“登录”按钮(需根据你手机分辨率微调,1080p 屏幕 y=1200 约在底部按钮区域);input tap 300 800:登录成功后,点击MainActivity中“图书列表”Tab(y=800 在中间偏上);- 最后启动充值页并点击充值按钮。
从那以后我每次准备课设答辩,都强制走一遍./gradlew.bat assembleRelease→adb install app-release.apk→adb shell am start ...这套流程,哪怕只是对着空气手机点三下。因为老师真的会掏出自己的华为 Mate 50,说“同学,我这装一个试试”,而那一刻,你包里那份app-release.apk就是唯一能让你不低头看脚尖的底气。希望帮到你。
本文还有配套的精品资源,点击获取