├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── values-night
│ │ │ └── themes.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── assets
│ │ └── monitor.properties
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── android
│ │ └── monitor
│ │ └── demo
│ │ └── MainActivity.kt
├── proguard-rules.pro
└── build.gradle
├── monitor
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── mipmap-xxhdpi
│ │ │ ├── monitor_logo.png
│ │ │ ├── monitor_icon_menu.png
│ │ │ ├── monitor_app_back_icon.png
│ │ │ ├── monitor_icon_tab_file.png
│ │ │ ├── monitor_icon_right_arrow.png
│ │ │ ├── monitor_icon_share_white.png
│ │ │ └── monitor_icon_tab_network.png
│ │ ├── drawable
│ │ │ ├── bg_flutter_tag.xml
│ │ │ ├── bg_monitor_theme.xml
│ │ │ ├── bg_radio_center.xml
│ │ │ ├── bg_radio_left.xml
│ │ │ └── bg_radio_right.xml
│ │ ├── anim
│ │ │ ├── bottom_to_top.xml
│ │ │ └── top_to_bottom.xml
│ │ ├── color
│ │ │ └── text_color_radio.xml
│ │ ├── menu
│ │ │ └── bottom_nav_menu.xml
│ │ └── layout
│ │ │ ├── include_status_bar_view_layout.xml
│ │ │ ├── fragment_monitor_main.xml
│ │ │ ├── fragment_sp_list.xml
│ │ │ ├── item_sp_file.xml
│ │ │ ├── item_sp_file_detail.xml
│ │ │ ├── activity_monitor_detail.xml
│ │ │ ├── dialog_sp_modify.xml
│ │ │ ├── activity_monitor_main.xml
│ │ │ ├── activity_sp_file_detail.xml
│ │ │ ├── item_monitor.xml
│ │ │ ├── fragment_monitor_request.xml
│ │ │ ├── fragment_monitor_response.xml
│ │ │ └── activity_monitor_config.xml
│ │ ├── java
│ │ └── com
│ │ │ └── lygttpod
│ │ │ └── monitor
│ │ │ ├── data
│ │ │ ├── HttpHeader.kt
│ │ │ ├── SpValueInfo.kt
│ │ │ ├── PropertiesData.kt
│ │ │ ├── SpData.kt
│ │ │ └── MonitorData.kt
│ │ │ ├── enum
│ │ │ ├── WeakNetworkType.kt
│ │ │ └── SPValueType.kt
│ │ │ ├── utils
│ │ │ ├── GlobalConfig.kt
│ │ │ ├── DateKtx.kt
│ │ │ ├── ServiceDataProvider.kt
│ │ │ ├── NetworkHelper.kt
│ │ │ ├── FormatHelper.kt
│ │ │ ├── GsonHelper.kt
│ │ │ ├── StringKtx.kt
│ │ │ ├── MonitorProperties.kt
│ │ │ ├── OkHttpKtx.kt
│ │ │ └── SPUtils.kt
│ │ │ ├── room
│ │ │ ├── MonitorDatabase.kt
│ │ │ └── MonitorDao.kt
│ │ │ ├── interceptor
│ │ │ ├── MonitorMockInterceptor.kt
│ │ │ ├── MonitorMockResponseInterceptor.kt
│ │ │ ├── MonitorWeakNetworkInterceptor.kt
│ │ │ └── MonitorInterceptor.kt
│ │ │ ├── adapter
│ │ │ ├── MonitorPagerAdapter.kt
│ │ │ └── MonitorListAdapter.kt
│ │ │ ├── provider
│ │ │ └── MonitorProvider.kt
│ │ │ ├── ui
│ │ │ ├── sp
│ │ │ │ ├── SpFileAdapter.kt
│ │ │ │ ├── SpFileDetailAdapter.kt
│ │ │ │ ├── SPFileListFragment.kt
│ │ │ │ └── SPFileDetailActivity.kt
│ │ │ ├── MonitorRequestFragment.kt
│ │ │ ├── MonitorResponseFragment.kt
│ │ │ ├── request
│ │ │ │ └── MonitorMainFragment.kt
│ │ │ ├── MonitorDetailActivity.kt
│ │ │ ├── MonitorMainActivity.kt
│ │ │ ├── dialog
│ │ │ │ └── SpModifyDialog.kt
│ │ │ └── MonitorConfigActivity.kt
│ │ │ ├── mock
│ │ │ └── MockHelper.kt
│ │ │ ├── weaknetwork
│ │ │ ├── SpeedLimitResponseBody.kt
│ │ │ └── WeakNetworkHelper.kt
│ │ │ ├── service
│ │ │ └── MonitorService.kt
│ │ │ └── MonitorHelper.kt
│ │ ├── AndroidManifest.xml
│ │ └── assets
│ │ └── sp_index.html
├── gradle.properties
├── proguard-rules.pro
└── build.gradle
├── monitor-plugin
├── .gitignore
├── src
│ └── main
│ │ ├── resources
│ │ └── META-INF
│ │ │ └── gradle-plugins
│ │ │ └── monitor-plugin.properties
│ │ └── kotlin
│ │ └── com
│ │ └── lygttpod
│ │ └── monitor
│ │ ├── okhttp
│ │ ├── OkHttpWeaver.kt
│ │ ├── OkHttpTransform.kt
│ │ ├── OkHttpMethodAdapter.kt
│ │ └── OkHttpClassVisitor.kt
│ │ └── plugin
│ │ └── MonitorPlugin.kt
├── gradle.properties
└── build.gradle
├── android-secring.gpg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── .gitignore
├── upload.gradle
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/monitor/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/monitor-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/monitor/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | # monitor
2 | -keep class com.lygttpod.monitor.** { *; }
--------------------------------------------------------------------------------
/android-secring.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/android-secring.gpg
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/monitor/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 抓包
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "AndroidMonitor"
2 | include ':app'
3 | include ':monitor'
4 | include ':monitor-plugin'
5 |
--------------------------------------------------------------------------------
/monitor/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=monitor
2 | VERSION_NAME=0.1.2
3 | POM_NAME=monitor
4 | # 是否发布到本地
5 | UPLOAD_LOCAL=false
--------------------------------------------------------------------------------
/monitor-plugin/src/main/resources/META-INF/gradle-plugins/monitor-plugin.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.lygttpod.monitor.plugin.MonitorPlugin
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/monitor-plugin/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=monitor-plugin
2 | VERSION_NAME=0.0.2
3 | POM_NAME=monitor-plugin
4 | # 是否发布到本地
5 | UPLOAD_LOCAL=false
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/data/HttpHeader.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.data
2 |
3 | data class HttpHeader(val name: String, val value: String)
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_menu.png
--------------------------------------------------------------------------------
/monitor/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 30dp
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | /.idea/
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 |
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_app_back_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_app_back_icon.png
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_file.png
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_right_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_right_arrow.png
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_share_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_share_white.png
--------------------------------------------------------------------------------
/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lygttpod/AndroidMonitor/HEAD/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_network.png
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/data/SpValueInfo.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.data
2 |
3 | import com.lygttpod.monitor.enum.SPValueType
4 |
5 | class SpValueInfo(val value: Any?, val type: SPValueType = SPValueType.String)
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/enum/WeakNetworkType.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.enum
2 |
3 | enum class WeakNetworkType {
4 | //超时
5 | TIME_OUT,
6 |
7 | //限速
8 | SPEED_LIMIT,
9 |
10 | //断网
11 | NO_NETWORK
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidMonitor
3 | AndroidMonitor抓包
4 | 1、局域网内通过电脑浏览器打开上述服务地址\n2、点击发送请求\n3、在网页端或手机抓包app可以查看请求数据
5 |
--------------------------------------------------------------------------------
/monitor/src/main/res/drawable/bg_flutter_tag.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/monitor/src/main/res/anim/bottom_to_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/monitor/src/main/res/anim/top_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jul 06 20:00:06 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/enum/SPValueType.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.enum
2 |
3 | enum class SPValueType(val value: kotlin.String) {
4 | Int("Int"),
5 | Double("Double"),
6 | Long("Long"),
7 | Float("Float"),
8 | String("String"),
9 | Boolean("Boolean");
10 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/color/text_color_radio.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/GlobalConfig.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 |
4 | var lastUpdateDataId = 0L
5 |
6 | /**
7 | * content-type类型 https://www.runoob.com/http/http-content-type.html
8 | */
9 | var defaultContentTypes = "application/json,application/xml,text/html,text/plain,text/xml"
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/monitor/src/main/res/drawable/bg_monitor_theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/data/PropertiesData.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.data
2 |
3 | class PropertiesData(
4 | var port: String,
5 | var dbName: String,
6 | var whiteContentTypes: String?,//ContentType白名单
7 | var whiteHosts: String?,//host白名单
8 | var blackHosts: String?,//host黑名单
9 | var isFilterIPAddressHost: Boolean = false//是否过滤纯IP地址的host
10 | )
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/data/SpData.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.data
2 |
3 | /**
4 | *
5 | * author : Allen
6 | * date : 2022/8/6
7 | * desc :
8 | *
9 | */
10 | data class SpData(
11 | val fileName: String?,
12 | val subList: List? = null
13 | )
14 |
15 | data class SpSubData(val keyName: String?, var keyValue: SpValueInfo?)
16 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/room/MonitorDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.room
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.lygttpod.monitor.data.MonitorData
6 |
7 | @Database(entities = [MonitorData::class], version = 1, exportSchema = false)
8 | abstract class MonitorDatabase : RoomDatabase() {
9 |
10 | abstract fun monitorDao(): MonitorDao
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/monitor-plugin/src/main/kotlin/com/lygttpod/monitor/okhttp/OkHttpWeaver.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.okhttp
2 |
3 | import com.quinn.hunter.transform.asm.BaseWeaver
4 | import org.objectweb.asm.ClassVisitor
5 | import org.objectweb.asm.ClassWriter
6 |
7 |
8 | class OkHttpWeaver : BaseWeaver() {
9 |
10 | override fun wrapClassWriter(classWriter: ClassWriter?): ClassVisitor {
11 | return OkHttpClassVisitor(classWriter)
12 | }
13 | }
--------------------------------------------------------------------------------
/monitor-plugin/src/main/kotlin/com/lygttpod/monitor/okhttp/OkHttpTransform.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.okhttp
2 |
3 | import com.quinn.hunter.transform.HunterTransform
4 | import com.quinn.hunter.transform.RunVariant
5 | import org.gradle.api.Project
6 |
7 | class OkHttpTransform(project: Project?) : HunterTransform(project) {
8 | init { this.bytecodeWeaver = OkHttpWeaver() }
9 |
10 | override fun getRunVariant(): RunVariant {
11 | return RunVariant.DEBUG
12 | }
13 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/DateKtx.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.*
5 |
6 | val TIME_SHORT = SimpleDateFormat("HH:mm:ss SSS", Locale.CHINA)
7 |
8 | val TIME_LONG = SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS", Locale.CHINA)
9 |
10 | fun Date?.formatData(format: SimpleDateFormat = TIME_SHORT): String {
11 | return if (this == null) {
12 | ""
13 | } else format.format(this)
14 | }
15 |
--------------------------------------------------------------------------------
/monitor/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/include_status_bar_view_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/ServiceDataProvider.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import com.lygttpod.monitor.MonitorHelper
4 | import com.lygttpod.monitor.data.MonitorData
5 |
6 | object ServiceDataProvider {
7 |
8 | fun getMonitorDataList(limit: Int = 50, offset: Int = 0): MutableList {
9 | return MonitorHelper.getMonitorDataList(limit, offset)
10 | }
11 |
12 | fun getMonitorDataByLastId(lastUpdateDataId: Long): MutableList {
13 | return MonitorHelper.getMonitorDataByLastId(lastUpdateDataId)
14 | }
15 | }
--------------------------------------------------------------------------------
/monitor-plugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 | apply plugin: 'kotlin'
3 |
4 | //gradle 开发 sdk 依赖
5 | dependencies {
6 | implementation gradleApi()
7 | implementation localGroovy()
8 |
9 | //noinspection GradleDependency
10 | implementation 'org.ow2.asm:asm:7.1'
11 | //noinspection GradleDependency
12 | implementation 'org.ow2.asm:asm-util:7.1'
13 | //noinspection GradleDependency
14 | implementation 'org.ow2.asm:asm-commons:7.1'
15 | implementation 'com.android.tools.build:gradle:4.1.3'
16 | implementation 'cn.quinnchen.hunter:hunter-transform:1.2.3'
17 | }
18 |
19 | apply from: '../upload.gradle'
--------------------------------------------------------------------------------
/monitor/src/main/res/drawable/bg_radio_center.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/assets/monitor.properties:
--------------------------------------------------------------------------------
1 | # 抓包助手参数配置
2 | # Default port = 9527
3 | # Default dbName = monitor_room_db
4 | # ContentTypes白名单,默认application/json,application/xml,text/html,text/plain,text/xml
5 | # Default whiteContentTypes = application/json,application/xml,text/html,text/plain,text/xml
6 | # Hosts白名单,默认全部是白名单
7 | # Default whiteHosts =
8 | # Hosts黑名单,默认没有黑名单
9 | # Default blackHosts =
10 | # 是否过滤纯IP地址的host 默认false
11 | # Default isFilterIPAddressHost = false
12 |
13 | # 如何多个项目都集成抓包工具,可以设置不同的端口进行访问,端口号唯一
14 | monitor.port=9527
15 | monitor.dbName=demo_monitor_room_db
16 | monitor.whiteContentTypes=application/json,application/xml,text/html,text/plain,text/xml
17 | monitor.isFilterIPAddressHost=false
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/interceptor/MonitorMockInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.interceptor
2 |
3 | import android.net.Uri
4 | import com.lygttpod.monitor.mock.MockHelper
5 | import okhttp3.Interceptor
6 | import okhttp3.Response
7 |
8 | class MonitorMockInterceptor : Interceptor {
9 | override fun intercept(chain: Interceptor.Chain): Response {
10 | val request = chain.request()
11 | val url = request.url.toString()
12 | val path = Uri.parse(url).path
13 | if (MockHelper.isMockByNetworkResponse(path)) {
14 | return MockHelper.buildMockServer(chain, request)
15 | }
16 | return chain.proceed(request)
17 | }
18 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/interceptor/MonitorMockResponseInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.interceptor
2 |
3 | import android.net.Uri
4 | import com.lygttpod.monitor.mock.MockHelper
5 | import okhttp3.Interceptor
6 | import okhttp3.Response
7 |
8 | class MonitorMockResponseInterceptor : Interceptor {
9 | override fun intercept(chain: Interceptor.Chain): Response {
10 | val request = chain.request()
11 | val url = request.url.toString()
12 | val path = Uri.parse(url).path
13 | if (MockHelper.isMockByCustomResponse(path)) {
14 | return MockHelper.mockResponseBody(chain)
15 | }
16 | return chain.proceed(request)
17 | }
18 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/drawable/bg_radio_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/monitor/src/main/res/drawable/bg_radio_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/monitor/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/monitor/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5dbdf7
4 | #5d91f7
5 | #D81B60
6 | #e6e6e6
7 |
8 | #2E3032
9 | #888F86
10 | #F44336
11 | #2196F3
12 | #673AB7
13 | #26E79F
14 |
15 | #ffffff
16 | #000000
17 | #22aaaaaa
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/interceptor/MonitorWeakNetworkInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.interceptor
2 |
3 | import com.lygttpod.monitor.enum.WeakNetworkType
4 | import com.lygttpod.monitor.weaknetwork.WeakNetworkHelper
5 | import okhttp3.Interceptor
6 | import okhttp3.Response
7 |
8 | class MonitorWeakNetworkInterceptor : Interceptor {
9 |
10 | override fun intercept(chain: Interceptor.Chain): Response {
11 | if (!WeakNetworkHelper.isOpen) {
12 | return chain.proceed(chain.request())
13 | }
14 | return when (WeakNetworkHelper.weakNetType()) {
15 | WeakNetworkType.TIME_OUT -> {
16 | WeakNetworkHelper.mockTimeout(chain)
17 | }
18 | WeakNetworkType.SPEED_LIMIT -> {
19 | WeakNetworkHelper.mockSpeedLimit(chain)
20 | }
21 | WeakNetworkType.NO_NETWORK -> {
22 | WeakNetworkHelper.mockNoNetwork(chain)
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/adapter/MonitorPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.adapter
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.fragment.app.FragmentManager
5 | import androidx.fragment.app.FragmentPagerAdapter
6 |
7 |
8 | class MonitorPagerAdapter constructor(fm: FragmentManager) :
9 | FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
10 |
11 | private val fragmentList = ArrayList()
12 |
13 | private val fragmentTitleList = ArrayList()
14 |
15 | internal fun addFragment(fragment: Fragment, title: String) {
16 | fragmentList.add(fragment)
17 | fragmentTitleList.add(title)
18 | }
19 |
20 | override fun getItem(position: Int): Fragment {
21 | return fragmentList[position]
22 | }
23 |
24 | override fun getCount(): Int {
25 | return fragmentList.size
26 | }
27 |
28 | override fun getPageTitle(position: Int): CharSequence {
29 | return fragmentTitleList[position]
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/NetworkHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import android.util.Log
4 | import java.net.Inet4Address
5 | import java.net.NetworkInterface
6 | import java.net.SocketException
7 |
8 | fun getPhoneWifiIpAddress(): String? {
9 | try {
10 | val networkInterfaces = NetworkInterface.getNetworkInterfaces() ?: return null
11 | while (networkInterfaces.hasMoreElements()) {
12 | val networkInterface = networkInterfaces.nextElement()
13 | val inetAddresses = networkInterface.inetAddresses
14 | while (inetAddresses.hasMoreElements()) {
15 | val inetAddress = inetAddresses.nextElement()
16 | if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
17 | return inetAddress.getHostAddress()
18 | }
19 | }
20 | }
21 | } catch (e: SocketException) {
22 | Log.e("MonitorPCService", "get ip", e)
23 | return null
24 | }
25 | return null
26 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/fragment_monitor_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/monitor-plugin/src/main/kotlin/com/lygttpod/monitor/okhttp/OkHttpMethodAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.okhttp
2 |
3 | import org.objectweb.asm.MethodVisitor
4 | import org.objectweb.asm.Opcodes
5 | import org.objectweb.asm.commons.AdviceAdapter
6 |
7 | class OkHttpMethodAdapter(methodVisitor: MethodVisitor?, access: Int, name: String?, descriptor: String?) : AdviceAdapter(Opcodes.ASM7, methodVisitor, access, name, descriptor) {
8 |
9 | override fun onMethodExit(opcode: Int) {
10 | super.onMethodExit(opcode)
11 | mv?.let {
12 | it.visitVarInsn(ALOAD, 0)
13 | it.visitFieldInsn(GETFIELD, "okhttp3/OkHttpClient\$Builder", "interceptors", "Ljava/util/List;")
14 | it.visitFieldInsn(GETSTATIC, "com/lygttpod/monitor/MonitorHelper", "INSTANCE", "Lcom/lygttpod/monitor/MonitorHelper;")
15 | it.visitMethodInsn(INVOKEVIRTUAL, "com/lygttpod/monitor/MonitorHelper", "getHookInterceptors", "()Ljava/util/List;", false)
16 | it.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true)
17 | it.visitInsn(POP)
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/upload.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.vanniktech.maven.publish'
2 |
3 | allprojects {
4 | plugins.withId("com.vanniktech.maven.publish") {
5 | mavenPublish {
6 | sonatypeHost = "S01"
7 | }
8 | }
9 | }
10 |
11 | publishing {
12 | repositories {
13 | maven {
14 | allowInsecureProtocol = true
15 | def uploadLocal = findProperty("UPLOAD_LOCAL") ? UPLOAD_LOCAL : "false"
16 | if (new Boolean(uploadLocal)) {
17 | String homeDir = System.getProperty("user.home")
18 | url = "file://$homeDir/maven/repos/"
19 | } else {
20 | url = version.endsWith('SNAPSHOT') ? mavenSnapshotsUrl : mavenReleasesUrl
21 | credentials {
22 | def localProperties = new Properties()
23 | localProperties.load(new FileInputStream(rootProject.file("local.properties")))
24 | username = localProperties.getProperty('mavenCentralUsername')
25 | password = localProperties.getProperty('mavenCentralPassword')
26 | }
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/monitor-plugin/src/main/kotlin/com/lygttpod/monitor/plugin/MonitorPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.plugin
2 |
3 | import com.android.build.gradle.AppExtension
4 | import com.lygttpod.monitor.okhttp.OkHttpTransform
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import java.util.*
8 |
9 | class MonitorPlugin : Plugin {
10 |
11 | override fun apply(project: Project) {
12 | var enableMonitorPlugin = true
13 | val properties = Properties()
14 | val file = project.rootProject.file("local.properties")
15 | if (file.exists()) {
16 | properties.load(file.inputStream())
17 | enableMonitorPlugin = properties.getProperty("monitor.enablePlugin", "true")?.toBoolean() ?: true
18 | }
19 | println("MonitorPlugin---->enableMonitorPlugin = $enableMonitorPlugin")
20 | if (!enableMonitorPlugin) return
21 | try {
22 | val appException: AppExtension = project.extensions.getByName("android") as AppExtension
23 | appException.registerTransform(OkHttpTransform(project))
24 | } catch (e: Exception) {
25 | e.printStackTrace()
26 | }
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/fragment_sp_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/room/MonitorDao.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.room
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.room.*
5 | import com.lygttpod.monitor.data.MonitorData
6 |
7 | @Dao
8 | interface MonitorDao {
9 | @Query("SELECT * FROM monitor WHERE id > :lastId ORDER BY id DESC")
10 | fun queryByLastIdForAndroid(lastId: Long): LiveData>
11 |
12 | @Query("SELECT * FROM monitor ORDER BY id DESC LIMIT :limit OFFSET :offset")
13 | fun queryByOffsetForAndroid(limit: Int, offset: Int): LiveData>
14 |
15 | @Query("SELECT * FROM monitor")
16 | fun queryAllForAndroid(): LiveData>
17 |
18 | @Query("SELECT * FROM monitor WHERE id > :lastId ORDER BY id DESC")
19 | fun queryByLastId(lastId: Long): MutableList
20 |
21 | @Query("SELECT * FROM monitor ORDER BY id DESC LIMIT :limit OFFSET :offset")
22 | fun queryByOffset(limit: Int, offset: Int): MutableList
23 |
24 | @Query("SELECT * FROM monitor")
25 | fun queryAll(): MutableList
26 |
27 | @Insert
28 | fun insert(data: MonitorData)
29 |
30 | @Update
31 | fun update(data: MonitorData)
32 |
33 | @Query("DELETE FROM monitor")
34 | fun deleteAll()
35 | }
--------------------------------------------------------------------------------
/monitor-plugin/src/main/kotlin/com/lygttpod/monitor/okhttp/OkHttpClassVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.okhttp
2 |
3 | import org.objectweb.asm.ClassVisitor
4 | import org.objectweb.asm.MethodVisitor
5 | import org.objectweb.asm.Opcodes
6 |
7 | class OkHttpClassVisitor : ClassVisitor {
8 |
9 | private var className: String? = null
10 |
11 | constructor(api: Int, classVisitor: ClassVisitor?) : super(api, classVisitor)
12 | constructor(classVisitor: ClassVisitor?) : this(Opcodes.ASM7, classVisitor)
13 |
14 | override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) {
15 | super.visit(version, access, name, signature, superName, interfaces)
16 | this.className = name
17 | }
18 |
19 | override fun visitMethod(access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor? {
20 | val methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions)
21 | return if (className == "okhttp3/OkHttpClient\$Builder" && name == "") {
22 | if (methodVisitor == null) null else OkHttpMethodAdapter(methodVisitor, access, name, descriptor)
23 | } else {
24 | methodVisitor
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/provider/MonitorProvider.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.provider
2 |
3 | import android.content.ContentProvider
4 | import android.content.ContentValues
5 | import android.database.Cursor
6 | import android.net.Uri
7 | import android.util.Log
8 | import com.lygttpod.monitor.MonitorHelper
9 |
10 | class MonitorProvider : ContentProvider() {
11 | override fun onCreate(): Boolean {
12 | val context = context
13 | if (context == null) {
14 | Log.e("MonitorProvider", "MonitorProvider初始化context失败")
15 | } else {
16 | MonitorHelper.init(context)
17 | }
18 | return true
19 | }
20 |
21 | override fun insert(uri: Uri, values: ContentValues?): Uri? = null
22 |
23 | override fun query(
24 | uri: Uri,
25 | projection: Array?,
26 | selection: String?,
27 | selectionArgs: Array?,
28 | sortOrder: String?
29 | ): Cursor? = null
30 |
31 | override fun update(
32 | uri: Uri,
33 | values: ContentValues?,
34 | selection: String?,
35 | selectionArgs: Array?
36 | ): Int = 0
37 |
38 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
39 |
40 | override fun getType(uri: Uri): String? = null
41 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
21 |
22 |
26 |
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | //2、应用抓包插件
5 | id 'monitor-plugin'
6 | }
7 |
8 | android {
9 | compileSdk 31
10 |
11 | defaultConfig {
12 | applicationId "com.android.monitor.demo"
13 | minSdk 21
14 | targetSdk 31
15 | versionCode 1
16 | versionName "1.0"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | kotlinOptions {
32 | jvmTarget = '1.8'
33 | }
34 | buildFeatures {
35 | viewBinding true
36 | }
37 | }
38 |
39 | dependencies {
40 |
41 | implementation 'androidx.core:core-ktx:1.7.0'
42 | implementation 'androidx.appcompat:appcompat:1.3.0'
43 | implementation 'com.google.android.material:material:1.4.0'
44 | implementation 'com.squareup.okhttp3:okhttp:4.9.3'
45 |
46 | //3、依赖抓包库 只在debug环境下依赖 所有使用 debugImplementation
47 | // debugImplementation 'io.github.lygttpod:monitor:0.0.8'
48 | debugImplementation project(":monitor")
49 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/FormatHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import org.xml.sax.InputSource
4 | import java.io.ByteArrayInputStream
5 | import java.io.ByteArrayOutputStream
6 | import javax.xml.transform.OutputKeys
7 | import javax.xml.transform.sax.SAXSource
8 | import javax.xml.transform.sax.SAXTransformerFactory
9 | import javax.xml.transform.stream.StreamResult
10 |
11 |
12 | fun formatBody(body: String, contentType: String?): String {
13 | return when {
14 | contentType?.contains("json", true) == true -> formatJson(body)
15 | contentType?.contains("xml", true) == true -> formatXml(body)
16 | else -> body
17 | }
18 | }
19 |
20 | private fun formatJson(json: String): String {
21 | return GsonHelper.setPrettyPrinting(json)
22 | }
23 |
24 | private fun formatXml(xml: String): String {
25 | return try {
26 | val serializer = SAXTransformerFactory.newInstance().newTransformer()
27 | serializer.setOutputProperty(OutputKeys.INDENT, "yes")
28 | serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
29 | val xmlSource = SAXSource(InputSource(ByteArrayInputStream(xml.toByteArray())))
30 | val res = StreamResult(ByteArrayOutputStream())
31 | serializer.transform(xmlSource, res)
32 | String((res.outputStream as ByteArrayOutputStream).toByteArray())
33 | } catch (e: Exception) {
34 | xml
35 | }
36 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/sp/SpFileAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui.sp
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.lygttpod.monitor.R
8 | import com.lygttpod.monitor.data.SpData
9 | import com.lygttpod.monitor.databinding.ItemSpFileBinding
10 |
11 | /**
12 | *
13 | * author : Allen
14 | * date : 2022/8/6
15 | * desc :
16 | *
17 | */
18 | class SpFileAdapter : RecyclerView.Adapter() {
19 |
20 | private var list: List = listOf()
21 |
22 | var onItemClick: ((SpData) -> Unit)? = null
23 |
24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpFileViewHolder {
25 | return SpFileViewHolder(
26 | LayoutInflater.from(parent.context).inflate(R.layout.item_sp_file, parent, false)
27 | )
28 | }
29 |
30 | override fun onBindViewHolder(holder: SpFileViewHolder, position: Int) {
31 | holder.bindData(list[position])
32 | holder.itemView.setOnClickListener {
33 | onItemClick?.invoke(list[position])
34 | }
35 | }
36 |
37 | override fun getItemCount() = list.size
38 |
39 | fun setData(list: List?) {
40 | this.list = list ?: listOf()
41 | notifyDataSetChanged()
42 | }
43 |
44 | inner class SpFileViewHolder(view: View) : RecyclerView.ViewHolder(view) {
45 | private val binding = ItemSpFileBinding.bind(view)
46 |
47 | fun bindData(data: SpData) {
48 | binding.tvSpFileName.text = "${data.fileName}.xml"
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/sp/SpFileDetailAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui.sp
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.lygttpod.monitor.R
8 | import com.lygttpod.monitor.data.SpSubData
9 | import com.lygttpod.monitor.databinding.ItemSpFileDetailBinding
10 |
11 | /**
12 | *
13 | * author : Allen
14 | * date : 2022/8/6
15 | * desc :
16 | *
17 | */
18 | class SpFileDetailAdapter : RecyclerView.Adapter() {
19 |
20 | private var list: List = listOf()
21 |
22 | var onItemClick: ((SpSubData) -> Unit)? = null
23 |
24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpFileDetailViewHolder {
25 | return SpFileDetailViewHolder(
26 | LayoutInflater.from(parent.context).inflate(R.layout.item_sp_file_detail, parent, false)
27 | )
28 | }
29 |
30 | override fun onBindViewHolder(holder: SpFileDetailViewHolder, position: Int) {
31 | holder.bindData(list[position])
32 | holder.itemView.setOnClickListener {
33 | onItemClick?.invoke(list[position])
34 | }
35 | }
36 |
37 | override fun getItemCount() = list.size
38 |
39 | fun setData(list: List) {
40 | this.list = list
41 | notifyDataSetChanged()
42 | }
43 |
44 | inner class SpFileDetailViewHolder(view: View) : RecyclerView.ViewHolder(view) {
45 | private val binding = ItemSpFileDetailBinding.bind(view)
46 |
47 | fun bindData(data: SpSubData) {
48 | binding.tvKey.text = data.keyName
49 | binding.tvValue.text = "${data.keyValue?.value}"
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/monitor/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | }
6 |
7 | android {
8 | compileSdk 28
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 28
13 |
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | buildFeatures {
31 | viewBinding true
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation 'androidx.core:core-ktx:1.1.0'
38 | implementation 'androidx.appcompat:appcompat:1.3.0'
39 | implementation 'com.google.android.material:material:1.4.0'
40 |
41 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
42 |
43 | implementation 'com.squareup.okhttp3:okhttp:4.3.1'
44 | implementation 'com.google.code.gson:gson:2.8.6'
45 | def room_version = "2.3.0"
46 | implementation "androidx.room:room-runtime:$room_version"
47 | kapt "androidx.room:room-compiler:$room_version"
48 | implementation "androidx.room:room-ktx:$room_version"
49 |
50 | def als_version = "0.0.1"
51 | implementation "io.github.lygttpod.android-local-service:core:$als_version"
52 | implementation "io.github.lygttpod.android-local-service:annotation:$als_version"
53 | kapt "io.github.lygttpod.android-local-service:processor:$als_version"
54 |
55 | implementation 'com.github.lygttpod:ShapeView:1.0.2'
56 |
57 | }
58 |
59 | apply from: '../upload.gradle'
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
22 |
32 |
33 |
42 |
43 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/item_sp_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
23 |
24 |
33 |
34 |
41 |
42 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/GsonHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import com.google.gson.FieldNamingPolicy
4 | import com.google.gson.GsonBuilder
5 | import com.google.gson.JsonParser
6 | import com.google.gson.internal.bind.DateTypeAdapter
7 | import java.lang.reflect.ParameterizedType
8 | import java.lang.reflect.Type
9 | import java.util.*
10 | import kotlin.collections.ArrayList
11 |
12 | object GsonHelper {
13 | private var gson = GsonBuilder()
14 | .setPrettyPrinting()
15 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
16 | .registerTypeAdapter(Date::class.java, DateTypeAdapter())
17 | .create()
18 |
19 | fun setPrettyPrinting(json: String): String {
20 | return try {
21 | gson.toJson(JsonParser.parseString(json))
22 | } catch (e: Exception) {
23 | json
24 | }
25 | }
26 |
27 | fun toJson(ob: Any): String {
28 | return gson.toJson(ob)
29 | }
30 |
31 | fun fromJson(json: String, t: Class): T {
32 | return gson.fromJson(json, t)
33 | }
34 |
35 | fun fromJson(json: String, t: Type): T {
36 | return gson.fromJson(json, t)
37 | }
38 |
39 | fun fromJsonArray(json: String, clazz: Class): List {
40 | val type =
41 | ParameterizedTypeImpl(
42 | clazz
43 | )
44 | var ob: List? = gson.fromJson>(json, type)
45 | if (ob == null) {
46 | ob = ArrayList()
47 | }
48 | return ob
49 | }
50 |
51 | private class ParameterizedTypeImpl constructor(val clazz: Class) : ParameterizedType {
52 |
53 | override fun getActualTypeArguments(): Array {
54 | return arrayOf(clazz)
55 | }
56 |
57 | override fun getRawType(): Type {
58 | return List::class.java
59 | }
60 |
61 | override fun getOwnerType(): Type? {
62 | return null
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/data/MonitorData.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.data
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import java.io.Serializable
7 |
8 | @Entity(tableName = "monitor")
9 | class MonitorData : Serializable {
10 | @PrimaryKey(autoGenerate = true)
11 | var id: Long = 0
12 |
13 | @ColumnInfo(name = "duration")
14 | var duration: Long = 0
15 |
16 | @ColumnInfo(name = "method")
17 | var method: String? = null
18 |
19 | @ColumnInfo(name = "url")
20 | var url: String? = null
21 |
22 | @ColumnInfo(name = "host")
23 | var host: String? = null
24 |
25 | @ColumnInfo(name = "path")
26 | var path: String? = null
27 |
28 | @ColumnInfo(name = "scheme")
29 | var scheme: String? = null
30 |
31 | @ColumnInfo(name = "protocol")
32 | var protocol: String? = null
33 |
34 | @ColumnInfo(name = "requestTime")
35 | var requestTime: String? = null
36 |
37 | @ColumnInfo(name = "requestHeaders")
38 | var requestHeaders: String? = null
39 |
40 | @ColumnInfo(name = "requestBody")
41 | var requestBody: String? = null
42 |
43 | @ColumnInfo(name = "requestContentType")
44 | var requestContentType: String? = null
45 |
46 | @ColumnInfo(name = "responseCode")
47 | var responseCode: Int = 0
48 |
49 | @ColumnInfo(name = "responseTime")
50 | var responseTime: String? = null
51 |
52 | @ColumnInfo(name = "responseHeaders")
53 | var responseHeaders: String? = null
54 |
55 | @ColumnInfo(name = "responseBody")
56 | var responseBody: String? = null
57 |
58 | @ColumnInfo(name = "responseMessage")
59 | var responseMessage: String? = null
60 |
61 | @ColumnInfo(name = "responseContentType")
62 | var responseContentType: String? = null
63 |
64 | @ColumnInfo(name = "responseContentLength")
65 | var responseContentLength: Long? = null
66 |
67 | @ColumnInfo(name = "errorMsg")
68 | var errorMsg: String? = null
69 |
70 | @ColumnInfo(name = "source")
71 | var source: String? = null
72 | }
73 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/StringKtx.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import com.lygttpod.monitor.MonitorHelper
4 | import java.util.regex.Matcher
5 | import java.util.regex.Pattern
6 |
7 |
8 | fun String?.isWhiteContentType(): Boolean {
9 | val whiteContentTypes = MonitorHelper.whiteContentTypes
10 | val intercept =
11 | whiteContentTypes.isNullOrBlank() || !this.isNullOrEmpty() && whiteContentTypes.contains(
12 | this.split(";")[0]
13 | )
14 | println("${MonitorHelper.TAG}---->whiteContentTypes = $whiteContentTypes 当前ContentType = $this 是否在白名单:$intercept")
15 | return intercept
16 | }
17 |
18 | fun String?.isWhiteHosts(): Boolean {
19 | val whiteHosts = MonitorHelper.whiteHosts
20 | val intercept = this.isNullOrBlank() || whiteHosts.isNullOrBlank() || whiteHosts.contains(this)
21 | println("${MonitorHelper.TAG}---->whiteHosts = $whiteHosts 当前host= $this 是否在白名单:$intercept")
22 | return intercept
23 | }
24 |
25 | fun String?.isBlackHosts(): Boolean {
26 | val blackHosts = MonitorHelper.blackHosts
27 | if (this.isNullOrBlank()) return false
28 | val intercept = !blackHosts.isNullOrBlank() && blackHosts.contains(this)
29 | println("${MonitorHelper.TAG}---->blackHosts = $blackHosts 当前host= $this 是否在黑名单:$intercept")
30 | return intercept
31 | }
32 |
33 | fun String?.isSkipInterceptByHost(): Boolean {
34 | return !this.isWhiteHosts() && this.isBlackHosts()
35 | }
36 |
37 |
38 | val IP_ADDRESS_PATTERN: Pattern =
39 | Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})")
40 |
41 | fun String?.isIpAddress(): Boolean {
42 | if (this.isNullOrBlank()) return false
43 | val matcher: Matcher = IP_ADDRESS_PATTERN.matcher(this)
44 | if (!matcher.matches()) return false
45 | for (i in 1..matcher.groupCount()) {
46 | try {
47 | val group = matcher.group(i) ?: return false
48 | if (group.toInt() > 255) {
49 | return false
50 | }
51 | } catch (e: Exception) {
52 | return false
53 | }
54 | }
55 | return true
56 | }
--------------------------------------------------------------------------------
/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=-Xmx2048m -Dfile.encoding=UTF-8
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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 |
21 | # maven
22 | mavenReleasesUrl=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
23 | mavenSnapshotsUrl=https://s01.oss.sonatype.org/content/repositories/snapshots/
24 |
25 | GROUP=io.github.lygttpod
26 |
27 | POM_DESCRIPTION=easy show android request data.
28 | POM_INCEPTION_YEAR=2022
29 | POM_URL=https://github.com/lygttpod/AndroidMonitor/
30 |
31 | POM_LICENSE_NAME=The Apache Software License, Version 2.0
32 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
33 | POM_LICENSE_DIST=repo
34 |
35 | POM_SCM_URL=https://github.com/lygttpod/AndroidMonitor
36 | POM_SCM_CONNECTION=scm:git:git://github.com/lygttpod/AndroidMonitor.git
37 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/lygttpod/AndroidMonitor.git
38 |
39 | POM_DEVELOPER_ID=lygttpod
40 | POM_DEVELOPER_NAME=lygttpod
41 | POM_DEVELOPER_URL=https://github.com/lygttpod
42 |
43 | # GPG密钥后8位
44 | signing.keyId=31486A9E
45 | # 申请GPG密钥时的那个密码
46 | signing.password=lygttpod-gpg-signing
47 | # GPG密钥路径
48 | signing.secretKeyRingFile=../android-secring.gpg
--------------------------------------------------------------------------------
/monitor/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
14 |
15 |
20 |
21 |
26 |
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
49 |
50 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/MonitorRequestFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import com.lygttpod.monitor.R
9 | import com.lygttpod.monitor.data.MonitorData
10 | import com.lygttpod.monitor.databinding.FragmentMonitorRequestBinding
11 | import com.lygttpod.monitor.utils.formatBody
12 |
13 |
14 | class MonitorRequestFragment : Fragment() {
15 |
16 | companion object {
17 | fun newInstance(monitorData: MonitorData?): MonitorRequestFragment {
18 | return MonitorRequestFragment().apply {
19 | this.monitorData = monitorData
20 | }
21 | }
22 | }
23 |
24 | private var monitorData: MonitorData? = null
25 |
26 | private lateinit var binding: FragmentMonitorRequestBinding
27 |
28 | override fun onCreateView(
29 | inflater: LayoutInflater,
30 | container: ViewGroup?,
31 | savedInstanceState: Bundle?
32 | ): View? {
33 | return inflater.inflate(R.layout.fragment_monitor_request, container, false)
34 | }
35 |
36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
37 | super.onViewCreated(view, savedInstanceState)
38 | binding = FragmentMonitorRequestBinding.bind(view)
39 | initData()
40 | }
41 |
42 | private fun initData() {
43 | binding.tvUrl.text = monitorData?.url
44 | binding.tvMethod.text = monitorData?.method
45 | binding.tvRequestDate.text = monitorData?.requestTime
46 | binding.tvHeader.text = if (monitorData?.source == "Flutter") formatBody(
47 | monitorData?.requestHeaders ?: "",
48 | "json"
49 | ) else monitorData?.requestHeaders
50 | if (monitorData?.requestBody.isNullOrBlank()) return
51 | binding.tvRequestBody.text = formatBody(
52 | monitorData?.requestBody
53 | ?: return, monitorData?.requestContentType
54 | )
55 | }
56 |
57 | override fun onDestroyView() {
58 | super.onDestroyView()
59 | monitorData = null
60 | }
61 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/sp/SPFileListFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui.sp
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.recyclerview.widget.LinearLayoutManager
11 | import com.lygttpod.monitor.MonitorHelper
12 | import com.lygttpod.monitor.R
13 | import com.lygttpod.monitor.data.SpData
14 | import com.lygttpod.monitor.databinding.FragmentSpListBinding
15 |
16 | /**
17 | *
18 | * author : Allen
19 | * date : 2022/8/6
20 | * desc :
21 | *
22 | */
23 | class SPFileListFragment : Fragment() {
24 |
25 | private lateinit var binding: FragmentSpListBinding
26 | private var adapter: SpFileAdapter? = null
27 | private var handle: Handler = Handler(Looper.getMainLooper())
28 |
29 | override fun onCreateView(
30 | inflater: LayoutInflater,
31 | container: ViewGroup?,
32 | savedInstanceState: Bundle?
33 | ): View? {
34 | return inflater.inflate(R.layout.fragment_sp_list, container, false)
35 | }
36 |
37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
38 | super.onViewCreated(view, savedInstanceState)
39 | binding = FragmentSpListBinding.bind(view)
40 | initView()
41 | initData()
42 | }
43 |
44 | private fun initData() {
45 | val list = MonitorHelper.getSharedPrefsFilesData().map { SpData(it.key) }.toList()
46 | adapter?.setData(list)
47 | }
48 |
49 | private fun initView() {
50 | binding.swipeRefresh.setOnRefreshListener {
51 | handle.postDelayed({
52 | binding.swipeRefresh.isRefreshing = false
53 | initData()
54 | }, 1000)
55 | }
56 | adapter = SpFileAdapter()
57 | adapter?.onItemClick = {
58 | startActivity(SPFileDetailActivity.buildIntent(requireContext(), it.fileName))
59 | }
60 | binding.recyclerVew.let {
61 | it.layoutManager = LinearLayoutManager(context)
62 | it.adapter = adapter
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/MonitorResponseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import com.lygttpod.monitor.R
9 | import com.lygttpod.monitor.data.MonitorData
10 | import com.lygttpod.monitor.databinding.FragmentMonitorResponseBinding
11 | import com.lygttpod.monitor.utils.formatBody
12 |
13 | class MonitorResponseFragment : Fragment() {
14 |
15 | companion object {
16 | fun newInstance(monitorData: MonitorData?): MonitorResponseFragment {
17 | return MonitorResponseFragment().apply {
18 | this.monitorData = monitorData
19 | }
20 | }
21 | }
22 |
23 | private var monitorData: MonitorData? = null
24 |
25 | private lateinit var binding: FragmentMonitorResponseBinding
26 |
27 | override fun onCreateView(
28 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
29 | ): View? {
30 | return inflater.inflate(R.layout.fragment_monitor_response, container, false)
31 | }
32 |
33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34 | super.onViewCreated(view, savedInstanceState)
35 | binding = FragmentMonitorResponseBinding.bind(view)
36 | initData()
37 | }
38 |
39 | private fun initData() {
40 | binding.tvUrl.text = monitorData?.url
41 | binding.tvMethod.text = monitorData?.method
42 | binding.tvCode.text = monitorData?.responseCode.toString()
43 | binding.tvResponseDate.text = monitorData?.responseTime
44 |
45 | val responseBody = if (monitorData?.source == "Flutter") formatBody(
46 | monitorData?.responseBody ?: "",
47 | "json"
48 | ) else monitorData?.responseBody
49 | val responseType = monitorData?.responseContentType
50 |
51 | binding.tvResponseBody.text = if (responseBody.isNullOrBlank()) (monitorData?.errorMsg
52 | ?: monitorData?.responseMessage) else formatBody(responseBody, responseType)
53 | }
54 |
55 | override fun onDestroyView() {
56 | super.onDestroyView()
57 | monitorData = null
58 | }
59 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/mock/MockHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.mock
2 |
3 | import okhttp3.*
4 | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
5 | import okhttp3.ResponseBody.Companion.toResponseBody
6 |
7 | object MockHelper {
8 |
9 | var isOpen = false
10 |
11 | /**
12 | * mock服务的baseUrl 例如PostMan的mock服务:https://881adba2-445e-4ad2-8633-3206a2b0ef94.mock.pstmn.io
13 | */
14 | var mockBaseUrl: String = ""
15 |
16 | /**
17 | * 需要mock的接口path 例如:/article/list/0/json
18 | */
19 | var mockPaths: String = ""
20 |
21 | /**
22 | * 手动创建的response
23 | */
24 | var mockResponse: String = ""
25 |
26 | /**
27 | * 请求成功的code
28 | */
29 | var mockSuccessResponseCode = 200
30 |
31 | fun isMockPath(path: String?): Boolean {
32 | if (path.isNullOrBlank()) return false
33 | return mockPaths.contains(path)
34 | }
35 |
36 | fun isMockByCustomResponse(path: String?): Boolean {
37 | return isOpen && isMockPath(path) && mockResponse.isNotBlank()
38 | }
39 |
40 | fun isMockByNetworkResponse(path: String?): Boolean {
41 | return isOpen && isMockPath(path) && mockBaseUrl.isNotBlank() && mockResponse.isBlank()
42 | }
43 |
44 | fun configMock(mockBaseUrl: String, mockPath: String, mockResponse: String) {
45 | MockHelper.mockBaseUrl = mockBaseUrl
46 | mockPaths = mockPath
47 | MockHelper.mockResponse = mockResponse
48 | }
49 |
50 | fun buildMockServer(chain: Interceptor.Chain, request: Request): Response {
51 | val oldHttpUrl = request.url
52 | val newBaseUrl = mockBaseUrl.toHttpUrlOrNull() ?: return chain.proceed(request)
53 | val newHttpUrl = oldHttpUrl
54 | .newBuilder()
55 | .scheme(newBaseUrl.scheme)
56 | .host(newBaseUrl.host)
57 | .port(newBaseUrl.port)
58 | .build()
59 | return chain.proceed(request.newBuilder().url(newHttpUrl).build())
60 | }
61 |
62 | fun mockResponseBody(chain: Interceptor.Chain): Response {
63 | if (mockResponse.isBlank()) return chain.proceed(chain.request())
64 | val response = chain.proceed(chain.request())
65 | val contentType = response.body?.contentType()
66 | val responseBody = mockResponse.toResponseBody(contentType)
67 | return response.newBuilder()
68 | .message("mock成功")
69 | .code(mockSuccessResponseCode)
70 | .body(responseBody)
71 | .build()
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/weaknetwork/SpeedLimitResponseBody.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.weaknetwork
2 |
3 | import android.os.SystemClock
4 | import okhttp3.MediaType
5 | import okhttp3.ResponseBody
6 | import okio.*
7 |
8 | class SpeedLimitResponseBody(
9 | private val mSpeedByte: Long = 0,
10 | private val mResponseBody: ResponseBody? = null
11 | ) : ResponseBody() {
12 |
13 | private var mBufferedSource: BufferedSource? = null
14 |
15 |
16 | override fun contentLength(): Long {
17 | return mResponseBody?.contentLength() ?: 0L
18 | }
19 |
20 | override fun contentType(): MediaType? {
21 | return mResponseBody?.contentType()
22 | }
23 |
24 | override fun source(): BufferedSource {
25 | if (mBufferedSource == null) {
26 | mBufferedSource = source(mResponseBody!!.source()).buffer()
27 | }
28 | return mBufferedSource!!
29 | }
30 |
31 | private fun source(source: Source): Source {
32 | return object : ForwardingSource(source) {
33 | /**
34 | * 如果小于1s 会重置
35 | */
36 | private var cacheTotalBytesRead: Long = 0
37 |
38 | /**
39 | * 分片读取1024个字节开始时间 小于1s会重置
40 | */
41 | private var cacheStartTime: Long = 0
42 |
43 | override fun read(sink: Buffer, byteCount: Long): Long {
44 | if (cacheStartTime == 0L) {
45 | cacheStartTime = SystemClock.uptimeMillis()
46 | }
47 |
48 | //默认8K 精确到1K -1代表已经读取完毕
49 | val bytesRead = super.read(sink.buffer(), 1024L)
50 | if (bytesRead == -1L) {
51 | return bytesRead
52 | }
53 | //一般为1024
54 | cacheTotalBytesRead += bytesRead
55 | /**
56 | * 判断当前请求累计消耗的时间 即相当于读取1024个字节所需要的时间
57 | */
58 | val costTime = SystemClock.uptimeMillis() - cacheStartTime
59 |
60 | //如果每次分片读取时间小于ls sleep 延迟时间
61 | if (costTime <= 1000L) {
62 | if (cacheTotalBytesRead >= mSpeedByte) {
63 | val sleep = 1000L - costTime
64 | SystemClock.sleep(sleep)
65 | //重置计算
66 | cacheStartTime = 0L
67 | cacheTotalBytesRead = 0L
68 | }
69 | }
70 | return bytesRead
71 | }
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/request/MonitorMainFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui.request
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.Observer
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import com.lygttpod.monitor.MonitorHelper
13 | import com.lygttpod.monitor.R
14 | import com.lygttpod.monitor.adapter.MonitorListAdapter
15 | import com.lygttpod.monitor.data.MonitorData
16 | import com.lygttpod.monitor.databinding.FragmentMonitorMainBinding
17 | import com.lygttpod.monitor.ui.MonitorDetailActivity
18 |
19 | /**
20 | *
21 | * author : Allen
22 | * date : 2022/8/6
23 | * desc :
24 | *
25 | */
26 | class MonitorMainFragment : Fragment() {
27 |
28 | private lateinit var binding: FragmentMonitorMainBinding
29 |
30 | private var adapter: MonitorListAdapter? = null
31 |
32 | private var handle: Handler = Handler(Looper.getMainLooper())
33 |
34 | override fun onCreateView(
35 | inflater: LayoutInflater,
36 | container: ViewGroup?,
37 | savedInstanceState: Bundle?
38 | ): View? {
39 | return inflater.inflate(R.layout.fragment_monitor_main, container, false)
40 | }
41 |
42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
43 | super.onViewCreated(view, savedInstanceState)
44 | binding = FragmentMonitorMainBinding.bind(view)
45 |
46 | binding.swipeRefresh.setOnRefreshListener {
47 | handle.postDelayed({
48 | binding.swipeRefresh.isRefreshing = false
49 | setData()
50 | }, 1000)
51 | }
52 |
53 | initRv()
54 | setData()
55 |
56 | }
57 |
58 | private fun setData() {
59 | MonitorHelper.getMonitorDataListForAndroid(limit = 100)
60 | ?.observe(viewLifecycleOwner, Observer {
61 | adapter?.setData(it)
62 | })
63 | }
64 |
65 | private fun initRv() {
66 | adapter = MonitorListAdapter()
67 | adapter?.itemClick = { gotoMonitorDetail(it) }
68 | binding.rvMonitor.layoutManager = LinearLayoutManager(context)
69 | binding.rvMonitor.adapter = adapter
70 | }
71 |
72 | private fun gotoMonitorDetail(monitorData: MonitorData) {
73 | startActivity(MonitorDetailActivity.buildIntent(requireContext(), monitorData))
74 | }
75 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/item_sp_file_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
16 |
30 |
31 |
45 |
46 |
53 |
54 |
61 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/weaknetwork/WeakNetworkHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.weaknetwork
2 |
3 | import android.os.SystemClock
4 | import com.lygttpod.monitor.enum.WeakNetworkType
5 | import okhttp3.Interceptor
6 | import okhttp3.Response
7 | import okhttp3.ResponseBody.Companion.toResponseBody
8 |
9 |
10 | object WeakNetworkHelper {
11 | private var weakNetworkType = WeakNetworkType.TIME_OUT
12 |
13 | var isOpen = false
14 |
15 | //读取速度 默认 1024byte/s = 1k/s
16 | var responseSpeedByte: Long = 1024L
17 |
18 | fun setWeakType(type: WeakNetworkType) {
19 | weakNetworkType = type
20 | }
21 |
22 | fun configWeak(typeString: String?) {
23 | isOpen = typeString == "超时" || typeString == "断网" || typeString == "限速"
24 | when(typeString) {
25 | "超时" -> setWeakType(WeakNetworkType.TIME_OUT)
26 | "断网" -> setWeakType(WeakNetworkType.NO_NETWORK)
27 | "限速" -> setWeakType(WeakNetworkType.SPEED_LIMIT)
28 | }
29 | }
30 |
31 | fun weakNetType() = weakNetworkType
32 |
33 | fun mockTimeout(chain: Interceptor.Chain): Response {
34 | val timeOutMillis = chain.connectTimeoutMillis()
35 | val host = chain.request().url.host
36 | SystemClock.sleep(timeOutMillis.toLong())
37 | val response = chain.proceed(chain.request())
38 | val responseBody = "".toResponseBody(response.body?.contentType())
39 | return response.newBuilder()
40 | .code(400)
41 | .message("模拟超时:failed to connect to $host after ${timeOutMillis}ms")
42 | .body(responseBody)
43 | .build()
44 | }
45 |
46 | fun mockNoNetwork(chain: Interceptor.Chain): Response {
47 | val host = chain.request().url.host
48 | val response = chain.proceed(chain.request())
49 | val contentType = response.body?.contentType()
50 | val responseBody = "".toResponseBody(contentType)
51 | return response.newBuilder()
52 | .code(400)
53 | .message("模拟断网:Unable to resolve $host : No address associated with hostname")
54 | .body(responseBody)
55 | .build()
56 | }
57 |
58 | fun mockSpeedLimit(chain: Interceptor.Chain): Response {
59 | val request = chain.request()
60 | val response = chain.proceed(request)
61 | //大于0使用限速的body 否则使用原始body
62 | val responseBody = response.body
63 | val newResponseBody = if (responseSpeedByte > 0) SpeedLimitResponseBody(
64 | responseSpeedByte,
65 | responseBody
66 | ) else responseBody
67 | return response.newBuilder().body(newResponseBody).build()
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/MonitorDetailActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import androidx.appcompat.app.AppCompatActivity
7 | import com.lygttpod.monitor.adapter.MonitorPagerAdapter
8 | import com.lygttpod.monitor.data.MonitorData
9 | import com.lygttpod.monitor.databinding.ActivityMonitorDetailBinding
10 | import com.lygttpod.monitor.utils.formatBody
11 |
12 | class MonitorDetailActivity : AppCompatActivity() {
13 |
14 | companion object {
15 | private var monitorData: MonitorData? = null
16 | fun buildIntent(context: Context, monitorData: MonitorData): Intent {
17 | return Intent(context, MonitorDetailActivity::class.java).apply {
18 | MonitorDetailActivity.monitorData = monitorData
19 | }
20 | }
21 | }
22 |
23 | private lateinit var binding: ActivityMonitorDetailBinding
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | binding = ActivityMonitorDetailBinding.inflate(layoutInflater)
28 | setContentView(binding.root)
29 | initView()
30 | }
31 |
32 | private fun initView() {
33 | binding.ivBack.setOnClickListener { finish() }
34 | binding.ivShare.setOnClickListener { share() }
35 | binding.tvTitle.text = monitorData?.path
36 | val fragmentPagerAdapter = MonitorPagerAdapter(supportFragmentManager)
37 | fragmentPagerAdapter.addFragment(MonitorResponseFragment.newInstance(monitorData), "响应")
38 | fragmentPagerAdapter.addFragment(MonitorRequestFragment.newInstance(monitorData), "请求")
39 | binding.viewPager.adapter = fragmentPagerAdapter
40 | binding.tabLayout.setupWithViewPager(binding.viewPager)
41 | }
42 |
43 | private fun share() {
44 | val shareString =
45 | "url = ${monitorData?.url} \n method = ${monitorData?.method} \n header = ${monitorData?.requestHeaders} \n requestBody = ${
46 | formatBody(
47 | monitorData?.requestBody
48 | ?: "", monitorData?.requestContentType
49 | )
50 | } \n responseBody = ${
51 | formatBody(
52 | monitorData?.responseBody
53 | ?: "", monitorData?.responseContentType
54 | )
55 | }"
56 | val shareIntent = Intent(Intent.ACTION_SEND)
57 | shareIntent.type = "text/plain"
58 | shareIntent.putExtra(Intent.EXTRA_TEXT, shareString)
59 | startActivity(Intent.createChooser(shareIntent, "分享抓包数据"))
60 | }
61 |
62 | override fun onDestroy() {
63 | super.onDestroy()
64 | monitorData = null
65 | }
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/android/monitor/demo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.android.monitor.demo
2 |
3 | import android.content.Context
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import android.widget.Toast
7 | import com.android.monitor.demo.databinding.ActivityMainBinding
8 | import com.lygttpod.monitor.MonitorHelper
9 | import com.lygttpod.monitor.utils.getPhoneWifiIpAddress
10 | import okhttp3.*
11 | import okio.IOException
12 |
13 | class MainActivity : AppCompatActivity() {
14 | private val client = OkHttpClient()
15 |
16 | private lateinit var binding: ActivityMainBinding
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | binding = ActivityMainBinding.inflate(layoutInflater)
21 | setContentView(binding.root)
22 | getServiceAddress()
23 | initData()
24 | binding.btnSendRequest.setOnClickListener {
25 | sendRequest("https://www.wanandroid.com/article/list/0/json")
26 | }
27 | }
28 |
29 | private fun initData() {
30 | sendRequest("https://www.wanandroid.com/banner/json")
31 | spFileCommit()
32 | }
33 |
34 | private fun sendRequest(url: String) {
35 | val request = Request.Builder().url(url).build();
36 | client.newCall(request).enqueue(object : Callback {
37 | override fun onFailure(call: Call, e: IOException) {
38 | runOnUiThread {
39 | Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
40 | }
41 | }
42 |
43 | override fun onResponse(call: Call, response: Response) {
44 | val result = response.body?.string()
45 | runOnUiThread {
46 | Toast.makeText(this@MainActivity, result, Toast.LENGTH_SHORT).show()
47 | }
48 | }
49 | })
50 | }
51 |
52 | private fun getServiceAddress() {
53 | getPhoneWifiIpAddress()?.let {
54 | val monitorUrl = "$it:${MonitorHelper.port}/index"
55 | binding.tvAddress.text = monitorUrl
56 | }
57 | }
58 |
59 | private fun spFileCommit() {
60 | getSharedPreferences("spFileName111", Context.MODE_PRIVATE).edit().also {
61 | it.putString("testString", "我是字符串")
62 | it.putInt("testInt", 111)
63 | it.putBoolean("testBoolean", true)
64 | it.putFloat("testFloat", 222f)
65 | it.putLong("testLong", System.currentTimeMillis())
66 | }.apply()
67 | getSharedPreferences("spFileName222", Context.MODE_PRIVATE).edit().also {
68 | it.putString("keyString", "我是字符串")
69 | it.putInt("keyInt", 111)
70 | it.putBoolean("keyBoolean", true)
71 | it.putFloat("keyFloat", 222f)
72 | it.putLong("keyLong", System.currentTimeMillis())
73 | }.apply()
74 | }
75 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/MonitorProperties.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import android.util.Log
4 | import com.lygttpod.monitor.MonitorHelper
5 | import com.lygttpod.monitor.data.PropertiesData
6 | import java.io.FileNotFoundException
7 | import java.io.IOException
8 | import java.io.InputStream
9 | import java.util.*
10 |
11 | class MonitorProperties {
12 |
13 | companion object {
14 | private const val TAG = "MonitorHelper"
15 | private const val KEY_MONITOR_PORT = "monitor.port"
16 | private const val KEY_MONITOR_DB_NAME = "monitor.dbName"
17 | private const val KEY_WHITE_CONTENT_TYPES = "monitor.whiteContentTypes"
18 | private const val KEY_WHITE_HOSTS = "monitor.whiteHosts"
19 | private const val KEY_BLACK_HOSTS = "monitor.blackHosts"
20 | private const val KEY_IS_FILTER_IPADDRESS_HOST = "monitor.isFilterIPAddressHost"
21 |
22 | private const val ASSETS_FILE_NAME = "monitor.properties"
23 | }
24 |
25 | fun paramsProperties(): PropertiesData? {
26 | var propertiesData: PropertiesData? = null
27 | var inputStream: InputStream? = null
28 | val p = Properties()
29 |
30 | try {
31 | val context = MonitorHelper.context
32 | if (context == null) {
33 | Log.d(TAG, "初始化获取context失败")
34 | return propertiesData
35 | }
36 | inputStream = context.assets.open(ASSETS_FILE_NAME)
37 | if (inputStream != null) {
38 | p.load(inputStream)
39 | val port = p.getProperty(KEY_MONITOR_PORT)
40 | val dbName = p.getProperty(KEY_MONITOR_DB_NAME)
41 | val whiteContentTypes = p.getProperty(KEY_WHITE_CONTENT_TYPES)
42 | val whiteHosts = p.getProperty(KEY_WHITE_HOSTS)
43 | val blackHosts = p.getProperty(KEY_BLACK_HOSTS)
44 | val isFilterIPAddressHost =
45 | p.getProperty(KEY_IS_FILTER_IPADDRESS_HOST)?.toBoolean() ?: false
46 |
47 | propertiesData =
48 | PropertiesData(
49 | port,
50 | dbName,
51 | whiteContentTypes,
52 | whiteHosts,
53 | blackHosts,
54 | isFilterIPAddressHost
55 | )
56 | }
57 | } catch (e: IOException) {
58 | if (e is FileNotFoundException) {
59 | Log.d(TAG, "not found monitor.properties")
60 | } else {
61 | e.printStackTrace()
62 | }
63 | } finally {
64 | if (inputStream != null) {
65 | try {
66 | inputStream.close()
67 | } catch (e: IOException) {
68 | e.printStackTrace()
69 | }
70 | }
71 | }
72 | return propertiesData
73 | }
74 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/service/MonitorService.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.service
2 |
3 | import com.android.local.service.annotation.Get
4 | import com.android.local.service.annotation.Page
5 | import com.android.local.service.annotation.Service
6 | import com.lygttpod.monitor.MonitorHelper
7 | import com.lygttpod.monitor.data.MonitorData
8 | import com.lygttpod.monitor.enum.SPValueType
9 | import com.lygttpod.monitor.mock.MockHelper
10 | import com.lygttpod.monitor.utils.ServiceDataProvider
11 | import com.lygttpod.monitor.utils.lastUpdateDataId
12 | import com.lygttpod.monitor.weaknetwork.WeakNetworkHelper
13 |
14 | @Service(port = 9527)
15 | abstract class MonitorService {
16 |
17 | @Page("index")
18 | fun showMonitorPage() = "monitor_index.html"
19 |
20 | @Page("sp_index")
21 | fun showSpPage() = "sp_index.html"
22 |
23 | @Page("mqtt_index")
24 | fun showMqttPage() = "mqtt_index.html"
25 |
26 | @Get("query")
27 | fun queryMonitorData(limit: Int, offset: Int): MutableList {
28 | return ServiceDataProvider.getMonitorDataList(limit, offset)
29 | }
30 |
31 | @Get("clean")
32 | fun cleanMonitorData() {
33 | MonitorHelper.deleteAll()
34 | }
35 |
36 | @Get("autoFetch")
37 | fun autoFetchData(lastFetchId: Long): MutableList {
38 | while (true) {
39 | Thread.sleep(1000)
40 | return if (lastFetchId != lastUpdateDataId || lastFetchId == 0L) {
41 | ServiceDataProvider.getMonitorDataByLastId(lastFetchId)
42 | } else {
43 | mutableListOf()
44 | }
45 | }
46 | }
47 |
48 | @Get("sharedPrefs")
49 | fun getSharedPrefsFilesData() = MonitorHelper.getSharedPrefsFilesData()
50 |
51 | @Get("getSharedPrefsByFileName")
52 | fun getSharedPrefsByFileName(fileName: String) = MonitorHelper.getSpFile(fileName)
53 |
54 | @Get("updateSpValue")
55 | fun updateSpValue(fileName: String, key: String, value: String, valueType: String) {
56 | val realValue = when (valueType) {
57 | SPValueType.Int.value -> value.toIntOrNull()
58 | SPValueType.Double.value -> value.toDoubleOrNull()
59 | SPValueType.Long.value -> value.toLongOrNull()
60 | SPValueType.Float.value -> value.toFloatOrNull()
61 | SPValueType.Boolean.value -> value.toBoolean()
62 | else -> value
63 | }
64 | MonitorHelper.updateSpValue(fileName, key, realValue)
65 | }
66 |
67 | @Get("setWeakNetConfig")
68 | fun configWeak(weakType: String) {
69 | WeakNetworkHelper.configWeak(weakType)
70 | }
71 |
72 | @Get("setMockConfig")
73 | fun setMockConfig(mockBaseUrl: String, mockPath: String, mockResponse: String) {
74 | MockHelper.configMock(mockBaseUrl, mockPath, mockResponse)
75 | }
76 |
77 | @Get("openMockService")
78 | fun openMockService(isOpen: Boolean) {
79 | MockHelper.isOpen = isOpen
80 | }
81 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/sp/SPFileDetailActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui.sp
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.os.Handler
7 | import android.os.Looper
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.recyclerview.widget.LinearLayoutManager
10 | import com.lygttpod.monitor.MonitorHelper
11 | import com.lygttpod.monitor.data.SpSubData
12 | import com.lygttpod.monitor.databinding.ActivitySpFileDetailBinding
13 | import com.lygttpod.monitor.ui.dialog.SpModifyDialog
14 |
15 | /**
16 | *
17 | * author : Allen
18 | * date : 2022/8/6
19 | * desc :
20 | *
21 | */
22 | class SPFileDetailActivity : AppCompatActivity() {
23 |
24 | companion object {
25 | private const val FILE_NAME = "file_name"
26 |
27 | fun buildIntent(context: Context, fileName: String?): Intent {
28 | return Intent(context, SPFileDetailActivity::class.java).apply {
29 | val bundle = Bundle()
30 | bundle.putString(FILE_NAME, fileName)
31 | this.putExtras(bundle)
32 | }
33 | }
34 | }
35 |
36 | private lateinit var binding: ActivitySpFileDetailBinding
37 | private var handle: Handler = Handler(Looper.getMainLooper())
38 | private var fileName: String? = null
39 | var list = listOf()
40 | private var adapter: SpFileDetailAdapter? = null
41 |
42 | override fun onCreate(savedInstanceState: Bundle?) {
43 | super.onCreate(savedInstanceState)
44 | binding = ActivitySpFileDetailBinding.inflate(layoutInflater)
45 | setContentView(binding.root)
46 | fileName = intent.getStringExtra(FILE_NAME)
47 | initView()
48 | initData()
49 | }
50 |
51 | private fun initData() {
52 | list = MonitorHelper.getSpFile(fileName ?: "").map { SpSubData(it.key, it.value) }.toList()
53 | adapter?.setData(list)
54 | }
55 |
56 | private fun initView() {
57 | binding.ivBack.setOnClickListener { finish() }
58 | binding.swipeRefresh.setOnRefreshListener {
59 | handle.postDelayed({
60 | binding.swipeRefresh.isRefreshing = false
61 | initData()
62 | }, 1000)
63 | }
64 | binding.tvTitle.text = fileName ?: "详情"
65 | adapter = SpFileDetailAdapter()
66 | adapter?.onItemClick = {
67 | SpModifyDialog.show(this, fileName, it.keyName, it.keyValue) { key, spValueInfo ->
68 | val position = list.indexOfFirst { it.keyName == key }
69 | if (position > -1) {
70 | list[position].keyValue = spValueInfo
71 | adapter?.notifyItemChanged(position)
72 | }
73 | }
74 | }
75 | binding.recyclerVew.let {
76 | it.layoutManager = LinearLayoutManager(this)
77 | it.adapter = adapter
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 基于[AndroidLocalService](https://github.com/lygttpod/android-local-service)实现的抓取OKHTTP请求的工具
2 |
3 | ### [**Demo下载体验**](https://www.pgyer.com/AndroidMonitor)
4 |
5 | ### [**文章介绍---->Android抓包从未如此简单**](https://juejin.cn/post/7119083753376317448)
6 |
7 |
8 |
9 | ## 切记:monitor需要配合monitor-plugin使用
10 |
11 | ### 1、monitor接入
12 |
13 | 添加依赖
14 | ```
15 | debugImplementation 'io.github.lygttpod:monitor:0.1.2'
16 | ```
17 | -备注: 使用debugImplementation是为了只在测试环境中引入
18 |
19 | ### 2、monitor-plugin接入
20 |
21 | 1. 根目录build.gradle下添加如下依赖
22 | ```
23 | buildscript {
24 | dependencies {
25 | ......
26 | //monitor-plugin需要
27 | classpath 'io.github.lygttpod:monitor-plugin:0.0.2'
28 | }
29 | }
30 |
31 | ```
32 | 2.添加插件
33 | ```
34 | 在APP的build.gradle中添加:
35 |
36 | //插件内部会自动判断debug模式下hook到okhttp
37 | apply plugin: 'monitor-plugin'
38 |
39 | ```
40 | > ## 原则上完成以上两步你的APP就成功集成了抓包工具,很简单有没有,如需定制化服务请看下边的个性化配置
41 |
42 | ### 3、 个性化配置
43 |
44 | 1、修改桌面抓包工具入口名字:在主项目string.xml中添加 monitor_app_name即可,例如:
45 | ```
46 | XXX-抓包
47 | ```
48 | 2、定制抓包入口logo图标:
49 | ```
50 | 添加 monitor_logo.png 即可
51 | ```
52 | 3、单个项目使用的话,添加依赖后可直接使用,无需初始化,库里会通过ContentProvider方式自动初始化
53 |
54 | 默认端口8080(端口号要唯一)
55 |
56 | 4、多个项目都集成抓包工具,需要对不同项目设置不同的端口和数据库名字,用来做区分
57 |
58 | 在主项目assets目录下新建 monitor.properties 文件,文件内如如下:对需要变更的参数修改即可
59 | ```
60 | # 抓包助手参数配置
61 | # Default port = 8080
62 | # Default dbName = monitor_db
63 | # ContentTypes白名单,默认application/json,application/xml,text/html,text/plain,text/xml
64 | # Default whiteContentTypes = application/json,application/xml,text/html,text/plain,text/xml
65 | # Host白名单,默认全部是白名单
66 | # Default whiteHosts =
67 | # Host黑名单,默认没有黑名单
68 | # Default blackHosts =
69 | # 是否过滤纯IP地址的host 默认false
70 | # Default isFilterIPAddressHost = false
71 | # 如何多个项目都集成抓包工具,可以设置不同的端口进行访问
72 | monitor.port=8080
73 | monitor.dbName=app_name_monitor_db
74 | ```
75 |
76 | ### 4、 proguard(默认已经添加混淆,如遇到问题可以添加如下混淆代码)
77 | ```
78 | # monitor
79 | -keep class com.lygttpod.monitor.** { *; }
80 | ```
81 |
82 | ### 5、 温馨提示
83 | ```
84 | 虽然monitor-plugin只会在debug环境hook代码,
85 | 但是release版编译的时候还是会走一遍Transform操作(空操作),
86 | 为了保险起见建议生产包禁掉此插件。
87 |
88 | 在jenkins打包机器的《生产环境》的local.properties中添加monitor.enablePlugin=false,全面禁用monitor插件
89 | ```
90 |
91 | ### 6、如何使用
92 | - 集成之后编译运行项目即可在手机上自动生成一个抓包入口的图标,点击即可打开可视化页面查看网络请求数据,这样就可以随时随地的查看我们的请求数据了。
93 | - 虽然可以很方便的查看请求数据了但是手机屏幕太小,看起来不方便怎么办呐,那就去寻找在PC上展示的方法,首先想到的是能不能直接在浏览器里边直接看呐,这样不用安装任何程序在浏览输入一个地址就可以直接查看数据
94 | - PC和手机在同一局域网的前提下:直接在任意浏览器输入 手机ip地址+抓包工具设置的端口号即可(地址可以在抓包app首页TitleBar上可以看到)
95 |
96 | ### 7、原理剖析
97 | - 拦截APP的OKHTTP请求(添加拦截器处理抓包请求)
98 | - 数据保存到本地数据库(room)
99 | - APP本地开启一个socket服务
100 | - 与本地socket服务通信
101 | - UI展示数据
102 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/activity_monitor_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
27 |
28 |
38 |
39 |
49 |
50 |
59 |
60 |
67 |
68 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/OkHttpKtx.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import com.lygttpod.monitor.data.HttpHeader
4 | import okhttp3.Headers
5 | import okhttp3.RequestBody
6 | import okhttp3.Response
7 | import okhttp3.internal.http.StatusLine
8 | import okio.Buffer
9 | import java.io.EOFException
10 | import java.net.HttpURLConnection
11 | import java.nio.charset.Charset
12 | import java.nio.charset.StandardCharsets
13 |
14 |
15 | fun Headers?.toJsonString(): String {
16 | return if (this != null) {
17 | val httpHeaders = ArrayList()
18 | var i = 0
19 | val count = this.size
20 | while (i < count) {
21 | httpHeaders.add(HttpHeader(this.name(i), this.value(i)))
22 | i++
23 | }
24 | GsonHelper.toJson(httpHeaders)
25 | } else {
26 | ""
27 | }
28 | }
29 |
30 | fun Response.promisesBody(): Boolean {
31 | // HEAD requests never yield a body regardless of the response headers.
32 | if (request.method == "HEAD") {
33 | return false
34 | }
35 |
36 | val responseCode = code
37 | if ((responseCode < StatusLine.HTTP_CONTINUE || responseCode >= 200) && responseCode != HttpURLConnection.HTTP_NO_CONTENT && responseCode != HttpURLConnection.HTTP_NOT_MODIFIED) {
38 | return true
39 | }
40 |
41 | // If the Content-Length or Transfer-Encoding headers disagree with the response code, the
42 | // response is malformed. For best compatibility, we honor the headers.
43 | if (headersContentLength() != -1L || "chunked".equals(
44 | header("Transfer-Encoding"),
45 | ignoreCase = true
46 | )
47 | ) {
48 | return true
49 | }
50 |
51 | return false
52 | }
53 |
54 | /** Returns the Content-Length as reported by the response headers. */
55 | fun Response.headersContentLength(): Long {
56 | return headers["Content-Length"]?.toLongOrDefault(-1L) ?: -1L
57 | }
58 |
59 |
60 | fun String.toLongOrDefault(defaultValue: Long): Long {
61 | return try {
62 | toLong()
63 | } catch (_: NumberFormatException) {
64 | defaultValue
65 | }
66 | }
67 |
68 | internal fun Buffer.isProbablyUtf8(): Boolean {
69 | try {
70 | val prefix = Buffer()
71 | val byteCount = size.coerceAtMost(64)
72 | copyTo(prefix, 0, byteCount)
73 | for (i in 0 until 16) {
74 | if (prefix.exhausted()) {
75 | break
76 | }
77 | val codePoint = prefix.readUtf8CodePoint()
78 | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
79 | return false
80 | }
81 | }
82 | return true
83 | } catch (_: EOFException) {
84 | return false // Truncated UTF-8 sequence.
85 | }
86 | }
87 |
88 | fun RequestBody.readString(): String {
89 | var result = ""
90 | try {
91 | val buffer = Buffer()
92 | this.writeTo(buffer)
93 | val charset: Charset =
94 | this.contentType()?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
95 | if (buffer.isProbablyUtf8()) {
96 | result = buffer.readString(charset)
97 | }
98 | } catch (e: Exception) {
99 |
100 | }
101 | return result
102 | }
103 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/utils/SPUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | object SPUtils {
7 |
8 | private fun getSharedPreference(context: Context, fileName: String): SharedPreferences {
9 | return context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
10 | }
11 |
12 | fun getEditor(context: Context, fileName: String): SharedPreferences.Editor {
13 | return getSharedPreference(context, fileName).edit()
14 | }
15 |
16 | fun saveValue(context: Context, filename: String, key: String, value: Any?) {
17 | when (value) {
18 | is Int -> saveInt(context, filename, key, value)
19 | is Long -> saveLong(context, filename, key, value)
20 | is Float -> saveFloat(context, filename, key, value)
21 | is Boolean -> saveBoolean(context, filename, key, value)
22 | else -> saveString(context, filename, key, value?.toString() ?: "")
23 | }
24 | }
25 |
26 | fun saveBoolean(context: Context, filename: String, key: String, value: Boolean) {
27 | getEditor(context, filename).putBoolean(key, value).commit()
28 | }
29 |
30 | fun getBoolean(context: Context, filename: String, key: String): Boolean {
31 | return getSharedPreference(context, filename).getBoolean(key, false)
32 | }
33 |
34 | fun getBoolean(context: Context, filename: String, key: String, defaultValue: Boolean): Boolean {
35 | return getSharedPreference(context, filename).getBoolean(key, defaultValue)
36 | }
37 |
38 | fun saveLong(context: Context, filename: String, key: String, value: Long) {
39 | getEditor(context, filename).putLong(key, value).commit()
40 | }
41 |
42 | fun saveFloat(context: Context, filename: String, key: String, value: Float) {
43 | getEditor(context, filename).putFloat(key, value).commit()
44 | }
45 |
46 | fun getLong(context: Context, filename: String, key: String): Long {
47 | return getSharedPreference(context, filename).getLong(key, 0)
48 | }
49 |
50 | fun getLong(context: Context, filename: String, key: String, defaultValue: Long): Long {
51 | return getSharedPreference(context, filename).getLong(key, defaultValue)
52 | }
53 |
54 | fun saveInt(context: Context, filename: String, key: String, value: Int) {
55 | getEditor(context, filename).putInt(key, value).commit()
56 | }
57 |
58 | fun getInt(context: Context, filename: String, key: String): Int {
59 | return getSharedPreference(context, filename).getInt(key, 0)
60 | }
61 |
62 | fun getInt(context: Context, filename: String, key: String, defaultValue: Int): Int {
63 | return getSharedPreference(context, filename).getInt(key, defaultValue)
64 | }
65 |
66 | fun saveString(context: Context, filename: String, key: String, value: String) {
67 | getEditor(context, filename).putString(key, value).commit()
68 | }
69 |
70 | fun getString(context: Context, filename: String, key: String): String? {
71 | return getSharedPreference(context, filename).getString(key, "")
72 | }
73 |
74 | fun getString(context: Context, filename: String, key: String, defaultValue: String): String? {
75 | return getSharedPreference(context, filename).getString(key, defaultValue)
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/dialog_sp_modify.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
26 |
27 |
41 |
42 |
56 |
57 |
72 |
73 |
86 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/adapter/MonitorListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.adapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.core.content.ContextCompat
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.lygttpod.monitor.R
9 | import com.lygttpod.monitor.data.MonitorData
10 | import com.lygttpod.monitor.databinding.ItemMonitorBinding
11 |
12 | class MonitorListAdapter : RecyclerView.Adapter() {
13 |
14 | var itemClick: ((MonitorData) -> Unit)? = null
15 |
16 | private var monitorList: MutableList = mutableListOf()
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MonitorListHolder {
19 | return MonitorListHolder(
20 | LayoutInflater.from(parent.context).inflate(R.layout.item_monitor, parent, false)
21 | )
22 | }
23 |
24 | override fun getItemCount(): Int = monitorList.size
25 |
26 | override fun onBindViewHolder(holder: MonitorListHolder, position: Int) {
27 | holder.bindData(monitorList[position])
28 | }
29 |
30 | fun setData(list: MutableList?) {
31 | this.monitorList = list ?: mutableListOf()
32 | notifyDataSetChanged()
33 | }
34 |
35 | inner class MonitorListHolder(var containerView: View) :
36 | RecyclerView.ViewHolder(containerView) {
37 |
38 | private val binding: ItemMonitorBinding = ItemMonitorBinding.bind(containerView)
39 |
40 | var monitorData: MonitorData? = null
41 |
42 | init {
43 | containerView.setOnClickListener {
44 | itemClick?.invoke(
45 | monitorData ?: return@setOnClickListener
46 | )
47 | }
48 | }
49 |
50 | fun bindData(monitorData: MonitorData) {
51 | this.monitorData = monitorData
52 |
53 | binding.tvHost.text = monitorData.host
54 | binding.tvPath.text = monitorData.path
55 | binding.tvRequestDate.text = monitorData.requestTime
56 | binding.tvDuration.visibility = if (monitorData.duration <= 0) View.GONE else View.VISIBLE
57 | binding.tvDuration.text = "${monitorData.duration}ms"
58 | binding.tvCode.text = monitorData.responseCode.toString()
59 | binding.tvMethod.text = monitorData.method
60 | binding.tvSource.text = monitorData.source
61 | binding.tvSource.visibility = if (monitorData.source.isNullOrBlank()) View.GONE else View.VISIBLE
62 |
63 | binding.tvPath.setTextColor(getColor(monitorData.responseCode))
64 | binding.tvCode.setTextColor(getColor(monitorData.responseCode))
65 | }
66 |
67 | private fun getColor(code: Int): Int {
68 | when (code) {
69 | 200 -> return ContextCompat.getColor(
70 | containerView.context,
71 | R.color.monitor_status_success
72 | )
73 | 300 -> return ContextCompat.getColor(
74 | containerView.context,
75 | R.color.monitor_status_300
76 | )
77 | 400 -> return ContextCompat.getColor(
78 | containerView.context,
79 | R.color.monitor_status_400
80 | )
81 | 500 -> return ContextCompat.getColor(
82 | containerView.context,
83 | R.color.monitor_status_500
84 | )
85 | else -> return ContextCompat.getColor(
86 | containerView.context,
87 | R.color.monitor_status_error
88 | )
89 | }
90 | }
91 |
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/activity_monitor_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
23 |
24 |
36 |
37 |
51 |
52 |
67 |
68 |
76 |
77 |
86 |
87 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/MonitorMainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.os.Handler
6 | import android.os.Looper
7 | import android.util.Log
8 | import android.view.MenuItem
9 | import android.view.View
10 | import androidx.appcompat.app.AppCompatActivity
11 | import androidx.fragment.app.Fragment
12 | import androidx.viewpager2.adapter.FragmentStateAdapter
13 | import androidx.viewpager2.widget.ViewPager2
14 | import com.google.android.material.navigation.NavigationBarView
15 | import com.lygttpod.monitor.MonitorHelper
16 | import com.lygttpod.monitor.R
17 | import com.lygttpod.monitor.databinding.ActivityMonitorMainBinding
18 | import com.lygttpod.monitor.ui.request.MonitorMainFragment
19 | import com.lygttpod.monitor.ui.sp.SPFileListFragment
20 | import com.lygttpod.monitor.utils.getPhoneWifiIpAddress
21 | import kotlin.concurrent.thread
22 |
23 | class MonitorMainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListener {
24 |
25 | private var handle: Handler = Handler(Looper.getMainLooper())
26 |
27 | private lateinit var binding: ActivityMonitorMainBinding
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | binding = ActivityMonitorMainBinding.inflate(layoutInflater)
32 | setContentView(binding.root)
33 | initView()
34 | initPage()
35 | }
36 |
37 | override fun onDestroy() {
38 | super.onDestroy()
39 | handle.removeCallbacksAndMessages(null)
40 | }
41 |
42 | private fun initView() {
43 | binding.tvTitle.text = getString(R.string.monitor_app_name)
44 | binding.tvClean.setOnClickListener {
45 | thread { MonitorHelper.deleteAll() }
46 | }
47 | getPhoneWifiIpAddress()?.let {
48 | binding.tvWifiAddress.visibility = View.VISIBLE
49 | val monitorUrl = "局域网内可访问:$it:${MonitorHelper.port}/index"
50 | binding.tvWifiAddress.text = monitorUrl
51 | Log.d("MonitorHelper", monitorUrl)
52 | }
53 |
54 | binding.ivSetting.setOnClickListener {
55 | startActivity(Intent(this, MonitorConfigActivity::class.java))
56 | }
57 |
58 | binding.bottomNavigationView.setOnItemSelectedListener(this)
59 | }
60 |
61 | private fun initPage() {
62 | val fragments = listOf(MonitorMainFragment(), SPFileListFragment())
63 | binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
64 | binding.viewPager.adapter = object : FragmentStateAdapter(this) {
65 | override fun getItemCount() = fragments.size
66 |
67 | override fun createFragment(position: Int): Fragment {
68 | return fragments[position]
69 | }
70 | }
71 | binding.viewPager.offscreenPageLimit = fragments.size
72 | binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
73 | override fun onPageSelected(position: Int) {
74 | super.onPageSelected(position)
75 | when (position) {
76 | 0 -> binding.bottomNavigationView.selectedItemId = R.id.navigation_monitor
77 | 1 -> binding.bottomNavigationView.selectedItemId = R.id.navigation_sharedPrefs
78 | }
79 | }
80 | })
81 | }
82 |
83 | override fun onNavigationItemSelected(item: MenuItem): Boolean {
84 | when (item.itemId) {
85 | R.id.navigation_monitor -> {
86 | binding.viewPager.currentItem = 0
87 | return true
88 | }
89 | R.id.navigation_sharedPrefs -> {
90 | binding.viewPager.currentItem = 1
91 | return true
92 | }
93 | }
94 | return false
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/dialog/SpModifyDialog.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui.dialog
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.graphics.Color
6 | import android.graphics.drawable.ColorDrawable
7 | import android.os.Bundle
8 | import android.view.Gravity
9 | import android.view.WindowManager
10 | import android.widget.Toast
11 | import com.lygttpod.monitor.MonitorHelper
12 | import com.lygttpod.monitor.R
13 | import com.lygttpod.monitor.data.SpValueInfo
14 | import com.lygttpod.monitor.databinding.DialogSpModifyBinding
15 | import com.lygttpod.monitor.enum.SPValueType
16 |
17 | /**
18 | *
19 | * author : Allen
20 | * date : 2022/8/7
21 | * desc :
22 | *
23 | */
24 | class SpModifyDialog(context: Context) : Dialog(context, R.style.B2TDialogTheme) {
25 |
26 | companion object {
27 | fun show(
28 | context: Context,
29 | fileName: String?,
30 | key: String?,
31 | valueInfo: SpValueInfo?,
32 | onModifySuccess: (String, SpValueInfo?) -> Unit
33 | ) {
34 | SpModifyDialog(context).apply {
35 | this.spFileName = fileName
36 | this.spKey = key
37 | this.spValueInfo = valueInfo
38 | this.onModifySuccess = onModifySuccess
39 | }.show()
40 | }
41 | }
42 |
43 | private lateinit var binding: DialogSpModifyBinding
44 | private var spFileName: String? = null
45 | private var spKey: String? = null
46 | private var spValueInfo: SpValueInfo? = null
47 |
48 | private var onModifySuccess: ((String, SpValueInfo?) -> Unit)? = null
49 |
50 | override fun onCreate(savedInstanceState: Bundle?) {
51 | super.onCreate(savedInstanceState)
52 | binding = DialogSpModifyBinding.inflate(layoutInflater)
53 | setContentView(binding.root)
54 | initWindow()
55 | initView()
56 | }
57 |
58 | private fun initView() {
59 | binding.btnModify.setOnClickListener {
60 | if (spFileName != null && spKey != null) {
61 | val modifyContent = binding.etContent.text.toString()
62 | val originType = spValueInfo!!.type
63 | val realValue = getRealValueByType(modifyContent, originType)
64 | if (realValue == null) {
65 | Toast.makeText(context, "请输入《${originType}》类型的值哦", Toast.LENGTH_LONG).show()
66 | return@setOnClickListener
67 | }
68 | MonitorHelper.updateSpValue(spFileName!!, spKey!!, realValue)
69 | onModifySuccess?.invoke(spKey!!, SpValueInfo(realValue, originType))
70 | Toast.makeText(context, "修改成功", Toast.LENGTH_SHORT).show()
71 | dismiss()
72 | }
73 | }
74 | binding.tvTitle.text = spFileName
75 | binding.tvKey.text = spKey
76 | binding.tvType.text = spValueInfo?.type?.value
77 | binding.etContent.setText("${spValueInfo?.value}")
78 | }
79 |
80 | private fun getRealValueByType(content: String, type: SPValueType): Any? {
81 | return when (type) {
82 | SPValueType.Boolean -> content.toBoolean()
83 | SPValueType.Int -> content.toIntOrNull()
84 | SPValueType.Float -> content.toFloatOrNull()
85 | SPValueType.Long -> content.toLongOrNull()
86 | SPValueType.Double -> content.toDoubleOrNull()
87 | else -> content
88 | }
89 | }
90 |
91 | private fun initWindow() {
92 | val window = window
93 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
94 | window?.setGravity(Gravity.BOTTOM)
95 | window?.setLayout(
96 | WindowManager.LayoutParams.MATCH_PARENT,
97 | dip2px(context, 300f)
98 | )
99 | }
100 |
101 | private fun dip2px(context: Context, dipValue: Float): Int {
102 | val scale = context.resources.displayMetrics.density
103 | return (dipValue * scale + 0.5f).toInt()
104 | }
105 | }
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/activity_sp_file_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
28 |
29 |
39 |
40 |
46 |
47 |
55 |
56 |
71 |
72 |
87 |
88 |
95 |
96 |
104 |
105 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/item_monitor.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
25 |
26 |
40 |
41 |
55 |
56 |
68 |
69 |
79 |
80 |
81 |
93 |
94 |
106 |
107 |
114 |
115 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/fragment_monitor_request.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
24 |
25 |
33 |
34 |
43 |
44 |
52 |
53 |
54 |
63 |
64 |
72 |
73 |
82 |
83 |
91 |
92 |
101 |
102 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/fragment_monitor_response.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
24 |
25 |
35 |
36 |
45 |
46 |
54 |
55 |
64 |
65 |
73 |
74 |
83 |
84 |
92 |
93 |
99 |
100 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/ui/MonitorConfigActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.ui
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.View
7 | import android.widget.Toast
8 | import androidx.appcompat.app.AppCompatActivity
9 | import com.lygttpod.monitor.MonitorHelper
10 | import com.lygttpod.monitor.R
11 | import com.lygttpod.monitor.databinding.ActivityMonitorConfigBinding
12 | import com.lygttpod.monitor.enum.WeakNetworkType
13 | import com.lygttpod.monitor.mock.MockHelper
14 | import com.lygttpod.monitor.weaknetwork.WeakNetworkHelper
15 |
16 | class MonitorConfigActivity : AppCompatActivity() {
17 |
18 | private lateinit var binding: ActivityMonitorConfigBinding
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | binding = ActivityMonitorConfigBinding.inflate(layoutInflater)
23 | setContentView(binding.root)
24 | initMonitorView()
25 | initWeakNetView()
26 | initMockView()
27 | initListener()
28 | }
29 |
30 | private fun initMonitorView() {
31 | binding.monitorSwitchBtn.isChecked = MonitorHelper.isOpenMonitor
32 | }
33 |
34 | private fun initMockView() {
35 | binding.mockSwitchBtn.isChecked = MockHelper.isOpen
36 | setMockSwitchUI(MockHelper.isOpen)
37 | binding.editBaseUrl.setText(MockHelper.mockBaseUrl)
38 | binding.editPath.setText(MockHelper.mockPaths)
39 | binding.editResponse.setText(MockHelper.mockResponse)
40 | }
41 |
42 | private fun initWeakNetView() {
43 | binding.switchBtn.isChecked = WeakNetworkHelper.isOpen
44 | setWeakSwitchUI(WeakNetworkHelper.isOpen)
45 | binding.editSpeed.setText(WeakNetworkHelper.responseSpeedByte.toString())
46 | setCurrentNetDes(WeakNetworkHelper.weakNetType())
47 | setSpeedContainerVisible(WeakNetworkHelper.weakNetType())
48 |
49 | when (WeakNetworkHelper.weakNetType()) {
50 | WeakNetworkType.TIME_OUT -> binding.radioBtnTimeOut.isChecked = true
51 | WeakNetworkType.NO_NETWORK -> binding.radioBtnNoNet.isChecked = true
52 | WeakNetworkType.SPEED_LIMIT -> binding.radioBtnSpeedLimit.isChecked = true
53 | }
54 | }
55 |
56 | private fun initListener() {
57 | binding.ivBack.setOnClickListener { finish() }
58 |
59 | binding.monitorSwitchBtn.setOnCheckedChangeListener { buttonView, isChecked ->
60 | MonitorHelper.isOpenMonitor = isChecked
61 | Toast.makeText(this, if (isChecked) "抓包功能已开启" else "抓包功能已关闭", Toast.LENGTH_SHORT).show()
62 | }
63 |
64 | binding.switchBtn.setOnCheckedChangeListener { buttonView, isChecked ->
65 | WeakNetworkHelper.isOpen = isChecked
66 | setWeakSwitchUI(isChecked)
67 | }
68 |
69 | binding.mockSwitchBtn.setOnCheckedChangeListener { buttonView, isChecked ->
70 | MockHelper.isOpen = isChecked
71 | setMockSwitchUI(isChecked)
72 | }
73 |
74 | binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
75 | when (checkedId) {
76 | R.id.radio_btn_time_out -> {
77 | WeakNetworkHelper.setWeakType(WeakNetworkType.TIME_OUT)
78 | }
79 | R.id.radio_btn_no_net -> {
80 | WeakNetworkHelper.setWeakType(WeakNetworkType.NO_NETWORK)
81 | }
82 | R.id.radio_btn_speed_limit -> {
83 | WeakNetworkHelper.setWeakType(WeakNetworkType.SPEED_LIMIT)
84 | }
85 | }
86 |
87 | setSpeedContainerVisible(WeakNetworkHelper.weakNetType())
88 | setCurrentNetDes(WeakNetworkHelper.weakNetType())
89 | }
90 |
91 | binding.editSpeed.addTextChangedListener(object : TextWatcher {
92 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
93 | }
94 |
95 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
96 | }
97 |
98 | override fun afterTextChanged(s: Editable?) {
99 | val speed = s?.toString()?.toLongOrNull() ?: 1024L
100 | WeakNetworkHelper.responseSpeedByte = speed
101 | }
102 |
103 | })
104 |
105 | binding.editBaseUrl.addTextChangedListener(object : TextWatcher {
106 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
107 | }
108 |
109 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
110 | }
111 |
112 | override fun afterTextChanged(s: Editable?) {
113 | val url = s?.toString() ?: ""
114 | MockHelper.mockBaseUrl = url
115 | }
116 |
117 | })
118 |
119 | binding.editPath.addTextChangedListener(object : TextWatcher {
120 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
121 | }
122 |
123 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
124 | }
125 |
126 | override fun afterTextChanged(s: Editable?) {
127 | val url = s?.toString() ?: ""
128 | MockHelper.mockPaths = url
129 | }
130 |
131 | })
132 |
133 | binding.editResponse.addTextChangedListener(object : TextWatcher {
134 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
135 | }
136 |
137 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
138 | }
139 |
140 | override fun afterTextChanged(s: Editable?) {
141 | MockHelper.mockResponse = s?.toString() ?: ""
142 | }
143 |
144 | })
145 |
146 | binding.editResponseCode.addTextChangedListener(object : TextWatcher {
147 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
148 | }
149 |
150 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
151 | }
152 |
153 | override fun afterTextChanged(s: Editable?) {
154 | MockHelper.mockSuccessResponseCode = (s?.toString() ?: "200").toIntOrNull() ?: 200
155 | }
156 |
157 | })
158 | }
159 |
160 | private fun setSpeedContainerVisible(type: WeakNetworkType) {
161 | binding.groupSpeed.visibility =
162 | if (type == WeakNetworkType.SPEED_LIMIT) View.VISIBLE else View.GONE
163 | }
164 |
165 | private fun setCurrentNetDes(type: WeakNetworkType) {
166 | val des = when (type) {
167 | WeakNetworkType.TIME_OUT -> "当前配置为:模拟网络<<请求超时>>"
168 | WeakNetworkType.NO_NETWORK -> "当前配置为:模拟网络<<请求断网>>"
169 | WeakNetworkType.SPEED_LIMIT -> "当前配置为:模拟网络<<请求限速>>"
170 | }
171 | binding.tvDes.text = des
172 | }
173 |
174 | private fun setWeakSwitchUI(checked: Boolean) {
175 | val visible = if (checked) View.VISIBLE else View.GONE
176 | binding.clWeakConfigContainer.visibility = visible
177 | }
178 |
179 | private fun setMockSwitchUI(checked: Boolean) {
180 | val visible = if (checked) View.VISIBLE else View.GONE
181 | binding.clMockConfigContainer.visibility = visible
182 | }
183 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/interceptor/MonitorInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor.interceptor
2 |
3 | import android.net.Uri
4 | import android.util.Log
5 | import com.lygttpod.monitor.MonitorHelper
6 | import com.lygttpod.monitor.data.MonitorData
7 | import com.lygttpod.monitor.utils.*
8 | import okhttp3.Headers
9 | import okhttp3.Interceptor
10 | import okhttp3.MultipartBody
11 | import okhttp3.Response
12 | import okio.Buffer
13 | import okio.GzipSource
14 | import java.io.EOFException
15 | import java.nio.charset.Charset
16 | import java.nio.charset.StandardCharsets
17 | import java.util.*
18 | import java.util.concurrent.TimeUnit
19 | import kotlin.math.min
20 |
21 | class MonitorInterceptor : Interceptor {
22 | companion object {
23 | private const val TAG = "MonitorInterceptor"
24 | }
25 |
26 | private var maxContentLength = 5L * 1024 * 1024
27 |
28 | override fun intercept(chain: Interceptor.Chain): Response {
29 | val request = chain.request()
30 | if (!MonitorHelper.isOpenMonitor) {
31 | return chain.proceed(request)
32 | }
33 | val monitorData = MonitorData()
34 | monitorData.method = request.method
35 | val url = request.url.toString()
36 | monitorData.url = url
37 | if (url.isNotBlank()) {
38 | val uri = Uri.parse(url)
39 | monitorData.host = uri.host
40 | monitorData.path = uri.path + if (uri.query != null) "?" + uri.query else ""
41 | monitorData.scheme = uri.scheme
42 | }
43 |
44 | if (!monitorData.host.isWhiteHosts()) {
45 | return chain.proceed(request)
46 | }
47 |
48 | if (monitorData.host.isBlackHosts()) {
49 | return chain.proceed(request)
50 | }
51 |
52 | if (MonitorHelper.isFilterIPAddressHost && monitorData.host.isIpAddress()) {
53 | return chain.proceed(request)
54 | }
55 |
56 | val requestBody = request.body
57 | monitorData.requestTime = Date().formatData(TIME_LONG)
58 |
59 | requestBody?.contentType()?.let { monitorData.requestContentType = it.toString() }
60 |
61 | val startTime = System.nanoTime()
62 | val response: Response
63 | try {
64 | response = chain.proceed(request)
65 | } catch (e: Exception) {
66 | monitorData.errorMsg = e.toString()
67 | insert(monitorData)
68 | throw e
69 | }
70 | try {
71 | monitorData.requestHeaders = response.request.headers.toJsonString()
72 |
73 | val contentType = response.headers["Content-Type"]
74 | if (!contentType.isWhiteContentType()) {
75 | return response
76 | }
77 |
78 | monitorData.responseTime = Date().formatData(TIME_LONG)
79 | monitorData.duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
80 | monitorData.protocol = response.protocol.toString()
81 | monitorData.responseCode = response.code
82 | monitorData.responseMessage = response.message
83 |
84 | when {
85 | requestBody == null || bodyHasUnknownEncoding(request.headers) || requestBody.isDuplex() || requestBody.isOneShot() -> {
86 | }
87 | requestBody is MultipartBody -> {
88 | var formatRequestBody = ""
89 | requestBody.parts.forEach {
90 | val isStream =
91 | it.body.contentType()?.toString()?.contains("otcet-stream") == true
92 | val key = it.headers?.value(0)
93 | formatRequestBody += if (isStream) {
94 | "${key}; value=文件流\n"
95 | } else {
96 | val value = it.body.readString()
97 | "${key}; value=${value}\n"
98 | }
99 | }
100 | monitorData.requestBody = formatRequestBody
101 | }
102 | else -> {
103 | val buffer = Buffer()
104 | requestBody.writeTo(buffer)
105 | val charset: Charset =
106 | requestBody.contentType()?.charset(StandardCharsets.UTF_8)
107 | ?: StandardCharsets.UTF_8
108 | if (buffer.isProbablyUtf8()) {
109 | monitorData.requestBody = buffer.readString(charset)
110 | }
111 | }
112 | }
113 |
114 | val responseBody = response.body
115 |
116 | responseBody?.let { body ->
117 | body.contentType()?.let { monitorData.responseContentType = it.toString() }
118 | }
119 |
120 | val bodyHasUnknownEncoding = bodyHasUnknownEncoding(response.headers)
121 |
122 | if (responseBody != null && response.promisesBody() && !bodyHasUnknownEncoding) {
123 | val source = responseBody.source()
124 | source.request(Long.MAX_VALUE) // Buffer the entire body.
125 | var buffer = source.buffer
126 |
127 | if (bodyGzipped(response.headers)) {
128 | GzipSource(buffer.clone()).use { gzippedResponseBody ->
129 | buffer = Buffer()
130 | buffer.writeAll(gzippedResponseBody)
131 | }
132 | }
133 |
134 | val charset: Charset = responseBody.contentType()?.charset(StandardCharsets.UTF_8)
135 | ?: StandardCharsets.UTF_8
136 |
137 | if (responseBody.contentLength() != 0L && buffer.isProbablyUtf8()) {
138 | val body = readFromBuffer(buffer.clone(), charset)
139 | monitorData.responseBody = formatBody(body, monitorData.responseContentType)
140 | }
141 | monitorData.responseContentLength = buffer.size
142 | }
143 | insert(monitorData)
144 | return response
145 | } catch (e: Exception) {
146 | Log.d("MonitorHelper", e.message ?: "")
147 | return response
148 | }
149 | }
150 |
151 | private fun insert(monitorData: MonitorData) {
152 | MonitorHelper.insert(monitorData)
153 | }
154 |
155 | private fun update(monitorData: MonitorData) {
156 | MonitorHelper.update(monitorData)
157 | }
158 |
159 | private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
160 | val contentEncoding = headers["Content-Encoding"] ?: return false
161 | return !contentEncoding.equals("identity", ignoreCase = true) &&
162 | !contentEncoding.equals("gzip", ignoreCase = true)
163 | }
164 |
165 | private fun readFromBuffer(buffer: Buffer, charset: Charset?): String {
166 | val bufferSize = buffer.size
167 | val maxBytes = min(bufferSize, maxContentLength)
168 | var body: String = try {
169 | buffer.readString(maxBytes, charset!!)
170 | } catch (e: EOFException) {
171 | "\\n\\n--- Unexpected end of content ---"
172 | }
173 | if (bufferSize > maxContentLength) {
174 | body += "\\n\\n--- Content truncated ---"
175 | }
176 | return body
177 | }
178 |
179 | private fun bodyGzipped(headers: Headers): Boolean {
180 | return "gzip".equals(headers["Content-Encoding"], ignoreCase = true)
181 | }
182 |
183 | }
--------------------------------------------------------------------------------
/monitor/src/main/java/com/lygttpod/monitor/MonitorHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lygttpod.monitor
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.util.Log
6 | import androidx.lifecycle.LiveData
7 | import androidx.room.Room
8 | import com.android.local.service.core.ALSHelper
9 | import com.android.local.service.core.data.ServiceConfig
10 | import com.lygttpod.monitor.data.MonitorData
11 | import com.lygttpod.monitor.data.SpValueInfo
12 | import com.lygttpod.monitor.enum.SPValueType
13 | import com.lygttpod.monitor.interceptor.MonitorInterceptor
14 | import com.lygttpod.monitor.interceptor.MonitorMockInterceptor
15 | import com.lygttpod.monitor.interceptor.MonitorMockResponseInterceptor
16 | import com.lygttpod.monitor.interceptor.MonitorWeakNetworkInterceptor
17 | import com.lygttpod.monitor.room.MonitorDao
18 | import com.lygttpod.monitor.room.MonitorDatabase
19 | import com.lygttpod.monitor.service.MonitorService
20 | import com.lygttpod.monitor.utils.MonitorProperties
21 | import com.lygttpod.monitor.utils.SPUtils
22 | import com.lygttpod.monitor.utils.defaultContentTypes
23 | import com.lygttpod.monitor.utils.lastUpdateDataId
24 | import com.google.gson.Gson
25 | import java.io.File
26 | import java.util.*
27 | import java.util.concurrent.ExecutorService
28 | import java.util.concurrent.Executors
29 | import kotlin.concurrent.thread
30 |
31 |
32 | @SuppressLint("StaticFieldLeak")
33 | object MonitorHelper {
34 |
35 | const val TAG = "MonitorHelper"
36 |
37 | var context: Context? = null
38 | var monitorDb: MonitorDatabase? = null
39 |
40 | var port = 0
41 |
42 | //有从来ASM修改字节码对OKHTTP进行hook用的
43 | val hookInterceptors = listOf(
44 | MonitorMockInterceptor(),
45 | MonitorInterceptor(),
46 | MonitorWeakNetworkInterceptor(),
47 | MonitorMockResponseInterceptor()
48 | )
49 |
50 | var whiteContentTypes: String? = null
51 | var whiteHosts: String? = null
52 | var blackHosts: String? = null
53 | var isFilterIPAddressHost: Boolean = false
54 |
55 | var isOpenMonitor = true
56 |
57 | private var singleThreadExecutor: ExecutorService? = null
58 |
59 | private fun threadExecutor(action: () -> Unit) {
60 | if (singleThreadExecutor == null || singleThreadExecutor?.isShutdown == true) {
61 | singleThreadExecutor = Executors.newSingleThreadExecutor()
62 | }
63 | singleThreadExecutor?.execute(action)
64 | }
65 |
66 | fun init(context: Context) {
67 | MonitorHelper.context = context
68 | thread {
69 | val propertiesData = MonitorProperties().paramsProperties()
70 | val dbName: String = propertiesData?.dbName ?: "monitor_room_db"
71 | val contentTypes = propertiesData?.whiteContentTypes
72 | whiteContentTypes = if (contentTypes.isNullOrBlank()) defaultContentTypes else contentTypes
73 | whiteHosts = propertiesData?.whiteHosts
74 | blackHosts = propertiesData?.blackHosts
75 | port = propertiesData?.port?.toInt() ?: 0
76 | isFilterIPAddressHost = propertiesData?.isFilterIPAddressHost ?: false
77 | initMonitorDataDao(context, dbName)
78 | initPCService(context, port)
79 | }
80 | }
81 |
82 | private fun initPCService(context: Context, port: Int = 0) {
83 | ALSHelper.init(context)
84 | ALSHelper.startService(
85 | if (port > 0) ServiceConfig(
86 | MonitorService::class.java,
87 | port
88 | ) else ServiceConfig(MonitorService::class.java)
89 | )
90 | MonitorHelper.port = ALSHelper.serviceList.firstOrNull()?.port ?: 0
91 | }
92 |
93 | private fun initMonitorDataDao(context: Context, dbName: String) {
94 | if (monitorDb == null) {
95 | monitorDb = Room
96 | .databaseBuilder(context.applicationContext, MonitorDatabase::class.java, dbName)
97 | .fallbackToDestructiveMigration()
98 | .build()
99 | }
100 | }
101 |
102 | private fun getMonitorDataDao(): MonitorDao? {
103 | return monitorDb?.monitorDao()
104 | }
105 |
106 | fun insert(monitorData: MonitorData) {
107 | lastUpdateDataId = monitorData.id
108 | getMonitorDataDao()?.insert(monitorData)
109 | }
110 |
111 | fun insertAsync(map: Map?) {
112 | if (map == null || map.isEmpty()) return
113 | threadExecutor {
114 | try {
115 | val monitor = Gson().fromJson(Gson().toJson(map), MonitorData::class.java)
116 | insert(monitor)
117 | } catch (e: Exception) {
118 | Log.d(TAG, "insertAsync--${e.message}")
119 | }
120 | }
121 | }
122 |
123 | fun update(monitorData: MonitorData) {
124 | getMonitorDataDao()?.update(monitorData)
125 | }
126 |
127 | fun deleteAll() {
128 | lastUpdateDataId = 0L
129 | getMonitorDataDao()?.deleteAll()
130 | }
131 |
132 | fun getMonitorDataListForAndroid(
133 | limit: Int = 50,
134 | offset: Int = 0
135 | ): LiveData>? {
136 | return getMonitorDataDao()?.queryByOffsetForAndroid(limit, offset)
137 | }
138 |
139 | fun getMonitorDataList(limit: Int = 50, offset: Int = 0): MutableList {
140 | return getMonitorDataDao()?.queryByOffset(limit, offset) ?: mutableListOf()
141 | }
142 |
143 | fun getMonitorDataByLastIdForAndroid(lastUpdateDataId: Long): LiveData>? {
144 | return getMonitorDataDao()?.queryByLastIdForAndroid(lastUpdateDataId)
145 | }
146 |
147 | fun getMonitorDataByLastId(lastUpdateDataId: Long): MutableList {
148 | return getMonitorDataDao()?.queryByLastId(lastUpdateDataId) ?: mutableListOf()
149 | }
150 |
151 | fun getSharedPrefsFilesData(): HashMap> {
152 | val ctx = context ?: return hashMapOf()
153 | val map = hashMapOf>()
154 | val targetFile = File("${ctx.cacheDir.parentFile?.absolutePath}/shared_prefs")
155 | if (!targetFile.exists()) return hashMapOf()
156 | if (targetFile.isDirectory) {
157 | targetFile.listFiles()?.forEach { spFile ->
158 | Log.d(TAG, "getSharedPrefsFiles: " + spFile.name)
159 | val fileName = spFile.name
160 | if (!fileName.isNullOrBlank()) {
161 | val name = if (fileName.endsWith(".xml")) fileName.split(".xml")[0] else fileName
162 | val value = getSpFile(name)
163 | map[name] = value
164 | }
165 | }
166 | }
167 | return map
168 | }
169 |
170 | fun getSpFile(name: String?): HashMap {
171 | if (name.isNullOrBlank()) return hashMapOf()
172 | val map = hashMapOf()
173 | context?.getSharedPreferences(name, Context.MODE_PRIVATE)?.all?.entries?.forEach {
174 | val valueType = when (it.value) {
175 | is Int -> SPValueType.Int
176 | is Double -> SPValueType.Double
177 | is Float -> SPValueType.Float
178 | is Long -> SPValueType.Long
179 | is Boolean -> SPValueType.Boolean
180 | is String -> SPValueType.String
181 | else -> SPValueType.String
182 | }
183 | val key = it.key
184 | if (!key.isNullOrBlank()) {
185 | map[key] = SpValueInfo(it.value, valueType)
186 | }
187 | }
188 | return map
189 | }
190 |
191 | fun updateSpValue(fileName: String, key: String, value: Any?) {
192 | SPUtils.saveValue(context ?: return, fileName, key, value)
193 | }
194 | }
--------------------------------------------------------------------------------
/monitor/src/main/assets/sp_index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 抓包助手-本地文件
6 |
7 |
8 |
9 |
10 |
220 |
265 |
266 |
267 |
268 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
290 |
291 |
292 |
295 |
308 |
309 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
--------------------------------------------------------------------------------
/monitor/src/main/res/layout/activity_monitor_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
22 |
23 |
33 |
34 |
41 |
42 |
45 |
46 |
57 |
58 |
69 |
70 |
81 |
82 |
93 |
94 |
106 |
107 |
115 |
116 |
127 |
128 |
138 |
139 |
149 |
150 |
151 |
159 |
160 |
169 |
170 |
182 |
183 |
192 |
193 |
203 |
204 |
211 |
212 |
213 |
214 |
225 |
226 |
237 |
238 |
250 |
251 |
258 |
259 |
273 |
274 |
282 |
283 |
298 |
299 |
309 |
310 |
311 |
319 |
320 |
335 |
336 |
344 |
345 |
361 |
362 |
363 |
364 |
365 |
--------------------------------------------------------------------------------