当前位置: 首页 > news >正文

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.lryc.cn/news/376580.html

相关文章:

  • NLP - word2vec详解
  • AI办公自动化:用通义千问批量翻译长篇英语TXT文档
  • 一键解压,无限可能——BetterZip,您的Mac必备神器!
  • 【数学】什么是最大似然估计?如何求解最大似然估计
  • 跟张良均老师学大数据人工智能|企业项目试岗实训开营
  • Pentest Muse:一款专为网络安全人员设计的AI助手
  • 10 SpringBoot 静态资源访问
  • Unity 之通过自定义协议从浏览器启动本地应用程序
  • Python抓取天气信息
  • 【超越拟合:深度学习中的过拟合与欠拟合应对策略】
  • 【Orange Pi 5与Linux内核编程】-理解Linux内核中的container_of宏
  • 003.Linux SSH协议工具
  • web前端组织分析:深入剖析其结构、功能与未来趋势
  • GitCode热门开源项目推荐:Spider网络爬虫框架
  • 实现一个二叉树的前序遍历、中序遍历和后序遍历方法。
  • 串扰(二)
  • 零基础入门学用Arduino 第四部分(三)
  • Mp3文件结构全解析(一)
  • ES 8.14 Java 代码调用,增加knnSearch 和 混合检索 mixSearch
  • 被腰斩的颍川郡守赵广汉
  • 【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 目录管理器(200分) - 三语言AC题解(Python/Java/Cpp)
  • 关于自学\跳槽\转行做网络安全行业的一些建议
  • 计算机网络(1) OSI七层模型与TCP/IP四层模型
  • 认识QML
  • llama-factory微调chatglm3
  • 大文件上传实现
  • 为何Proteus用户争相拥抱SmartEDA?揭秘背后的强大吸引力!
  • 万界星空科技QMS质量管理介绍
  • 神经网络 torch.nn---nn.LSTM()
  • Web前端JSP软件:深度解析与探索之旅