├── app
├── .gitignore
├── .DS_Store
├── src
│ ├── main
│ │ ├── .DS_Store
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── icon_back.png
│ │ │ │ ├── icon_menu.png
│ │ │ │ ├── icon_next.png
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── icon_cancel.png
│ │ │ │ ├── icon_connect.png
│ │ │ │ ├── icon_folder.png
│ │ │ │ ├── icon_select.png
│ │ │ │ ├── icon_setting.png
│ │ │ │ ├── icon_last_page.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ ├── ic_launcher_web.png
│ │ │ │ ├── icon_disconnect.png
│ │ │ │ ├── icon_new_folder.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── strings.xml
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ └── icon_down.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable
│ │ │ │ ├── ripple_bg.xml
│ │ │ │ └── socket_spinner_bg.xml
│ │ │ ├── layout
│ │ │ │ ├── item_socket_spinner.xml
│ │ │ │ ├── activity_history.xml
│ │ │ │ ├── item_history.xml
│ │ │ │ ├── item_file.xml
│ │ │ │ ├── activity_setting.xml
│ │ │ │ ├── activity_select_folder.xml
│ │ │ │ ├── activity_tcp_server_setting.xml
│ │ │ │ ├── activity_ping.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── activity_tcp_client_setting.xml
│ │ │ ├── values-night
│ │ │ │ └── colors.xml
│ │ │ └── values-en
│ │ │ │ └── strings.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── dlong
│ │ │ │ └── networkdebugassistant
│ │ │ │ ├── bean
│ │ │ │ ├── TcpServerConfiguration.kt
│ │ │ │ ├── TcpClientConfiguration.kt
│ │ │ │ ├── UdpBroadConfiguration.kt
│ │ │ │ ├── FolderInfo.kt
│ │ │ │ ├── UdpMultiConfiguration.kt
│ │ │ │ ├── ReceiveInfo.kt
│ │ │ │ ├── HistoryInfo.kt
│ │ │ │ └── BaseConfiguration.kt
│ │ │ │ ├── app
│ │ │ │ ├── MyApp.kt
│ │ │ │ └── BaseHandler.kt
│ │ │ │ ├── db
│ │ │ │ ├── dao
│ │ │ │ │ └── HistoryDao.kt
│ │ │ │ └── HistoryDB.kt
│ │ │ │ ├── utils
│ │ │ │ ├── SpfUtils.kt
│ │ │ │ ├── NetUtils.kt
│ │ │ │ ├── AssetsUtils.kt
│ │ │ │ ├── AppUtils.kt
│ │ │ │ ├── ByteUtils.kt
│ │ │ │ ├── StringUtils.kt
│ │ │ │ ├── DateUtils.kt
│ │ │ │ └── FileProviderUtils.java
│ │ │ │ ├── model
│ │ │ │ └── HistoryModel.kt
│ │ │ │ ├── activity
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── UdpBroadActivity.kt
│ │ │ │ ├── TcpClientActivity.kt
│ │ │ │ ├── UdpMultiActivity.kt
│ │ │ │ ├── SettingActivity.kt
│ │ │ │ ├── PingActivity.kt
│ │ │ │ ├── TcpServerActivity.kt
│ │ │ │ ├── TcpServerSettingActivity.kt
│ │ │ │ ├── HistoryActivity.kt
│ │ │ │ ├── BaseActivity.kt
│ │ │ │ ├── TcpClientSettingActivity.kt
│ │ │ │ ├── BaseSettingActivity.kt
│ │ │ │ ├── UdpBroadSettingActivity.kt
│ │ │ │ └── UdpMultiSettingActivity.kt
│ │ │ │ ├── adapter
│ │ │ │ ├── FolderAdapter.kt
│ │ │ │ └── HistoryAdapter.kt
│ │ │ │ └── constant
│ │ │ │ └── DBConstant.kt
│ │ ├── assets
│ │ │ └── readme.json
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── dlong
│ │ │ └── networkdebugassistant
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── dlong
│ │ └── networkdebugassistant
│ │ └── ExampleInstrumentedTest.kt
├── dlong0232372.keystore
├── build.gradle
└── proguard-rules.pro
├── dl10netassistant
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── com
│ └── d10ng
│ └── net
│ └── assistant
│ ├── NetUtils.kt
│ ├── TcpClientThread.kt
│ ├── UdpBroadThread.kt
│ ├── UdpMultiThread.kt
│ ├── BaseNetThread.kt
│ └── TcpServerThread.kt
├── dlong.keystore
├── private_key.pepk
├── networkdebugassistant-logo.png
├── networkdebugassistant-logo.psd
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── networkdebugassistant-logo-512.png
├── .gitignore
├── settings.gradle.kts
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/dl10netassistant/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/.DS_Store
--------------------------------------------------------------------------------
/dlong.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/dlong.keystore
--------------------------------------------------------------------------------
/private_key.pepk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/private_key.pepk
--------------------------------------------------------------------------------
/app/src/main/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/.DS_Store
--------------------------------------------------------------------------------
/app/dlong0232372.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/dlong0232372.keystore
--------------------------------------------------------------------------------
/networkdebugassistant-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/networkdebugassistant-logo.png
--------------------------------------------------------------------------------
/networkdebugassistant-logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/networkdebugassistant-logo.psd
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/networkdebugassistant-logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/networkdebugassistant-logo-512.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_back.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_menu.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_next.png
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/icon_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/drawable-xxxhdpi/icon_down.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_cancel.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_connect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_connect.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_folder.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_select.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_last_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_last_page.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_web.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_disconnect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_disconnect.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_new_folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_new_folder.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/D10NGYANG/NetworkDebugAssistant/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #AAAAAA
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | /*/build
12 | /*/.idea
13 | /*/.gradle
14 | /*/release
15 | /*/debug
16 | /app/*/release
17 | /app/*/debug
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jul 27 14:57:12 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ripple_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/TcpServerConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import com.dlong.jsonentitylib.annotation.DLField
4 |
5 | /**
6 | * tcp 服务器配置
7 | *
8 | * @author D10NG
9 | * @date on 2019-12-10 10:48
10 | */
11 | data class TcpServerConfiguration(
12 | /** 本地端口 */
13 | @DLField
14 | var localPort: Int = 8089
15 | ) : BaseConfiguration() {
16 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/dlong/networkdebugassistant/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_socket_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | jcenter()
14 | maven("https://jitpack.io")
15 | }
16 | }
17 |
18 | rootProject.name = "NetworkDebugAssistant"
19 | include(":app")
20 | include(":dl10netassistant")
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/TcpClientConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import com.dlong.jsonentitylib.annotation.DLField
4 |
5 | /**
6 | * Tcp 客户端配置
7 | *
8 | * @author D10NG
9 | * @date on 2019-12-09 14:44
10 | */
11 | data class TcpClientConfiguration(
12 |
13 | /** 服务器地址 */
14 | @DLField
15 | var serverIpAddress: String = "192.168.1.11",
16 |
17 | /** 服务器端口 */
18 | @DLField
19 | var serverPort: Int = 8126
20 | ) : BaseConfiguration() {
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/app/MyApp.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.app
2 |
3 | import android.app.Application
4 | import com.dlong.networkdebugassistant.R
5 | import com.simple.spiderman.SpiderMan
6 |
7 | /**
8 | * @author D10NG
9 | * @date on 2019-12-05 10:17
10 | */
11 | class MyApp : Application() {
12 |
13 | override fun onCreate() {
14 | super.onCreate()
15 |
16 | //调试工具初始化
17 | SpiderMan.init(this) //设置主题样式,内置了两种主题样式light和dark
18 | .setTheme(R.style.SpiderManTheme_Dark)
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/UdpBroadConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import com.dlong.jsonentitylib.annotation.DLField
4 |
5 | /**
6 | * Udp广播配置
7 | *
8 | * @author D10NG
9 | * @date on 2019-12-05 15:11
10 | */
11 | data class UdpBroadConfiguration (
12 |
13 | /** 本地端口 */
14 | @DLField
15 | var localPort: Int = 8089,
16 |
17 | /** 目标地址 */
18 | @DLField
19 | var targetIpAddress: String = "255.255.255.255",
20 |
21 | /** 目标端口 */
22 | @DLField
23 | var targetPort: Int = 8089
24 |
25 | ) : BaseConfiguration() {
26 | }
--------------------------------------------------------------------------------
/app/src/main/assets/readme.json:
--------------------------------------------------------------------------------
1 | {"0.0.1":{"content":["创建工程;"],"author":"dlong","time":"2019.12.05"},"0.1.2":{"content":["适配Android10深色模式;"],"author":"dlong","time":"2020.03.18"},"0.1.4":{"content":["优化代码;","修复BUG;"],"author":"dlong","time":"2020.08.12"},"0.1.6":{"content":["TCP服务器增加单独Socket断开方法;","增加PING功能;","优化IP地址刷新;"],"author":"dlong","time":"2021.02.24"},"0.1.1":{"content":["完成全部功能与测试;","修改UI布局;","优化架构;","调整TCP的连接状态响应逻辑;"],"author":"dlong","time":"2019.12.10"},"0.1.3":{"content":["优化发送数据功能;","抽离UDP、TCP功能为单独库;"],"author":"dlong","time":"2020.07.27"},"0.1.5":{"content":["优化代码;","修复TCP服务器关闭后无法再打开的问题;"],"author":"dlong","time":"2020.10.17"}}
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/FolderInfo.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import com.dlong.networkdebugassistant.R
4 | import java.io.Serializable
5 |
6 | /**
7 | * 文件夹信息
8 | *
9 | * @author D10NG
10 | * @date on 2019-12-06 10:04
11 | */
12 | data class FolderInfo(
13 | /** 图标 */
14 | var iconRes: Int = R.mipmap.icon_folder,
15 | /** 名称 */
16 | var name: String = "",
17 | /** 子文件个数 */
18 | var sonNum: Long = 0L,
19 | /** 最后修改时间 */
20 | var lastEditTime: String = "",
21 | /** 路径 */
22 | var path: String = ""
23 | ) : Serializable {
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/UdpMultiConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import com.dlong.jsonentitylib.annotation.DLField
4 |
5 | /**
6 | * Udp 组播配置
7 | *
8 | * @author D10NG
9 | * @date on 2019-12-09 10:49
10 | */
11 | data class UdpMultiConfiguration(
12 | /** 本地端口 */
13 | @DLField
14 | var localPort: Int = 4032,
15 |
16 | /** 目标多播地址 224.0.0.0 ~ 239.255.255.255 */
17 | @DLField
18 | var targetIpAddress: String = "234.255.255.255",
19 |
20 | /** 目标端口 */
21 | @DLField
22 | var targetPort: Int = 4032
23 |
24 | ) : BaseConfiguration() {
25 | }
--------------------------------------------------------------------------------
/dl10netassistant/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id(Kotlin.Plugin.ID.kapt)
4 | id(Maven.Plugin.public)
5 | }
6 |
7 | group = "com.github.D10NGYANG"
8 | version = "1.0"
9 |
10 | java {
11 | sourceCompatibility = JavaVersion.VERSION_1_8
12 | targetCompatibility = JavaVersion.VERSION_1_8
13 | }
14 |
15 | dependencies {
16 | implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_ver")
17 |
18 | // 协程
19 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
20 | }
21 |
22 | publishing {
23 | publications {
24 | create("release", MavenPublication::class) {
25 | from(components.getByName("java"))
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/db/dao/HistoryDao.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.db.dao
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.room.*
5 | import com.dlong.networkdebugassistant.bean.HistoryInfo
6 |
7 | /**
8 | * @author D10NG
9 | * @date on 2019-12-07 11:13
10 | */
11 | @Dao
12 | interface HistoryDao {
13 |
14 | // 查询所有数据
15 | @Query("SELECT * FROM history_table ORDER BY time desc")
16 | fun getAllData() : LiveData>
17 |
18 | // 插入
19 | @Insert(onConflict = OnConflictStrategy.REPLACE)
20 | suspend fun insert(info: HistoryInfo): Long
21 |
22 | // 修改
23 | @Update(onConflict = OnConflictStrategy.REPLACE)
24 | suspend fun update(info: HistoryInfo)
25 |
26 | // 删除
27 | @Delete
28 | suspend fun delete(info: HistoryInfo)
29 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dlong/networkdebugassistant/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.dlong.networkdebugassistant", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/app/BaseHandler.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.app
2 |
3 | import android.os.Handler
4 | import android.os.Message
5 |
6 | import androidx.appcompat.app.AppCompatActivity
7 |
8 | import java.lang.ref.WeakReference
9 |
10 | /**
11 | * 封装Handler子类
12 | * $ 解决handler内存泄漏问题
13 | *
14 | * @author D10NG
15 | * @date on 2019-09-28 11:11
16 | */
17 | class BaseHandler(c: AppCompatActivity, private val callBack: BaseHandlerCallBack) : Handler() {
18 |
19 | private val act: WeakReference = WeakReference(c)
20 |
21 | override fun handleMessage(msg: Message) {
22 | super.handleMessage(msg)
23 | val c = act.get()
24 | if (c != null) {
25 | callBack.callBack(msg)
26 | }
27 | }
28 |
29 | interface BaseHandlerCallBack {
30 | fun callBack(msg: Message)
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/SpfUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | /**
7 | * 轻量键值参数存储
8 | *
9 | * @author D10NG
10 | * @date on 2019-11-07 15:26
11 | */
12 | class SpfUtils constructor(context: Context) {
13 |
14 | private val mSpf = context.getSharedPreferences("config_data", Context.MODE_PRIVATE)
15 |
16 | companion object {
17 |
18 | @Volatile
19 | private var INSTANCE: SpfUtils? = null
20 |
21 | @JvmStatic
22 | fun getInstance(context: Context) : SpfUtils =
23 | INSTANCE?: synchronized(this) {
24 | INSTANCE?: SpfUtils(context).also {
25 | INSTANCE = it
26 | }
27 | }
28 | }
29 |
30 | fun getSpf() : SharedPreferences {
31 | return mSpf
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/socket_spinner_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
11 |
12 |
13 | -
14 |
15 |
16 |
17 |
20 |
21 |
22 | -
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #CC3333
4 | #CC3333
5 | #4D88FF
6 |
7 | #aaaaaa
8 | #dddddd
9 | #ffffff
10 |
11 | #BF000000
12 |
13 |
14 | #4D88FF
15 | #222222
16 | #444444
17 | #666666
18 | #FF0000
19 | #ffff00
20 |
21 |
22 | #eeeeee
23 | #cccccc
24 | #eeeeee
25 | #cccccc
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/ReceiveInfo.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import java.io.Serializable
4 |
5 | /**
6 | * 接收到的信息实体类
7 | *
8 | * @author D10NG
9 | * @date on 2019-12-06 15:03
10 | */
11 | data class ReceiveInfo(
12 | /** 字节数据 */
13 | var byteData: ByteArray = byteArrayOf(),
14 | /** 时间 */
15 | var time: Long = 0L,
16 | /** 地址 */
17 | var ipAddress: String = "",
18 | /** 端口 */
19 | var port: Int = 0
20 | ) : Serializable {
21 |
22 |
23 | override fun equals(other: Any?): Boolean {
24 | if (this === other) return true
25 | if (javaClass != other?.javaClass) return false
26 |
27 | other as ReceiveInfo
28 |
29 | if (!byteData.contentEquals(other.byteData)) return false
30 | if (time != other.time) return false
31 | if (ipAddress != other.ipAddress) return false
32 | if (port != other.port) return false
33 | return true
34 | }
35 |
36 | override fun hashCode(): Int {
37 | return (byteData.contentHashCode() + time + ipAddress.hashCode() + port).toInt()
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #8F2424
4 | #8F2424
5 | #4D88FF
6 |
7 | #111111
8 | #333333
9 | #ffffff
10 |
11 | #BF000000
12 |
13 |
14 | #4D88FF
15 | #ffffff
16 | #dddddd
17 | #aaaaaa
18 | #FF0000
19 | #ffff00
20 |
21 |
22 | #333333
23 | #444444
24 | #555555
25 | #666666
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/NetUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import java.net.Inet4Address
4 | import java.net.InetAddress
5 | import java.net.NetworkInterface
6 | import java.util.*
7 |
8 |
9 | /**
10 | * @author D10NG
11 | * @date on 2019-12-10 14:37
12 | */
13 | object NetUtils {
14 |
15 | /**
16 | * 获取内网IP地址
17 | */
18 | val localIPAddress: String
19 | get() {
20 | val en: Enumeration = NetworkInterface.getNetworkInterfaces()
21 | while (en.hasMoreElements()) {
22 | val intf: NetworkInterface = en.nextElement()
23 | val enumIpAddr: Enumeration = intf.inetAddresses
24 | while (enumIpAddr.hasMoreElements()) {
25 | val inetAddress: InetAddress = enumIpAddr.nextElement()
26 | if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
27 | return inetAddress.hostAddress.toString()
28 | }
29 | }
30 | }
31 | return "null"
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/AssetsUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import android.content.Context
4 | import java.io.BufferedReader
5 | import java.io.InputStreamReader
6 |
7 | /**
8 | * Assets资源读取工具
9 | *
10 | * @author D10NG
11 | * @date on 2019-10-31 09:54
12 | */
13 | object AssetsUtils {
14 |
15 | /**
16 | * 读取成字符串
17 | * @param context
18 | * @param fileName
19 | * @return
20 | */
21 | fun getJsonString(context: Context, fileName: String): String {
22 | val stringBuilder = StringBuilder()
23 | //获取assets资源管理器
24 | val assetManager = context.assets
25 | //通过管理器打开文件并读取
26 | val bf = BufferedReader(
27 | InputStreamReader(
28 | assetManager.open(fileName)
29 | )
30 | )
31 | var line: String?
32 | do {
33 | line = bf.readLine()
34 | if (line != null) {
35 | stringBuilder.append(line)
36 | } else {
37 | break
38 | }
39 | } while (true)
40 | bf.close()
41 | return stringBuilder.toString()
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/HistoryInfo.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import android.widget.TextView
4 | import androidx.databinding.BindingAdapter
5 | import androidx.room.Entity
6 | import androidx.room.Index
7 | import androidx.room.PrimaryKey
8 | import com.dlong.jsonentitylib.BaseJsonEntity
9 | import com.dlong.jsonentitylib.annotation.DLField
10 | import com.dlong.networkdebugassistant.utils.DateUtils
11 |
12 | /**
13 | * 历史发送
14 | *
15 | * @author D10NG
16 | * @date on 2019-12-07 11:05
17 | */
18 | @Entity(tableName = "history_table", indices = [Index(value = ["text"], unique = true)])
19 | data class HistoryInfo(
20 |
21 | // ID,主键,自增长
22 | @PrimaryKey(autoGenerate = true)
23 | @DLField
24 | var hId : Long = 0,
25 |
26 | /** 发送文本 */
27 | @DLField
28 | var text: String = "",
29 |
30 | /** 发送时间 */
31 | @DLField
32 | var time: Long = 0L
33 | ) : BaseJsonEntity() {
34 |
35 | companion object{
36 | @JvmStatic
37 | @BindingAdapter("setTime")
38 | fun setTime(textView: TextView, time: Long) {
39 | textView.text = DateUtils.getDateStr(time, "yyyy-MM-dd hh:mm:ss")
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/model/HistoryModel.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.model
2 |
3 | import android.app.Application
4 | import androidx.lifecycle.AndroidViewModel
5 | import androidx.lifecycle.LiveData
6 | import androidx.lifecycle.viewModelScope
7 | import com.dlong.networkdebugassistant.bean.HistoryInfo
8 | import com.dlong.networkdebugassistant.db.HistoryDB
9 | import kotlinx.coroutines.launch
10 |
11 | /**
12 | * @author D10NG
13 | * @date on 2019-12-07 14:47
14 | */
15 | class HistoryModel(application: Application) : AndroidViewModel(application) {
16 |
17 | private val db = HistoryDB.getDatabase(application)
18 | private var allData: LiveData>
19 |
20 | init {
21 | allData = db.getHistoryDao().getAllData()
22 | }
23 |
24 | fun getAllData() = allData
25 |
26 | fun insertHistory(info : HistoryInfo) = viewModelScope.launch{
27 | db.getHistoryDao().insert(info)
28 | }
29 |
30 | fun updateHistory(info: HistoryInfo) = viewModelScope.launch {
31 | db.getHistoryDao().update(info)
32 | }
33 |
34 | fun deleteHistory(info: HistoryInfo) = viewModelScope.launch {
35 | db.getHistoryDao().delete(info)
36 | }
37 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/bean/BaseConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.bean
2 |
3 | import com.dlong.jsonentitylib.BaseJsonEntity
4 | import com.dlong.jsonentitylib.annotation.DLField
5 |
6 | /**
7 | * 基础配置信息
8 | *
9 | * @author D10NG
10 | * @date on 2019-12-09 14:59
11 | */
12 | open class BaseConfiguration constructor(
13 |
14 | /** 发送 是否以16进制格式 */
15 | @DLField
16 | var isSendHex: Boolean = false,
17 |
18 | /** 发送 是否自动添加最后一位校验位 */
19 | @DLField
20 | var isAutoAddHexCheck: Boolean = false,
21 |
22 | /** 发送 自动循环发送时间间隔 毫秒 */
23 | @DLField
24 | var autoSendTime: Long = 1000L,
25 |
26 | /** 接收 是否以16进制格式 */
27 | @DLField
28 | var isReceiveHex: Boolean = false,
29 |
30 | /** 接收 是否显示接收时间 */
31 | @DLField
32 | var isReceiveShowTime: Boolean = false,
33 |
34 | /** 接收 是否显示IP地址 */
35 | @DLField
36 | var isReceiveShowIpAddress: Boolean = false,
37 |
38 | /** 接收 是否显示端口 */
39 | @DLField
40 | var isReceiveShowPort: Boolean = false,
41 |
42 | /** 接收 是否自动保存到本地 */
43 | @DLField
44 | var isAutoSaveToLocal: Boolean = false,
45 |
46 | /** 接收 存储本地地址 */
47 | @DLField
48 | var receiveSaveLocalPath: String = "NULL"
49 | ) : BaseJsonEntity() {
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.databinding.DataBindingUtil
6 | import com.dlong.networkdebugassistant.R
7 | import com.dlong.networkdebugassistant.databinding.ActivityMainBinding
8 |
9 | class MainActivity : BaseActivity() {
10 |
11 | private lateinit var binding: ActivityMainBinding
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
16 |
17 | // 左上角点击事件
18 | setSupportActionBar(binding.toolbar)
19 | binding.toolbar.setNavigationOnClickListener { clearGoTo(SettingActivity::class.java) }
20 | }
21 |
22 | fun goAct(view: View) {
23 | when(view.id) {
24 | R.id.btn_udp -> clearGoTo(UdpBroadActivity::class.java)
25 | R.id.btn_udp_multi -> clearGoTo(UdpMultiActivity::class.java)
26 | R.id.btn_tcp_client -> clearGoTo(TcpClientActivity::class.java)
27 | R.id.btn_tcp_server -> clearGoTo(TcpServerActivity::class.java)
28 | R.id.btn_ping -> clearGoTo(PingActivity::class.java)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/db/HistoryDB.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.db
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 | import com.dlong.networkdebugassistant.bean.HistoryInfo
8 | import com.dlong.networkdebugassistant.db.dao.HistoryDao
9 |
10 | /**
11 | * @author D10NG
12 | * @date on 2019-12-07 11:12
13 | */
14 | @Database(entities = [HistoryInfo::class], version = 1, exportSchema = false)
15 | abstract class HistoryDB : RoomDatabase() {
16 |
17 | abstract fun getHistoryDao() : HistoryDao
18 |
19 | companion object {
20 |
21 | // 单例
22 | @Volatile
23 | private var INSTANCE : HistoryDB? = null
24 |
25 | @JvmStatic
26 | fun getDatabase(context : Context) : HistoryDB {
27 | val temp = INSTANCE
28 | if (null != temp) {
29 | return temp
30 | }
31 | synchronized(this) {
32 | val instance = Room.databaseBuilder(
33 | context.applicationContext,
34 | HistoryDB::class.java,
35 | "history_db"
36 | ).build()
37 | INSTANCE = instance
38 | return instance
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/UdpBroadActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import com.d10ng.net.assistant.UdpBroadThread
5 | import com.dlong.networkdebugassistant.R
6 | import com.dlong.networkdebugassistant.bean.UdpBroadConfiguration
7 | import com.dlong.networkdebugassistant.constant.DBConstant
8 |
9 | class UdpBroadActivity : BaseSendReceiveActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setTittle(resources.getString(R.string.main_udp_broad))
14 | binding.isShowSocketList = false
15 | }
16 |
17 | override fun initConfig() {
18 | super.initConfig()
19 | config = DBConstant.getInstance(this).getUdpBroadConfiguration()
20 | }
21 |
22 | override fun initThread() {
23 | super.initThread()
24 | val cc = config as UdpBroadConfiguration
25 | thread = UdpBroadThread( cc.localPort, netListener)
26 | }
27 |
28 | override fun openSetting() {
29 | super.openSetting()
30 | clearGoTo(UdpBroadSettingActivity::class.java)
31 | }
32 |
33 | override fun sendData(data: ByteArray) {
34 | super.sendData(data)
35 | val cc = config as UdpBroadConfiguration
36 | thread?.send(cc.targetIpAddress, cc.targetPort, data)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/TcpClientActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import com.d10ng.net.assistant.TcpClientThread
5 | import com.dlong.networkdebugassistant.R
6 | import com.dlong.networkdebugassistant.bean.TcpClientConfiguration
7 | import com.dlong.networkdebugassistant.constant.DBConstant
8 |
9 | /**
10 | * @author D10NG
11 | * @date on 2019-12-09 14:49
12 | */
13 | class TcpClientActivity : BaseSendReceiveActivity() {
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setTittle(resources.getString(R.string.main_tcp_client))
18 | binding.isShowSocketList = false
19 | }
20 |
21 | override fun initConfig() {
22 | super.initConfig()
23 | config = DBConstant.getInstance(this).getTcpClientConfiguration()
24 | }
25 |
26 | override fun initThread() {
27 | super.initThread()
28 | val cc = config as TcpClientConfiguration
29 | thread = TcpClientThread(cc.serverIpAddress, cc.serverPort, netListener)
30 | }
31 |
32 | override fun openSetting() {
33 | super.openSetting()
34 | clearGoTo(TcpClientSettingActivity::class.java)
35 | }
36 |
37 | override fun sendData(data: ByteArray) {
38 | super.sendData(data)
39 | thread?.send(data)
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/UdpMultiActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import com.d10ng.net.assistant.UdpMultiThread
5 | import com.dlong.networkdebugassistant.R
6 | import com.dlong.networkdebugassistant.bean.UdpMultiConfiguration
7 | import com.dlong.networkdebugassistant.constant.DBConstant
8 |
9 | /**
10 | * @author D10NG
11 | * @date on 2019-12-09 11:09
12 | */
13 | class UdpMultiActivity : BaseSendReceiveActivity() {
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setTittle(resources.getString(R.string.main_udp_multi))
18 | binding.isShowSocketList = false
19 | }
20 |
21 | override fun initConfig() {
22 | super.initConfig()
23 | config = DBConstant.getInstance(this).getUdpMultiConfiguration()
24 | }
25 |
26 | override fun initThread() {
27 | super.initThread()
28 | val cc = config as UdpMultiConfiguration
29 | thread = UdpMultiThread(cc.targetIpAddress, cc.localPort, netListener)
30 | }
31 |
32 | override fun openSetting() {
33 | super.openSetting()
34 | clearGoTo(UdpMultiSettingActivity::class.java)
35 | }
36 |
37 | override fun sendData(data: ByteArray) {
38 | super.sendData(data)
39 | val cc = config as UdpMultiConfiguration
40 | thread?.send(cc.targetIpAddress, cc.targetPort, data)
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/SettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.databinding.DataBindingUtil
6 | import com.dlong.dialog.*
7 | import com.dlong.networkdebugassistant.R
8 | import com.dlong.networkdebugassistant.databinding.ActivitySettingBinding
9 | import com.dlong.networkdebugassistant.utils.AppUtils
10 |
11 | class SettingActivity : BaseActivity() {
12 |
13 | private lateinit var binding: ActivitySettingBinding
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | binding = DataBindingUtil.setContentView(this, R.layout.activity_setting)
18 |
19 | // 设置返回按钮
20 | setSupportActionBar(binding.toolbar)
21 | binding.toolbar.setNavigationOnClickListener { finish() }
22 |
23 | // 显示版本号
24 | binding.txtVersion = AppUtils.getAppVersion(this)
25 | }
26 |
27 | /**
28 | * 显示版本信息弹窗
29 | */
30 | fun openVersionDialog(view: View) {
31 | val info = AppUtils.getVersionReadMe(this, binding.txtVersion?: "")
32 | VersionInfoDialog(this).create()
33 | .setTittle(resources.getString(R.string.setting_version))
34 | .setIcon(R.mipmap.ic_launcher_web)
35 | .addVersionInfo(info.version, info.time, info.author, info.content)
36 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME)
37 | .show()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/dl10netassistant/src/main/java/com/d10ng/net/assistant/NetUtils.kt:
--------------------------------------------------------------------------------
1 | package com.d10ng.net.assistant
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.catch
7 | import kotlinx.coroutines.flow.flow
8 | import kotlinx.coroutines.flow.flowOn
9 | import kotlinx.coroutines.launch
10 | import java.io.BufferedReader
11 | import java.io.InputStreamReader
12 | import kotlin.coroutines.resume
13 | import kotlin.coroutines.suspendCoroutine
14 |
15 | /**
16 | * PING
17 | * @param address String 地址
18 | * @param count Int 测试次数
19 | * @return Flow
20 | */
21 | fun ping(address: String, count: Int = 6): Flow {
22 | return flow {
23 | val process = Runtime.getRuntime().exec("ping -c $count $address")
24 | val inS = process.inputStream
25 | val reader = BufferedReader(InputStreamReader(inS))
26 | var line: String?
27 | do {
28 | line = reader.readLine()
29 | if (line != null) {
30 | emit("$line\n")
31 | } else {
32 | break
33 | }
34 | } while (true)
35 | }.flowOn(Dispatchers.IO)
36 | .catch { t: Throwable ->
37 | emit("catch error ${t.message}\n")
38 | }
39 | }
40 |
41 | /**
42 | * PING 一次
43 | * @param address String
44 | * @return Boolean
45 | */
46 | suspend fun pingOnce(address: String): Boolean {
47 | return suspendCoroutine { cont ->
48 | CoroutineScope(Dispatchers.IO).launch {
49 | try {
50 | val process = Runtime.getRuntime().exec("ping -c 1 $address")
51 | cont.resume(process.waitFor() == 0)
52 | } catch (e: Exception) {
53 | e.printStackTrace()
54 | cont.resume(false)
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/PingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.text.method.ScrollingMovementMethod
5 | import android.view.View
6 | import androidx.databinding.DataBindingUtil
7 | import com.d10ng.net.assistant.ping
8 | import com.d10ng.net.assistant.pingOnce
9 | import com.dlong.networkdebugassistant.R
10 | import com.dlong.networkdebugassistant.databinding.ActivityPingBinding
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.GlobalScope
13 | import kotlinx.coroutines.launch
14 | import kotlinx.coroutines.withContext
15 |
16 | /**
17 | * PING
18 | * @Author: D10NG
19 | * @Time: 2021/2/24 11:21 上午
20 | */
21 | class PingActivity : BaseActivity() {
22 |
23 | private lateinit var binding: ActivityPingBinding
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | binding = DataBindingUtil.setContentView(this, R.layout.activity_ping)
28 |
29 | // 接收文本滚动方式
30 | binding.txtReceive.movementMethod = ScrollingMovementMethod.getInstance()
31 |
32 | // 点击返回
33 | binding.toolbar.setNavigationOnClickListener { finish() }
34 | }
35 |
36 | fun sure(view: View) {
37 | val address = binding.edtAddress.text?.toString()
38 | if (address.isNullOrEmpty()) {
39 | return
40 | }
41 | GlobalScope.launch {
42 | ping(address).collect {
43 | withContext(Dispatchers.Main) {
44 | binding.txtReceive.append(it)
45 | }
46 | }
47 | val isSuccess = pingOnce(address)
48 | withContext(Dispatchers.Main) {
49 | showToast("PING测试结果=$isSuccess")
50 | }
51 | }
52 | }
53 |
54 | fun clean(view: View) {
55 | binding.txtReceive.text = ""
56 | binding.txtReceive.scrollTo(0, 0)
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
30 |
31 |
39 |
40 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/adapter/FolderAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.adapter
2 |
3 | import android.os.Handler
4 | import android.os.Message
5 | import android.view.LayoutInflater
6 | import android.view.ViewGroup
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.dlong.networkdebugassistant.R
10 | import com.dlong.networkdebugassistant.bean.FolderInfo
11 | import com.dlong.networkdebugassistant.databinding.ItemFileBinding
12 |
13 | /**
14 | * 文件夹列表适配器
15 | *
16 | * @author D10NG
17 | * @date on 2019-12-06 10:29
18 | */
19 | class FolderAdapter constructor(
20 | private val mHandler: Handler,
21 | private var mList: MutableList
22 | ) : RecyclerView.Adapter() {
23 |
24 | companion object{
25 | const val FOLDER_SELECT = 100
26 | const val FOLDER_EDIT = 101
27 | }
28 |
29 | fun update(list: MutableList) {
30 | this.mList.clear()
31 | this.mList.addAll(list)
32 | notifyDataSetChanged()
33 | }
34 |
35 | inner class ViewHolder constructor(
36 | val binding: ItemFileBinding
37 | ) : RecyclerView.ViewHolder(binding.root) {
38 |
39 | fun bind(info: FolderInfo) {
40 | binding.folderInfo = info
41 | binding.executePendingBindings()
42 | }
43 | }
44 |
45 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
46 | val binding: ItemFileBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context),
47 | R.layout.item_file, parent, false)
48 | return ViewHolder(binding)
49 | }
50 |
51 | override fun getItemCount(): Int = this.mList.size
52 |
53 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
54 | holder.bind(this.mList[position])
55 | holder.binding.llContent.setOnClickListener {
56 | val m = Message.obtain()
57 | m.what = FOLDER_SELECT
58 | m.obj = this.mList[position]
59 | mHandler.sendMessage(m)
60 | }
61 | holder.binding.llContent.setOnLongClickListener {
62 | val m = Message.obtain()
63 | m.what = FOLDER_EDIT
64 | m.obj = this.mList[position]
65 | mHandler.sendMessage(m)
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/AppUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import android.content.ClipData
4 | import android.content.ClipboardManager
5 | import android.content.Context
6 | import org.json.JSONArray
7 | import org.json.JSONObject
8 | import java.io.Serializable
9 |
10 | /**
11 | * @author D10NG
12 | * @date on 2019-11-27 11:32
13 | */
14 | object AppUtils {
15 |
16 | /**
17 | * 获取当前应用的版本号
18 | *
19 | * @return
20 | */
21 | fun getAppVersion(context: Context): String {
22 | // 获取packagemanager的实例
23 | val packageManager = context.packageManager
24 | // getPackageName()是你当前类的包名,0代表是获取版本信息
25 | val packInfo = packageManager.getPackageInfo(context.packageName, 0)
26 | return packInfo.versionName
27 | }
28 |
29 | /**
30 | * 版本信息实体
31 | */
32 | data class VersionReadMe(
33 | var version: String = "",
34 | var author: String = "",
35 | var time: String = "",
36 | var content: String = ""
37 | ) : Serializable {
38 |
39 | fun parseFromJson(json: JSONObject, version: String) {
40 | this.version = version
41 | this.author = json.optString("author")
42 | this.time = json.optString("time")
43 | val array = json.optJSONArray("content")?: JSONArray()
44 | val builder = StringBuilder()
45 | for (i in 0 until array.length()) {
46 | builder.append("${i + 1}、").append(array.getString(i)).append("\n")
47 | }
48 | this.content = builder.toString()
49 | }
50 | }
51 |
52 | /**
53 | * 获取版本信息
54 | */
55 | fun getVersionReadMe(context: Context, version: String) : VersionReadMe {
56 | val jsonObject = JSONObject(AssetsUtils.getJsonString(context, "readme.json"))
57 | val versionObj = jsonObject.optJSONObject(version)?: return VersionReadMe()
58 | val vrm = VersionReadMe()
59 | vrm.parseFromJson(versionObj, version)
60 | return vrm
61 | }
62 |
63 | /**
64 | * 复制字符串到系统剪贴板
65 | */
66 | fun copyToClipboard(context: Context, text: String) {
67 | val clip = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
68 | val data = ClipData.newPlainText("receive", text)
69 | clip.setPrimaryClip(data)
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
38 |
39 |
42 |
43 |
46 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
15 |
16 |
28 |
29 |
40 |
41 |
50 |
51 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/ByteUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import java.lang.StringBuilder
4 |
5 | /**
6 | * byte 转换工具
7 | *
8 | * @author D10NG
9 | * @date on 2019-11-23 16:52
10 | */
11 | object ByteUtils {
12 |
13 | /**
14 | * 检验校验和
15 | * @param data 数据,最后一个byte为校验和
16 | */
17 | fun checkEndNum(data: ByteArray) : Boolean {
18 | var num = (0).toByte()
19 | for (i in 0 until data.size -1) {
20 | num = (num + data[i]).toByte()
21 | }
22 | return num == data[data.size -1]
23 | }
24 |
25 | /**
26 | * 获取校验和
27 | * @param data 数据
28 | */
29 | fun getEndNum(data: ByteArray) : Byte {
30 | var num = (0).toByte()
31 | for (element in data) {
32 | num = (num + element).toByte()
33 | }
34 | return num
35 | }
36 |
37 | /**
38 | * 将 byte 转为 8位二进制字符串 "00110011"
39 | * @param byte
40 | */
41 | fun getBinFromByte(byte: Byte) : String {
42 | val str = Integer.toBinaryString(byte.toInt())
43 | return StringUtils.upToNString(str, 8)
44 | }
45 |
46 | /**
47 | * 将 boolean 数组 转换为 byte
48 | * @param bools
49 | */
50 | fun getByteFromBool(vararg bools: Boolean) : Byte {
51 | val builder = StringBuilder()
52 | for (b in bools.iterator()) {
53 | builder.append(if (b) "1" else "0")
54 | }
55 | return getByteFromBin(builder.toString())
56 | }
57 |
58 | /**
59 | * 将二进制字符串 "00110011" 转为 byte
60 | * @param bin
61 | */
62 | fun getByteFromBin(bin: String) : Byte {
63 | val value = Integer.valueOf(bin, 2)
64 | return value.toByte()
65 | }
66 |
67 | /**
68 | * 将两个字节的byte数组转换成有符号整型
69 | * @param byte1 高位
70 | * @param byte2 低位
71 | */
72 | fun convertSignInt(byte1: Byte, byte2: Byte): Int =
73 | (byte1.toInt() shl 8) or (byte2.toInt() and 0xFF)
74 |
75 | /**
76 | * 将两个字节的byte数组转换成无符号整型
77 | * @param byte1 高位
78 | * @param byte2 低位
79 | */
80 | fun convertUnSignInt(byte1: Byte, byte2: Byte): Int =
81 | (byte1.toInt() and 0xFF) shl 8 or (byte2.toInt() and 0xFF)
82 |
83 | /**
84 | * 获取整型数据的 高位 byte
85 | * @param value 整型数据
86 | */
87 | fun convertUnSignByteHeight(value: Int): Byte =
88 | value.ushr(8).toByte()
89 |
90 | /**
91 | * 获取整型数据的 低位 byte
92 | * @param value 整型数据
93 | */
94 | fun convertUnSignByteLow(value: Int): Byte =
95 | (value and 0xff).toByte()
96 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
18 |
19 |
28 |
29 |
34 |
35 |
44 |
45 |
49 |
50 |
57 |
58 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/dl10netassistant/src/main/java/com/d10ng/net/assistant/TcpClientThread.kt:
--------------------------------------------------------------------------------
1 | package com.d10ng.net.assistant
2 |
3 | import java.net.Socket
4 |
5 | /**
6 | * TCP客户端
7 | *
8 | * @author D10NG
9 | * @date on 2020/4/27 4:56 PM
10 | */
11 | class TcpClientThread constructor(
12 | // 地址
13 | private val mAddress: String,
14 | // 端口
15 | private val mPort: Int
16 | ) : BaseNetThread() {
17 |
18 | constructor(mAddress: String, mPort: Int, listener: OnNetThreadListener): this(mAddress, mPort) {
19 | super.setThreadListener(listener)
20 | }
21 |
22 | constructor(mAddress: String, mPort: Int, listener: NetThreadListener.() -> Unit): this(mAddress, mPort) {
23 | super.setThreadListener(listener)
24 | }
25 |
26 | private var socket: Socket? = null
27 |
28 | override fun run() {
29 | super.run()
30 | try {
31 | // 连接服务器
32 | socket = Socket(mAddress, mPort).apply {
33 | reuseAddress = true
34 | }
35 | } catch (e: Exception) {
36 | e.printStackTrace()
37 | // 连接失败
38 | listener?.onConnectFailed(mAddress)
39 | listenerLambda?.onConnectFailed(mAddress)
40 | return
41 | }
42 | // 连接成功
43 | listener?.onConnected(mAddress)
44 | listenerLambda?.onConnected(mAddress)
45 |
46 | // 获取输入流
47 | val inputStream = socket?.getInputStream()
48 | while (isConnected()){
49 | val buffer = ByteArray(1024)
50 | val len =
51 | try {
52 | inputStream?.read(buffer)?: 0
53 | } catch (e: Exception) {
54 | -1
55 | }
56 | if (len == -1) {
57 | break
58 | }
59 | if (len > 0) {
60 | // 接收到数据
61 | listener?.onReceive(mAddress, mPort, curTime, buffer.copyOfRange(0, len))
62 | listenerLambda?.onReceive(mAddress, mPort, curTime, buffer.copyOfRange(0, len))
63 | }
64 | }
65 | // 已断开连接
66 | listener?.onDisconnect(mAddress)
67 | listenerLambda?.onDisconnect(mAddress)
68 | }
69 |
70 | override fun isConnected(): Boolean {
71 | return socket?.isConnected == true
72 | }
73 |
74 | override fun send(data: ByteArray) {
75 | super.send(data)
76 | if (!isConnected()) return
77 | Thread {
78 | try {
79 | socket?.getOutputStream()?.write(data)
80 | socket?.getOutputStream()?.flush()
81 | } catch (e: Exception) {
82 | e.printStackTrace()
83 | listener?.onError(mAddress, e.toString())
84 | listenerLambda?.onError(mAddress, e.toString())
85 | }
86 | }.start()
87 | }
88 |
89 | override fun close() {
90 | socket?.close()
91 | super.close()
92 | }
93 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/StringUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import java.util.regex.Pattern
4 |
5 | /**
6 | * @author D10NG
7 | * @date on 2019-11-22 12:27
8 | */
9 | object StringUtils {
10 |
11 | /**
12 | * 由于Java是基于Unicode编码的,因此,一个汉字的长度为1,而不是2。
13 | * 但有时需要以字节单位获得字符串的长度。例如,“123abc长城”按字节长度计算是10,而按Unicode计算长度是8。
14 | * 为了获得10,需要从头扫描根据字符的Ascii来获得具体的长度。如果是标准的字符,Ascii的范围是0至255,如果是汉字或其他全角字符,Ascii会大于255。
15 | * 因此,可以编写如下的方法来获得以字节为单位的字符串长度。
16 | */
17 | fun getWordCount(s: String): Int {
18 | var length = 0
19 | for (i in s.indices) {
20 | val ascii = Character.codePointAt(s, i)
21 | if (ascii in 0..255)
22 | length++
23 | else
24 | length += 2
25 |
26 | }
27 | return length
28 | }
29 |
30 | /**
31 | * 补全length位,不够的在前面加0
32 | * @param str
33 | * @return
34 | */
35 | fun upToNString(str: String, length: Int): String {
36 | var result = StringBuilder()
37 | if (str.length < length) {
38 | for (i in 0 until length - str.length) {
39 | result.append("0")
40 | }
41 | result.append(str)
42 | } else {
43 | result = StringBuilder(str)
44 | }
45 | return result.toString().substring(result.length - length)
46 | }
47 |
48 | /**
49 | * 补全length位,不够的在后面加0
50 | * @param str
51 | * @return
52 | */
53 | fun upToNStringInBack(str: String, length: Int): String {
54 | var result = StringBuilder()
55 | if (str.length < length) {
56 | result.append(str)
57 | for (i in 0 until length - str.length) {
58 | result.append("0")
59 | }
60 | } else {
61 | result = StringBuilder(str)
62 | }
63 | return result.toString()
64 | }
65 |
66 | /**
67 | * 将输入的16进制文本转换成byte数组
68 | */
69 | fun getByteFromHex(text: String) : ByteArray {
70 | // 使用正则截取16进制相关数字和字母
71 | val reg = "[^a-fA-F0-9]"
72 | val pat = Pattern.compile(reg)
73 | val mat = pat.matcher(text)
74 | var value = mat.replaceAll("").trim()
75 | if (value.length % 2 != 0) {
76 | value = "${value}0"
77 | }
78 | val chars = value.toCharArray()
79 | val list = mutableListOf()
80 | for (i in chars.indices step 2) {
81 | list.add(Integer.parseInt("${chars[i]}${chars[i + 1]}", 16).toByte())
82 | }
83 | return list.toByteArray()
84 | }
85 |
86 | /**
87 | * 判断文本是否为纯数字
88 | */
89 | fun isNumeric(text: String) : Boolean {
90 | val chars = text.toCharArray()
91 | for (char in chars.iterator()) {
92 | if (char.toInt() < 48 || char.toInt() > 57) {
93 | return false
94 | }
95 | }
96 | return true
97 | }
98 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/adapter/HistoryAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.adapter
2 |
3 | import android.os.Handler
4 | import android.os.Message
5 | import android.text.TextUtils
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.databinding.DataBindingUtil
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.dlong.networkdebugassistant.R
12 | import com.dlong.networkdebugassistant.bean.HistoryInfo
13 | import com.dlong.networkdebugassistant.databinding.ItemHistoryBinding
14 | /**
15 | * @author D10NG
16 | * @date on 2019-12-07 14:53
17 | */
18 | class HistoryAdapter constructor(
19 | private val mHandler: Handler,
20 | private var mList: List
21 | ) : RecyclerView.Adapter() {
22 |
23 | companion object{
24 | const val CLICK_COPY = 1
25 | const val LONG_CLICK = 2
26 | }
27 |
28 | fun update(list: List) {
29 | this.mList = list
30 | notifyDataSetChanged()
31 | }
32 |
33 | inner class ViewHolder constructor(
34 | val binding: ItemHistoryBinding
35 | ) : RecyclerView.ViewHolder(binding.root) {
36 |
37 | fun bind(info: HistoryInfo) {
38 | binding.historyInfo = info
39 | binding.executePendingBindings()
40 | }
41 | }
42 |
43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
44 | val binding: ItemHistoryBinding = DataBindingUtil.inflate(
45 | LayoutInflater.from(parent.context), R.layout.item_history, parent, false)
46 | return ViewHolder(binding)
47 | }
48 |
49 | override fun getItemCount(): Int = this.mList.size
50 |
51 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
52 | holder.bind(this.mList[position])
53 | holder.binding.btnCopy.setOnClickListener {
54 | val m = Message.obtain()
55 | m.what = CLICK_COPY
56 | m.obj = this.mList[position]
57 | mHandler.sendMessage(m)
58 | }
59 | holder.binding.txtContent.setOnClickListener(object : View.OnClickListener{
60 | private var isShowAll = false
61 | override fun onClick(p0: View?) {
62 | isShowAll = !isShowAll
63 | if (isShowAll) {
64 | holder.binding.txtContent.isSingleLine = false
65 | holder.binding.txtContent.ellipsize = null
66 | } else {
67 | holder.binding.txtContent.isSingleLine = true
68 | holder.binding.txtContent.ellipsize = TextUtils.TruncateAt.END
69 | }
70 | }
71 | })
72 | holder.binding.txtContent.setOnLongClickListener {
73 | val m = Message.obtain()
74 | m.what = LONG_CLICK
75 | m.obj = this.mList[position]
76 | mHandler.sendMessage(m)
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/TcpServerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.widget.ArrayAdapter
5 | import com.d10ng.net.assistant.TcpServerThread
6 | import com.dlong.networkdebugassistant.R
7 | import com.dlong.networkdebugassistant.bean.TcpServerConfiguration
8 | import com.dlong.networkdebugassistant.constant.DBConstant
9 |
10 | /**
11 | * @author D10NG
12 | * @date on 2019-12-10 11:21
13 | */
14 | class TcpServerActivity : BaseSendReceiveActivity() {
15 |
16 | private val socketList: MutableList = mutableListOf()
17 | private lateinit var socketAdapter: ArrayAdapter
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | setTittle(resources.getString(R.string.main_tcp_server))
22 | binding.isShowSocketList = true
23 |
24 | socketAdapter = ArrayAdapter(this, R.layout.item_socket_spinner, socketList)
25 | socketAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
26 | binding.spSocket.adapter = socketAdapter
27 | }
28 |
29 | override fun initConfig() {
30 | super.initConfig()
31 | config = DBConstant.getInstance(this).getTcpServerConfiguration()
32 | }
33 |
34 | override fun initThread() {
35 | super.initThread()
36 | val cc = config as TcpServerConfiguration
37 | thread = TcpServerThread(cc.localPort, netListener)
38 | }
39 |
40 | override fun openSetting() {
41 | super.openSetting()
42 | clearGoTo(TcpServerSettingActivity::class.java)
43 | }
44 |
45 | override fun updateSocketList() {
46 | super.updateSocketList()
47 | if (thread == null) return
48 | val oldSelect = (binding.spSocket.selectedItem as String?)?: ""
49 | val tt = thread as TcpServerThread
50 | val list = tt.getSocketNameList()
51 | socketList.clear()
52 | socketList.addAll(list)
53 | socketAdapter.notifyDataSetChanged()
54 | val pos = 0.coerceAtLeast(socketList.indexOf(oldSelect))
55 | binding.spSocket.setSelection(pos)
56 | }
57 |
58 | override fun disconnectSocket() {
59 | super.disconnectSocket()
60 | val name = (binding.spSocket.selectedItem as String?)?: ""
61 | val tt = thread as TcpServerThread
62 | val socket = tt.getSocketByName(name)?: return
63 | tt.disconnectSocket(socket)
64 | }
65 |
66 | override fun sendData(data: ByteArray) {
67 | super.sendData(data)
68 | val all = TcpServerThread.ALL_CLIENT_NAME
69 | val selectSocket = (binding.spSocket.selectedItem as String?)?: all
70 | if (selectSocket == all) {
71 | thread?.send(data)
72 | } else {
73 | val params = selectSocket.split(":")
74 | if (params.size != 2) return
75 | thread?.send(params[0], params[1].toInt(), data)
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/dl10netassistant/src/main/java/com/d10ng/net/assistant/UdpBroadThread.kt:
--------------------------------------------------------------------------------
1 | package com.d10ng.net.assistant
2 |
3 | import java.net.DatagramPacket
4 | import java.net.DatagramSocket
5 | import java.net.InetAddress
6 | import java.net.InetSocketAddress
7 |
8 | /**
9 | * udp 广播线程
10 | *
11 | * @author D10NG
12 | * @date on 2019-12-06 14:59
13 | */
14 | class UdpBroadThread constructor(
15 | // 端口
16 | private val mPort: Int
17 | ) : BaseNetThread() {
18 |
19 | constructor(mPort: Int, listener: OnNetThreadListener): this(mPort) {
20 | super.setThreadListener(listener)
21 | }
22 |
23 | constructor(mPort: Int, listener: NetThreadListener.() -> Unit): this(mPort) {
24 | super.setThreadListener(listener)
25 | }
26 |
27 | private var dgSocket: DatagramSocket? = null
28 | /** 运行标记位 */
29 | private var isRun = false
30 |
31 | override fun run() {
32 | super.run()
33 |
34 | try {
35 | // 启动端口
36 | dgSocket = DatagramSocket(null).apply {
37 | reuseAddress = true
38 | bind(InetSocketAddress(mPort))
39 | }
40 | } catch (e: Exception) {
41 | e.printStackTrace()
42 | // 启动失败
43 | listener?.onConnectFailed("")
44 | listenerLambda?.onConnectFailed("")
45 | return
46 | }
47 | // 启动成功
48 | listener?.onConnected("")
49 | listenerLambda?.onConnected("")
50 |
51 | isRun = true
52 | var packet: DatagramPacket
53 | val by = ByteArray(1024)
54 | while (isRun) {
55 | println("test, ${dgSocket?.isConnected}")
56 | // 循环等待接收
57 | packet = DatagramPacket(by, by.size)
58 | dgSocket?.receive(packet)
59 | // 拿到广播地址
60 | val address = packet.address.toString().replace("/", "")
61 | // 拿到数据
62 | val data = packet.data.copyOfRange(0, packet.length)
63 |
64 | listener?.onReceive(address, packet.port, curTime, data)
65 | listenerLambda?.onReceive(address, packet.port, curTime, data)
66 | }
67 | // 关闭
68 | dgSocket?.disconnect()
69 | dgSocket?.close()
70 | listener?.onDisconnect("")
71 | listenerLambda?.onDisconnect("")
72 | }
73 |
74 | override fun isConnected(): Boolean {
75 | return isRun
76 | }
77 |
78 | override fun send(address: String, toPort: Int, data: ByteArray) {
79 | super.send(address, toPort, data)
80 | Thread {
81 | try {
82 | val packet = DatagramPacket(data, data.size, InetAddress.getByName(address), toPort)
83 | dgSocket?.send(packet)
84 | } catch (e: Exception) {
85 | e.printStackTrace()
86 | listener?.onError(address, e.toString())
87 | listenerLambda?.onError(address, e.toString())
88 | }
89 | }.start()
90 | }
91 |
92 | override fun close() {
93 | isRun = false
94 | super.close()
95 | }
96 | }
--------------------------------------------------------------------------------
/dl10netassistant/src/main/java/com/d10ng/net/assistant/UdpMultiThread.kt:
--------------------------------------------------------------------------------
1 | package com.d10ng.net.assistant
2 |
3 | import java.net.DatagramPacket
4 | import java.net.InetAddress
5 | import java.net.MulticastSocket
6 |
7 | /**
8 | * udp 组播线程
9 | *
10 | * @author D10NG
11 | * @date on 2019-12-09 10:24
12 | */
13 | class UdpMultiThread constructor(
14 | // 组播地址
15 | private val multiAddress: String,
16 | // 端口
17 | private val mPort: Int
18 | ) : BaseNetThread() {
19 |
20 | constructor(multiAddress: String, mPort: Int, listener: OnNetThreadListener): this(multiAddress, mPort) {
21 | super.setThreadListener(listener)
22 | }
23 |
24 | constructor(multiAddress: String, mPort: Int, listener: NetThreadListener.() -> Unit): this(multiAddress, mPort) {
25 | super.setThreadListener(listener)
26 | }
27 |
28 | private var mcSocket: MulticastSocket? = null
29 | /** 运行标记位 */
30 | private var isRun = false
31 |
32 | override fun run() {
33 | super.run()
34 |
35 | try {
36 | // 启动端口
37 | mcSocket = MulticastSocket(mPort)
38 | } catch (e: Exception) {
39 | // 启动失败
40 | e.printStackTrace()
41 | listener?.onConnectFailed("")
42 | listenerLambda?.onConnectFailed("")
43 | return
44 | }
45 | // 启动成功
46 | listener?.onConnected("")
47 | listenerLambda?.onConnected("")
48 | // 加入组
49 | val group = InetAddress.getByName(multiAddress)
50 | mcSocket?.joinGroup(group)
51 | mcSocket?.timeToLive = 5
52 | mcSocket?.loopbackMode = false
53 |
54 | isRun = true
55 | var packet: DatagramPacket
56 | val by = ByteArray(1024)
57 | while (isRun) {
58 | // 循环等待接收
59 | packet = DatagramPacket(by, by.size)
60 | mcSocket?.receive(packet)
61 |
62 | // 拿到广播地址
63 | val address = packet.address.toString().replace("/", "")
64 | // 拿到数据
65 | val data = packet.data.copyOfRange(0, packet.length)
66 |
67 | listener?.onReceive(address, packet.port, curTime, data)
68 | listenerLambda?.onReceive(address, packet.port, curTime, data)
69 | }
70 | // 关闭
71 | mcSocket?.leaveGroup(group)
72 | mcSocket?.disconnect()
73 | mcSocket?.close()
74 | listener?.onDisconnect("")
75 | listenerLambda?.onDisconnect("")
76 | }
77 | override fun send(address: String, toPort: Int, data: ByteArray) {
78 | super.send(address, toPort, data)
79 | Thread {
80 | try {
81 | val packet = DatagramPacket(data, data.size, InetAddress.getByName(address), toPort)
82 | mcSocket?.send(packet)
83 | } catch (e: Exception) {
84 | e.printStackTrace()
85 | listener?.onError(address, e.toString())
86 | listenerLambda?.onError(address, e.toString())
87 | }
88 | }.start()
89 | }
90 |
91 | override fun isConnected(): Boolean {
92 | return isRun
93 | }
94 |
95 | override fun close() {
96 | isRun = false
97 | }
98 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/TcpServerSettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.text.InputType
5 | import android.view.View
6 | import androidx.databinding.DataBindingUtil
7 | import com.dlong.dialog.*
8 | import com.dlong.networkdebugassistant.R
9 | import com.dlong.networkdebugassistant.bean.TcpServerConfiguration
10 | import com.dlong.networkdebugassistant.constant.DBConstant
11 | import com.dlong.networkdebugassistant.databinding.ActivityTcpServerSettingBinding
12 |
13 | class TcpServerSettingActivity : BaseSettingActivity() {
14 |
15 | private lateinit var binding: ActivityTcpServerSettingBinding
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | binding = DataBindingUtil.setContentView(this, R.layout.activity_tcp_server_setting)
20 |
21 | // 设置返回按钮
22 | setSupportActionBar(binding.toolbar)
23 | binding.toolbar.setNavigationOnClickListener { finish() }
24 |
25 | initContentBinding(binding.contentBase)
26 | initConfig()
27 | }
28 |
29 | override fun initConfig() {
30 | super.initConfig()
31 | // 初始化配置信息
32 | binding.contentBase.config = DBConstant.getInstance(this).getTcpServerConfiguration()
33 | updateConfigShow()
34 | }
35 |
36 | override fun updateConfigShow() {
37 | binding.contentBase.config = binding.contentBase.config?: TcpServerConfiguration()
38 | val cc = binding.contentBase.config as TcpServerConfiguration
39 | binding.localPort = "${cc.localPort}"
40 | // 保存配置信息
41 | DBConstant.getInstance(this).setTcpServerConfiguration(cc)
42 | }
43 |
44 | fun setLocalPort(view: View) {
45 | val cc = binding.contentBase.config as TcpServerConfiguration
46 | val tag = "port"
47 | EditDialog(this).create()
48 | .setTittle(resources.getString(R.string.prompt))
49 | .setMsg(resources.getString(R.string.local_port))
50 | .addEdit(tag, "${cc.localPort}", resources.getString(R.string.please_input_port))
51 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
52 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
53 | onClick { dialog, _ ->
54 | val value = dialog.getInputText(tag)
55 | if (value.isEmpty()) {
56 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_empty))
57 | } else {
58 | val num = value.toIntOrNull()?: -1
59 | if (num in 0x0000..0xffff) {
60 | cc.localPort = num
61 | updateConfigShow()
62 | dialog.dismiss()
63 | } else {
64 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_over_range))
65 | }
66 | }
67 | }
68 | }
69 | .addAction(resources.getString(R.string.cancel))
70 | .show()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/HistoryActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.os.Message
5 | import androidx.databinding.DataBindingUtil
6 | import androidx.lifecycle.Observer
7 | import androidx.lifecycle.ViewModelProvider
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import com.dlong.dialog.*
10 | import com.dlong.networkdebugassistant.R
11 | import com.dlong.networkdebugassistant.adapter.HistoryAdapter
12 | import com.dlong.networkdebugassistant.bean.HistoryInfo
13 | import com.dlong.networkdebugassistant.databinding.ActivityHistoryBinding
14 | import com.dlong.networkdebugassistant.model.HistoryModel
15 | import com.dlong.networkdebugassistant.utils.AppUtils
16 |
17 | class HistoryActivity : BaseActivity() {
18 |
19 | private lateinit var binding: ActivityHistoryBinding
20 | private lateinit var viewModel: HistoryModel
21 | private lateinit var historyAdapter: HistoryAdapter
22 |
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | binding = DataBindingUtil.setContentView(this, R.layout.activity_history)
26 | viewModel = ViewModelProvider(this).get(HistoryModel::class.java)
27 | historyAdapter = HistoryAdapter(mHandler, emptyList())
28 |
29 | // 设置返回按钮
30 | setSupportActionBar(binding.toolbar)
31 | binding.toolbar.setNavigationOnClickListener { finish() }
32 |
33 | // 初始化列表
34 | binding.rcv.layoutManager = LinearLayoutManager(this)
35 | binding.rcv.adapter = historyAdapter
36 |
37 | // 绑定数据
38 | viewModel.getAllData().observe(this, Observer { list ->
39 | list?.let {
40 | historyAdapter.update(it)
41 | }
42 | })
43 | }
44 |
45 | override fun callBack(msg: Message) {
46 | super.callBack(msg)
47 | when(msg.what) {
48 | HistoryAdapter.CLICK_COPY -> {
49 | val info = msg.obj as HistoryInfo
50 | AppUtils.copyToClipboard(this, info.text)
51 | showToast(resources.getString(R.string.copy_to_clip_success))
52 | }
53 | HistoryAdapter.LONG_CLICK -> {
54 | val info = msg.obj as HistoryInfo
55 | var tips = resources.getString(R.string.history_edit_tips)
56 | val length = 10
57 | tips = tips.replace("**", info.text.substring(0, length.coerceAtMost(info.text.length)))
58 | if (info.text.length > length) tips = "$tips..."
59 | ButtonDialog(this).create()
60 | .setTittle(resources.getString(R.string.prompt))
61 | .setMsg(tips)
62 | .addAction(resources.getString(R.string.delete), ButtonStyle.ERROR) {
63 | onClick { dialog, _ ->
64 | viewModel.deleteHistory(info)
65 | dialog.dismiss()
66 | }
67 | }
68 | .addAction(resources.getString(R.string.cancel))
69 | .show()
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/DateUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils
2 |
3 | import android.annotation.SuppressLint
4 |
5 | import java.text.SimpleDateFormat
6 | import java.util.Calendar
7 | import java.util.Date
8 |
9 | /**
10 | * 时间工具
11 | *
12 | * @author D10NG
13 | * @date on 2019-10-08 11:28
14 | */
15 | object DateUtils {
16 |
17 | /**
18 | * 获取系统时间戳
19 | * @return
20 | */
21 | val curTime: Long
22 | get() = System.currentTimeMillis()
23 |
24 | val curYear: Int
25 | get() = Integer.parseInt(getCurDateStr("yyyy"))
26 |
27 | val curMonth: Int
28 | get() = Integer.parseInt(getCurDateStr("MM"))
29 |
30 | val curDay: Int
31 | get() = Integer.parseInt(getCurDateStr("dd"))
32 |
33 | /**
34 | * 时间戳转换成字符窜
35 | * @param milSecond
36 | * @param pattern
37 | * @return
38 | */
39 | fun getDateStr(milSecond: Long, pattern: String): String {
40 | val date = Date(milSecond)
41 | @SuppressLint("SimpleDateFormat")
42 | val format = SimpleDateFormat(pattern)
43 | return format.format(date)
44 | }
45 |
46 | /**
47 | * 获取当前时间字符串
48 | * @param pattern
49 | * @return
50 | */
51 | fun getCurDateStr(pattern: String): String {
52 | return getDateStr(curTime, pattern)
53 | }
54 |
55 | /**
56 | * 将字符串转为时间戳
57 | * @param dateString
58 | * @param pattern
59 | * @return
60 | */
61 | fun getDateFromStr(dateString: String, pattern: String): Long {
62 | @SuppressLint("SimpleDateFormat")
63 | val dateFormat = SimpleDateFormat(pattern)
64 | var date: Date? = Date()
65 | try {
66 | date = dateFormat.parse(dateString)
67 | } catch (e: Exception) {
68 | e.printStackTrace()
69 | }
70 |
71 | return date?.time ?: 0
72 | }
73 |
74 | /**
75 | * 根据年月日获取时间戳
76 | */
77 | fun getDateFromYMD(year: Int, month: Int, day: Int) : Long {
78 | val builder = StringBuilder()
79 | builder.append(StringUtils.upToNString(year.toString(), 4)).append("-")
80 | builder.append(StringUtils.upToNString(month.toString(), 2)).append("-")
81 | builder.append(StringUtils.upToNString(day.toString(), 2))
82 | return getDateFromStr(builder.toString(), "yyyy-MM-dd")
83 | }
84 |
85 | /**
86 | * 获取指定月份的天数
87 | * @param year
88 | * @param month
89 | * @return
90 | */
91 | fun getDaysOfMonth(year: Int, month: Int): Int {
92 | val calendar = Calendar.getInstance()
93 | try {
94 | val sdf = SimpleDateFormat("yyyy-MM-dd")
95 | calendar.time = sdf.parse(
96 | StringUtils.upToNString(year.toString() + "", 4) + "-" +
97 | StringUtils.upToNString(month.toString() + "", 2) + "-01"
98 | )!!
99 | } catch (e: Exception) {
100 | e.printStackTrace()
101 | }
102 |
103 | return calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
104 | }
105 |
106 | fun getDateYear(time: Long): Int {
107 | return Integer.parseInt(getDateStr(time, "yyyy"))
108 | }
109 |
110 | fun getDateMonth(time: Long): Int {
111 | return Integer.parseInt(getDateStr(time, "MM"))
112 | }
113 |
114 | fun getDateDay(time: Long): Int {
115 | return Integer.parseInt(getDateStr(time, "dd"))
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/constant/DBConstant.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.constant
2 |
3 | import android.content.Context
4 | import com.dlong.networkdebugassistant.bean.TcpClientConfiguration
5 | import com.dlong.networkdebugassistant.bean.TcpServerConfiguration
6 | import com.dlong.networkdebugassistant.bean.UdpBroadConfiguration
7 | import com.dlong.networkdebugassistant.bean.UdpMultiConfiguration
8 | import com.dlong.networkdebugassistant.utils.SpfUtils
9 | import org.json.JSONObject
10 |
11 | /**
12 | * 配置参数
13 | *
14 | * @author D10NG
15 | * @date on 2019-12-05 14:49
16 | */
17 | class DBConstant constructor(context: Context) {
18 |
19 | private val mSpf = SpfUtils.getInstance(context).getSpf()
20 |
21 | companion object{
22 | @Volatile
23 | private var INSTANCE: DBConstant? = null
24 |
25 | @JvmStatic
26 | fun getInstance(context: Context): DBConstant =
27 | INSTANCE?: synchronized(this) {
28 | INSTANCE?: DBConstant(context).also {
29 | INSTANCE = it
30 | }
31 | }
32 |
33 | /** udp 广播 */
34 | private const val SPF_UDP_BROAD = "udp_broad_constant"
35 | /** udp 组播 */
36 | private const val SPF_UDP_MULTI = "udp_multi_constant"
37 | /** tcp 客户端 */
38 | private const val SPF_TCP_CLIENT = "tcp_client_constant"
39 | /** tcp 服务器 */
40 | private const val SPF_TCP_SERVER = "tcp_server_constant"
41 | }
42 |
43 | fun getUdpBroadConfiguration() : UdpBroadConfiguration {
44 | val constant = mSpf.getString(SPF_UDP_BROAD, "")?: ""
45 | if (constant.isEmpty()) return UdpBroadConfiguration()
46 | val configuration = UdpBroadConfiguration()
47 | configuration.setFromJson(JSONObject(constant))
48 | return configuration
49 | }
50 |
51 | fun setUdpBroadConfiguration(configuration: UdpBroadConfiguration) {
52 | val constant = configuration.toJson().toString()
53 | mSpf.edit().putString(SPF_UDP_BROAD, constant).apply()
54 | }
55 |
56 | fun getUdpMultiConfiguration() : UdpMultiConfiguration {
57 | val constant = mSpf.getString(SPF_UDP_MULTI, "")?: ""
58 | if (constant.isEmpty()) return UdpMultiConfiguration()
59 | val configuration = UdpMultiConfiguration()
60 | configuration.setFromJson(JSONObject(constant))
61 | return configuration
62 | }
63 |
64 | fun setUdpMultiConfiguration(configuration: UdpMultiConfiguration) {
65 | val constant = configuration.toJson().toString()
66 | mSpf.edit().putString(SPF_UDP_MULTI, constant).apply()
67 | }
68 |
69 | fun getTcpClientConfiguration() : TcpClientConfiguration {
70 | val constant = mSpf.getString(SPF_TCP_CLIENT, "")?: ""
71 | if (constant.isEmpty()) return TcpClientConfiguration()
72 | val configuration = TcpClientConfiguration()
73 | configuration.setFromJson(JSONObject(constant))
74 | return configuration
75 | }
76 |
77 | fun setTcpClientConfiguration(configuration: TcpClientConfiguration) {
78 | val constant = configuration.toJson().toString()
79 | mSpf.edit().putString(SPF_TCP_CLIENT, constant).apply()
80 | }
81 |
82 | fun getTcpServerConfiguration() : TcpServerConfiguration {
83 | val constant = mSpf.getString(SPF_TCP_SERVER, "")?: ""
84 | if (constant.isEmpty()) return TcpServerConfiguration()
85 | val configuration = TcpServerConfiguration()
86 | configuration.setFromJson(JSONObject(constant))
87 | return configuration
88 | }
89 |
90 | fun setTcpServerConfiguration(configuration: TcpServerConfiguration) {
91 | val constant = configuration.toJson().toString()
92 | mSpf.edit().putString(SPF_TCP_SERVER, constant).apply()
93 | }
94 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
12 |
17 |
18 |
31 |
32 |
40 |
41 |
47 |
48 |
54 |
55 |
59 |
60 |
68 |
69 |
75 |
76 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/dl10netassistant/src/main/java/com/d10ng/net/assistant/BaseNetThread.kt:
--------------------------------------------------------------------------------
1 | package com.d10ng.net.assistant
2 |
3 | /**
4 | * 基础线程工具
5 | *
6 | * @author D10NG
7 | * @date on 2020/4/27 4:41 PM
8 | */
9 | open class BaseNetThread constructor() : Thread() {
10 |
11 | constructor(listener: NetThreadListener.() -> Unit): this() {
12 | this.listenerLambda = NetThreadListener()
13 | this.listenerLambda?.listener()
14 | }
15 |
16 | constructor(listener: OnNetThreadListener): this() {
17 | this.listener = listener
18 | }
19 |
20 | protected var listener: OnNetThreadListener? = null
21 | protected var listenerLambda: NetThreadListener? = null
22 |
23 | open fun setThreadListener(listener: OnNetThreadListener) {
24 | this.listener = listener
25 | }
26 |
27 | open fun setThreadListener(listener: NetThreadListener.() -> Unit) {
28 | this.listenerLambda = NetThreadListener()
29 | this.listenerLambda?.listener()
30 | }
31 |
32 | /**
33 | * 获取系统时间戳
34 | * @return
35 | */
36 | val curTime: Long
37 | get() = System.currentTimeMillis()
38 |
39 | /** 是否连接 */
40 | open fun isConnected() : Boolean = false
41 |
42 | /** 发送数据 */
43 | open fun send(data: ByteArray) {}
44 |
45 | /** 给指定地址的指定端口发送数据 */
46 | open fun send(address: String, toPort: Int, data: ByteArray) {}
47 |
48 | /** 关闭线程 */
49 | open fun close() {}
50 | }
51 |
52 | /** 通讯线程监听器 */
53 | interface OnNetThreadListener {
54 |
55 | /** 链接成功 */
56 | fun onConnected(ipAddress: String)
57 |
58 | /** 链接失败 */
59 | fun onConnectFailed(ipAddress: String)
60 |
61 | /** 断开链接 */
62 | fun onDisconnect(ipAddress: String)
63 |
64 | /** 出错 */
65 | fun onError(ipAddress: String, error: String)
66 |
67 | /** 接收到客户端链接 */
68 | fun onAcceptSocket(ipAddress: String)
69 |
70 | /** 接收到数据 */
71 | fun onReceive(ipAddress: String, port: Int, time: Long, data: ByteArray)
72 | }
73 |
74 | class NetThreadListener: OnNetThreadListener {
75 | private lateinit var listener1: (ipAddress: String) -> Unit
76 | fun onThreadConnected(listener: (ipAddress: String) -> Unit) {
77 | this.listener1 = listener
78 | }
79 | override fun onConnected(ipAddress: String) {
80 | this.listener1.invoke(ipAddress)
81 | }
82 |
83 | private lateinit var listener2: (ipAddress: String) -> Unit
84 | fun onThreadConnectFailed(listener: (ipAddress: String) -> Unit) {
85 | this.listener2 = listener
86 | }
87 | override fun onConnectFailed(ipAddress: String) {
88 | this.listener2.invoke(ipAddress)
89 | }
90 |
91 | private lateinit var listener3: (ipAddress: String) -> Unit
92 | fun onThreadDisconnect(listener: (ipAddress: String) -> Unit) {
93 | this.listener3 = listener
94 | }
95 | override fun onDisconnect(ipAddress: String) {
96 | this.listener3.invoke(ipAddress)
97 | }
98 |
99 | private lateinit var listener4: (ipAddress: String, error: String) -> Unit
100 | fun onThreadError(listener: (ipAddress: String, error: String) -> Unit) {
101 | this.listener4 = listener
102 | }
103 | override fun onError(ipAddress: String, error: String) {
104 | this.listener4.invoke(ipAddress, error)
105 | }
106 |
107 | private lateinit var listener5: (ipAddress: String) -> Unit
108 | fun onThreadAcceptSocket(listener: (ipAddress: String) -> Unit) {
109 | this.listener5 = listener
110 | }
111 | override fun onAcceptSocket(ipAddress: String) {
112 | this.listener5.invoke(ipAddress)
113 | }
114 |
115 | private lateinit var listener6: (ipAddress: String, port: Int, time: Long, data: ByteArray) -> Unit
116 | fun onThreadReceive(listener: (ipAddress: String, port: Int, time: Long, data: ByteArray) -> Unit) {
117 | this.listener6 = listener
118 | }
119 | override fun onReceive(ipAddress: String, port: Int, time: Long, data: ByteArray) {
120 | this.listener6.invoke(ipAddress, port, time, data)
121 | }
122 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.Manifest
4 | import android.content.Intent
5 | import android.content.pm.PackageManager
6 | import android.os.Build
7 | import android.os.Bundle
8 | import android.os.Message
9 | import android.util.Log
10 | import android.view.View
11 | import android.widget.Toast
12 | import androidx.appcompat.app.AppCompatActivity
13 | import com.d10ng.stringlib.toHexString
14 | import com.dlong.networkdebugassistant.app.BaseHandler
15 | import com.dlong.networkdebugassistant.bean.ReceiveInfo
16 | import com.dlong.networkdebugassistant.utils.DateUtils
17 | import com.google.android.material.snackbar.Snackbar
18 | import java.io.BufferedWriter
19 | import java.io.File
20 | import java.io.FileOutputStream
21 | import java.io.OutputStreamWriter
22 |
23 | /**
24 | * @author D10NG
25 | * @date on 2019-12-05 11:03
26 | */
27 | open class BaseActivity : AppCompatActivity(), BaseHandler.BaseHandlerCallBack {
28 |
29 | lateinit var mHandler: BaseHandler
30 |
31 | override fun callBack(msg: Message) {
32 | // 回调
33 | }
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | mHandler = BaseHandler(this, this)
38 | }
39 |
40 | fun finishGoTo(clz: Class<*>) {
41 | clearGoTo(clz)
42 | finish()
43 | }
44 |
45 | fun clearGoTo(clz: Class<*>) {
46 | val intent = getClearTopIntent(clz)
47 | startActivity(intent)
48 | }
49 |
50 | fun getClearTopIntent(clz: Class<*>) : Intent {
51 | val intent = Intent(this, clz)
52 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
53 | return intent
54 | }
55 |
56 | // 检查权限
57 | fun checkPermission(permission: String) : Boolean {
58 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true
59 | return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
60 | }
61 |
62 | // 请求权限
63 | fun reqPermission(permissions: Array, reqCode: Int) {
64 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
65 | requestPermissions(permissions, reqCode)
66 | }
67 |
68 | fun showToast(msg: String) {
69 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
70 | }
71 |
72 | fun showSnackBar(view: View, msg: String) {
73 | Snackbar.make(view, msg, Snackbar.LENGTH_LONG).show()
74 | }
75 |
76 | // 保存数据到本地
77 | fun saveReceiveDataToLocal(receive: ReceiveInfo, path: String, isHex: Boolean) {
78 | // 检查权限
79 | if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
80 | !checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) return
81 | // 检查路径是否存在
82 | val parentPath = File(path)
83 | if (!parentPath.exists()) return
84 | // 创建以日期为参数的txt文本
85 | val fileName = "receive_text_${DateUtils.getCurDateStr("yyyy_MM_dd")}.txt"
86 | val file = File("$path/$fileName")
87 | if (!file.exists()) {
88 | file.createNewFile()
89 | }
90 | // 创建新加文本
91 | val builder = StringBuilder()
92 | builder.append("[")
93 | builder.append(DateUtils.getDateStr(receive.time, "yyyy-MM-dd hh:mm:ss"))
94 | builder.append("][")
95 | builder.append(receive.ipAddress).append(":")
96 | builder.append(receive.port).append("]")
97 | if (isHex) {
98 | for (byte in receive.byteData.iterator()) {
99 | builder.append(byte.toHexString())
100 | builder.append(" ")
101 | }
102 | } else {
103 | builder.append(String(receive.byteData, 0, receive.byteData.size))
104 | }
105 | builder.append("\r\n")
106 | Log.e("测试","接收到:$builder")
107 | // 写入文本内容
108 | val outputStream = FileOutputStream(file, true)
109 | val writer = BufferedWriter(OutputStreamWriter(outputStream))
110 | writer.write(builder.toString())
111 | writer.close()
112 | }
113 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_select_folder.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
12 |
17 |
18 |
32 |
33 |
45 |
46 |
59 |
60 |
68 |
69 |
81 |
82 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'kotlin-parcelize'
5 | id 'kotlin-kapt'
6 | }
7 |
8 | android {
9 | compileSdkVersion 31
10 | defaultConfig {
11 | applicationId "com.dlong.networkdebugassistant"
12 | minSdkVersion 21
13 | targetSdkVersion 29
14 | versionCode 7
15 | versionName "0.1.7"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 | signingConfigs {
19 | release {
20 | storeFile file('dlong0232372.keystore')
21 | storePassword "0232372"
22 | keyAlias 'ydl'
23 | keyPassword "0232372"
24 | }
25 | }
26 | buildTypes {
27 | debug {
28 | // 开启打印
29 | buildConfigField "boolean", "IS_LOG_ENABLE", "true"
30 | minifyEnabled false
31 | signingConfig signingConfigs.release
32 | }
33 | release {
34 | // 关闭打印
35 | buildConfigField "boolean", "IS_LOG_ENABLE", "false"
36 | minifyEnabled true
37 | signingConfig signingConfigs.release
38 | // Zipalign优化
39 | zipAlignEnabled true
40 | // 移除无用的resource文件
41 | shrinkResources true
42 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
43 |
44 | applicationVariants.all { variant ->
45 | variant.outputs.all {
46 | outputFileName = "NetworkDebugAssistant-${buildType.name}-${defaultConfig.versionName}-${releaseTime()}.apk"
47 | }
48 | }
49 | }
50 | }
51 | bundle {
52 | language {
53 | enableSplit = true
54 | }
55 | density {
56 | enableSplit = true
57 | }
58 | abi {
59 | enableSplit = true
60 | }
61 | }
62 |
63 | compileOptions {
64 | sourceCompatibility JavaVersion.VERSION_1_8
65 | targetCompatibility JavaVersion.VERSION_1_8
66 | }
67 | kotlinOptions {
68 | jvmTarget = '1.8'
69 | }
70 |
71 | buildFeatures {
72 | dataBinding true
73 | viewBinding true
74 | }
75 | packagingOptions {
76 | exclude 'META-INF/atomicfu.kotlin_module'
77 | }
78 |
79 | kapt.includeCompileClasspath = false
80 | }
81 |
82 | static def releaseTime() {
83 | return new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("Asia/Shanghai"))
84 | }
85 |
86 | dependencies {
87 | implementation fileTree(dir: 'libs', include: ['*.jar'])
88 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.10"
89 | implementation 'androidx.appcompat:appcompat:1.2.0'
90 | implementation 'androidx.core:core-ktx:1.3.2'
91 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
92 | testImplementation 'junit:junit:4.12'
93 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
94 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
95 | implementation 'androidx.cardview:cardview:1.0.0'
96 | implementation 'com.google.android.material:material:1.2.1'
97 |
98 | // Room components 数据库
99 | implementation "androidx.room:room-runtime:2.4.2"
100 | implementation "androidx.room:room-ktx:2.4.2"
101 | kapt "androidx.room:room-compiler:2.4.2"
102 | androidTestImplementation "androidx.room:room-testing:2.4.2"
103 | // Lifecycle components
104 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
105 | kapt "androidx.lifecycle:lifecycle-common-java8:2.3.1"
106 | androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
107 | // ViewModel Kotlin support
108 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
109 | // Coroutines
110 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
111 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
112 |
113 | // 弹窗工具
114 | implementation 'com.github.D10NGYANG:DL10Dialog:1.7.2'
115 |
116 | // 实体转换工具
117 | implementation 'com.github.D10NGYANG:JsonEntityManager:1.4'
118 |
119 | // 字符串字节数据工具
120 | implementation 'com.github.D10NGYANG:DLStringUtil:1.7'
121 |
122 | // 调试工具
123 | debugImplementation 'com.simple:spiderman:1.1.0'
124 | releaseImplementation 'com.simple:spiderman-no-op:1.1.0'
125 |
126 | // UPD/TCP工具
127 | //implementation 'com.github.D10NGYANG:NetworkDebugAssistant:0.1.3'
128 | implementation project(path: ':dl10netassistant')
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Network Debug Assistant
3 |
4 |
5 | 确定
6 | 取消
7 | 注意
8 | 提示
9 | 删除
10 | 编辑
11 | 保存
12 | 执行
13 | 复制
14 | 设置
15 |
16 |
17 | 网络调试助手 - DL
18 | UDP广播
19 | UDP组播
20 | TCP客户端
21 | TCP服务器
22 | ping指令
23 |
24 |
25 | 设置
26 | 版本信息
27 |
28 |
29 | 清除
30 | 历史
31 | 发送
32 | 本机IP:
33 | 断开
34 |
35 |
36 | UDP广播配置
37 | 网络设置
38 | 本地端口
39 | 目标地址
40 | 目标端口
41 | 发送设置
42 | Ascii / Hex
43 | 是否自动添加校验位
44 | 是否自动循环发送
45 | 循环发送间隔时间(毫秒)
46 | 接收设置
47 | 是否显示接收时间
48 | 是否显示IP地址
49 | 是否显示端口
50 | 本地存储地址
51 | 是否自动保存接收数据到本地
52 | 请输入端口号
53 | 端口号不能为空
54 | 端口号范围为0~65535
55 | 请输入IP地址(eg: 255.255.255.255)
56 | IP地址格式不正确
57 | 请输入循环发送时间间隔 ms
58 | 循环发送时间间隔不能为空
59 | 循环发送时间间隔范围为1~999999999
60 | 正在创建连接…
61 | 已取消创建连接!
62 | 连接成功!
63 | 连接失败!
64 | 已断开连接!
65 | 正在断开连接…
66 | 已将接收文本复制到系统粘贴板!
67 |
68 |
69 | UDP组播配置
70 | 目标组播地址
71 | 请输入组播地址(224.0.0.0 ~ 239.255.255.255)
72 | 组播地址格式不正确
73 |
74 |
75 | TCP客户端配置
76 | 服务器地址
77 | 服务器端口
78 |
79 |
80 | 所有连接客户端
81 |
82 |
83 | TCP服务器配置
84 |
85 |
86 | 选择存储路径
87 | 个子项
88 | 创建新的文件夹
89 | 请输入新的文件夹名称
90 | 文件夹名称不能为空
91 | 文件夹名称长度最大为50个字符
92 | 您可以对文件夹【**】进行如下操作
93 | 重命名
94 | 重命名文件夹【**】
95 | 您确定删除【**】文件夹吗?
96 |
97 |
98 | 发送历史
99 | 您可以对历史发送文本【**】进行如下操作
100 |
101 |
102 | 请输入目标地址
103 |
104 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tcp_server_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
12 |
17 |
18 |
32 |
33 |
42 |
43 |
47 |
48 |
58 |
59 |
66 |
67 |
73 |
74 |
78 |
79 |
87 |
88 |
94 |
95 |
102 |
103 |
104 |
105 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/TcpClientSettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.text.InputType
5 | import android.view.View
6 | import androidx.databinding.DataBindingUtil
7 | import com.dlong.dialog.*
8 | import com.dlong.networkdebugassistant.R
9 | import com.dlong.networkdebugassistant.bean.TcpClientConfiguration
10 | import com.dlong.networkdebugassistant.constant.DBConstant
11 | import com.dlong.networkdebugassistant.databinding.ActivityTcpClientSettingBinding
12 | import com.dlong.networkdebugassistant.utils.StringUtils
13 |
14 | class TcpClientSettingActivity : BaseSettingActivity() {
15 |
16 | private lateinit var binding: ActivityTcpClientSettingBinding
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | binding = DataBindingUtil.setContentView(this, R.layout.activity_tcp_client_setting)
21 |
22 | // 设置返回按钮
23 | setSupportActionBar(binding.toolbar)
24 | binding.toolbar.setNavigationOnClickListener { finish() }
25 |
26 | initContentBinding(binding.contentBase)
27 | initConfig()
28 | }
29 |
30 | override fun initConfig() {
31 | super.initConfig()
32 | // 初始化配置信息
33 | binding.contentBase.config = DBConstant.getInstance(this).getTcpClientConfiguration()
34 | updateConfigShow()
35 | }
36 |
37 | override fun updateConfigShow() {
38 | binding.contentBase.config = binding.contentBase.config?: TcpClientConfiguration()
39 | val cc = binding.contentBase.config as TcpClientConfiguration
40 | binding.serverPort = "${cc.serverPort}"
41 | binding.serverIpAddress = cc.serverIpAddress
42 | // 保存配置信息
43 | DBConstant.getInstance(this).setTcpClientConfiguration(cc)
44 | }
45 |
46 | fun setTargetIpAddress(view: View) {
47 | val cc = binding.contentBase.config as TcpClientConfiguration
48 | val tag = "ip"
49 | EditDialog(this).create()
50 | .setTittle(resources.getString(R.string.prompt))
51 | .setMsg(resources.getString(R.string.target_ip_address))
52 | .addEdit(tag, cc.serverIpAddress, resources.getString(R.string.please_input_ip_address))
53 | .setInputType(tag, InputType.TYPE_NUMBER_FLAG_DECIMAL)
54 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
55 | onClick { dialog, _ ->
56 | val value = dialog.getInputText(tag)
57 | if (value.isEmpty()) {
58 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
59 | return@onClick
60 | }
61 | val items = value.split(".")
62 | if (items.size != 4) {
63 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
64 | return@onClick
65 | }
66 | val builder = StringBuilder()
67 | for (str in items.iterator()) {
68 | if (!StringUtils.isNumeric(str)) {
69 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
70 | return@onClick
71 | }
72 | val num = str.toIntOrNull()?: -1
73 | if (num < 0 || num > 255) {
74 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
75 | return@onClick
76 | }
77 | builder.append(num).append(".")
78 | }
79 | cc.serverIpAddress = builder.substring(0, builder.length -1)
80 | updateConfigShow()
81 | dialog.dismiss()
82 | }
83 | }
84 | .addAction(resources.getString(R.string.cancel))
85 | .show()
86 | }
87 |
88 | fun setTargetPort(view: View) {
89 | val cc = binding.contentBase.config as TcpClientConfiguration
90 | val tag = "port"
91 | EditDialog(this).create()
92 | .setTittle(resources.getString(R.string.prompt))
93 | .setMsg(resources.getString(R.string.target_port))
94 | .addEdit(tag, "${cc.serverPort}", resources.getString(R.string.please_input_port))
95 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
96 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
97 | onClick { dialog, _ ->
98 | val value = dialog.getInputText(tag)
99 | if (value.isEmpty()) {
100 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_empty))
101 | } else {
102 | val num = value.toIntOrNull()?: -1
103 | if (num in 0x0000..0xffff) {
104 | cc.serverPort = num
105 | updateConfigShow()
106 | dialog.dismiss()
107 | } else {
108 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_over_range))
109 | }
110 | }
111 | }
112 | }
113 | .addAction(resources.getString(R.string.cancel))
114 | .show()
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_ping.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
29 |
30 |
39 |
40 |
45 |
46 |
61 |
62 |
70 |
71 |
72 |
78 |
79 |
84 |
85 |
105 |
106 |
110 |
111 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/app/src/main/res/values-en/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Network Debug Assistant
3 |
4 |
5 | Confirm
6 | Cancel
7 | Notice
8 | Prompt
9 | Delete
10 | Edit
11 | Save
12 | Run
13 | Copy
14 | Setting
15 |
16 |
17 | Network Debug Assistant - DL
18 | UDP Broadcast
19 | UDP Multicast
20 | TCP Client
21 | TCP Server
22 | PING
23 |
24 |
25 | Setting
26 | Version Info
27 |
28 |
29 | Clean
30 | History
31 | Send
32 | LOCAL IP:
33 | disconnect
34 |
35 |
36 | UDP Broadcast Setting
37 | Internet Setting
38 | Local port
39 | Target IP address
40 | Target port
41 | Send Setting
42 | Ascii / Hex
43 | Auto add check code
44 | Auto loop send
45 | Loop send interval (ms)
46 | Receive Setting
47 | Show time
48 | Show IP address
49 | Show port
50 | Local storage address
51 | Auto save receive data to local
52 | Please input port
53 | Port number cannot be empty
54 | The port number ranges from 0 to 65535.
55 | Please input the IP address (eg: 255.255.255.255)
56 | IP address format is wrong
57 | Please input cyclic sending interval ms
58 | The cyclic sending interval cannot be empty
59 | The interval of cyclic sending is 1 ~ 999999999
60 | Creating connection…
61 | Canceled connection creation!
62 | Connect success!
63 | Connect failed!
64 | Disconnected!
65 | Disconnecting…
66 | Received text copied to system pasteboard!
67 |
68 |
69 | UDP Multicast Setting
70 | Target multicast address
71 | Please input a multicast address (224.0.0.0 to 239.255.255.255)
72 | Multicast address format is wrong
73 |
74 |
75 | TCP Client Setting
76 | Server IP address
77 | Server port
78 |
79 |
80 | All connected clients
81 |
82 |
83 | TCP Server Setting
84 |
85 |
86 | Select storage path
87 | children
88 | Create new folder
89 | Please input a new folder name
90 | Folder name cannot be empty
91 | Folder name can be up to 50 characters long
92 | You can perform the following operations on the folder [**]
93 | Rename
94 | Rename folder [**]
95 | Are you sure you want to delete the [**] folder?
96 |
97 |
98 | Send History
99 | You can perform the following operations on the history sending text [**]
100 |
101 |
102 | Please enter the address
103 |
104 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/BaseSettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Intent
6 | import android.content.pm.PackageManager
7 | import android.text.InputType
8 | import android.view.View
9 | import android.widget.CompoundButton
10 | import com.dlong.dialog.*
11 | import com.dlong.networkdebugassistant.R
12 | import com.dlong.networkdebugassistant.databinding.ContentBaseSettingBinding
13 |
14 | /**
15 | * 基础设置布局
16 | *
17 | * @author D10NG
18 | * @date on 2019-12-09 16:46
19 | */
20 | open class BaseSettingActivity : BaseActivity(), CompoundButton.OnCheckedChangeListener {
21 |
22 | protected lateinit var contentBind: ContentBaseSettingBinding
23 |
24 | companion object{
25 | const val SELECT_LOCAL_PATH = 1001
26 | const val P_WR_EXTERNAL_STORAGE = 200
27 | }
28 |
29 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
30 | super.onActivityResult(requestCode, resultCode, data)
31 | if (resultCode != Activity.RESULT_OK) return
32 | when(requestCode) {
33 | SELECT_LOCAL_PATH -> {
34 | val path = data?.getStringExtra("path")?: "NULL"
35 | contentBind.config?.receiveSaveLocalPath = path
36 | updateConfigShow()
37 | }
38 | }
39 | }
40 |
41 | override fun onRequestPermissionsResult(
42 | requestCode: Int,
43 | permissions: Array,
44 | grantResults: IntArray
45 | ) {
46 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
47 | val pass = !grantResults.contains(PackageManager.PERMISSION_DENIED)
48 | when(requestCode) {
49 | P_WR_EXTERNAL_STORAGE -> {
50 | // 获取读写权限
51 | contentBind.config?.isAutoSaveToLocal = pass
52 | updateConfigShow()
53 | }
54 | }
55 | }
56 |
57 | /**
58 | * 初始化基础设置布局
59 | */
60 | fun initContentBinding(bind: ContentBaseSettingBinding) {
61 | contentBind = bind
62 | contentBind.swSendHex.setOnCheckedChangeListener(this)
63 | contentBind.swAutoAddCheck.setOnCheckedChangeListener(this)
64 | contentBind.swReceiveHex.setOnCheckedChangeListener(this)
65 | contentBind.swShowTime.setOnCheckedChangeListener(this)
66 | contentBind.swShowIpAddress.setOnCheckedChangeListener(this)
67 | contentBind.swShowPort.setOnCheckedChangeListener(this)
68 | contentBind.swAutoSaveLocal.setOnCheckedChangeListener(this)
69 | }
70 |
71 | /**
72 | * 初始化配置信息
73 | */
74 | open fun initConfig() {}
75 |
76 | /**
77 | * 刷新页面显示
78 | */
79 | open fun updateConfigShow() {}
80 |
81 | override fun onCheckedChanged(p0: CompoundButton?, p1: Boolean) {
82 | val sw = p0?: return
83 | when(sw.id) {
84 | R.id.sw_send_hex -> contentBind.config?.isSendHex = p1
85 | R.id.sw_auto_add_check -> contentBind.config?.isAutoAddHexCheck = p1
86 | R.id.sw_receive_hex -> contentBind.config?.isReceiveHex = p1
87 | R.id.sw_show_time -> contentBind.config?.isReceiveShowTime = p1
88 | R.id.sw_show_ip_address -> contentBind.config?.isReceiveShowIpAddress = p1
89 | R.id.sw_show_port -> contentBind.config?.isReceiveShowPort = p1
90 | R.id.sw_auto_save_local -> {
91 | if (p1) {
92 | if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
93 | !checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
94 | reqPermission(arrayOf(
95 | Manifest.permission.WRITE_EXTERNAL_STORAGE,
96 | Manifest.permission.READ_EXTERNAL_STORAGE),
97 | P_WR_EXTERNAL_STORAGE
98 | )
99 | return
100 | }
101 | }
102 | contentBind.config?.isAutoSaveToLocal = p1
103 | }
104 | }
105 | updateConfigShow()
106 | }
107 |
108 | fun setLoopTime(view: View) {
109 | val tag = "time"
110 | EditDialog(this).create()
111 | .setTittle(resources.getString(R.string.prompt))
112 | .setMsg(resources.getString(R.string.setting_auto_send_loop_time))
113 | .addEdit(tag, "${contentBind.config?.autoSendTime?: 1000}", resources.getString(R.string.please_input_loop_time))
114 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
115 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
116 | onClick { dialog, _ ->
117 | val value = dialog.getInputText(tag)
118 | if (value.isEmpty()) {
119 | dialog.setError(tag, resources.getString(R.string.input_loop_time_can_not_empty))
120 | } else {
121 | val num = value.toLongOrNull()?: -1
122 | if (num in 1..999999999) {
123 | contentBind.config?.autoSendTime = num
124 | updateConfigShow()
125 | dialog.dismiss()
126 | } else {
127 | dialog.setError(tag, resources.getString(R.string.input_loop_time_can_not_over_range))
128 | }
129 | }
130 | }
131 | }
132 | .addAction(resources.getString(R.string.cancel))
133 | .show()
134 | }
135 |
136 | fun openLocalPathManager(view: View) {
137 | val intent = getClearTopIntent(SelectFolderActivity::class.java)
138 | var path = contentBind.config?.receiveSaveLocalPath
139 | if (path != null && path.equals("NULL", true)) {
140 | path = null
141 | }
142 | intent.putExtra("path", path)
143 | startActivityForResult(intent, SELECT_LOCAL_PATH)
144 | }
145 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
30 |
31 |
39 |
40 |
46 |
47 |
56 |
57 |
58 |
64 |
65 |
74 |
75 |
76 |
82 |
83 |
92 |
93 |
94 |
100 |
101 |
110 |
111 |
112 |
118 |
119 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/dl10netassistant/src/main/java/com/d10ng/net/assistant/TcpServerThread.kt:
--------------------------------------------------------------------------------
1 | package com.d10ng.net.assistant
2 |
3 | import java.net.InetSocketAddress
4 | import java.net.ServerSocket
5 | import java.net.Socket
6 |
7 | /**
8 | * TCP 服务器
9 | *
10 | * @author D10NG
11 | * @date on 2020/4/27 5:16 PM
12 | */
13 | class TcpServerThread constructor(
14 | // 服务器端口
15 | private val mPort: Int
16 | ) : BaseNetThread() {
17 |
18 | constructor(mPort: Int, listener: OnNetThreadListener): this(mPort) {
19 | super.setThreadListener(listener)
20 | }
21 |
22 | constructor(mPort: Int, listener: NetThreadListener.() -> Unit): this(mPort) {
23 | super.setThreadListener(listener)
24 | }
25 |
26 | private var serverSocket: ServerSocket? = null
27 | /** 连接列表 */
28 | private val socketList: MutableMap = mutableMapOf()
29 | /** 运行标记位 */
30 | private var isRun = false
31 |
32 | companion object {
33 | const val ALL_CLIENT_NAME = "all clients"
34 | }
35 |
36 | override fun run() {
37 | super.run()
38 | try {
39 | // 打开服务器
40 | serverSocket = ServerSocket().apply {
41 | reuseAddress = true
42 | bind(InetSocketAddress(mPort))
43 | }
44 | } catch (e: Exception) {
45 | // 打开服务器失败
46 | e.printStackTrace()
47 | listener?.onConnectFailed("")
48 | listenerLambda?.onConnectFailed("")
49 | listener?.onError("", e.toString())
50 | listenerLambda?.onError("", e.toString())
51 | return
52 | }
53 | // 打开服务器成功
54 | listener?.onConnected("")
55 | listenerLambda?.onConnected("")
56 |
57 | isRun = true
58 | while (isRun) {
59 | val socket =
60 | try {
61 | serverSocket!!.accept()
62 | } catch (e: Exception) {
63 | e.printStackTrace()
64 | listener?.onError("", e.toString())
65 | listenerLambda?.onError("", e.toString())
66 | break
67 | }
68 | if (socket != null) {
69 | val key = socket.remoteSocketAddress.toString()
70 | println("Tcp Server Accept, address=$key")
71 | socketList[key] = socket
72 | // 开始循环监听
73 | startAcceptSocket(key, socket)
74 | }
75 | }
76 | this.close()
77 | // 服务器关闭
78 | listener?.onDisconnect("")
79 | listenerLambda?.onDisconnect("")
80 | }
81 |
82 | /** 启动循环监听连接消息 */
83 | private fun startAcceptSocket(key: String, socket: Socket) {
84 | // 有客户端连接进来
85 | listener?.onAcceptSocket(socket.remoteSocketAddress.toString())
86 | listenerLambda?.onAcceptSocket(socket.remoteSocketAddress.toString())
87 | Thread {
88 | val inputStream = socket.getInputStream()
89 | while (isRun && socket.isConnected) {
90 | val buffer = ByteArray(1024)
91 | val len =
92 | try {
93 | inputStream.read(buffer)
94 | } catch (e: Exception) {
95 | -1
96 | }
97 | if (len == -1) {
98 | break
99 | }
100 | if (len > 0) {
101 | // 接受数据
102 | listener?.onReceive(
103 | socket.remoteSocketAddress.toString(),
104 | socket.port, curTime, buffer.copyOfRange(0, len)
105 | )
106 | listenerLambda?.onReceive(
107 | socket.remoteSocketAddress.toString(),
108 | socket.port, curTime, buffer.copyOfRange(0, len)
109 | )
110 | }
111 | }
112 | // 断开连接
113 | println("TCP SERVER, $key 已断开连接")
114 | listener?.onDisconnect(socket.remoteSocketAddress.toString())
115 | listenerLambda?.onDisconnect(socket.remoteSocketAddress.toString())
116 | socketList.remove(key)
117 | }.start()
118 | }
119 |
120 | /** 获取名字列表 */
121 | fun getSocketNameList() : List {
122 | val list: MutableList = mutableListOf()
123 | list.add(ALL_CLIENT_NAME)
124 | for (socket in socketList.values) {
125 | list.add(socket.remoteSocketAddress.toString().replace("/", ""))
126 | }
127 | return list.toList()
128 | }
129 |
130 | /** 根据名字获取socket */
131 | fun getSocketByName(name: String): Socket? {
132 | for (socket in socketList.values) {
133 | if (socket.remoteSocketAddress.toString().replace("/", "") == name) return socket
134 | }
135 | return null
136 | }
137 |
138 | /** 读取接入列表 */
139 | fun getSocketList(): List {
140 | return socketList.values.toList()
141 | }
142 |
143 | /** 断开链接 */
144 | fun disconnectSocket(s: Socket) {
145 | try {
146 | s.close()
147 | } catch (e: Exception) {
148 | e.printStackTrace()
149 | }
150 | }
151 |
152 | override fun isConnected(): Boolean {
153 | return isRun
154 | }
155 |
156 | override fun send(address: String, toPort: Int, data: ByteArray) {
157 | super.send(address, toPort, data)
158 | Thread {
159 | try {
160 | val socket = socketList["/$address:$toPort"]?: return@Thread
161 | socket.getOutputStream().write(data)
162 | socket.getOutputStream().flush()
163 | } catch (e: Exception) {
164 | e.printStackTrace()
165 | listener?.onError(address, e.toString())
166 | listenerLambda?.onError(address, e.toString())
167 | }
168 | }.start()
169 | }
170 |
171 | override fun send(data: ByteArray) {
172 | super.send(data)
173 | Thread {
174 | try {
175 | for (socket in socketList.values) {
176 | socket.getOutputStream().write(data)
177 | socket.getOutputStream().flush()
178 | }
179 | } catch (e: Exception) {
180 | e.printStackTrace()
181 | listener?.onError("", e.toString())
182 | listenerLambda?.onError("", e.toString())
183 | }
184 | }.start()
185 | }
186 |
187 | override fun close() {
188 | isRun = false
189 | try {
190 | serverSocket?.close()
191 | } catch (e: Exception) {
192 | e.printStackTrace()
193 | listener?.onError("", e.toString())
194 | listenerLambda?.onError("", e.toString())
195 | }
196 | super.close()
197 | }
198 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | #-------------------------------------------基本不用动区域--------------------------------------------
2 | #---------------------------------基本指令区----------------------------------
3 | # 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。
4 | -optimizationpasses 5
5 | # 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
6 | -dontusemixedcaseclassnames
7 | # 指定不去忽略非公共的库类(不跳过library中的非public的类)
8 | -dontskipnonpubliclibraryclasses
9 | # 指定不去忽略包可见的库类的成员
10 | -dontskipnonpubliclibraryclassmembers
11 | #不进行优化,建议使用此选项,
12 | -dontoptimize
13 | # 不进行预校验,Android不需要,可加快混淆速度。
14 | -dontpreverify
15 | # 屏蔽警告
16 | -ignorewarnings
17 | # 指定混淆是采用的算法,后面的参数是一个过滤器
18 | # 这个过滤器是谷歌推荐的算法,一般不做更改
19 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
20 | # 保护代码中的Annotation不被混淆
21 | -keepattributes *Annotation*
22 | # 避免混淆泛型, 这在JSON实体映射时非常重要
23 | -keepattributes Signature
24 | # 抛出异常时保留代码行号
25 | -keepattributes SourceFile,LineNumberTable
26 | #优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
27 | # 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
28 | # 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
29 | #指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
30 | -allowaccessmodification
31 | #当有优化和使用-repackageclasses时才适用。
32 | -repackageclasses ''
33 | # 混淆时记录日志(打印混淆的详细信息)
34 | # 这句话能够使我们的项目混淆后产生映射文件
35 | # 包含有类名->混淆后类名的映射关系
36 | -verbose
37 | #----------------------------------------------------------------------------
38 | #---------------------------------默认保留区---------------------------------
39 | # 保持哪些类不被混淆
40 | #继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
41 | -keep public class * extends android.app.Activity
42 | -keep public class * extends android.app.Application
43 | -keep public class * extends android.support.multidex.MultiDexApplication
44 | -keep public class * extends android.app.Service
45 | -keep public class * extends android.content.BroadcastReceiver
46 | -keep public class * extends android.content.ContentProvider
47 | -keep public class * extends android.app.backup.BackupAgentHelper
48 | -keep public class * extends android.preference.Preference
49 | -keep public class * extends android.view.View
50 | -keep class android.support.** {*;}## 保留support下的所有类及其内部类
51 | -keep public class com.google.vending.licensing.ILicensingService
52 | -keep public class com.android.vending.licensing.ILicensingService
53 | #表示不混淆上面声明的类,最后这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
54 | #----------------------------------------------------
55 | # 保留继承的
56 | -keep public class * extends android.support.v4.**
57 | -keep public class * extends android.support.v7.**
58 | -keep public class * extends android.support.annotation.**
59 | # androidx
60 | -keep class com.google.android.material.** {*;}
61 | -keep class androidx.** {*;}
62 | -keep public class * extends androidx.**
63 | -keep interface androidx.** {*;}
64 | -dontwarn com.google.android.material.**
65 | -dontnote com.google.android.material.**
66 | -dontwarn androidx.**
67 | #kotlin
68 | -keep class kotlin.** { *; }
69 | -keep class kotlin.Metadata { *; }
70 | -dontwarn kotlin.**
71 | -keepclassmembers class **$WhenMappings {
72 | ;
73 | }
74 | -keepclassmembers class kotlin.Metadata {
75 | public ;
76 | }
77 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
78 | static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
79 | }
80 | # 协程混淆
81 | -keepclassmembernames class kotlinx.** {
82 | volatile ;
83 | }
84 | #表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
85 | -keepclasseswithmembernames class * {
86 | native ;
87 | }
88 | #这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
89 | #表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
90 | #当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
91 | -keepclassmembers class * extends android.app.Activity{
92 | public void *(android.view.View);
93 | }
94 | #表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了
95 | -keepclassmembers enum * {
96 | public static **[] values();
97 | public static ** valueOf(java.lang.String);
98 | }
99 | #表示不混淆任何一个View中的setXxx()和getXxx()方法,
100 | #因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
101 | -keep public class * extends android.view.View{
102 | *** get*();
103 | void set*(***);
104 | public (android.content.Context);
105 | public (android.content.Context, android.util.AttributeSet);
106 | public (android.content.Context, android.util.AttributeSet, int);
107 | }
108 | -keepclasseswithmembers class * {
109 | public (android.content.Context, android.util.AttributeSet);
110 | public (android.content.Context, android.util.AttributeSet, int);
111 | }
112 | #表示不混淆Parcelable实现类中的CREATOR字段,
113 | #毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
114 | -keep class * implements android.os.Parcelable {
115 | public static final android.os.Parcelable$Creator *;
116 | }
117 | # 这指定了继承Serizalizable的类的如下成员不被移除混淆
118 | -keepclassmembers class * implements java.io.Serializable {
119 | static final long serialVersionUID;
120 | private static final java.io.ObjectStreamField[] serialPersistentFields;
121 | private void writeObject(java.io.ObjectOutputStream);
122 | private void readObject(java.io.ObjectInputStream);
123 | java.lang.Object writeReplace();
124 | java.lang.Object readResolve();
125 | }
126 | # 保留R下面的资源
127 | #-keep class **.R$* {
128 | # *;
129 | #}
130 | #不混淆资源类下static的
131 | -keepclassmembers class **.R$* {
132 | public static ;
133 | }
134 | # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
135 | -keepclassmembers class * {
136 | void *(**On*Event);
137 | void *(**On*Listener);
138 | }
139 | # 保留我们自定义控件(继承自View)不被混淆
140 | -keep public class * extends android.view.View{
141 | *** get*();
142 | void set*(***);
143 | public (android.content.Context);
144 | public (android.content.Context, android.util.AttributeSet);
145 | public (android.content.Context, android.util.AttributeSet, int);
146 | }
147 | #---------------------------------------------------------------------------------------------------
148 |
149 | # 保留引用的第三方
150 | # 调试工具 spiderman
151 | -keep class com.simple.spiderman.** { *; }
152 | -keepnames class com.simple.spiderman.** { *; }
153 | -keep public class * extends androidx.annotation.** { *; }
154 | -keep public class * extends androidx.core.content.FileProvider
155 |
156 | # 实体转换工具
157 | -keep class com.dlong.jsonentitylib.** {*;}
158 | -dontwarn com.dlong.jsonentitylib.**
159 | -keep class * extends com.dlong.jsonentitylib.BaseJsonEntity {*;}
160 | -keepclassmembers class * {
161 | @com.dlong.jsonentitylib.annotation.DLField ;
162 | }
163 |
164 | # 弹窗工具
165 | -keep class com.dlong.dialog.** {*;}
166 | -dontwarn com.dlong.dialog.**
167 |
168 | # 字符串、字节、类处理
169 | -keep class com.d10ng.stringlib.** {*;}
170 | -dontwarn com.d10ng.stringlib.**
171 |
172 | # 保留实体类
173 | -keep class com.dlong.networkdebugassistant.bean.** {*;}
174 |
175 | # 保留UDP/TCP库
176 | -keep class com.dlong.dl10netassistant.** {*;}
177 | -dontwarn com.dlong.dl10netassistant.**
178 |
179 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/UdpBroadSettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.text.InputType
5 | import android.view.View
6 | import androidx.databinding.DataBindingUtil
7 | import com.dlong.dialog.*
8 | import com.dlong.networkdebugassistant.R
9 | import com.dlong.networkdebugassistant.bean.UdpBroadConfiguration
10 | import com.dlong.networkdebugassistant.constant.DBConstant
11 | import com.dlong.networkdebugassistant.databinding.ActivityUdpBroadSettingBinding
12 | import com.dlong.networkdebugassistant.utils.StringUtils
13 |
14 | class UdpBroadSettingActivity : BaseSettingActivity() {
15 |
16 | private lateinit var binding: ActivityUdpBroadSettingBinding
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | binding = DataBindingUtil.setContentView(this, R.layout.activity_udp_broad_setting)
21 |
22 | // 设置返回按钮
23 | setSupportActionBar(binding.toolbar)
24 | binding.toolbar.setNavigationOnClickListener { finish() }
25 |
26 | initContentBinding(binding.contentBase)
27 | initConfig()
28 | }
29 |
30 | override fun initConfig() {
31 | super.initConfig()
32 | // 初始化配置信息
33 | binding.contentBase.config = DBConstant.getInstance(this).getUdpBroadConfiguration()
34 | updateConfigShow()
35 | }
36 |
37 | override fun updateConfigShow() {
38 | binding.contentBase.config = binding.contentBase.config?: UdpBroadConfiguration()
39 | val cc = binding.contentBase.config as UdpBroadConfiguration
40 | binding.localPort = "${cc.localPort}"
41 | binding.targetIpAddress = cc.targetIpAddress
42 | binding.targetPort = "${cc.targetPort}"
43 | // 保存配置信息
44 | DBConstant.getInstance(this).setUdpBroadConfiguration(cc)
45 | }
46 |
47 | fun setLocalPort(view: View) {
48 | val cc = binding.contentBase.config as UdpBroadConfiguration
49 | val tag = "port"
50 | EditDialog(this).create()
51 | .setTittle(resources.getString(R.string.prompt))
52 | .setMsg(resources.getString(R.string.local_port))
53 | .addEdit(tag, "${cc.localPort}", resources.getString(R.string.please_input_port))
54 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
55 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
56 | onClick { dialog, _ ->
57 | val value = dialog.getInputText(tag)
58 | if (value.isEmpty()) {
59 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_empty))
60 | } else {
61 | val num = value.toIntOrNull()?: -1
62 | if (num in 0x0000..0xffff) {
63 | cc.localPort = num
64 | updateConfigShow()
65 | dialog.dismiss()
66 | } else {
67 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_over_range))
68 | }
69 | }
70 | }
71 | }
72 | .addAction(resources.getString(R.string.cancel))
73 | .show()
74 | }
75 |
76 | fun setTargetIpAddress(view: View) {
77 | val cc = binding.contentBase.config as UdpBroadConfiguration
78 | val tag = "ip"
79 | EditDialog(this).create()
80 | .setTittle(resources.getString(R.string.prompt))
81 | .setMsg(resources.getString(R.string.target_ip_address))
82 | .addEdit(tag, cc.targetIpAddress, resources.getString(R.string.please_input_ip_address))
83 | .setInputType(tag, InputType.TYPE_NUMBER_FLAG_DECIMAL)
84 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
85 | onClick { dialog, _ ->
86 | val value = dialog.getInputText(tag)
87 | if (value.isEmpty()) {
88 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
89 | return@onClick
90 | }
91 | val items = value.split(".")
92 | if (items.size != 4) {
93 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
94 | return@onClick
95 | }
96 | val builder = StringBuilder()
97 | for (str in items.iterator()) {
98 | if (!StringUtils.isNumeric(str)) {
99 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
100 | return@onClick
101 | }
102 | val num = str.toIntOrNull()?: -1
103 | if (num < 0 || num > 255) {
104 | dialog.setError(tag, resources.getString(R.string.input_ip_address_wrong_format))
105 | return@onClick
106 | }
107 | builder.append(num).append(".")
108 | }
109 | cc.targetIpAddress = builder.substring(0, builder.length -1)
110 | updateConfigShow()
111 | dialog.dismiss()
112 | }
113 | }
114 | .addAction(resources.getString(R.string.cancel))
115 | .show()
116 | }
117 |
118 | fun setTargetPort(view: View) {
119 | val cc = binding.contentBase.config as UdpBroadConfiguration
120 | val tag = "port"
121 | EditDialog(this).create()
122 | .setTittle(resources.getString(R.string.prompt))
123 | .setMsg(resources.getString(R.string.target_port))
124 | .addEdit(tag, "${cc.targetPort}", resources.getString(R.string.please_input_port))
125 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
126 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
127 | onClick { dialog, _ ->
128 | val value = dialog.getInputText(tag)
129 | if (value.isEmpty()) {
130 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_empty))
131 | } else {
132 | val num = value.toIntOrNull()?: -1
133 | if (num in 0x0000..0xffff) {
134 | cc.targetPort = num
135 | updateConfigShow()
136 | dialog.dismiss()
137 | } else {
138 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_over_range))
139 | }
140 | }
141 | }
142 | }
143 | .addAction(resources.getString(R.string.cancel))
144 | .show()
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/activity/UdpMultiSettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.activity
2 |
3 | import android.os.Bundle
4 | import android.text.InputType
5 | import android.view.View
6 | import androidx.databinding.DataBindingUtil
7 | import com.dlong.dialog.*
8 | import com.dlong.networkdebugassistant.R
9 | import com.dlong.networkdebugassistant.bean.UdpMultiConfiguration
10 | import com.dlong.networkdebugassistant.constant.DBConstant
11 | import com.dlong.networkdebugassistant.databinding.ActivityUdpMultiSettingBinding
12 | import com.dlong.networkdebugassistant.utils.StringUtils
13 |
14 | /**
15 | * @author D10NG
16 | * @date on 2019-12-09 11:14
17 | */
18 | class UdpMultiSettingActivity : BaseSettingActivity() {
19 |
20 | private lateinit var binding: ActivityUdpMultiSettingBinding
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | binding = DataBindingUtil.setContentView(this, R.layout.activity_udp_multi_setting)
25 |
26 | // 设置返回按钮
27 | setSupportActionBar(binding.toolbar)
28 | binding.toolbar.setNavigationOnClickListener { finish() }
29 |
30 | initContentBinding(binding.contentBase)
31 | initConfig()
32 | }
33 |
34 | override fun initConfig() {
35 | super.initConfig()
36 | // 初始化配置信息
37 | binding.contentBase.config = DBConstant.getInstance(this).getUdpMultiConfiguration()
38 | updateConfigShow()
39 | }
40 |
41 | override fun updateConfigShow() {
42 | binding.contentBase.config = binding.contentBase.config?: UdpMultiConfiguration()
43 | val cc = binding.contentBase.config as UdpMultiConfiguration
44 | binding.localPort = "${cc.localPort}"
45 | binding.targetIpAddress = cc.targetIpAddress
46 | binding.targetPort = "${cc.targetPort}"
47 | // 保存配置信息
48 | DBConstant.getInstance(this).setUdpMultiConfiguration(cc)
49 | }
50 |
51 | fun setLocalPort(view: View) {
52 | val cc = binding.contentBase.config as UdpMultiConfiguration
53 | val tag = "port"
54 | EditDialog(this).create()
55 | .setTittle(resources.getString(R.string.prompt))
56 | .setMsg(resources.getString(R.string.local_port))
57 | .addEdit(tag, "${cc.localPort}", resources.getString(R.string.please_input_port))
58 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
59 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
60 | onClick { dialog, _ ->
61 | val value = dialog.getInputText(tag)
62 | if (value.isEmpty()) {
63 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_empty))
64 | } else {
65 | val num = value.toIntOrNull()?: -1
66 | if (num in 0x0000..0xffff) {
67 | cc.localPort = num
68 | updateConfigShow()
69 | dialog.dismiss()
70 | } else {
71 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_over_range))
72 | }
73 | }
74 | }
75 | }
76 | .addAction(resources.getString(R.string.cancel))
77 | .show()
78 | }
79 |
80 | fun setTargetIpAddress(view: View) {
81 | val cc = binding.contentBase.config as UdpMultiConfiguration
82 | val tag = "ip"
83 | EditDialog(this).create()
84 | .setTittle(resources.getString(R.string.prompt))
85 | .setMsg(resources.getString(R.string.target_multi_ip_address))
86 | .addEdit(tag, cc.targetIpAddress, resources.getString(R.string.please_input_multi_ip_address))
87 | .setInputType(tag, InputType.TYPE_NUMBER_FLAG_DECIMAL)
88 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
89 | onClick { dialog, _ ->
90 | val value = dialog.getInputText(tag)
91 | if (value.isEmpty()) {
92 | dialog.setError(tag, resources.getString(R.string.input_multi_ip_address_wrong_format))
93 | return@onClick
94 | }
95 | val items = value.split(".")
96 | if (items.size != 4) {
97 | dialog.setError(tag, resources.getString(R.string.input_multi_ip_address_wrong_format))
98 | return@onClick
99 | }
100 | val builder = StringBuilder()
101 | for (i in items.indices) {
102 | if (!StringUtils.isNumeric(items[i])) {
103 | dialog.setError(tag, resources.getString(R.string.input_multi_ip_address_wrong_format))
104 | return@onClick
105 | }
106 | val num = items[i].toIntOrNull()?: -1
107 | if (i == 0 && (num < 224 || num > 239)) {
108 | dialog.setError(tag, resources.getString(R.string.input_multi_ip_address_wrong_format))
109 | return@onClick
110 | }
111 | if (num < 0 || num > 255) {
112 | dialog.setError(tag, resources.getString(R.string.input_multi_ip_address_wrong_format))
113 | return@onClick
114 | }
115 | builder.append(num).append(".")
116 | }
117 | cc.targetIpAddress = builder.substring(0, builder.length -1)
118 | updateConfigShow()
119 | dialog.dismiss()
120 | }
121 | }
122 | .addAction(resources.getString(R.string.cancel))
123 | .show()
124 | }
125 |
126 | fun setTargetPort(view: View) {
127 | val cc = binding.contentBase.config as UdpMultiConfiguration
128 | val tag = "port"
129 | EditDialog(this).create()
130 | .setTittle(resources.getString(R.string.prompt))
131 | .setMsg(resources.getString(R.string.target_port))
132 | .addEdit(tag, "${cc.targetPort}", resources.getString(R.string.please_input_port))
133 | .setInputType(tag, InputType.TYPE_CLASS_NUMBER)
134 | .addAction(resources.getString(R.string.sure), ButtonStyle.THEME) {
135 | onClick { dialog, _ ->
136 | val value = dialog.getInputText(tag)
137 | if (value.isEmpty()) {
138 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_empty))
139 | } else {
140 | val num = value.toIntOrNull()?: -1
141 | if (num in 0x0000..0xffff) {
142 | cc.targetPort = num
143 | updateConfigShow()
144 | dialog.dismiss()
145 | } else {
146 | dialog.setError(tag, resources.getString(R.string.input_port_can_not_over_range))
147 | }
148 | }
149 | }
150 | }
151 | .addAction(resources.getString(R.string.cancel))
152 | .show()
153 | }
154 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NetworkDebugAssistant
2 |
3 | 网络调试助手[](https://jitpack.io/#D10NGYANG/NetworkDebugAssistant)
4 |
5 | 博客链接:[Android TCP/UDP开源库NetworkDebugAssistant使用教程](https://blog.csdn.net/sinat_38184748/article/details/107616704)
6 |
7 | **从0.1.4版本开始支持Lambda语法**
8 |
9 | # 前言
10 |
11 | 之前开发的通讯工具,已经上架酷安和Google play store,链接在下方:
12 |
13 | [酷安 - Network Debug Assistant](https://www.coolapk.com/apk/257820)
14 |
15 | [Google play store - Network Debug Assistant](https://play.google.com/store/apps/details?id=com.dlong.networkdebugassistant)
16 |
17 |
18 |
19 | 
20 |
21 | 包括以下功能:
22 |
23 | 1. udp广播;
24 | 2. udp组播;
25 | 3. Tcp客户端;
26 | 4. Tcp服务器;
27 | 5. PING IP;
28 |
29 | 现在,终于有时间将它整理成一个单独的库,能直接在其他项目中快捷接入使用。
30 |
31 | # 使用方法
32 |
33 | ## 添加依赖
34 |
35 | 1. 将JitPack存储库添加到您的构建文件中
36 | 将其添加到存储库末尾的root build.gradle中:
37 |
38 | ```kotlin
39 | allprojects {
40 | repositories {
41 | google()
42 | jcenter()
43 | maven { url 'https://jitpack.io' }
44 |
45 | }
46 | }
47 | ```
48 |
49 | 2. 添加依赖项
50 |
51 | ```kotlin
52 | dependencies {
53 | // 必须
54 | implementation 'com.github.D10NGYANG:NetworkDebugAssistant:0.1.7'
55 | // 必须 协程
56 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
57 | }
58 | ```
59 |
60 | ## UDP广播
61 |
62 | ```kotlin
63 | val thread = UdpBroadThread(this, 12345, object : OnNetThreadListener{
64 | override fun onAcceptSocket(ipAddress: String) {
65 | // 不需要
66 | }
67 |
68 | override fun onConnectFailed(ipAddress: String) {
69 | // 打开端口失败
70 | }
71 |
72 | override fun onConnected(ipAddress: String) {
73 | // 打开端口成功
74 | }
75 |
76 | override fun onDisconnect(ipAddress: String) {
77 | // 关闭UDP
78 | }
79 |
80 | override fun onError(ipAddress: String, error: String) {
81 | // 发生错误
82 | }
83 |
84 | override fun onReceive(ipAddress: String, port: Int, time: Long, data: ByteArray) {
85 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
86 | }
87 | })
88 | thread.start()
89 | // 发送数据
90 | thread.send("192.168.1.123", 12345, data)
91 | ```
92 |
93 | lambda:
94 |
95 | ```kotlin
96 | val thread = UdpBroadThread(this, 12345) {
97 | onThreadAcceptSocket {
98 | // 不需要
99 | }
100 | onThreadConnectFailed {
101 | // 打开端口失败
102 | }
103 | onThreadConnected {
104 | // 打开端口成功
105 | }
106 | onThreadDisconnect {
107 | // 关闭UDP
108 | }
109 | onThreadError { ipAddress, error ->
110 | // 发生错误
111 | }
112 | onThreadReceive { ipAddress, port, time, data ->
113 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
114 | }
115 | }
116 | ```
117 |
118 |
119 |
120 | ## UDP组播
121 |
122 | ```kotlin
123 | val thread = UdpMultiThread(this, "192.168.11.255", 12345, object : OnNetThreadListener{
124 | override fun onAcceptSocket(ipAddress: String) {
125 | // 不需要
126 | }
127 |
128 | override fun onConnectFailed(ipAddress: String) {
129 | // 打开端口失败
130 | }
131 |
132 | override fun onConnected(ipAddress: String) {
133 | // 打开端口成功
134 | }
135 |
136 | override fun onDisconnect(ipAddress: String) {
137 | // 关闭UDP
138 | }
139 |
140 | override fun onError(ipAddress: String, error: String) {
141 | // 发生错误
142 | }
143 |
144 | override fun onReceive(ipAddress: String, port: Int, time: Long, data: ByteArray) {
145 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
146 | }
147 | })
148 | thread.start()
149 | // 发送数据
150 | thread.send("192.168.11.255", "12345", data)
151 | ```
152 |
153 | lambda:
154 |
155 | ```kotlin
156 | val thread = UdpMultiThread(this, "192.168.11.255", 12345) {
157 | onThreadAcceptSocket {
158 | // 不需要
159 | }
160 | onThreadConnectFailed {
161 | // 打开端口失败
162 | }
163 | onThreadConnected {
164 | // 打开端口成功
165 | }
166 | onThreadDisconnect {
167 | // 关闭UDP
168 | }
169 | onThreadError { ipAddress, error ->
170 | // 发生错误
171 | }
172 | onThreadReceive { ipAddress, port, time, data ->
173 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
174 | }
175 | }
176 | ```
177 |
178 |
179 |
180 | ## TCP客户端
181 |
182 | ```kotlin
183 | val thread = TcpClientThread("192.168.1.123", 12345, object : OnNetThreadListener{
184 | override fun onAcceptSocket(ipAddress: String) {
185 | // 不需要
186 | }
187 |
188 | override fun onConnectFailed(ipAddress: String) {
189 | // 连接服务器失败
190 | }
191 |
192 | override fun onConnected(ipAddress: String) {
193 | // 连接服务器成功
194 | }
195 |
196 | override fun onDisconnect(ipAddress: String) {
197 | // 服务器连接断开
198 | }
199 |
200 | override fun onError(ipAddress: String, error: String) {
201 | // 发生错误
202 | }
203 |
204 | override fun onReceive(ipAddress: String, port: Int, time: Long, data: ByteArray) {
205 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
206 | }
207 | })
208 | thread.start()
209 | // 发送数据
210 | thread.send(data)
211 | ```
212 |
213 | lambda:
214 |
215 | ```kotlin
216 | val thread = TcpClientThread("192.168.1.123", 12345) {
217 | onThreadAcceptSocket {
218 | // 不需要
219 | }
220 | onThreadConnectFailed {
221 | // 连接服务器失败
222 | }
223 | onThreadConnected {
224 | // 连接服务器成功
225 | }
226 | onThreadDisconnect {
227 | // 服务器连接断开
228 | }
229 | onThreadError { ipAddress, error ->
230 | // 发生错误
231 | }
232 | onThreadReceive { ipAddress, port, time, data ->
233 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
234 | }
235 | }
236 | ```
237 |
238 |
239 |
240 | ## TCP服务器
241 |
242 | ```kotlin
243 | val thread = TcpServerThread(12345, object : OnNetThreadListener{
244 | override fun onAcceptSocket(ipAddress: String) {
245 | // 有客户端连接上来了,ipAddress客户端IP地址
246 | }
247 |
248 | override fun onConnectFailed(ipAddress: String) {
249 | // 启动服务器失败
250 | }
251 |
252 | override fun onConnected(ipAddress: String) {
253 | // 启动服务器成功
254 | }
255 |
256 | override fun onDisconnect(ipAddress: String) {
257 | // 关闭服务器
258 | }
259 |
260 | override fun onError(ipAddress: String, error: String) {
261 | // 发生错误
262 | }
263 |
264 | override fun onReceive(ipAddress: String, port: Int, time: Long, data: ByteArray) {
265 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
266 | }
267 | })
268 | thread.start()
269 | // 发送数据给特定客户端
270 | thread.send("192.168.11.255", "12345", data)
271 | // 发送数据给全部已连接的客户端
272 | thread.send(data)
273 | // 断开客户端
274 | thread.disconnectSocket(socket)
275 | ```
276 |
277 | lambda:
278 |
279 | ```kotlin
280 | val thread = TcpServerThread(12345) {
281 | onThreadAcceptSocket {
282 | // 有客户端连接上来了,ipAddress客户端IP地址
283 | }
284 | onThreadConnectFailed {
285 | // 启动服务器失败
286 | }
287 | onThreadConnected {
288 | // 启动服务器成功
289 | }
290 | onThreadDisconnect {
291 | // 关闭服务器
292 | }
293 | onThreadError { ipAddress, error ->
294 | // 发生错误
295 | }
296 | onThreadReceive { ipAddress, port, time, data ->
297 | // 接受到信息,ipAddress消息来源地址,port消息来源端口,time消息到达时间,data消息内容
298 | }
299 | }
300 | ```
301 |
302 |
303 | ## PING
304 | PING并且查看详细结果
305 | ```kotlin
306 | // 连续PING几次,监听返回内容,进行打印显示
307 | GlobalScope.launch {
308 | ping(address).collect {
309 | withContext(Dispatchers.Main) {
310 | binding.txtReceive.append(it)
311 | }
312 | }
313 | }
314 | ```
315 | PING一次得到是否通过的结果
316 | ```kotlin
317 | GlobalScope.launch {
318 | // 这里可能耗费挺长时间,所以在子线程工作
319 | val isSuccess = pingOnce(address)
320 | withContext(Dispatchers.Main) {
321 | showToast("PING测试结果=$isSuccess")
322 | }
323 | }
324 | ```
325 |
326 | # 混淆规则
327 |
328 | ```kotlin
329 | # 保留UDP/TCP库
330 | -keep class com.dlong.dl10netassistant.** {*;}
331 | -dontwarn com.dlong.dl10netassistant.**
332 | ```
333 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tcp_client_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
13 |
14 |
15 |
20 |
21 |
35 |
36 |
45 |
46 |
50 |
51 |
61 |
62 |
69 |
70 |
76 |
77 |
81 |
82 |
90 |
91 |
97 |
98 |
105 |
106 |
107 |
108 |
116 |
117 |
123 |
124 |
128 |
129 |
137 |
138 |
144 |
145 |
152 |
153 |
154 |
155 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dlong/networkdebugassistant/utils/FileProviderUtils.java:
--------------------------------------------------------------------------------
1 | package com.dlong.networkdebugassistant.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.ContentUris;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.pm.PackageManager;
8 | import android.content.pm.ResolveInfo;
9 | import android.database.Cursor;
10 | import android.net.Uri;
11 | import android.os.Build;
12 | import android.os.Environment;
13 | import android.provider.DocumentsContract;
14 | import android.provider.MediaStore;
15 | import android.util.Log;
16 |
17 | import androidx.core.content.FileProvider;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.util.List;
22 |
23 | /**
24 | * 适配Android 7 读取文件工具
25 | *
26 | * @author D10NG
27 | * @date on 2019-05-15 08:45
28 | */
29 | public class FileProviderUtils {
30 |
31 | /**
32 | * 获取文件的Uri
33 | * @param context
34 | * @param file
35 | * @return
36 | */
37 | public static Uri getUriForFile(Context context, File file) {
38 | Uri fileUri = null;
39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
40 | fileUri = getUriForFile24(context, file);
41 | } else {
42 | fileUri = Uri.fromFile(file);
43 | }
44 | return fileUri;
45 | }
46 |
47 | /**
48 | * Android 7 获取文件的Uri
49 | * @param context
50 | * @param file
51 | * @return
52 | */
53 | private static Uri getUriForFile24(Context context, File file) {
54 | return FileProvider.getUriForFile(context,
55 | context.getPackageName() + ".fileprovider", file);
56 | }
57 |
58 | /**
59 | * 设定intent的data和type
60 | * @param context
61 | * @param intent
62 | * @param type
63 | * @param file
64 | * @param writeAble
65 | */
66 | public static void setIntentDataAndType(Context context,
67 | Intent intent,
68 | String type,
69 | File file,
70 | boolean writeAble) {
71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
72 | intent.setDataAndType(getUriForFile(context, file), type);
73 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
74 | if (writeAble) {
75 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
76 | }
77 | } else {
78 | intent.setDataAndType(Uri.fromFile(file), type);
79 | // apk放在cache文件中,需要获取读写权限
80 | chmod("777", file.getAbsolutePath());
81 | }
82 | }
83 |
84 | /**
85 | * 修改文件权限
86 | * @param permission
87 | * @param path
88 | */
89 | public static void chmod(String permission, String path) {
90 | try {
91 | String command = "chmod " + permission + " " + path;
92 | Runtime runtime = Runtime.getRuntime();
93 | runtime.exec(command);
94 | } catch (IOException e) {
95 | e.printStackTrace();
96 | }
97 | }
98 |
99 | /**
100 | * 设定intent的data
101 | * @param context
102 | * @param intent
103 | * @param file
104 | * @param writeAble
105 | */
106 | public static void setIntentData(Context context,
107 | Intent intent,
108 | File file,
109 | boolean writeAble) {
110 | if (Build.VERSION.SDK_INT >= 24) {
111 | intent.setData(getUriForFile(context, file));
112 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
113 | if (writeAble) {
114 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
115 | }
116 | } else {
117 | intent.setData(Uri.fromFile(file));
118 | }
119 | }
120 |
121 | /**
122 | * 授予权限
123 | * @param context
124 | * @param intent
125 | * @param uri
126 | * @param writeAble
127 | */
128 | public static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) {
129 |
130 | int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION;
131 | if (writeAble) {
132 | flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
133 | }
134 | intent.addFlags(flag);
135 | List resInfoList = context.getPackageManager()
136 | .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
137 | for (ResolveInfo resolveInfo : resInfoList) {
138 | String packageName = resolveInfo.activityInfo.packageName;
139 | context.grantUriPermission(packageName, uri, flag);
140 | }
141 | }
142 |
143 | /**
144 | * 根据URI获取文件真实路径(兼容多机型)
145 | * @param context
146 | * @param uri
147 | * @return
148 | */
149 | public static String getFilePathByUri(Context context, Uri uri) {
150 | // 判断uri的标头是 content 还是 file,分别用不同的方法处理
151 | if ("content".equalsIgnoreCase(uri.getScheme())) {
152 | int sdkVersion = Build.VERSION.SDK_INT;
153 | if (sdkVersion >= 19) {
154 | // api >= 19
155 | return getRealPathFromUriAboveApi19(context, uri);
156 | } else {
157 | // api < 19
158 | return getRealPathFromUriBelowAPI19(context, uri);
159 | }
160 | } else if ("file".equalsIgnoreCase(uri.getScheme())) {
161 | return uri.getPath();
162 | }
163 | return null;
164 | }
165 |
166 | /**
167 | * 适配api19及以上,根据uri获取图片的绝对路径
168 | *
169 | * @param context 上下文对象
170 | * @param uri 图片的Uri
171 | * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
172 | */
173 | @SuppressLint("NewApi")
174 | private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {
175 | String filePath = null;
176 | if (DocumentsContract.isDocumentUri(context, uri)) {
177 | // 如果是document类型的 uri, 则通过document id来进行处理
178 | String documentId = DocumentsContract.getDocumentId(uri);
179 | if (isMediaDocument(uri)) { // MediaProvider
180 | // 使用':'分割
181 | String type = documentId.split(":")[0];
182 | String id = documentId.split(":")[1];
183 |
184 | String selection = MediaStore.Images.Media._ID + "=?";
185 | String[] selectionArgs = {id};
186 |
187 | // 判断文件类型
188 | Uri contentUri = null;
189 | if ("image".equals(type)) {
190 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
191 | } else if ("video".equals(type)) {
192 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
193 | } else if ("audio".equals(type)) {
194 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
195 | }
196 |
197 | filePath = getDataColumn(context, contentUri, selection, selectionArgs);
198 | } else if (isDownloadsDocument(uri)) {
199 | // DownloadsProvider
200 | Uri contentUri = ContentUris.withAppendedId(
201 | Uri.parse("content://downloads/public_downloads"),
202 | Long.valueOf(documentId));
203 | filePath = getDataColumn(context, contentUri, null, null);
204 | }else if (isExternalStorageDocument(uri)) {
205 | // ExternalStorageProvider
206 | final String docId = DocumentsContract.getDocumentId(uri);
207 | final String[] split = docId.split(":");
208 | final String type = split[0];
209 | if ("primary".equalsIgnoreCase(type)) {
210 | filePath = Environment.getExternalStorageDirectory() + "/" + split[1];
211 | }
212 | }else {
213 | Log.e("FileHandlerUtil", "路径错误");
214 | }
215 | } else if ("content".equalsIgnoreCase(uri.getScheme())) {
216 | // 如果是 content 类型的 Uri
217 | filePath = getDataColumn(context, uri, null, null);
218 | } else if ("file".equals(uri.getScheme())) {
219 | // 如果是 file 类型的 Uri,直接获取图片对应的路径
220 | filePath = uri.getPath();
221 | }
222 | return filePath;
223 | }
224 |
225 | /**
226 | * 适配api19以下(不包括api19),根据uri获取图片的绝对路径
227 | *
228 | * @param context 上下文对象
229 | * @param uri 图片的Uri
230 | * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
231 | */
232 | private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {
233 | return getDataColumn(context, uri, null, null);
234 | }
235 |
236 | /**
237 | * 获取数据库表中的 _data 列,即返回Uri对应的文件路径
238 | *
239 | * @return
240 | */
241 | private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
242 | String path = null;
243 |
244 | String[] projection = new String[]{MediaStore.Images.Media.DATA};
245 | Cursor cursor = null;
246 | try {
247 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
248 | if (cursor != null && cursor.moveToFirst()) {
249 | int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
250 | path = cursor.getString(columnIndex);
251 | }
252 | } catch (Exception e) {
253 | if (cursor != null) {
254 | cursor.close();
255 | }
256 | }
257 | return path;
258 | }
259 |
260 | /**
261 | * @param uri the Uri to check
262 | * @return Whether the Uri authority is MediaProvider
263 | */
264 | private static boolean isMediaDocument(Uri uri) {
265 | return "com.android.providers.media.documents".equals(uri.getAuthority());
266 | }
267 |
268 | private static boolean isExternalStorageDocument(Uri uri) {
269 | return "com.android.externalstorage.documents".equals(uri.getAuthority());
270 | }
271 |
272 | /**
273 | * @param uri the Uri to check
274 | * @return Whether the Uri authority is DownloadsProvider
275 | */
276 | private static boolean isDownloadsDocument(Uri uri) {
277 | return "com.android.providers.downloads.documents".equals(uri.getAuthority());
278 | }
279 | }
280 |
--------------------------------------------------------------------------------