diff --git a/app/src/main/kotlin/com/neko/v2ray/service/V2RayTestService.kt b/app/src/main/kotlin/com/neko/v2ray/service/V2RayTestService.kt index 857d7a42..b75de6bf 100644 --- a/app/src/main/kotlin/com/neko/v2ray/service/V2RayTestService.kt +++ b/app/src/main/kotlin/com/neko/v2ray/service/V2RayTestService.kt @@ -3,12 +3,12 @@ package com.neko.v2ray.service import android.app.Service import android.content.Intent import android.os.IBinder -import com.google.gson.Gson import com.neko.v2ray.AppConfig.MSG_MEASURE_CONFIG import com.neko.v2ray.AppConfig.MSG_MEASURE_CONFIG_CANCEL import com.neko.v2ray.AppConfig.MSG_MEASURE_CONFIG_SUCCESS import com.neko.v2ray.dto.ConfigResult import com.neko.v2ray.extension.serializable +import com.neko.v2ray.util.JsonUtil import com.neko.v2ray.util.MessageUtil import com.neko.v2ray.util.SpeedtestUtil import com.neko.v2ray.util.Utils @@ -33,8 +33,8 @@ class V2RayTestService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.getIntExtra("key", 0)) { MSG_MEASURE_CONFIG -> { - val content = intent.serializable("content") - val config = Gson().fromJson(content, ConfigResult::class.java) + val content = intent.serializable("content") ?: "" + val config = JsonUtil.fromJson(content, ConfigResult::class.java) realTestScope.launch { val result = SpeedtestUtil.realPing(config.content) MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(config.guid, result)) diff --git a/app/src/main/kotlin/com/neko/v2ray/ui/RoutingSettingActivity.kt b/app/src/main/kotlin/com/neko/v2ray/ui/RoutingSettingActivity.kt index 98633a3a..6175e78c 100644 --- a/app/src/main/kotlin/com/neko/v2ray/ui/RoutingSettingActivity.kt +++ b/app/src/main/kotlin/com/neko/v2ray/ui/RoutingSettingActivity.kt @@ -12,13 +12,13 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.CollapsingToolbarLayout -import com.google.gson.Gson import com.neko.v2ray.AppConfig import com.neko.v2ray.R import com.neko.v2ray.databinding.ActivityRoutingSettingBinding import com.neko.v2ray.dto.RulesetItem import com.neko.v2ray.extension.toast import com.neko.v2ray.helper.SimpleItemTouchHelperCallback +import com.neko.v2ray.util.JsonUtil import com.neko.v2ray.util.MmkvManager import com.neko.v2ray.util.MmkvManager.settingsStorage import com.neko.v2ray.util.SettingsManager @@ -145,7 +145,7 @@ class RoutingSettingActivity : BaseActivity() { if (rulesetList.isNullOrEmpty()) { toast(R.string.toast_failure) } else { - Utils.setClipboard(this, Gson().toJson(rulesetList)) + Utils.setClipboard(this, JsonUtil.toJson(rulesetList)) toast(R.string.toast_success) } true diff --git a/app/src/main/kotlin/com/neko/v2ray/ui/ServerCustomConfigActivity.kt b/app/src/main/kotlin/com/neko/v2ray/ui/ServerCustomConfigActivity.kt index 9e888014..55de3f12 100644 --- a/app/src/main/kotlin/com/neko/v2ray/ui/ServerCustomConfigActivity.kt +++ b/app/src/main/kotlin/com/neko/v2ray/ui/ServerCustomConfigActivity.kt @@ -12,13 +12,14 @@ import com.google.android.material.appbar.MaterialToolbar import com.blacksquircle.ui.editorkit.utils.EditorTheme import com.blacksquircle.ui.language.json.JsonLanguage import com.google.android.material.appbar.CollapsingToolbarLayout -import com.google.gson.Gson + import com.neko.v2ray.R import com.neko.v2ray.databinding.ActivityServerCustomConfigBinding import com.neko.v2ray.dto.EConfigType import com.neko.v2ray.dto.ServerConfig import com.neko.v2ray.dto.V2rayConfig import com.neko.v2ray.extension.toast +import com.neko.v2ray.util.JsonUtil import com.neko.v2ray.util.MmkvManager import com.neko.v2ray.util.SoftInputAssist import com.neko.v2ray.util.Utils @@ -135,7 +136,7 @@ class ServerCustomConfigActivity : BaseActivity() { } val v2rayConfig = try { - Gson().fromJson(binding.editor.text.toString(), V2rayConfig::class.java) + JsonUtil.fromJson(binding.editor.text.toString(), V2rayConfig::class.java) } catch (e: Exception) { e.printStackTrace() ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show() diff --git a/app/src/main/kotlin/com/neko/v2ray/util/AngConfigManager.kt b/app/src/main/kotlin/com/neko/v2ray/util/AngConfigManager.kt index 2609405f..0f5f6b1c 100644 --- a/app/src/main/kotlin/com/neko/v2ray/util/AngConfigManager.kt +++ b/app/src/main/kotlin/com/neko/v2ray/util/AngConfigManager.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.text.TextUtils import android.util.Log -import com.google.gson.Gson + import com.google.gson.GsonBuilder import com.google.gson.JsonPrimitive import com.google.gson.JsonSerializationContext @@ -277,34 +277,21 @@ object AngConfigManager { && server.contains("routing") ) { try { - //val gson = GsonBuilder().setPrettyPrinting().create() - val gson = GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start - object : TypeToken() {}.type, - JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? -> - JsonPrimitive( - src?.toInt() - ) - } - ) - .create() val serverList: Array = - Gson().fromJson(server, Array::class.java) + JsonUtil.fromJson(server, Array::class.java) if (serverList.isNotEmpty()) { var count = 0 for (srv in serverList.reversed()) { val config = ServerConfig.create(EConfigType.CUSTOM) config.fullConfig = - Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java) + JsonUtil.fromJson(JsonUtil.toJson(srv), V2rayConfig::class.java) config.remarks = config.fullConfig?.remarks ?: ("%04d-".format(count + 1) + System.currentTimeMillis() .toString()) config.subscriptionId = subid val key = MmkvManager.encodeServerConfig("", config) - MmkvManager.encodeServerRaw(key, gson.toJson(srv)) + MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv)) count += 1 } return count @@ -317,7 +304,7 @@ object AngConfigManager { // For compatibility val config = ServerConfig.create(EConfigType.CUSTOM) config.subscriptionId = subid - config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) + config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java) config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString() val key = MmkvManager.encodeServerConfig("", config) MmkvManager.encodeServerRaw(key, server) diff --git a/app/src/main/kotlin/com/neko/v2ray/util/JsonUtil.kt b/app/src/main/kotlin/com/neko/v2ray/util/JsonUtil.kt new file mode 100644 index 00000000..29641392 --- /dev/null +++ b/app/src/main/kotlin/com/neko/v2ray/util/JsonUtil.kt @@ -0,0 +1,37 @@ +package com.neko.v2ray.util + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import com.google.gson.reflect.TypeToken +import java.lang.reflect.Type + +object JsonUtil { + private var gson = Gson() + + fun toJson(src: Any?): String { + return gson.toJson(src) + } + + fun fromJson(json: String, cls: Class): T { + return gson.fromJson(json, cls) + } + + fun toJsonPretty(src: Any?): String { + val gsonPre = GsonBuilder() + .setPrettyPrinting() + .disableHtmlEscaping() + .registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start + object : TypeToken() {}.type, + JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? -> + JsonPrimitive( + src?.toInt() + ) + } + ) + .create() + return gsonPre.toJson(src) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/neko/v2ray/util/MmkvManager.kt b/app/src/main/kotlin/com/neko/v2ray/util/MmkvManager.kt index d1105652..30a73b35 100644 --- a/app/src/main/kotlin/com/neko/v2ray/util/MmkvManager.kt +++ b/app/src/main/kotlin/com/neko/v2ray/util/MmkvManager.kt @@ -1,6 +1,6 @@ package com.neko.v2ray.util -import com.google.gson.Gson + import com.tencent.mmkv.MMKV import com.neko.v2ray.AppConfig.PREF_IS_BOOTED import com.neko.v2ray.AppConfig.PREF_ROUTING_RULESET @@ -49,7 +49,7 @@ object MmkvManager { } fun encodeServerList(serverList: MutableList) { - mainStorage.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList)) + mainStorage.encode(KEY_ANG_CONFIGS, JsonUtil.toJson(serverList)) } fun decodeServerList(): MutableList { @@ -57,7 +57,7 @@ object MmkvManager { return if (json.isNullOrBlank()) { mutableListOf() } else { - Gson().fromJson(json, Array::class.java).toMutableList() + JsonUtil.fromJson(json, Array::class.java).toMutableList() } } @@ -69,7 +69,7 @@ object MmkvManager { if (json.isNullOrBlank()) { return null } - return Gson().fromJson(json, ServerConfig::class.java) + return JsonUtil.fromJson(json, ServerConfig::class.java) } fun decodeProfileConfig(guid: String): ProfileItem? { @@ -80,12 +80,12 @@ object MmkvManager { if (json.isNullOrBlank()) { return null } - return Gson().fromJson(json, ProfileItem::class.java) + return JsonUtil.fromJson(json, ProfileItem::class.java) } fun encodeServerConfig(guid: String, config: ServerConfig): String { val key = guid.ifBlank { Utils.getUuid() } - serverStorage.encode(key, Gson().toJson(config)) + serverStorage.encode(key, JsonUtil.toJson(config)) val serverList = decodeServerList() if (!serverList.contains(key)) { serverList.add(0, key) @@ -101,7 +101,7 @@ object MmkvManager { server = config.getProxyOutbound()?.getServerAddress(), serverPort = config.getProxyOutbound()?.getServerPort(), ) - profileStorage.encode(key, Gson().toJson(profile)) + profileStorage.encode(key, JsonUtil.toJson(profile)) return key } @@ -141,7 +141,7 @@ object MmkvManager { if (json.isNullOrBlank()) { return null } - return Gson().fromJson(json, ServerAffiliationInfo::class.java) + return JsonUtil.fromJson(json, ServerAffiliationInfo::class.java) } fun encodeServerTestDelayMillis(guid: String, testResult: Long) { @@ -150,14 +150,14 @@ object MmkvManager { } val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo() aff.testDelayMillis = testResult - serverAffStorage.encode(guid, Gson().toJson(aff)) + serverAffStorage.encode(guid, JsonUtil.toJson(aff)) } fun clearAllTestDelayResults(keys: List?) { keys?.forEach { key -> decodeServerAffiliationInfo(key)?.let { aff -> aff.testDelayMillis = 0 - serverAffStorage.encode(key, Gson().toJson(aff)) + serverAffStorage.encode(key, JsonUtil.toJson(aff)) } } } @@ -217,7 +217,7 @@ object MmkvManager { decodeSubsList().forEach { key -> val json = subStorage.decodeString(key) if (!json.isNullOrBlank()) { - subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java))) + subscriptions.add(Pair(key, JsonUtil.fromJson(json, SubscriptionItem::class.java))) } } return subscriptions @@ -234,7 +234,7 @@ object MmkvManager { fun encodeSubscription(guid: String, subItem: SubscriptionItem) { val key = guid.ifBlank { Utils.getUuid() } - subStorage.encode(key, Gson().toJson(subItem)) + subStorage.encode(key, JsonUtil.toJson(subItem)) val subsList = decodeSubsList() if (!subsList.contains(key)) { @@ -245,11 +245,11 @@ object MmkvManager { fun decodeSubscription(subscriptionId: String): SubscriptionItem? { val json = subStorage.decodeString(subscriptionId) ?: return null - return Gson().fromJson(json, SubscriptionItem::class.java) + return JsonUtil.fromJson(json, SubscriptionItem::class.java) } fun encodeSubsList(subsList: MutableList) { - mainStorage.encode(KEY_SUB_IDS, Gson().toJson(subsList)) + mainStorage.encode(KEY_SUB_IDS, JsonUtil.toJson(subsList)) } fun decodeSubsList(): MutableList { @@ -257,7 +257,7 @@ object MmkvManager { return if (json.isNullOrBlank()) { mutableListOf() } else { - Gson().fromJson(json, Array::class.java).toMutableList() + JsonUtil.fromJson(json, Array::class.java).toMutableList() } } @@ -270,7 +270,7 @@ object MmkvManager { assetStorage.allKeys()?.forEach { key -> val json = assetStorage.decodeString(key) if (!json.isNullOrBlank()) { - assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java))) + assetUrlItems.add(Pair(key, JsonUtil.fromJson(json, AssetUrlItem::class.java))) } } return assetUrlItems.sortedBy { (_, value) -> value.addedTime } @@ -282,12 +282,12 @@ object MmkvManager { fun encodeAsset(assetid: String, assetItem: AssetUrlItem) { val key = assetid.ifBlank { Utils.getUuid() } - assetStorage.encode(key, Gson().toJson(assetItem)) + assetStorage.encode(key, JsonUtil.toJson(assetItem)) } fun decodeAsset(assetid: String): AssetUrlItem? { val json = assetStorage.decodeString(assetid) ?: return null - return Gson().fromJson(json, AssetUrlItem::class.java) + return JsonUtil.fromJson(json, AssetUrlItem::class.java) } //endregion @@ -297,14 +297,14 @@ object MmkvManager { fun decodeRoutingRulesets(): MutableList? { val ruleset = settingsStorage.decodeString(PREF_ROUTING_RULESET) if (ruleset.isNullOrEmpty()) return null - return Gson().fromJson(ruleset, Array::class.java).toMutableList() + return JsonUtil.fromJson(ruleset, Array::class.java).toMutableList() } fun encodeRoutingRulesets(rulesetList: MutableList?) { if (rulesetList.isNullOrEmpty()) settingsStorage.encode(PREF_ROUTING_RULESET, "") else - settingsStorage.encode(PREF_ROUTING_RULESET, Gson().toJson(rulesetList)) + settingsStorage.encode(PREF_ROUTING_RULESET, JsonUtil.toJson(rulesetList)) } //endregion diff --git a/app/src/main/kotlin/com/neko/v2ray/util/PluginUtil.kt b/app/src/main/kotlin/com/neko/v2ray/util/PluginUtil.kt index 5bd53a67..1e48332e 100644 --- a/app/src/main/kotlin/com/neko/v2ray/util/PluginUtil.kt +++ b/app/src/main/kotlin/com/neko/v2ray/util/PluginUtil.kt @@ -3,7 +3,6 @@ package com.neko.v2ray.util import android.content.Context import android.os.SystemClock import android.util.Log -import com.google.gson.Gson import com.neko.v2ray.AppConfig.ANG_PACKAGE import com.neko.v2ray.dto.EConfigType import com.neko.v2ray.dto.ServerConfig @@ -38,8 +37,8 @@ object PluginUtil { Log.d(packageName, "runPlugin ${configFile.absolutePath}") configFile.parentFile?.mkdirs() - configFile.writeText(Gson().toJson(hy2Config)) - Log.d(packageName, Gson().toJson(hy2Config)) + configFile.writeText(JsonUtil.toJson(hy2Config)) + Log.d(packageName, JsonUtil.toJson(hy2Config)) runHy2(context, configFile) } diff --git a/app/src/main/kotlin/com/neko/v2ray/util/SettingsManager.kt b/app/src/main/kotlin/com/neko/v2ray/util/SettingsManager.kt index 63a7cc06..1a2348d5 100644 --- a/app/src/main/kotlin/com/neko/v2ray/util/SettingsManager.kt +++ b/app/src/main/kotlin/com/neko/v2ray/util/SettingsManager.kt @@ -2,7 +2,6 @@ package com.neko.v2ray.util import android.content.Context import android.text.TextUtils -import com.google.gson.Gsonl import com.neko.v2ray.AppConfig import com.neko.v2ray.dto.RulesetItem import com.neko.v2ray.dto.ServerConfig @@ -35,7 +34,7 @@ object SettingsManager { return null } - return Gson().fromJson(assets, Array::class.java).toMutableList() + return JsonUtil.fromJson(assets, Array::class.java).toMutableList() } fun resetRoutingRulesets(context: Context, index: Int) { @@ -49,7 +48,7 @@ object SettingsManager { } try { - val rulesetList = Gson().fromJson(content, Array::class.java).toMutableList() + val rulesetList = JsonUtil.fromJson(content, Array::class.java).toMutableList() if (rulesetList.isNullOrEmpty()) { return false } diff --git a/app/src/main/kotlin/com/neko/v2ray/util/V2rayConfigUtil.kt b/app/src/main/kotlin/com/neko/v2ray/util/V2rayConfigUtil.kt index 624dd35c..c7993449 100644 --- a/app/src/main/kotlin/com/neko/v2ray/util/V2rayConfigUtil.kt +++ b/app/src/main/kotlin/com/neko/v2ray/util/V2rayConfigUtil.kt @@ -3,7 +3,6 @@ package com.neko.v2ray.util import android.content.Context import android.text.TextUtils import android.util.Log -import com.google.gson.Gson import com.neko.v2ray.AppConfig import com.neko.v2ray.AppConfig.ANG_PACKAGE import com.neko.v2ray.AppConfig.LOOPBACK @@ -67,7 +66,7 @@ object V2rayConfigUtil { if (TextUtils.isEmpty(assets)) { return result } - val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result + val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning" v2rayConfig.remarks = config.remarks @@ -206,7 +205,7 @@ object V2rayConfigUtil { return } - val rule = Gson().fromJson(Gson().toJson(item), RulesBean::class.java) ?: return + val rule = JsonUtil.fromJson(JsonUtil.toJson(item), RulesBean::class.java) ?: return v2rayConfig.routing.rules.add(rule) @@ -440,7 +439,7 @@ object V2rayConfigUtil { val requestString: String by lazy { """{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""" } - outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson( + outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson( requestString, V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java ) diff --git a/app/src/main/kotlin/com/neko/v2ray/util/fmt/VmessFmt.kt b/app/src/main/kotlin/com/neko/v2ray/util/fmt/VmessFmt.kt index 1a283b7e..8e9ca01a 100644 --- a/app/src/main/kotlin/com/neko/v2ray/util/fmt/VmessFmt.kt +++ b/app/src/main/kotlin/com/neko/v2ray/util/fmt/VmessFmt.kt @@ -2,13 +2,13 @@ package com.neko.v2ray.util.fmt import android.text.TextUtils import android.util.Log -import com.google.gson.Gson import com.neko.v2ray.AppConfig import com.neko.v2ray.dto.EConfigType import com.neko.v2ray.dto.ServerConfig import com.neko.v2ray.dto.V2rayConfig import com.neko.v2ray.dto.VmessQRCode import com.neko.v2ray.extension.idnHost +import com.neko.v2ray.util.JsonUtil import com.neko.v2ray.util.MmkvManager.settingsStorage import com.neko.v2ray.util.Utils import java.net.URI @@ -29,7 +29,7 @@ object VmessFmt { Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed") return null } - val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) + val vmessQRCode = JsonUtil.fromJson(result, VmessQRCode::class.java) // Although VmessQRCode fields are non null, looks like Gson may still create null fields if (TextUtils.isEmpty(vmessQRCode.add) || TextUtils.isEmpty(vmessQRCode.port) @@ -100,7 +100,7 @@ object VmessFmt { vmessQRCode.host = transportDetails[1] vmessQRCode.path = transportDetails[2] } - val json = Gson().toJson(vmessQRCode) + val json = JsonUtil.toJson(vmessQRCode) return Utils.encode(json) } diff --git a/app/src/main/kotlin/com/neko/v2ray/viewmodel/MainViewModel.kt b/app/src/main/kotlin/com/neko/v2ray/viewmodel/MainViewModel.kt index 18458a1c..41ae1f2e 100644 --- a/app/src/main/kotlin/com/neko/v2ray/viewmodel/MainViewModel.kt +++ b/app/src/main/kotlin/com/neko/v2ray/viewmodel/MainViewModel.kt @@ -11,7 +11,6 @@ import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import com.google.gson.Gson import com.neko.v2ray.AngApplication import com.neko.v2ray.AppConfig import com.neko.v2ray.AppConfig.ANG_PACKAGE @@ -25,6 +24,7 @@ import com.neko.v2ray.extension.serializable import com.neko.v2ray.extension.toast import com.neko.v2ray.util.AngConfigManager import com.neko.v2ray.util.AngConfigManager.updateConfigViaSub +import com.neko.v2ray.util.JsonUtil import com.neko.v2ray.util.MessageUtil import com.neko.v2ray.util.MmkvManager import com.neko.v2ray.util.SpeedtestUtil @@ -100,7 +100,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { try { val config = ServerConfig.create(EConfigType.CUSTOM) config.subscriptionId = subscriptionId - config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) + config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java) config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString() val key = MmkvManager.encodeServerConfig("", config) MmkvManager.encodeServerRaw(key, server) @@ -218,7 +218,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { MessageUtil.sendMsg2TestService( getApplication(), AppConfig.MSG_MEASURE_CONFIG, - Gson().toJson(config) + JsonUtil.toJson(config) ) } }