• Android开启HTTP服务


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

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

    1. 用到的第三方库

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

    2. 添加权限

    1. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    2. <uses-permission android:name="android.permission.INTERNET" />
    3. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    4. <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    5. <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    6. <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    7. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    8. <uses-permission
    9. android:name="android.permission.NEARBY_WIFI_DEVICES"
    10. android:usesPermissionFlags="neverForLocation"
    11. tools:targetApi="s" />
    12. <uses-permission
    13. android:name="android.permission.ACCESS_FINE_LOCATION"
    14. android:maxSdkVersion="32" />
    15. <uses-permission
    16. android:name="android.permission.READ_EXTERNAL_STORAGE"
    17. android:maxSdkVersion="32" />

    3. 开启热点 

    APUtil.kt

    1. import android.Manifest
    2. import android.content.ActivityNotFoundException
    3. import android.content.ComponentName
    4. import android.content.Context
    5. import android.content.Intent
    6. import android.content.pm.PackageManager
    7. import android.net.wifi.SoftApConfiguration
    8. import android.net.wifi.WifiManager
    9. import android.os.Build
    10. import android.util.Log
    11. import androidx.annotation.RequiresApi
    12. import androidx.core.app.ActivityCompat
    13. import java.lang.reflect.Field
    14. import java.lang.reflect.Method
    15. class APUtil(private val context: Context) {
    16. val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
    17. private fun securityTypeString(securityType: Int): String {
    18. return when (securityType) {
    19. SoftApConfiguration.SECURITY_TYPE_OPEN -> "OPEN"
    20. SoftApConfiguration.SECURITY_TYPE_WPA2_PSK -> "WPA2_PSK"
    21. SoftApConfiguration.SECURITY_TYPE_WPA3_OWE -> "WPA3_OWE"
    22. SoftApConfiguration.SECURITY_TYPE_WPA3_SAE -> "WPA3_SAE"
    23. SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION -> "WPA3_OWE_TRANSITION"
    24. SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION -> "WPA3_SAE_TRANSITION"
    25. else -> "Unknown security type: $securityType"
    26. }
    27. }
    28. @RequiresApi(Build.VERSION_CODES.O)
    29. private val localOnlyHotspotCallback = object : WifiManager.LocalOnlyHotspotCallback() {
    30. override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) {
    31. super.onStarted(reservation)
    32. if (Build.VERSION.SDK_INT >= 30) {
    33. reservation?.softApConfiguration?.let { config ->
    34. Log.i(TAG, "-------------------------- softApConfiguration --------------------------")
    35. if (Build.VERSION.SDK_INT >= 33) {
    36. Log.i(TAG, "热点名称: ${config.wifiSsid}")
    37. } else {
    38. Log.i(TAG, "热点名称: ${config.ssid}")
    39. }
    40. Log.i(TAG, "热点密码: ${config.passphrase}")
    41. Log.i(TAG, "securityType=${securityTypeString(config.securityType)}")
    42. Log.i(TAG, "mac=${config.bssid}")
    43. }
    44. } else {
    45. reservation?.wifiConfiguration?.let { config ->
    46. Log.i(TAG, "-------------------------- wifiConfiguration --------------------------")
    47. Log.i(TAG, "热点名称: ${config.SSID}")
    48. Log.i(TAG, "热点密码: ${config.preSharedKey}")
    49. Log.i(TAG, "mac=${config.BSSID}")
    50. Log.i(TAG, "status=${config.status}")
    51. config.httpProxy?.let { httpProxy ->
    52. Log.i(TAG, "http://${httpProxy.host}:${httpProxy.port}")
    53. }
    54. }
    55. }
    56. }
    57. override fun onStopped() {
    58. super.onStopped()
    59. Log.e(TAG, "onStopped()")
    60. }
    61. override fun onFailed(reason: Int) {
    62. super.onFailed(reason)
    63. Log.e(TAG, "onFailed() - reason=$reason")
    64. }
    65. }
    66. fun startHotspot() {
    67. if (Build.VERSION.SDK_INT >= 26) {
    68. if (ActivityCompat.checkSelfPermission(
    69. context,
    70. Manifest.permission.ACCESS_FINE_LOCATION
    71. ) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(
    72. context,
    73. Manifest.permission.NEARBY_WIFI_DEVICES
    74. ) != PackageManager.PERMISSION_GRANTED
    75. ) {
    76. return
    77. }
    78. // 官方文档
    79. // https://developer.android.google.cn/reference/android/net/wifi/WifiManager#startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback,%20android.os.Handler)
    80. wifiManager.startLocalOnlyHotspot(localOnlyHotspotCallback, null)
    81. }
    82. }
    83. fun printHotSpotState() {
    84. Log.i(TAG, "热点是否已经打开:方式1 ${isHotSpotApOpen(context)} 方式2 ${isHotSpotApOpen2(context)}")
    85. Log.i(TAG, "手机型号:${Build.MANUFACTURER} ${Build.MODEL}")
    86. }
    87. companion object {
    88. private const val TAG = "APUtil"
    89. //获取热点是否打开方式1
    90. fun isHotSpotApOpen(context: Context): Boolean {
    91. var isAPEnable = false
    92. try {
    93. val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
    94. val method: Method = wifiManager.javaClass.getDeclaredMethod("getWifiApState")
    95. val state = method.invoke(wifiManager) as Int
    96. val field: Field = wifiManager.javaClass.getDeclaredField("WIFI_AP_STATE_ENABLED")
    97. val value = field.get(wifiManager) as Int
    98. isAPEnable = state == value
    99. Log.i(TAG, "state=$state, value=$value")
    100. } catch (e: Exception) {
    101. e.printStackTrace()
    102. }
    103. return isAPEnable
    104. }
    105. //获取热点是否打开方式2
    106. fun isHotSpotApOpen2(context: Context): Boolean {
    107. var isAPEnable = false
    108. try {
    109. val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
    110. val method: Method = wifiManager.javaClass.getDeclaredMethod("isWifiApEnabled")
    111. method.isAccessible = true
    112. isAPEnable = method.invoke(wifiManager) as Boolean
    113. } catch (e: Exception) {
    114. e.printStackTrace()
    115. }
    116. return isAPEnable
    117. }
    118. /**
    119. * 打开设置热点的页面
    120. */
    121. fun openSettings(context: Context) {
    122. val intent = Intent()
    123. intent.addCategory(Intent.CATEGORY_DEFAULT)
    124. intent.action = "android.intent.action.MAIN"
    125. val cn = ComponentName(
    126. "com.android.settings",
    127. "com.android.settings.Settings\$WirelessSettingsActivity"
    128. )
    129. intent.component = cn
    130. try {
    131. context.startActivity(intent)
    132. } catch (ex: ActivityNotFoundException) {
    133. intent.component = ComponentName(
    134. "com.android.settings",
    135. "com.android.settings.Settings\$TetherSettingsActivity"
    136. )
    137. context.startActivity(intent)
    138. }
    139. }
    140. }
    141. }

    4. 开启HTTP服务

    使用 nanohttpd 实现

     HttpFileServer.kt

    1. import android.content.Context
    2. import android.util.Log
    3. import fi.iki.elonen.NanoHTTPD
    4. import org.json.JSONObject
    5. import java.io.FileInputStream
    6. import java.io.FileNotFoundException
    7. import java.io.IOException
    8. import java.util.stream.Collectors
    9. /**
    10. * 手机开热点时使用的IP
    11. */
    12. const val SERVER_IP = "192.168.43.1"
    13. const val SERVER_PORT = 8080
    14. private const val TAG = "HttpFileServer"
    15. // HFS
    16. class HttpFileServer(context: Context, ipAddress: String) : NanoHTTPD(ipAddress, SERVER_PORT) {
    17. init {
    18. rootDir = context.getExternalFilesDir("OTA")!!.absolutePath
    19. Log.i(TAG, "rootDir=$rootDir")
    20. }
    21. override fun serve(session: IHTTPSession): Response? {
    22. val params = session.parameters
    23. Log.e(TAG, String.format("uri=%s", session.uri))
    24. for (entry in params.entries) {
    25. Log.e(TAG, String.format("%s=%s", entry.key, entry.value.stream().collect(Collectors.joining(", "))))
    26. }
    27. return responseFile(session)
    28. }
    29. private fun responseFile(session: IHTTPSession): Response? {
    30. //目前使用的是 http://192.168.43.1:8080/filename
    31. val uri = session.uri
    32. val filename = uri.substring(uri.lastIndexOf('/') + 1)
    33. return try {
    34. //文件输入流
    35. val fis = FileInputStream("$rootDir/${filename}")
    36. newFixedLengthResponse(Response.Status.OK, "application/octet-stream", fis, fis.available().toLong())
    37. } catch (e: FileNotFoundException) {
    38. Log.e(TAG, "$filename 文件不存在!", e)
    39. val jsonObj = JSONObject()
    40. jsonObj.put("message", "$filename 文件不存在!")
    41. Log.e(TAG, jsonObj.toString(2))
    42. newFixedLengthResponse(Response.Status.NOT_FOUND, "application/json", jsonObj.toString(2))
    43. } catch (e: IOException) {
    44. e.printStackTrace()
    45. newFixedLengthResponse(session.uri + " 异常 " + e)
    46. }
    47. }
    48. companion object {
    49. var rootDir: String = ""
    50. }
    51. }

    使用 sparkjava 实现 

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

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

    5. 使用 telnet

    WifiUtil.kt

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

    1. import android.content.Context
    2. import android.net.ConnectivityManager
    3. import android.net.Network
    4. import android.net.NetworkCapabilities
    5. import android.net.NetworkRequest
    6. import android.net.wifi.WifiInfo
    7. import android.net.wifi.WifiManager
    8. import android.net.wifi.WifiNetworkSpecifier
    9. import android.os.Build
    10. import android.os.PatternMatcher
    11. import android.util.Log
    12. import androidx.annotation.RequiresApi
    13. import site.feiyuliuxing.wifitest.toIPAddress
    14. class WifiUtil(context: Context) {
    15. private val mNetworkCallback: ConnectivityManager.NetworkCallback
    16. private val mConnectivityManager: ConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    17. private val wifiManager: WifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
    18. private var mNetwork: Network? = null
    19. //Android系统热点的默认IP地址是192.168.43.1
    20. var hostIP = "192.168.43.1"
    21. private set
    22. val network: Network?
    23. get() = mNetwork
    24. val targetIP: String
    25. get() {
    26. val arr = hostIP.split(".").toMutableList()
    27. arr[arr.lastIndex] = "1"
    28. return arr.joinToString(".")
    29. }
    30. init {
    31. if (Build.VERSION.SDK_INT < 29) {
    32. //Android10以下系统
    33. mNetworkCallback = object : ConnectivityManager.NetworkCallback() {
    34. override fun onAvailable(network: Network) {
    35. callbackAvailable(network)
    36. }
    37. override fun onLost(network: Network) {
    38. callbackLost(network)
    39. }
    40. }
    41. } else if (Build.VERSION.SDK_INT < 31) {
    42. //Android10 Android11
    43. mNetworkCallback = object : ConnectivityManager.NetworkCallback() {
    44. override fun onAvailable(network: Network) {
    45. callbackAvailable(network)
    46. }
    47. override fun onLost(network: Network) {
    48. callbackLost(network)
    49. }
    50. @RequiresApi(Build.VERSION_CODES.Q)
    51. override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
    52. // Android10才支持该回调方法
    53. callbackCapabilitiesChanged(network, networkCapabilities)
    54. }
    55. }
    56. } else {
    57. //Android12获取 SSID 需要 FLAG_INCLUDE_LOCATION_INFO 并获得 ACCESS_FINE_LOCATION 权限
    58. mNetworkCallback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
    59. override fun onAvailable(network: Network) {
    60. callbackAvailable(network)
    61. }
    62. override fun onLost(network: Network) {
    63. callbackLost(network)
    64. }
    65. @RequiresApi(Build.VERSION_CODES.Q)
    66. override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
    67. // Android10才支持该回调方法
    68. callbackCapabilitiesChanged(network, networkCapabilities)
    69. }
    70. }
    71. }
    72. }
    73. private fun callbackAvailable(network: Network) {
    74. mNetwork = network
    75. hostIP = getIP(network)
    76. }
    77. private fun callbackLost(network: Network) {
    78. mNetwork = null
    79. }
    80. @RequiresApi(Build.VERSION_CODES.Q)
    81. private fun callbackCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
    82. val wifiInfo = networkCapabilities.transportInfo as WifiInfo?
    83. if (wifiInfo != null) {
    84. println("onCapabilitiesChanged() - SSID=${wifiInfo.ssid}, IP=${wifiInfo.ipAddress.toIP()}")
    85. }
    86. }
    87. private val networkRequest: NetworkRequest
    88. get() = NetworkRequest.Builder()
    89. .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    90. .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    91. .apply {
    92. if (Build.VERSION.SDK_INT >= 29) {
    93. val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
    94. // .setBand(ScanResult.WIFI_BAND_24_GHZ)//Android12 但是设置这个参数后无法连接
    95. .setSsidPattern(PatternMatcher("ZS[0-9a-fA-F]{8}", PatternMatcher.PATTERN_ADVANCED_GLOB))
    96. // .setSsid("ZS3755a0e2")
    97. .setWpa2Passphrase("12345678")
    98. .build()
    99. setNetworkSpecifier(wifiNetworkSpecifier)
    100. }
    101. }
    102. .build()
    103. fun requestConnectWIFI() {
    104. mConnectivityManager.requestNetwork(networkRequest, mNetworkCallback)
    105. }
    106. fun close() {
    107. mConnectivityManager.unregisterNetworkCallback(mNetworkCallback)
    108. }
    109. //无需运行时申请权限
    110. fun getGatewayIP(): String {
    111. if (Build.VERSION.SDK_INT < 32) {
    112. /*This method was deprecated in API level 31.
    113. Use the methods on LinkProperties which can be obtained either via
    114. NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties) or
    115. ConnectivityManager#getLinkProperties(Network).*/
    116. val dhcpInfo = wifiManager.dhcpInfo
    117. // 网关IP地址是一个整数,需要转换为可读的IP地址格式
    118. val gatewayIpAddress: String = dhcpInfo.gateway.toIPAddress()
    119. println("Gateway IP: $gatewayIpAddress")
    120. return gatewayIpAddress
    121. } else {
    122. var addr = ""
    123. mConnectivityManager.activeNetwork?.let { network ->
    124. mConnectivityManager.getLinkProperties(network)?.let { linkProperties ->
    125. linkProperties.dhcpServerAddress?.let { inet4Address ->
    126. addr = inet4Address.hostAddress ?: ""
    127. }
    128. }
    129. }
    130. println("网关IP: $addr")
    131. return addr
    132. }
    133. }
    134. private fun getIP(network: Network): String {
    135. mConnectivityManager.getLinkProperties(network)?.linkAddresses?.let { linkAddresses ->
    136. linkAddresses.forEach { linkAddress ->
    137. // hostAddress: the IP address string in textual presentation.
    138. linkAddress.address.hostAddress?.let { hostAddress ->
    139. if (hostAddress.isValidIP) {
    140. println("手机IP:${hostAddress}")
    141. return hostAddress
    142. }
    143. }
    144. }
    145. }
    146. return ""
    147. }
    148. private val ipRegex = """\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}""".toRegex()
    149. private val String.isValidIP: Boolean
    150. get() = ipRegex.matches(this)
    151. private fun Int.toIP(): String {
    152. return "${this.and(0xff)}" +
    153. ".${this.shr(8).and(0xff)}" +
    154. ".${this.shr(16).and(0xff)}" +
    155. ".${this.shr(24).and(0xff)}"
    156. }
    157. }

    TelnetUtil.kt

    1. import android.net.Network
    2. import org.apache.commons.net.telnet.EchoOptionHandler
    3. import org.apache.commons.net.telnet.SuppressGAOptionHandler
    4. import org.apache.commons.net.telnet.TelnetClient
    5. import org.apache.commons.net.telnet.TerminalTypeOptionHandler
    6. class TelnetUtil {
    7. private val telnetClient = TelnetClient()
    8. private fun connectServer(ip: String, network: Network?): Boolean {
    9. try {
    10. if (network != null) {
    11. telnetClient.setSocketFactory(network.socketFactory)
    12. }
    13. /*val ttopt = TerminalTypeOptionHandler("VT220", false, false, true, false)
    14. val echoopt = EchoOptionHandler(true, false, true, false)
    15. val gaopt = SuppressGAOptionHandler(true, true, true, true)
    16. telnetClient.addOptionHandler(ttopt)
    17. telnetClient.addOptionHandler(echoopt)
    18. telnetClient.addOptionHandler(gaopt)*/
    19. telnetClient.connect(ip, 23)
    20. if (telnetClient.isConnected) {
    21. println("端口可用 $ip")
    22. return true
    23. }
    24. } catch (ex: Exception) {
    25. ex.printStackTrace()
    26. }
    27. return false
    28. }
    29. fun readUntil(match: String, timeout: Long = 10_000): Boolean {
    30. val sb = StringBuilder()
    31. val startTime = System.currentTimeMillis()
    32. while (true) {
    33. if ((System.currentTimeMillis() - startTime) >= timeout) {
    34. //超时
    35. break
    36. }
    37. var len = telnetClient.inputStream.available()
    38. while (len > 0) {
    39. val ch = telnetClient.inputStream.read()
    40. if (ch != -1) {
    41. sb.append(ch.toChar())
    42. print(ch.toChar())
    43. if (ch == 0x0D || ch == 0x0A) {
    44. System.out.flush()
    45. }
    46. if (sb.contains(match)) {
    47. println(sb.toString())
    48. return true
    49. }
    50. }
    51. len--
    52. }
    53. }
    54. println(sb.toString())
    55. return false
    56. }
    57. fun send(msg: String) {
    58. telnetClient.outputStream.apply {
    59. write(msg.toByteArray(Charsets.UTF_8))
    60. flush()
    61. }
    62. }
    63. }

    6. 解压缩文件

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

  • 相关阅读:
    直播倒计时 1 天|SOFAChannel#35《SOFABoot 4.0 — 迈向 JDK 17 新时代》
    【牛客刷题】带你在牛客刷题第五弹(简单排序)
    vue入门
    Linux 程序开发流程 / 基本开发工具 / Vim / GCC工具链 / Make 工具 / Makefile 模板
    PostgreSQL数据库统计信息——acquire_sample_rows采样函数
    Chrome添加扩展程序
    springMVC基础技术使用
    Idea同时切换多个项目的分支
    A-level成绩BBB能申英国哪些大学?
    LAMMPS实操系列(四): 高熵合金FCC-CoCrCuFeNi的退火模拟
  • 原文地址:https://blog.csdn.net/jjf19891208/article/details/139710406