{
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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------