Android开启HTTP服务

需求:通过手机给设备升级固件,设备有WIFI

方案:升级包放到APP可以访问的目录,手机开热点并启动一个HTTP服务,设备连接手机热点,另外,设备端开启一个 telnet 服务,手机通过 telnet 登录到设备系统(Android系统热点的默认IP地址是192.168.43.1,APP可以遍历192.168.43这个IP段的IP以及固定的端口),通过指令下载固件,完成升级。

1. 用到的第三方库

implementation 'commons-net:commons-net:3.9.0' // telnet
implementation 'org.nanohttpd:nanohttpd:2.3.1' // NanoHttpd
implementation 'org.apache.commons:commons-compress:1.23.0'//解压缩文件
implementation 'com.github.junrar:junrar:7.5.4'//解压rar
implementation 'org.tukaani:xz:1.9'//解压.7z文件需要
implementation 'com.sparkjava:spark-core:2.9.4'// sparkjava

2. 添加权限

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /><uses-permission android:name="android.permission.WRITE_SETTINGS" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><!-- If your app targets Android 13 (API level 33)or higher, you must declare the NEARBY_WIFI_DEVICES permission. --><!-- If your app derives location information fromWi-Fi APIs, don't include the "usesPermissionFlags"attribute. --><uses-permissionandroid:name="android.permission.NEARBY_WIFI_DEVICES"android:usesPermissionFlags="neverForLocation"tools:targetApi="s" /><!-- If any feature in your app relies onprecise location information, don't include the"maxSdkVersion" attribute. --><!--  Android12获取SSID需要 ACCESS_FINE_LOCATION 权限  --><uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"android:maxSdkVersion="32" /><uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32" />

3. 开启热点 

APUtil.kt

import android.Manifest
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.wifi.SoftApConfiguration
import android.net.wifi.WifiManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import java.lang.reflect.Field
import java.lang.reflect.Methodclass APUtil(private val context: Context) {val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManagerprivate fun securityTypeString(securityType: Int): String {return when (securityType) {SoftApConfiguration.SECURITY_TYPE_OPEN -> "OPEN"SoftApConfiguration.SECURITY_TYPE_WPA2_PSK -> "WPA2_PSK"SoftApConfiguration.SECURITY_TYPE_WPA3_OWE -> "WPA3_OWE"SoftApConfiguration.SECURITY_TYPE_WPA3_SAE -> "WPA3_SAE"SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION -> "WPA3_OWE_TRANSITION"SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION -> "WPA3_SAE_TRANSITION"else -> "Unknown security type: $securityType"}}@RequiresApi(Build.VERSION_CODES.O)private val localOnlyHotspotCallback = object : WifiManager.LocalOnlyHotspotCallback() {override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) {super.onStarted(reservation)if (Build.VERSION.SDK_INT >= 30) {reservation?.softApConfiguration?.let { config ->Log.i(TAG, "-------------------------- softApConfiguration --------------------------")if (Build.VERSION.SDK_INT >= 33) {Log.i(TAG, "热点名称: ${config.wifiSsid}")} else {Log.i(TAG, "热点名称: ${config.ssid}")}Log.i(TAG, "热点密码: ${config.passphrase}")Log.i(TAG, "securityType=${securityTypeString(config.securityType)}")Log.i(TAG, "mac=${config.bssid}")}} else {reservation?.wifiConfiguration?.let { config ->Log.i(TAG, "-------------------------- wifiConfiguration --------------------------")Log.i(TAG, "热点名称: ${config.SSID}")Log.i(TAG, "热点密码: ${config.preSharedKey}")Log.i(TAG, "mac=${config.BSSID}")Log.i(TAG, "status=${config.status}")config.httpProxy?.let { httpProxy ->Log.i(TAG, "http://${httpProxy.host}:${httpProxy.port}")}}}}override fun onStopped() {super.onStopped()Log.e(TAG, "onStopped()")}override fun onFailed(reason: Int) {super.onFailed(reason)Log.e(TAG, "onFailed() - reason=$reason")}}fun startHotspot() {if (Build.VERSION.SDK_INT >= 26) {if (ActivityCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(context,Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED) {return}// 官方文档// https://developer.android.google.cn/reference/android/net/wifi/WifiManager#startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback,%20android.os.Handler)wifiManager.startLocalOnlyHotspot(localOnlyHotspotCallback, null)}}fun printHotSpotState() {Log.i(TAG, "热点是否已经打开:方式1 ${isHotSpotApOpen(context)} 方式2 ${isHotSpotApOpen2(context)}")Log.i(TAG, "手机型号:${Build.MANUFACTURER} ${Build.MODEL}")}companion object {private const val TAG = "APUtil"//获取热点是否打开方式1fun isHotSpotApOpen(context: Context): Boolean {var isAPEnable = falsetry {val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManagerval method: Method = wifiManager.javaClass.getDeclaredMethod("getWifiApState")val state = method.invoke(wifiManager) as Intval field: Field = wifiManager.javaClass.getDeclaredField("WIFI_AP_STATE_ENABLED")val value = field.get(wifiManager) as IntisAPEnable = state == valueLog.i(TAG, "state=$state, value=$value")} catch (e: Exception) {e.printStackTrace()}return isAPEnable}//获取热点是否打开方式2fun isHotSpotApOpen2(context: Context): Boolean {var isAPEnable = falsetry {val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManagerval method: Method = wifiManager.javaClass.getDeclaredMethod("isWifiApEnabled")method.isAccessible = trueisAPEnable = method.invoke(wifiManager) as Boolean} catch (e: Exception) {e.printStackTrace()}return isAPEnable}/*** 打开设置热点的页面*/fun openSettings(context: Context) {val intent = Intent()intent.addCategory(Intent.CATEGORY_DEFAULT)intent.action = "android.intent.action.MAIN"val cn = ComponentName("com.android.settings","com.android.settings.Settings\$WirelessSettingsActivity")intent.component = cntry {context.startActivity(intent)} catch (ex: ActivityNotFoundException) {intent.component = ComponentName("com.android.settings","com.android.settings.Settings\$TetherSettingsActivity")context.startActivity(intent)}}}
}

4. 开启HTTP服务

使用 nanohttpd 实现

 HttpFileServer.kt

import android.content.Context
import android.util.Log
import fi.iki.elonen.NanoHTTPD
import org.json.JSONObject
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.util.stream.Collectors/*** 手机开热点时使用的IP*/
const val SERVER_IP = "192.168.43.1"
const val SERVER_PORT = 8080private const val TAG = "HttpFileServer"// HFS
class HttpFileServer(context: Context, ipAddress: String) : NanoHTTPD(ipAddress, SERVER_PORT) {init {rootDir = context.getExternalFilesDir("OTA")!!.absolutePathLog.i(TAG, "rootDir=$rootDir")}override fun serve(session: IHTTPSession): Response? {val params = session.parametersLog.e(TAG, String.format("uri=%s", session.uri))for (entry in params.entries) {Log.e(TAG, String.format("%s=%s", entry.key, entry.value.stream().collect(Collectors.joining(", "))))}return responseFile(session)}private fun responseFile(session: IHTTPSession): Response? {//目前使用的是 http://192.168.43.1:8080/filenameval uri = session.urival filename = uri.substring(uri.lastIndexOf('/') + 1)return try {//文件输入流val fis = FileInputStream("$rootDir/${filename}")newFixedLengthResponse(Response.Status.OK, "application/octet-stream", fis, fis.available().toLong())} catch (e: FileNotFoundException) {Log.e(TAG, "$filename 文件不存在!", e)val jsonObj = JSONObject()jsonObj.put("message", "$filename 文件不存在!")Log.e(TAG, jsonObj.toString(2))newFixedLengthResponse(Response.Status.NOT_FOUND, "application/json", jsonObj.toString(2))} catch (e: IOException) {e.printStackTrace()newFixedLengthResponse(session.uri + " 异常 " + e)}}companion object {var rootDir: String = ""}
}

使用 sparkjava 实现 

 注意:使用的是sparkjava,而不是大数据分析的那个Spark!

// http://192.168.43.1:8080/download?filename=dog.jpg
Spark.port(8080)        
Spark.get("download") { req, res ->val filename = req.queryParams("filename")println("thread ${Thread.currentThread().id} - $filename")res.status(200)res.header("Content-Type", "application/octet-stream")res.header("Content-disposition", "attachment; filename=$filename")context.assets.open(filename).use { istream ->istream.copyTo(res.raw().outputStream)}res
}

5. 使用 telnet

WifiUtil.kt

telnet 主要用到了 WifiUtil 的一个属性:network

import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSpecifier
import android.os.Build
import android.os.PatternMatcher
import android.util.Log
import androidx.annotation.RequiresApi
import site.feiyuliuxing.wifitest.toIPAddressclass WifiUtil(context: Context) {private val mNetworkCallback: ConnectivityManager.NetworkCallbackprivate val mConnectivityManager: ConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManagerprivate val wifiManager: WifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManagerprivate var mNetwork: Network? = null//Android系统热点的默认IP地址是192.168.43.1var hostIP = "192.168.43.1"private setval network: Network?get() = mNetworkval targetIP: Stringget() {val arr = hostIP.split(".").toMutableList()arr[arr.lastIndex] = "1"return arr.joinToString(".")}init {if (Build.VERSION.SDK_INT < 29) {//Android10以下系统mNetworkCallback = object : ConnectivityManager.NetworkCallback() {override fun onAvailable(network: Network) {callbackAvailable(network)}override fun onLost(network: Network) {callbackLost(network)}}} else if (Build.VERSION.SDK_INT < 31) {//Android10 Android11mNetworkCallback = object : ConnectivityManager.NetworkCallback() {override fun onAvailable(network: Network) {callbackAvailable(network)}override fun onLost(network: Network) {callbackLost(network)}@RequiresApi(Build.VERSION_CODES.Q)override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {// Android10才支持该回调方法callbackCapabilitiesChanged(network, networkCapabilities)}}} else {//Android12获取 SSID 需要 FLAG_INCLUDE_LOCATION_INFO 并获得 ACCESS_FINE_LOCATION 权限mNetworkCallback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {override fun onAvailable(network: Network) {callbackAvailable(network)}override fun onLost(network: Network) {callbackLost(network)}@RequiresApi(Build.VERSION_CODES.Q)override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {// Android10才支持该回调方法callbackCapabilitiesChanged(network, networkCapabilities)}}}}private fun callbackAvailable(network: Network) {mNetwork = networkhostIP = getIP(network)}private fun callbackLost(network: Network) {mNetwork = null}@RequiresApi(Build.VERSION_CODES.Q)private fun callbackCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {val wifiInfo = networkCapabilities.transportInfo as WifiInfo?if (wifiInfo != null) {println("onCapabilitiesChanged() - SSID=${wifiInfo.ssid}, IP=${wifiInfo.ipAddress.toIP()}")}}private val networkRequest: NetworkRequestget() = NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).apply {if (Build.VERSION.SDK_INT >= 29) {val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
//                        .setBand(ScanResult.WIFI_BAND_24_GHZ)//Android12 但是设置这个参数后无法连接.setSsidPattern(PatternMatcher("ZS[0-9a-fA-F]{8}", PatternMatcher.PATTERN_ADVANCED_GLOB))
//                        .setSsid("ZS3755a0e2").setWpa2Passphrase("12345678").build()setNetworkSpecifier(wifiNetworkSpecifier)}}.build()fun requestConnectWIFI() {mConnectivityManager.requestNetwork(networkRequest, mNetworkCallback)}fun close() {mConnectivityManager.unregisterNetworkCallback(mNetworkCallback)}//无需运行时申请权限fun getGatewayIP(): String {if (Build.VERSION.SDK_INT < 32) {/*This method was deprecated in API level 31.Use the methods on LinkProperties which can be obtained either viaNetworkCallback#onLinkPropertiesChanged(Network, LinkProperties) orConnectivityManager#getLinkProperties(Network).*/val dhcpInfo = wifiManager.dhcpInfo// 网关IP地址是一个整数,需要转换为可读的IP地址格式val gatewayIpAddress: String = dhcpInfo.gateway.toIPAddress()println("Gateway IP: $gatewayIpAddress")return gatewayIpAddress} else {var addr = ""mConnectivityManager.activeNetwork?.let { network ->mConnectivityManager.getLinkProperties(network)?.let { linkProperties ->linkProperties.dhcpServerAddress?.let { inet4Address ->addr = inet4Address.hostAddress ?: ""}}}println("网关IP: $addr")return addr}}private fun getIP(network: Network): String {mConnectivityManager.getLinkProperties(network)?.linkAddresses?.let { linkAddresses ->linkAddresses.forEach { linkAddress ->// hostAddress: the IP address string in textual presentation.linkAddress.address.hostAddress?.let { hostAddress ->if (hostAddress.isValidIP) {println("手机IP:${hostAddress}")return hostAddress}}}}return ""}private val ipRegex = """\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}""".toRegex()private val String.isValidIP: Booleanget() = ipRegex.matches(this)private fun Int.toIP(): String {return "${this.and(0xff)}" +".${this.shr(8).and(0xff)}" +".${this.shr(16).and(0xff)}" +".${this.shr(24).and(0xff)}"}
}

TelnetUtil.kt

import android.net.Network
import org.apache.commons.net.telnet.EchoOptionHandler
import org.apache.commons.net.telnet.SuppressGAOptionHandler
import org.apache.commons.net.telnet.TelnetClient
import org.apache.commons.net.telnet.TerminalTypeOptionHandlerclass TelnetUtil {private val telnetClient = TelnetClient()private fun connectServer(ip: String, network: Network?): Boolean {try {if (network != null) {telnetClient.setSocketFactory(network.socketFactory)}/*val ttopt = TerminalTypeOptionHandler("VT220", false, false, true, false)val echoopt = EchoOptionHandler(true, false, true, false)val gaopt = SuppressGAOptionHandler(true, true, true, true)telnetClient.addOptionHandler(ttopt)telnetClient.addOptionHandler(echoopt)telnetClient.addOptionHandler(gaopt)*/telnetClient.connect(ip, 23)if (telnetClient.isConnected) {println("端口可用 $ip")return true}} catch (ex: Exception) {ex.printStackTrace()}return false}fun readUntil(match: String, timeout: Long = 10_000): Boolean {val sb = StringBuilder()val startTime = System.currentTimeMillis()while (true) {if ((System.currentTimeMillis() - startTime) >= timeout) {//超时break}var len = telnetClient.inputStream.available()while (len > 0) {val ch = telnetClient.inputStream.read()if (ch != -1) {sb.append(ch.toChar())print(ch.toChar())if (ch == 0x0D || ch == 0x0A) {System.out.flush()}if (sb.contains(match)) {println(sb.toString())return true}}len--}}println(sb.toString())return false}fun send(msg: String) {telnetClient.outputStream.apply {write(msg.toByteArray(Charsets.UTF_8))flush()}}
}

6. 解压缩文件

参考这篇文章:Android解压 zip rar 7z 文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/30103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

制作一个苹果软件自动运行工具需要用到的源代码!

在数字化时代的浪潮中&#xff0c;自动化运行工具扮演着越来越重要的角色&#xff0c;这些工具可以极大地提高工作效率&#xff0c;减少人为操作的繁琐和错误。 在苹果软件生态系统中&#xff0c;制作一个自动运行工具同样具有广泛的应用前景&#xff0c;本文将围绕“制作一个…

模拟原神圣遗物系统-小森设计项目,需求分析

需求分析 我操控某个角色的圣遗物时发现&#xff0c;一开始玩啥也不懂慢慢了解&#xff0c;今天才想起要不做一个 &#xff0c;然后开始想需求 跟Ai聊技术 聊着聊着 发现圣遗物 这个东西有点意思 本来今天打算写一下数据库 的外键想起了一些高兴的事情&#xff08;美人鱼&#…

C# WinForm —— 36 布局控件 GroupBox 和 Panel

1. 简介 两个可以盛放其他控件的容器&#xff0c;可以用于把不同的控件分组&#xff0c;一般不会注册事件 GroupBox&#xff1a;为其他控件提供可识别的分组。可通过Text属性设置标题&#xff1b;有边框&#xff1b;没有滚动条&#xff0c;一般用于按功能分组 Panel&#xff…

[BFS广搜]迷阵

描述 小Z每年都会为程设课出一道大作业&#xff0c;荼毒学弟学妹&#xff0c;可谓罪大恶极不可饶恕。 终于有一天&#xff0c;神明也看不下去了&#xff0c;他唤醒上古四大神兽&#xff0c;决定围困小Z&#xff0c;威慑一番。 于是&#xff0c;在小Z下一次醒来时&#xff0c;他…

SpringBoot+Maven项目的配置构建

文章目录 1、application.properties2、pom.xml 1、application.properties 也可使用yml yaml #静态资源 spring.mvc.static-path-pattern/images/** #上传文件大小设置 spring.http.multipart.max-file-size10MB spring.http.multipart.max-request-size10MBspring.mvc.path…

什么是交错计算,有哪些场景会出现交错计算

交错计算指的是在编程技术中&#xff0c;多个进程或任务以交替或交织的方式执行&#xff0c;共享资源并轮流取得进展。这种做法对于提高系统效率特别有用&#xff0c;确保没有单一资源&#xff08;如CPU核心&#xff09;在有其他任务可以使用时保持空闲。 在计算领域&#xff…

Sui的Fastcrypto加密库刷新速度记录

Sui使用的加密库Fastcrypto打破了许多速度记录&#xff0c;Mysten Labs在基准测试和安全分析中的工作修复了许多安全漏洞&#xff0c;同时通过识别新的优化技巧为创新开辟了道路。 最近在伦敦帝国理工学院举行的国际性能工程会议&#xff08;ICPE&#xff09;基准测试研讨会上…

【LeetCode:394. 字符串解码 + 栈 | 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

C# —— while循环语句

作用 让顺序执行的代码 可以停下来 循环执行某一代码块 // 条件分支语句: 让代码产生分支 进行执行 // 循环语句 : 让代码可以重复执行 语法 while循环 while (bool值) { 循环体(条件满足时执行的代码块) …

智慧路灯:照亮未来城市的智慧之光

智慧路灯&#xff0c;顾名思义&#xff0c;是在传统路灯基础上集成物联网、大数据、云计算、人工智能等现代信息技术的新型照明系统。它不仅提供节能高效的照明服务&#xff0c;更成为城市信息采集、传输、发布的载体&#xff0c;以及多种增值服务的平台。 核心功能与技术创新 …

聊聊 Mybatis 动态 SQL

这篇文章&#xff0c;我们聊聊 Mybatis 动态 SQL &#xff0c;以及我对于编程技巧的几点思考 &#xff0c;希望对大家有所启发。 1 什么是 Mybatis 动态SQL 如果你使用过 JDBC 或其它类似的框架&#xff0c;你应该能理解根据不同条件拼接 SQL 语句有多痛苦&#xff0c;例如拼…

如何设置文件夹密码?文件夹加密如何操作!分享4款安全加密软件!

在数字化时代&#xff0c;数据安全显得尤为重要。设置文件夹密码和加密操作是保护个人或企业数据不被非法访问的有效手段。本文将为您详细介绍如何设置文件夹密码和加密操作&#xff0c;并分享四款安全加密软件&#xff0c;助您轻松提升数据安全防护能力。 一、如何设置文件夹/…

工作人员能从轧钢测径仪上获取哪些有效信息?

轧钢测径仪安装在轧钢生产线中&#xff0c;无论是热轧还是冷轧&#xff0c;都不能阻挡测径仪的高速无损高精检测。它采用八轴测量系统&#xff0c;能全方位检测外径尺寸&#xff0c;并且配备了测控软件系统&#xff0c;为工作人员提供更加丰富的产线信息。 普通轧钢测径仪能获…

Playwright 入门教程

1. 环境说明 操作系统&#xff1a;macOS 11.7Python&#xff1a;3.10.6 2. 安装 2.1. 创建测试环境 mkdir playwright-demo cd playwright-demo/ python3 -m venv venv # 安装 Pytest 插件 venv/bin/pip3 install pytest-playwright # 安装需要的浏览器 venv/bin/playwrigh…

DLP数据防泄密系统有什么功能?四款特别好用的DLP仿泄密系统

DLP&#xff08;Data Loss Prevention&#xff0c;数据丢失防护&#xff09;系统是一类专门用于保护组织内部数据不被非法访问、泄露或误用的安全解决方案。 这类系统通常具备以下关键功能&#xff1a; 1.数据识别与分类&#xff1a;自动发现并分类存储在网络、终端和云环境中…

24计算机应届生的活路是什么

不够大胆❗ 很多小伙伴在找工作时觉得自己没有竞争力&#xff0c;很没有自信&#xff0c;以至于很害怕找工作面试&#xff0c;被人否定的感觉很不好受。 其实很多工作并没有想象中的高大上&#xff0c;不要害怕&#xff0c;计算机就业的方向是真的广&#xff0c;不要走窄了&…

朋友圈新功能:实现定时发圈,自动跟圈

1.多号同时发圈 可以选择多个号同时发圈&#xff0c;提高工作效率。 2.定时发布 可以一次性设置完很多天的朋友圈&#xff0c;选好发送时间就可以解放双手。 3.一键转发 点击转发&#xff0c;可直接跳转到编辑页面。无需复制粘贴。 4.自动转发&#xff08;跟圈&#xff09; …

机能学实验通过ZL-620C一体化信息化生物信号采集系统具体呈现

ZL-621大屏教学试教系统为了实施机能学实验的教学改革&#xff0c;大力减轻教师的实验教学负担&#xff0c;主要功能电子白板&#xff0c;同步教学、控制、过程仿真、虚拟现实、三维动画、管理、音视频广播、PPT教材等于一体&#xff0c;大屏教学试教系统并能同时实现屏幕监视和…

湖北文理学院2024年成人高等继续教育招生简章

湖北文理学院&#xff0c;作为一所历史悠久、底蕴深厚的学府&#xff0c;始终致力于为社会各界培养具备高素质、专业技能和创新精神的优秀人才。在成人高等继续教育领域&#xff0c;湖北文理学院更是凭借其卓越的教学质量和丰富的教育资源&#xff0c;吸引了众多有志于提升自身…