├── .gitignore ├── README.md ├── android-secring.gpg ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── monitor.properties │ ├── java │ └── com │ │ └── android │ │ └── monitor │ │ └── demo │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── 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-night │ └── themes.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── monitor-plugin ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── kotlin │ └── com │ │ └── lygttpod │ │ └── monitor │ │ ├── okhttp │ │ ├── OkHttpClassVisitor.kt │ │ ├── OkHttpMethodAdapter.kt │ │ ├── OkHttpTransform.kt │ │ └── OkHttpWeaver.kt │ │ └── plugin │ │ └── MonitorPlugin.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ └── monitor-plugin.properties ├── monitor ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── monitor_index.html │ └── sp_index.html │ ├── java │ └── com │ │ └── lygttpod │ │ └── monitor │ │ ├── MonitorHelper.kt │ │ ├── adapter │ │ ├── MonitorListAdapter.kt │ │ └── MonitorPagerAdapter.kt │ │ ├── data │ │ ├── HttpHeader.kt │ │ ├── MonitorData.kt │ │ ├── PropertiesData.kt │ │ ├── SpData.kt │ │ └── SpValueInfo.kt │ │ ├── enum │ │ ├── SPValueType.kt │ │ └── WeakNetworkType.kt │ │ ├── interceptor │ │ ├── MonitorInterceptor.kt │ │ ├── MonitorMockInterceptor.kt │ │ ├── MonitorMockResponseInterceptor.kt │ │ └── MonitorWeakNetworkInterceptor.kt │ │ ├── mock │ │ └── MockHelper.kt │ │ ├── provider │ │ └── MonitorProvider.kt │ │ ├── room │ │ ├── MonitorDao.kt │ │ └── MonitorDatabase.kt │ │ ├── service │ │ └── MonitorService.kt │ │ ├── ui │ │ ├── MonitorConfigActivity.kt │ │ ├── MonitorDetailActivity.kt │ │ ├── MonitorMainActivity.kt │ │ ├── MonitorRequestFragment.kt │ │ ├── MonitorResponseFragment.kt │ │ ├── dialog │ │ │ └── SpModifyDialog.kt │ │ ├── request │ │ │ └── MonitorMainFragment.kt │ │ └── sp │ │ │ ├── SPFileDetailActivity.kt │ │ │ ├── SPFileListFragment.kt │ │ │ ├── SpFileAdapter.kt │ │ │ └── SpFileDetailAdapter.kt │ │ ├── utils │ │ ├── DateKtx.kt │ │ ├── FormatHelper.kt │ │ ├── GlobalConfig.kt │ │ ├── GsonHelper.kt │ │ ├── MonitorProperties.kt │ │ ├── NetworkHelper.kt │ │ ├── OkHttpKtx.kt │ │ ├── SPUtils.kt │ │ ├── ServiceDataProvider.kt │ │ └── StringKtx.kt │ │ └── weaknetwork │ │ ├── SpeedLimitResponseBody.kt │ │ └── WeakNetworkHelper.kt │ └── res │ ├── anim │ ├── bottom_to_top.xml │ └── top_to_bottom.xml │ ├── color │ └── text_color_radio.xml │ ├── drawable │ ├── bg_flutter_tag.xml │ ├── bg_monitor_theme.xml │ ├── bg_radio_center.xml │ ├── bg_radio_left.xml │ └── bg_radio_right.xml │ ├── layout │ ├── activity_monitor_config.xml │ ├── activity_monitor_detail.xml │ ├── activity_monitor_main.xml │ ├── activity_sp_file_detail.xml │ ├── dialog_sp_modify.xml │ ├── fragment_monitor_main.xml │ ├── fragment_monitor_request.xml │ ├── fragment_monitor_response.xml │ ├── fragment_sp_list.xml │ ├── include_status_bar_view_layout.xml │ ├── item_monitor.xml │ ├── item_sp_file.xml │ └── item_sp_file_detail.xml │ ├── menu │ └── bottom_nav_menu.xml │ ├── mipmap-xxhdpi │ ├── monitor_app_back_icon.png │ ├── monitor_icon_menu.png │ ├── monitor_icon_right_arrow.png │ ├── monitor_icon_share_white.png │ ├── monitor_icon_tab_file.png │ ├── monitor_icon_tab_network.png │ └── monitor_logo.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle └── upload.gradle /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android-secring.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/android-secring.gpg -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 32 | 33 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /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/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/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/data/HttpHeader.kt: -------------------------------------------------------------------------------- 1 | package com.lygttpod.monitor.data 2 | 3 | data class HttpHeader(val name: String, val value: String) -------------------------------------------------------------------------------- /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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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/res/anim/bottom_to_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /monitor/src/main/res/anim/top_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /monitor/src/main/res/color/text_color_radio.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /monitor/src/main/res/drawable/bg_flutter_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /monitor/src/main/res/drawable/bg_monitor_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /monitor/src/main/res/drawable/bg_radio_center.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/dialog_sp_modify.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 26 | 27 | 41 | 42 | 56 | 57 | 72 | 73 | 86 | -------------------------------------------------------------------------------- /monitor/src/main/res/layout/fragment_monitor_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /monitor/src/main/res/layout/fragment_sp_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /monitor/src/main/res/layout/include_status_bar_view_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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/item_sp_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 33 | 34 | 41 | 42 | -------------------------------------------------------------------------------- /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/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /monitor/src/main/res/mipmap-xxhdpi/monitor_app_back_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/monitor/src/main/res/mipmap-xxhdpi/monitor_app_back_icon.png -------------------------------------------------------------------------------- /monitor/src/main/res/mipmap-xxhdpi/monitor_icon_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_menu.png -------------------------------------------------------------------------------- /monitor/src/main/res/mipmap-xxhdpi/monitor_icon_right_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/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/6753f79d3db54cb98f47338e73c17a27724a388f/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_share_white.png -------------------------------------------------------------------------------- /monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_file.png -------------------------------------------------------------------------------- /monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/monitor/src/main/res/mipmap-xxhdpi/monitor_icon_tab_network.png -------------------------------------------------------------------------------- /monitor/src/main/res/mipmap-xxhdpi/monitor_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lygttpod/AndroidMonitor/6753f79d3db54cb98f47338e73c17a27724a388f/monitor/src/main/res/mipmap-xxhdpi/monitor_logo.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /monitor/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30dp 4 | -------------------------------------------------------------------------------- /monitor/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 抓包 3 | 4 | -------------------------------------------------------------------------------- /monitor/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 21 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "AndroidMonitor" 2 | include ':app' 3 | include ':monitor' 4 | include ':monitor-plugin' 5 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------