☰
Android校园图书系统:Gradle构建+SQLite+Gson全链路实战
2026/10/22 3:19:36 网站建设 项目流程

简介:本资源是一个基于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损坏。
解决:

  1. 删除项目根目录下的.gradle/文件夹;
  2. 重新运行./gradlew.bat --refresh-dependencies;
  3. 若仍失败,手动下载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/目录为空。
解决:

  1. 检查app/src/main/res/mipmap-*下是否有ic_launcher.png;
  2. 若无,从 Android Asset Studio 生成一套,放入mipmap-hdpi等目录;
  3. 在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 不匹配。
解决:

  1. 打开app/src/main/res/layout/activity_main.xml;
  2. 找到<TextView android:id="@+id/tv_balance" ... />,确认 ID 为tv_balance;
  3. 若实际是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)。
解决:

  1. 检查app/src/main/res/layout/下是否存在activity_recharge.xml;
  2. 在RechargeActivity.java的onCreate()中,确认setContentView(R.layout.activity_recharge)的R.layout.activity_recharge是否存在(Android Studio 会自动高亮不存在的资源);
  3. 若存在,检查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)。
解决:

  1. 在BookAdapter构造函数中,添加日志:Log.d("BookAdapter", "Cursor count: " + cursor.getCount());;
  2. 若为 0,检查BookDBHelper.getAllBooks()是否执行了return db.query("books", ...),且selection参数未误写为"stock > ?"但传入null;
  3. 在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 并自签名:

  1. 在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' } } }
  1. 用 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
  1. 执行./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 1
adb 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就是唯一能让你不低头看脚尖的底气。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询