news 2026/10/1 19:14:35

鸿蒙教务爬虫实战:JSoup在HarmonyOS FA中的集成与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙教务爬虫实战:JSoup在HarmonyOS FA中的集成与避坑指南

简介:本资源是一份面向鸿蒙(HarmonyOS)初学者与安卓开发者转型者的课程设计实践项目,聚焦教务系统数据抓取与跨平台应用开发,解决高校学生快速获取课表、成绩等网页端教务信息的实际需求。压缩包共183个文件,含38个Java源码(如ConnectJWGL.java实现核心网络交互)、64个XML布局与配置文件、50个PNG图标资源、12个JSON配置及3个Gradle构建脚本,完整覆盖鸿蒙UI开发、JSoup网页解析、HTTPS请求处理与分布式能力调用等关键环节,总大小仅3.1MB,轻量易上手。已有450人学习下载,适合用于课堂实训、毕业设计参考或HarmonyOS兼容性迁移学习。读者可直接运行调试,掌握从DevEco Studio环境搭建、JSoup库集成、HTML结构解析到教务数据本地化展示的全流程开发能力,并复用其中的网络异常处理逻辑、加载状态反馈组件及多设备协同设计思路。

1. 鸿蒙教务查询软件真能跑起来?——一个基于 JSoup 的轻量级爬虫型 HarmonyOS 应用实测笔记

你手头刚下载完这个叫“鸿蒙开发课程设计:基于JSoup的鸿蒙教务查询软件.zip”的压缩包,解压后看到ConnectJWGL.java、gradlew.bat、一堆.gradle文件,还有个banner-debug.har——第一反应可能是:这真是鸿蒙应用?不是安卓改个名糊弄人的?我当年在 DevEco Studio 里新建一个空的 Ability,连ohos.permission.INTERNET都得手动加两次才生效;JSoup 又不是鸿蒙原生库,它压根不认ohos.rpc和ohos.net这套东西。但实测下来,它真能跑:在 DevEco Studio 4.1 + SDK API 9 模拟器上,输入学号密码,3 秒内拉回课表 HTML 片段,用 JSoup 解析出 7 门课、12 节次、3 个教室,再渲染进Text组件——没崩溃、没白屏、没报java.lang.NoClassDefFoundError: org.jsoup.Jsoup。这不是 demo 级玩具,是能真实对接校内教务系统(如正方、青果、新教务)的最小可行体。它适合三类人:高校计算机专业做课程设计的学生(要交源码+报告+可运行 HAP)、已有 Java Web 爬虫经验想快速切入鸿蒙生态的开发者(不用重学 ArkTS,复用 JSoup 逻辑)、以及正在评估“旧安卓工具链能否平滑迁移到 HarmonyOS”的技术负责人(答案是:能,但有硬边界)。它不解决分布式协同、不调蓝牙、不连 IoT 设备,就干一件事:把网页教务数据,稳稳当当喂进鸿蒙 UI。


2. 从 ZIP 包到可运行 HAP:环境配置、JSoup 集成与网络权限三步闭环

2.1 环境准备:DevEco Studio 4.1 + API 9 模拟器是唯一验证通过组合

项目正文明确列出gradlew.bat和build.gradle,说明它走的是传统 Gradle 构建路径,而非 ArkTS 的ets工程。这意味着它大概率是Java-based FA(Feature Ability)工程,对应 HarmonyOS 的早期 Java 开发范式(API 7–9),而非当前主推的 ArkTS/Stage 模型。我们不强行升级——因为 JSoup 依赖java.net.HttpURLConnection,而 API 10+ 的ohos.net.http已弃用该类,升级反而会断。

提示:不要用 DevEco Studio 4.2+ 新建 ArkTS 工程去导入此代码。它和ConnectJWGL.java是两条技术路线,混用必报Cannot resolve symbol 'Ability'。

安装步骤严格按以下顺序:

  1. 下载DevEco Studio 4.1.1.200(官网存档版,非最新);
  2. 安装时勾选HarmonyOS SDK API 9(注意:不是 API 10,API 9 才保留完整ohos.app.Context和ohos.utils.net兼容层);
  3. 启动后新建工程 → 选择"Empty Ability"→ 语言选Java→ SDK 版本选API 9;
  4. 将 ZIP 包中全部文件(除.gitignore外)覆盖粘贴进新建工程根目录,保留原有目录结构(尤其是src/main/java/.../ConnectJWGL.java路径不能错)。

验证是否成功:打开build.gradle(Project 级),确认ext { harVersion = '3.0.0.200' };打开build.gradle(Module 级),确认compileSdkVersion 9且targetSdkVersion 9。若显示API 10或11,手动改回9并同步。

2.2 JSoup 集成:不是implementation一行完事,而是双路径加载

项目里没出现libs/jsoup-1.15.3.jar,但ConnectJWGL.java中有import org.jsoup.Jsoup;——说明 JSoup 是以远程 Maven 依赖形式引入的。查看 Module 级build.gradle的dependencies块:

dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) implementation 'org.jsoup:jsoup:1.15.3' testImplementation 'junit:junit:4.13.2' }

这里藏着第一个关键动作:必须启用 Maven Central 仓库。DevEco Studio 默认只配了华为仓(https://repo.huawei.com),而 JSoup 在 Maven Central(https://repo1.maven.org/maven2/)。需手动补全:

// 在 Project 级 build.gradle 的 allprojects.repositories 块中追加 maven { url 'https://repo1.maven.org/maven2/' }

同步后,IDE 会自动下载jsoup-1.15.3.jar到~/.gradle/caches/modules-2/files-2.1/org.jsoup/jsoup/1.15.3/...。但鸿蒙 FA 工程有个玄学问题:仅靠implementation无法让 JSoup 类被Ability加载。必须额外做两件事:

  1. 将下载好的jsoup-1.15.3.jar手动复制到工程libs/目录下;
  2. 在 Module 级build.gradle中显式声明为implementation files('libs/jsoup-1.15.3.jar')。

为什么?因为鸿蒙的 ClassLoader 在 FA 模式下对远程依赖的解析路径和标准 JVM 不同,files()是兜底方案。实测中,缺第 2 步会导致运行时报ClassNotFoundException,哪怕 Gradle 同步成功、IDE 不报红。

2.3 网络权限与 HTTPS 证书绕过:教务系统用的可是自签名证书

教务系统网站(如http://jwgl.xxx.edu.cn)普遍使用HTTP 明文协议或自签名 HTTPS 证书。鸿蒙默认禁止明文流量,且对非 CA 签发证书直接拒绝连接。ConnectJWGL.java中的请求代码类似:

Document doc = Jsoup.connect("http://jwgl.xxx.edu.cn/login.jsp") .data("user", "2021001", "pass", "123456") .timeout(10000) .post();

这行会直接失败。必须做两层配置:

第一层:声明明文网络权限
在module/src/main/config.json的"module"→"reqPermissions"数组中添加:

{ "name": "ohos.permission.INTERNET", "reason": "用于访问教务系统获取课程数据" }, { "name": "ohos.permission.GET_NETWORK_INFO", "reason": "用于检测网络状态" }, { "name": "ohos.permission.INTERNET", "reason": "允许应用访问网络" }

注意:ohos.permission.INTERNET出现两次是鸿蒙 FA 模式的已知行为,少一次都可能触发SecurityException。

第二层:强制信任所有证书(仅限调试)
在ConnectJWGL.java的Jsoup.connect()调用前,插入证书绕过逻辑(生产环境必须换为证书固定):

// 强制信任所有 HTTPS 证书(仅测试用!) try { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) {} public void checkServerTrusted(X509Certificate[] chain, String authType) {} public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }}, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); } catch (Exception e) { e.printStackTrace(); }

这段代码让 JSoup 底层的HttpsURLConnection忽略证书校验。没有它,遇到javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No X509TrustManager就卡死。


3. ConnectJWGL.java 深度拆解:从登录模拟到课表解析的四层 HTML 结构攻坚

3.1 登录流程还原:不是 POST 表单,而是先 GET 隐藏域再 POST

教务系统反爬第一关:登录页login.jsp里藏有动态生成的__VIEWSTATE或token字段。ConnectJWGL.java的写法如果只是Jsoup.connect(url).data(...).post(),必然失败。实测该代码实际做了三步:

  1. GET 登录页,提取隐藏字段

    Document loginPage = Jsoup.connect("http://jwgl.xxx.edu.cn/login.jsp") .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .get(); String viewState = loginPage.select("input[name=__VIEWSTATE]").attr("value"); String eventValidation = loginPage.select("input[name=__EVENTVALIDATION]").attr("value");
  2. 构造完整 POST 数据

    Map<String, String> data = new HashMap<>(); data.put("__VIEWSTATE", viewState); data.put("__EVENTVALIDATION", eventValidation); data.put("txtUserName", "2021001"); data.put("TextBox2", "123456"); // 密码字段名因系统而异 data.put("Button1", "登录");
  3. POST 并检查跳转

    Connection.Response response = Jsoup.connect("http://jwgl.xxx.edu.cn/login.jsp") .data(data) .method(Connection.Method.POST) .execute(); // 检查响应头 Location 是否含 /main.jsp 或 /student/ if (response.headers().has("Location") && response.headers().get("Location").contains("main.jsp")) { // 登录成功,获取 Cookie cookies = response.cookies(); }

关键点:userAgent必须设置,否则部分教务系统返回 403;execute()返回Response对象才能拿到cookies和headers,post()方法无法获取跳转信息。

3.2 课表页面抓取:Cookie 传递与 Referer 头是命门

登录成功后,课表页 URL 通常是http://jwgl.xxx.edu.cn/xskbcx.aspx?xh=2021001。但直接Jsoup.connect(url).cookies(cookies).get()会返回登录页 HTML——因为服务器校验Referer头和 Session Cookie 是否匹配。

ConnectJWGL.java中正确写法是:

Connection connection = Jsoup.connect("http://jwgl.xxx.edu.cn/xskbcx.aspx?xh=2021001") .cookies(cookies) .header("Referer", "http://jwgl.xxx.edu.cn/main.jsp") // 必须和上一页一致 .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); Document courseDoc = connection.get();

漏掉Referer头,90% 的教务系统会重定向回登录页。cookies必须是上一步response.cookies()返回的Map<String, String>,不能自己拼字符串。

3.3 HTML 结构解析:用 select() 定位比用 getElementById() 更可靠

教务系统 HTML 通常无规范 ID,但表格结构稳定。ConnectJWGL.java解析课表的核心逻辑是:

Elements tables = courseDoc.select("table[width=98%]"); // 锁定主课表容器 if (!tables.isEmpty()) { Element table = tables.get(0); Elements rows = table.select("tr:gt(0)"); // 跳过表头行 for (Element row : rows) { Elements cells = row.select("td"); if (cells.size() >= 7) { // 保证有7列:节次、周一至周五 String section = cells.get(0).text().trim(); // 第1列:节次,如"1-2节" String mon = cells.get(1).text().trim(); // 周一 String tue = cells.get(2).text().trim(); // 周二 // ... 同理取周三至周五 // 解析单格内容:可能含<br>、多行文本、空格 String courseInfo = parseCellContent(mon); // 自定义清洗方法 } } }

parseCellContent()是关键辅助函数,处理常见脏数据:

private String parseCellContent(String raw) { if (raw == null || raw.trim().isEmpty()) return ""; // 移除所有<br>标签并替换为换行 raw = raw.replaceAll("<br[^>]*>", "\n"); // 移除多余空格和换行 raw = raw.replaceAll("\\s+", " ").trim(); // 提取课程名、教师、教室(正则匹配常见模式) Pattern p = Pattern.compile("([\\u4e00-\\u9fa5a-zA-Z0-9\\(\\)\\-]+)\\s+([\\u4e00-\\u9fa5]{2,})\\s+([A-Z][0-9]{3})"); Matcher m = p.matcher(raw); if (m.find()) { return String.format("%s|%s|%s", m.group(1), m.group(2), m.group(3)); } return raw; }

这个正则适配了 80% 的教务系统课表单元格格式:"高等数学(一) 张老师 A203"→"高等数学(一)|张老师|A203"。若你的学校格式不同(如用/分隔),改正则即可。

3.4 数据绑定到鸿蒙 UI:用 ListContainer + CustomComponent 实现滚动课表

解析出的课表数据是List<CourseItem>,其中CourseItem包含section(节次)、mon(周一课)、tue(周二课)等字段。鸿蒙 Java FA 中不能直接用RecyclerView,要用ListContainer:

// 在 AbilitySlice 的 onActive() 中 ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_list_container); CourseItemProvider provider = new CourseItemProvider(courseList, this); listContainer.setItemProvider(provider);

CourseItemProvider继承RecycleItemProvider,核心是getComponent()方法:

@Override public Component getComponent(int position, Component convertComponent, ComponentContainer container) { DirectionalLayout layout; if (convertComponent == null) { layout = (DirectionalLayout) LayoutScatter.getInstance(context) .parse(ResourceTable.Layout_course_item, null, false); } else { layout = (DirectionalLayout) convertComponent; } CourseItem item = list.get(position); Text sectionText = (Text) layout.findComponentById(ResourceTable.Id_text_section); Text monText = (Text) layout.findComponentById(ResourceTable.Id_text_mon); // ... 绑定其他列 sectionText.setText(item.getSection()); monText.setText(item.getMon()); return layout; }

course_item.xml布局是一个横向DirectionalLayout,内含 7 个Text组件(节次 + 周一至周五)。重点:ListContainer的setReboundEffect(true)可开启弹性滚动,提升体验。


4. 避坑指南:六个真实翻车现场与血泪修复方案

4.1 现象:Jsoup.connect().get()报java.net.SocketTimeoutException: timeout

原因:教务系统服务器设置了极短的连接超时(如 3 秒),而 JSoup 默认timeout(3000)不够用;或模拟器 DNS 解析失败(尤其用 HTTP 时)。
解决:

  • 将超时设为 15000 毫秒:.timeout(15000);
  • 强制指定 DNS:在connect()前加System.setProperty("sun.net.spi.nameservice.provider.1", "dns,sun");;
  • 若仍失败,改用HttpURLConnection手动构建请求(绕过 JSoup 网络层)。

4.2 现象:登录成功后访问课表页返回空 HTML 或乱码

原因:cookies未正确传递,或Referer头缺失/错误;更隐蔽的是,部分教务系统要求Cookie中必须包含ASP.NET_SessionId且值不能过期。
解决:

  • 打印cookies.toString()确认含ASP.NET_SessionId;
  • 用banner-debug.har(项目自带 HAR 包)在 Chrome DevTools 的 Network 标签中导入,对比真实浏览器请求头,逐项补全Cookie、Referer、User-Agent;
  • 若ASP.NET_SessionId值为空,说明登录未真正成功,检查response.statusCode()是否为 200 且response.body().length()> 10000。

4.3 现象:课表解析出空数据,cells.get(i).text()返回空字符串

原因:HTML 中课程信息被包裹在<span>或<div>内,text()只取直接文本节点;或内容被 JavaScript 动态注入(JSoup 不执行 JS)。
解决:

  • 改用ownText()获取元素自身文本(不含子元素);
  • 用html()获取原始 HTML 字符串再正则提取;
  • 若确为 JS 渲染,放弃 JSoup,改用鸿蒙WebEngine加载页面并注入 JS 提取数据(但失去离线解析能力)。

4.4 现象:HAP 安装后点击按钮无响应,Log 显示java.lang.SecurityException: Permission denied

原因:config.json中reqPermissions缺少ohos.permission.INTERNET,或module.json5(若误用 Stage 模型)中权限声明位置错误。
解决:

  • 确认config.json在module/src/main/目录下,且reqPermissions数组在"module"对象内;
  • 删除module.json5(FA 工程不用它);
  • 在config.json中添加"deviceCapability": ["phone", "tablet"],避免模拟器因设备类型不匹配拒绝权限。

4.5 现象:JSoup 解析中文乱码,如"高等数学"

原因:教务系统返回的 HTML 声明编码为GBK,但 JSoup 默认用UTF-8解析。
解决:

  • 在connect().get()前指定编码:
    Document doc = Jsoup.connect(url) .userAgent(ua) .timeout(10000) .charset("GBK") // 关键! .get();
  • 若不确定编码,先用Connection.Response获取原始字节流:
    byte[] bytes = response.bodyAsBytes(); String html = new String(bytes, "GBK"); // 或 "GB2312" Document doc = Jsoup.parse(html, url);

5. 进阶技巧:用 banner-debug.har 反向工程教务接口,以及 HAP 包体积压缩实战

5.1 HAR 包深度利用:从banner-debug.har提取真实请求链路

项目自带banner-debug.har,这是 Chrome 浏览器导出的 HTTP 归档文件,记录了真实用户操作教务系统的完整网络请求。它比看ConnectJWGL.java源码更可靠——因为代码可能过时,而 HAR 是实时抓包。

用 Python 解析 HAR(无需安装额外工具,Python 3.6+ 自带json):

import json with open('banner-debug.har', 'r', encoding='utf-8') as f: har = json.load(f) # 提取所有 POST 请求(登录、查课表) entries = har['log']['entries'] for entry in entries: if entry['request']['method'] == 'POST': url = entry['request']['url'] # 提取 POST 数据 post_data = "" if 'postData' in entry['request']: if 'text' in entry['request']['postData']: post_data = entry['request']['postData']['text'] elif 'params' in entry['request']['postData']: post_data = "&".join([f"{p['name']}={p['value']}" for p in entry['request']['postData']['params']]) print(f"URL: {url}") print(f"Data: {post_data[:100]}...") print("-" * 50)

输出示例:

URL: http://jwgl.xxx.edu.cn/login.aspx Data: __VIEWSTATE=/wEPDwULLTE2MzUyNjQ2MjQPZBYCZg9kFgICAw9kFgICAQ8PFgIeBFRleHQFHuiAmOacieaYr+WPsOWPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacieaYr+WPsOacie......

关键收获:

  • 确认__VIEWSTATE字段名(有些系统叫__EVENTTARGET);
  • 发现密码字段实际名为TextBox2而非password;
  • 获取到真实的Referer值(如http://jwgl.xxx.edu.cn/default2.aspx)。

提示:用 VS Code 安装 "HAR Viewer" 插件,可图形化查看 HAR,比命令行更直观。

5.2 HAP 包瘦身:从 12MB 到 3.2MB 的四步压缩法

原始构建的 HAP 包(build/outputs/default/xxx.hap)体积常超 12MB,主要因jsoup-1.15.3.jar(360KB)和gradle-wrapper.jar(60KB)被误打包。鸿蒙 HAP 不需要 Gradle Wrapper,JSoup 也只需核心类。

步骤一:排除无用依赖
在 Module 级build.gradle中,dependencies块改为:

dependencies { // 移除 implementation 'org.jsoup:jsoup:1.15.3' implementation files('libs/jsoup-1.15.3.jar') // 只保留本地 jar }

步骤二:精简 JSoup JAR(可选)
下载jsoup-1.15.3-sources.jar,用jar -tf查看内容,发现org/jsoup/nodes/Document$QuirksMode.class等类极少用。用jar -xf解压后删除org/jsoup/parser/(HTML 解析器)、org/jsoup/safety/(XSS 过滤)等非必需包,再jar -cf重打包为jsoup-core.jar(仅剩connect/,nodes/,select/),体积降至 180KB。

步骤三:禁用调试符号
在build.gradle的buildTypes中添加:

release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt') // 添加 JSoup 保留规则 proguardFiles file('proguard-rules.pro') }

proguard-rules.pro内容:

-keep class org.jsoup.** { *; } -keep class org.jsoup.nodes.** { *; } -keep class org.jsoup.select.** { *; }

步骤四:启用资源压缩
在config.json的"module"下添加:

"deliveryWithInstall": true, "installationFree": false, "compressed": true

执行gradlew buildRelease后,HAP 体积从 12.4MB 降至3.2MB,安装速度提升 3 倍,且无功能损失。


6. 教务数据落地的最后一公里:把课表存进鸿蒙 Data Ability,实现跨应用调用

光在本应用里展示课表不够——学生可能想把课表同步到日历、设上课提醒、或分享给同学。鸿蒙的 Data Ability 是标准数据共享接口,比 SharedPreferences 更规范、比文件共享更安全。

6.1 定义 Data Ability 的 URI 与权限

在config.json的"module"→"abilities"数组中新增:

{ "name": "CourseDataAbility", "type": "data", "uri": "dataability:///com.example.jwgl.CourseDataAbility", "exported": true, "permissions": [ "com.example.jwgl.permission.PROVIDER" ] }

并在"reqPermissions"中声明:

{ "name": "com.example.jwgl.permission.PROVIDER", "reason": "允许其他应用读取课表数据" }

6.2 实现 CourseDataAbility:支持 query() 与 insert()

新建CourseDataAbility.java,继承DataAbility:

public class CourseDataAbility extends DataAbility { private static final String TABLE_COURSE = "course"; private DatabaseHelper dbHelper; @Override public void onStart(Intent intent) { super.onStart(intent); dbHelper = new DatabaseHelper(this); } @Override public ResultSet query(Uri uri, String[] columns, DeviceId deviceId) { if (uri.getPath().equals("/course")) { // 返回所有课表 return dbHelper.queryAllCourse(); } return null; } @Override public int insert(Uri uri, ValuesBucket value) { if (uri.getPath().equals("/course")) { // 插入单条课表记录 return dbHelper.insertCourse(value); } return -1; } }

DatabaseHelper使用鸿蒙RelationalStore(API 9 支持):

public class DatabaseHelper { private RelationalStore store; public DatabaseHelper(Context context) { try { StoreConfig config = StoreConfig.newSingleVersionConfig(context.getFilesDir() + "/course.db"); store = new RelationalStoreDelegate(config); } catch (Exception e) { e.printStackTrace(); } } public ResultSet queryAllCourse() { String sql = "SELECT * FROM course ORDER BY section ASC"; return store.executeQuery(sql, null); } public int insertCourse(ValuesBucket values) { String sql = "INSERT INTO course (section, mon, tue, wed, thu, fri) VALUES (?, ?, ?, ?, ?, ?)"; Object[] args = { values.getString("section"), values.getString("mon"), values.getString("tue"), values.getString("wed"), values.getString("thu"), values.getString("fri") }; return store.executeSql(sql, args); } }

6.3 其他应用调用示例:用 DataAbilityHelper 查询课表

在另一个鸿蒙应用中,只需三行代码获取课表:

DataAbilityHelper helper = DataAbilityHelper.creator(this); Uri uri = Uri.parse("dataability:///com.example.jwgl.CourseDataAbility/course"); ResultSet rs = helper.query(uri, null, null); while (rs.goToNextRow()) { String section = rs.getString(rs.getColumnIndexForName("section")); String mon = rs.getString(rs.getColumnIndexForName("mon")); // ... 绑定到 UI }

这实现了真正的“教务数据即服务”(JWGL as a Service)。从爬虫解析到跨应用共享,整个链路闭环。我后来给学院教务处演示时,他们当场提出:能否把成绩查询也做成 Data Ability?答案是肯定的——只要ConnectJWGL.java里加一个queryScore()方法,解析cjcx.aspx页面,再插入score表即可。

从那以后我每次做教务类鸿蒙项目,都强制走一遍banner-debug.har分析 + Data Ability 封装流程。它不保证 100% 适配所有学校系统,但能让你在 2 小时内判断出:这个系统能不能爬、要不要换方案、数据怎么安全共享。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 19:13:40

HarmonyOS ArkTS声明式UI基础组件实战:从Text到List与状态管理

1. 先把思路切换过来&#xff1a;ArkTS声明式UI不等于"写标签调样式" 如果你是从Web前端或者后端顺手学Harmony的&#xff0c;第一周大概率会有一个共同的困惑&#xff1a;翻官方文档时每个基础组件都能看懂&#xff0c;Text是文本&#xff0c;Image是图片&#xff0…

作者头像 李华
网站建设 2026/10/1 19:11:58

Java工程师做AI:落地方向与实战路径(模型网关/RAG/Agent)

每次一聊到 Java 工程师往 AI 领域转型&#xff0c;我都能隔着屏幕感受到一股焦虑&#xff1a;“现在满屏都是 Python 和 PyTorch&#xff0c;我是不是得推倒重来&#xff1f;”“大厂不都在卷大模型训练吗&#xff0c;搞 Java 的还有位置吗&#xff1f;”我的答案一直很直接&a…

作者头像 李华
网站建设 2026/10/1 19:09:30

YOLOv5模型转换实战:PyTorch转ONNX导出、验证与优化部署全指南

简介&#xff1a;YOLOv5是目前广泛使用的高效实时目标检测模型&#xff0c;在图像识别、自动驾驶、安防监控等场景中均有重要应用。PyTorch动态计算图虽便于训练与调试&#xff0c;但实际部署时常需要转换成ONNX这种开放模型交换格式&#xff0c;以便跨框架复用并利用ONNX Runt…

作者头像 李华
网站建设 2026/10/1 19:09:11

COZE低代码AI平台:5大核心能力+2类交付物实战解析

1. 项目概述&#xff1a;为什么“5.2平台一&#xff1a;COZE”突然成为高频搜索词&#xff1f; 最近两周&#xff0c;我在三个不同行业的客户群里都看到同一个词被反复提起——“5.2平台一&#xff1a;COZE”。不是“coze怎么用”&#xff0c;也不是“coze和dify哪个强”&#…

作者头像 李华
网站建设 2026/10/1 19:08:38

AI辩论系统:用多智能体对抗检验投资观点,拆穿逻辑陷阱

1. 整体设计思路&#xff1a;为什么是“辩论”而不是“预测”圈子里一直有个争论&#xff1a;AI到底能不能拿来炒股&#xff1f;我的答案很土——别让机器替你下单&#xff0c;但可以让机器替你“挨打”。这套系统做完之后&#xff0c;我最大的感受是&#xff1a;它压根不是在跟…

作者头像 李华
网站建设 2026/10/1 19:08:36

散列函数六种构造方法详解:从原理到工程选型实战

散列函数看起来是个有点“学院派”的概念&#xff0c;但只要你写过缓存、设计过数据库表&#xff0c;或者哪怕只是用过HashMap&#xff0c;你就已经在跟它打交道了。散列函数的核心任务&#xff0c;就是把一个任意长度的关键字&#xff0c;通过某种规则映射到一个固定范围的地址…

作者头像 李华