29人参与 • 2025-02-21 • Android
悬浮窗实现效果 :
在 android 开发中 , 自 android 6.0(api 级别 23)版本开始引入 " 动态权限 " ,
动态权限 指的是 在应用程序运行时向用户请求权限 , 而不是在安装时一次性请求所有权限 , 旨在提高用户隐私和安全性 ;
动态权限 请求 流程 :
settings.action_manage_overlay_permission 是一个用于请求和管理 悬浮窗权限(overlay permission) 的系统设置页面 ;
悬浮窗权限允许应用在其他应用或系统界面上绘制悬浮窗口(如悬浮球、弹窗等);
由于悬浮窗权限涉及用户隐私和安全,android 要求开发者显式请求该权限,并引导用户手动开启。
悬浮窗权限允许应用执行以下操作:
检查动态权限 , android sdk 23 以上才检查动态权限 , 对应的版本是 android 6.0(marshmallow), 低于该版本不需要 动态权限 , 直接使用对应功能即可 ,
通过 build.version.sdk_int >= build.version_codes.m
函数可以判定是否 当前版本 是否高于 android sdk 23 android 6.0(marshmallow)版本 , 是否需要
通过调用 settings.candrawoverlays(this)
函数 , 可以检查是否浮云了 悬浮窗权限 , 如果是 android 6.0 以上的系统 , 并且没有该 动态权限 , 则 动态请求该权限 ;
/** * 检查悬浮窗权限的方法 */ private fun checkoverlaypermission(): boolean { // android sdk23 对应的版本是 android 6.0(marshmallow) // 6.0 以上的 android 系统需要动态申请权限 if (build.version.sdk_int >= build.version_codes.m ) { /* 根据当前应用是否有悬浮窗权限进行不同的操作 - 如果 有 悬浮窗权限 直接返回 true 显示悬浮窗 - 如果 没有悬浮窗权限, 开始请求悬浮窗权限 */ if (!settings.candrawoverlays(this)) { // 没有悬浮窗权限, 开始请求悬浮窗权限 requestoverlaypermission() return false } else { // 有 悬浮窗权限 直接返回 true 显示悬浮窗 return true } } else { // 6.0 以下的 android 系统不需要申请权限 // 已经请求悬浮窗权限成功 可进行后续操作 return true } }
申请动态权限时 , 需要弹出一个对话框 , 提示用户要跳转到指定界面 , 进行某个设置 ;
这里需要跳转到 settings.action_manage_overlay_permission 权限设置界面 , 为某个应用开启 " 显示在其他应用的上层 " 权限 ;
在界面中 , 选中要设置的应用 , 设置该应用可以显示在其它应用的上层 ;
代码示例 :
/** * 请求悬浮窗权限 */ private fun requestoverlaypermission() { // 弹出 " 请允许显示在其他应用上方 " 的提示对话框 alertdialog.builder(this) // 创建alertdialog构建器 .settitle("需要悬浮窗权限") // 设置标题 .setmessage("请允许显示在其他应用上方") // 设置消息 .setpositivebutton("去设置") { _, _ -> // 设置“去设置”按钮 val intent = intent( settings.action_manage_overlay_permission, // 设置操作为管理悬浮窗权限 uri.parse("package:$packagename") // 设置uri为当前应用的包名 ) startactivityforresult(intent, overlay_permission_request_code) // 启动设置界面,等待结果 } .setnegativebutton("取消", null) // 设置“取消”按钮 .show() // 显示对话框 }
设定一个请求码 , 自定义的请求码 , 用于 跳转到 申请 动态权限 页面 , 返回后判定返回结果 ;
/** * 请求悬浮窗权限的请求码 */ private val overlay_permission_request_code = 1001
设置完 悬浮窗权限 后 , 从 settings.action_manage_overlay_permission 界面返回 , 会回调 onactivityresult 函数 , 返回后 再次验证 是否已经获得了 悬浮窗权限 ,
override fun onactivityresult(requestcode: int, resultcode: int, data: intent?) { super.onactivityresult(requestcode, resultcode, data) if (requestcode == overlay_permission_request_code) { // 如果权限请求成功, 会根据 请求码 命中该分支 if (checkoverlaypermission()) { // 检查是否获得悬浮窗权限 startfloatingservice() // 启动悬浮窗服务 } } }
为什么必须用 前台服务 启动 悬浮窗 :
如果不使用前台服务 , 会出现以下情况 :
android 悬浮窗开发 , 需要 保证 悬浮窗 的持续存活 ,
场景 | 问题 | 前台服务的作用 |
---|---|---|
应用退到后台 | 普通 service 可能被系统回收 → 悬浮窗消失 | 前台服务优先级更高,系统更倾向于保留(即使内存不足) → 悬浮窗持续显示 |
android 8.0+ 后台限制 | 后台应用无法创建 activity 或 window (如 type_application_overlay ) | 前台服务属于“用户可见”状态 → 允许在后台显示悬浮窗 |
doze 模式 / 应用待机 | 系统限制后台应用的 cpu/网络等资源 → 普通服务可能被中断 | 前台服务可绕过部分 doze 限制 → 悬浮窗逻辑持续运行 |
在 android 系统中 , 运行了一个 悬浮在 操作系统 中的 悬浮窗 , 这需要满足 悬浮窗相关权限 和 用户感知要求 , 要让用户知道是哪个应用启动了 悬浮窗 , 并且用户可以随时关闭该 悬浮窗 ;
使用 前台服务 可以满足上述要求 ;
要求 | 前台服务的解决方案 |
---|---|
权限依赖 | 悬浮窗需要 system_alert_window 权限,但 android 10+ 要求动态申请并用户授权。前台服务通过通知栏提示用户应用正在运行,减少被系统判定为“滥用权限”的风险。 |
用户可感知性 | 前台服务必须显示通知栏通知 → 用户明确知道悬浮窗关联的服务在运行(符合 android 设计规范)。 |
避免后台限制 | 从 android 12 开始,后台应用启动前台服务需用户授权(start_foreground_services 权限),但启动后系统允许其显示悬浮窗。 |
android 系统中 , 不同的版本中 , 启动悬浮窗各自都有不同的限制 , 只有使用前台服务 , 可以满足所有的限制 , 因此前台服务在不同版本均有关键作用 , 所有的版本都可以使用 前台服务 启动 和 保持 悬浮窗 , 避免了不同 android 系统版本 开发出的 悬浮窗 不兼容的问题 ;
android 版本 | 前台服务的关键作用 |
---|---|
android 8.0 (api 26) | 禁止后台应用创建 window → 必须通过前台服务绑定悬浮窗逻辑。 |
android 10 (api 29) | 禁止后台应用启动 activity → 前台服务可绕过此限制显示悬浮窗。 |
android 12 (api 31) | 前台服务需声明 foregroundservicetype (如 mediaplayback )→ 明确服务用途,提升系统信任度。 |
这里需要为 悬浮窗 设置一个绑定的服务 , 以确保悬浮窗一直保持存在 ;
服务类型 | 使用场景 | 特点 |
---|---|---|
前台服务 | 需要在后台持续运行且用户可感知的任务,如播放音乐、导航等。 | 需要在通知栏显示持续的通知,告知用户服务正在运行。 |
workmanager | 需要可靠执行的后台任务,即使应用退出或设备重启后仍需执行的任务,如上传日志、定期同步数据等。 | 适用于需要持久性和可靠性的任务,支持链式任务、延迟执行、重试机制等特性。 |
jobscheduler | 需要在特定条件下执行的后台任务,如网络连接、设备充电等条件下执行的任务。 | 适用于 android 5.0(api 级别 21)及以上版本,允许在满足特定条件时调度任务。 |
alarmmanager | 需要在特定时间或周期性执行的任务,如定时提醒、定期同步等。 | 适用于设置一次性任务、周期重复任务、定时重复任务。 |
前台服务(foreground service):
workmanager 服务 :
jobscheduler 服务 :
alarmmanager 服务 :
android sdk 版本大于 26, android 8.0 (oreo) 需要 调用 startforegroundservice 函数 启动 前台服务 , 前台服务 是 android 8.0 之后才有的概念 , 之前 全都是 普通的 服务 , 只是通过 startservice 和 bindservice 两种启动方式 区别服务 ;
如果 android 的 sdk 版本低于 26, android 8.0 (oreo) 则直接 调用 startservice 函数 启动普通服务即可 ;
启动悬浮窗前台服务代码 :
/** * 启动悬浮窗服务 */ private fun startfloatingservice() { if (build.version.sdk_int >= build.version_codes.o) { // 如果 sdk 版本大于 26, android 8.0 (oreo) 需要启动前台服务 startforegroundservice(intent(this, floatingwindowservice::class.java)) // 启动前台服务 } else { // 如果 sdk 版本低于 26, android 8.0 (oreo) 则直接启动普通服务即可 startservice(intent(this, floatingwindowservice::class.java)) // 启动普通服务 } }
android sdk 版本大于 26 , 对应的系统版本是 android 8.0 (oreo) , 通过调用 startforegroundservice 函数 启动 前台服务 , 必须在 启动服务 的 5 秒内 , 启动 前台通知 , 否则应用会崩溃退出 ;
启动通知代码如下 :
// sdk 版本大于 26, android 8.0 (oreo) , 才创建通知渠道, 并启动前台应用 if (build.version.sdk_int >= build.version_codes.o) { createnotificationchannel() val notification = buildnotification() // 启动服务后, 必须在 5 秒内设置 前台服务通知信息 startforeground(notification_id, notification) }
首先 , 要创建 通知渠道 :
/** * 创建通知渠道 * 通知渠道是 sdk 26 android 8.0 (oreo) 引入的新特性 */ private fun createnotificationchannel() { if (build.version.sdk_int >= build.version_codes.o) { // 创建通知渠道 val channel = notificationchannel( channel_id, "悬浮窗", notificationmanager.importance_low ) // 注册通知渠道 getsystemservice(notificationmanager::class.java) .createnotificationchannel(channel) } }
然后 , 创建通知 :
/** * 创建通知 */ private fun buildnotification(): notification { return notificationcompat.builder(this, channel_id) .setcontenttitle("悬浮窗") // 设置通知标题 .setcontenttext("显示前台悬浮窗服务") // 设置通知内容 .setsmallicon(r.mipmap.ic_launcher) // 设置通知小图标 .setpriority(notificationcompat.priority_low) // 设置通知优先级 .build() // 构建并返回通知 }
创建浮动窗口流程 :
// 获取 windowmanager 实例 windowmanager = getsystemservice(window_service) as windowmanager // 设置布局类型 val layoutflag: int = if (build.version.sdk_int >= build.version_codes.o) { // 如果 sdk 版本大于等于 o windowmanager.layoutparams.type_application_overlay // 设置布局类型为应用覆盖层 } else { windowmanager.layoutparams.type_phone // 设置布局类型为电话 }
// 设置布局参数 val params = windowmanager.layoutparams( windowmanager.layoutparams.wrap_content, // 宽度自适应 windowmanager.layoutparams.wrap_content, // 高度自适应 layoutflag, // 布局类型 windowmanager.layoutparams.flag_not_focusable, // 不获取焦点 pixelformat.translucent // 半透明 ).apply { gravity = gravity.top or gravity.start // 设置重力为顶部和左侧 x = 0 // 设置x坐标 y = 0 // 设置y坐标, 将浮动窗口显示在左上角 }
// 加载 浮动窗口 布局 val inflater = getsystemservice(layout_inflater_service) as layoutinflater // 获取layoutinflater实例 floatingview = inflater.inflate(r.layout.floating_window, null) // 加载悬浮窗布局
// 设置关闭按钮的点击事件 floatingview.findviewbyid<button>(r.id.close_btn).setonclicklistener { stopself() // 停止服务 } // 设置拖动事件 floatingview.setontouchlistener { view, event -> when (event.action) { motionevent.action_down -> { // 按下事件 initialx = params.x // 记录初始x坐标 initialy = params.y // 记录初始y坐标 initialtouchx = event.rawx // 记录初始触摸x坐标 initialtouchy = event.rawy // 记录初始触摸y坐标 true } motionevent.action_move -> { // 移动事件 params.x = initialx + (event.rawx - initialtouchx).toint() // 更新x坐标 params.y = initialy + (event.rawy - initialtouchy).toint() // 更新y坐标 windowmanager.updateviewlayout(floatingview, params) // 更新悬浮窗位置 true } else -> false } }
// 正式添加悬浮窗到窗口 windowmanager.addview(floatingview, params)
完整代码如下 :
/** * 创建悬浮窗口 */ private fun createfloatingwindow() { // 创建悬浮窗的方法 // 获取 windowmanager 实例 windowmanager = getsystemservice(window_service) as windowmanager // 设置布局类型 val layoutflag: int = if (build.version.sdk_int >= build.version_codes.o) { // 如果 sdk 版本大于等于 o windowmanager.layoutparams.type_application_overlay // 设置布局类型为应用覆盖层 } else { windowmanager.layoutparams.type_phone // 设置布局类型为电话 } // 设置布局参数 val params = windowmanager.layoutparams( windowmanager.layoutparams.wrap_content, // 宽度自适应 windowmanager.layoutparams.wrap_content, // 高度自适应 layoutflag, // 布局类型 windowmanager.layoutparams.flag_not_focusable, // 不获取焦点 pixelformat.translucent // 半透明 ).apply { gravity = gravity.top or gravity.start // 设置重力为顶部和左侧 x = 0 // 设置x坐标 y = 0 // 设置y坐标, 将浮动窗口显示在左上角 } // 加载 浮动窗口 布局 val inflater = getsystemservice(layout_inflater_service) as layoutinflater // 获取layoutinflater实例 floatingview = inflater.inflate(r.layout.floating_window, null) // 加载悬浮窗布局 // 设置关闭按钮的点击事件 floatingview.findviewbyid<button>(r.id.close_btn).setonclicklistener { stopself() // 停止服务 } // 设置拖动事件 floatingview.setontouchlistener { view, event -> when (event.action) { motionevent.action_down -> { // 按下事件 initialx = params.x // 记录初始x坐标 initialy = params.y // 记录初始y坐标 initialtouchx = event.rawx // 记录初始触摸x坐标 initialtouchy = event.rawy // 记录初始触摸y坐标 true } motionevent.action_move -> { // 移动事件 params.x = initialx + (event.rawx - initialtouchx).toint() // 更新x坐标 params.y = initialy + (event.rawy - initialtouchy).toint() // 更新y坐标 windowmanager.updateviewlayout(floatingview, params) // 更新悬浮窗位置 true } else -> false } } // 正式添加悬浮窗到窗口 windowmanager.addview(floatingview, params) }
浮动窗口所在 前台服务 代码 floatingwindowservice.kt :
package hsl.floatingwindow import android.app.notification import android.app.notificationchannel import android.app.notificationmanager import android.app.service import android.content.intent import android.graphics.pixelformat import android.os.build import android.os.ibinder import android.view.* import android.widget.button import androidx.core.app.notificationcompat class floatingwindowservice : service() { /** * 窗口管理器 */ private lateinit var windowmanager: windowmanager /** * 悬浮窗组件 */ private lateinit var floatingview: view /* 声明 浮动窗口 的 初始坐标 */ private var initialx = 0 private var initialy = 0 /* 声明 浮动窗口 的 初始触摸坐标 */ private var initialtouchx = 0f private var initialtouchy = 0f /** * 定义通知 id */ private val notification_id = 1001 /** * 定义通知渠道 id, 通知渠道需要 * 调用 service.createnotificationchannel 函数创建 */ private val channel_id = "floating_window_channel" /** * 重写 onbind 函数, 返回 null */ override fun onbind(intent: intent?): ibinder? = null override fun oncreate() { super.oncreate() // sdk 版本大于 26, android 8.0 (oreo) , 才创建通知渠道, 并启动前台应用 if (build.version.sdk_int >= build.version_codes.o) { createnotificationchannel() val notification = buildnotification() // 启动服务后, 必须在 5 秒内设置 前台服务通知信息 startforeground(notification_id, notification) } // 创建悬浮窗 createfloatingwindow() } /** * 创建通知渠道 * 通知渠道是 sdk 26 android 8.0 (oreo) 引入的新特性 */ private fun createnotificationchannel() { if (build.version.sdk_int >= build.version_codes.o) { // 创建通知渠道 val channel = notificationchannel( channel_id, "悬浮窗", notificationmanager.importance_low ) // 注册通知渠道 getsystemservice(notificationmanager::class.java) .createnotificationchannel(channel) } } /** * 创建通知 */ private fun buildnotification(): notification { return notificationcompat.builder(this, channel_id) .setcontenttitle("悬浮窗") // 设置通知标题 .setcontenttext("显示前台悬浮窗服务") // 设置通知内容 .setsmallicon(r.mipmap.ic_launcher) // 设置通知小图标 .setpriority(notificationcompat.priority_low) // 设置通知优先级 .build() // 构建并返回通知 } /** * 创建悬浮窗口 */ private fun createfloatingwindow() { // 创建悬浮窗的方法 // 获取 windowmanager 实例 windowmanager = getsystemservice(window_service) as windowmanager // 设置布局类型 val layoutflag: int = if (build.version.sdk_int >= build.version_codes.o) { // 如果 sdk 版本大于等于 o windowmanager.layoutparams.type_application_overlay // 设置布局类型为应用覆盖层 } else { windowmanager.layoutparams.type_phone // 设置布局类型为电话 } // 设置布局参数 val params = windowmanager.layoutparams( windowmanager.layoutparams.wrap_content, // 宽度自适应 windowmanager.layoutparams.wrap_content, // 高度自适应 layoutflag, // 布局类型 windowmanager.layoutparams.flag_not_focusable, // 不获取焦点 pixelformat.translucent // 半透明 ).apply { gravity = gravity.top or gravity.start // 设置重力为顶部和左侧 x = 0 // 设置x坐标 y = 0 // 设置y坐标, 将浮动窗口显示在左上角 } // 加载 浮动窗口 布局 val inflater = getsystemservice(layout_inflater_service) as layoutinflater // 获取layoutinflater实例 floatingview = inflater.inflate(r.layout.floating_window, null) // 加载悬浮窗布局 // 设置关闭按钮的点击事件 floatingview.findviewbyid<button>(r.id.close_btn).setonclicklistener { stopself() // 停止服务 } // 设置拖动事件 floatingview.setontouchlistener { view, event -> when (event.action) { motionevent.action_down -> { // 按下事件 initialx = params.x // 记录初始x坐标 initialy = params.y // 记录初始y坐标 initialtouchx = event.rawx // 记录初始触摸x坐标 initialtouchy = event.rawy // 记录初始触摸y坐标 true } motionevent.action_move -> { // 移动事件 params.x = initialx + (event.rawx - initialtouchx).toint() // 更新x坐标 params.y = initialy + (event.rawy - initialtouchy).toint() // 更新y坐标 windowmanager.updateviewlayout(floatingview, params) // 更新悬浮窗位置 true } else -> false } } // 正式添加悬浮窗到窗口 windowmanager.addview(floatingview, params) } /** * 重写 ondestroy 方法 */ override fun ondestroy() { super.ondestroy() if (::floatingview.isinitialized) { // 如果 floatingview 已初始化 windowmanager.removeview(floatingview) // 移除悬浮窗 } } }
下面是 activity 主界面代码 mainactivity.kt , 主要作用就是 申请 浮动窗口所需权限 和 启动前台服务 ;
package hsl.floatingwindow import android.content.intent import android.net.uri import android.os.build import android.os.bundle import android.provider.settings import androidx.appcompat.app.alertdialog import androidx.appcompat.app.appcompatactivity class mainactivity : appcompatactivity() { /** * 请求悬浮窗权限的请求码 */ private val overlay_permission_request_code = 1001 override fun oncreate(savedinstancestate: bundle?) { super.oncreate(savedinstancestate) setcontentview(r.layout.activity_main) // 检查是否具有悬浮窗权限 if (checkoverlaypermission()) { // 启动悬浮窗服务 startfloatingservice() } } /** * 检查悬浮窗权限的方法 */ private fun checkoverlaypermission(): boolean { // android sdk23 对应的版本是 android 6.0(marshmallow) // 6.0 以上的 android 系统需要动态申请权限 if (build.version.sdk_int >= build.version_codes.m ) { /* 根据当前应用是否有悬浮窗权限进行不同的操作 - 如果 有 悬浮窗权限 直接返回 true 显示悬浮窗 - 如果 没有悬浮窗权限, 开始请求悬浮窗权限 */ if (!settings.candrawoverlays(this)) { // 没有悬浮窗权限, 开始请求悬浮窗权限 requestoverlaypermission() return false } else { // 有 悬浮窗权限 直接返回 true 显示悬浮窗 return true } } else { // 6.0 以下的 android 系统不需要申请权限 // 已经请求悬浮窗权限成功 可进行后续操作 return true } } /** * 请求悬浮窗权限 */ private fun requestoverlaypermission() { // 弹出 " 请允许显示在其他应用上方 " 的提示对话框 alertdialog.builder(this) // 创建alertdialog构建器 .settitle("需要悬浮窗权限") // 设置标题 .setmessage("请允许显示在其他应用上方") // 设置消息 .setpositivebutton("去设置") { _, _ -> // 设置“去设置”按钮 val intent = intent( settings.action_manage_overlay_permission, // 设置操作为管理悬浮窗权限 uri.parse("package:$packagename") // 设置uri为当前应用的包名 ) startactivityforresult(intent, overlay_permission_request_code) // 启动设置界面,等待结果 } .setnegativebutton("取消", null) // 设置“取消”按钮 .show() // 显示对话框 } override fun onactivityresult(requestcode: int, resultcode: int, data: intent?) { super.onactivityresult(requestcode, resultcode, data) if (requestcode == overlay_permission_request_code) { // 如果权限请求成功, 会根据 请求码 命中该分支 if (checkoverlaypermission()) { // 检查是否获得悬浮窗权限 startfloatingservice() // 启动悬浮窗服务 } } } /** * 启动悬浮窗服务 */ private fun startfloatingservice() { if (build.version.sdk_int >= build.version_codes.o) { // 如果 sdk 版本大于 26, android 8.0 (oreo) 需要启动前台服务 startforegroundservice(intent(this, floatingwindowservice::class.java)) // 启动前台服务 } else { // 如果 sdk 版本低于 26, android 8.0 (oreo) 则直接启动普通服务即可 startservice(intent(this, floatingwindowservice::class.java)) // 启动普通服务 } } }
在该 androidmanifest.xml 配置文件中 , 主要需要声明 :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="hsl.floatingwindow"> <!-- 浮动窗口权限 --> <uses-permission android:name="android.permission.system_alert_window" /> <!-- 前台服务权限 --> <uses-permission android:name="android.permission.foreground_service" /> <application android:allowbackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundicon="@mipmap/ic_launcher_round" android:supportsrtl="true" android:theme="@style/theme.floatingwindow"> <!-- activity 组件注册, 注意必须配置 android:exported="true" 属性, 否则报错 --> <activity android:name=".mainactivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- service 组件注册 --> <service android:name=".floatingwindowservice" /> </application> </manifest>
浮动窗口布局文件 floating_window.xml :
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/floating_layout" android:layout_width="200dp" android:layout_height="100dp" android:orientation="vertical" android:background="#80ffffff" android:padding="8dp"> <textview android:layout_width="match_parent" android:layout_height="wrap_content" android:text="floating window" android:textsize="18sp"/> <button android:id="@+id/close_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="close"/> </linearlayout>
activity 组件布局文件 activity_main.xml :
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.constraintlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity"> <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="hello world!" app:layout_constraintbottom_tobottomof="parent" app:layout_constraintleft_toleftof="parent" app:layout_constraintright_torightof="parent" app:layout_constrainttop_totopof="parent" /> </androidx.constraintlayout.widget.constraintlayout>
执行效果 :
到此这篇关于android 悬浮窗开发 ((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )的文章就介绍到这了,更多相关android 悬浮窗内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论