增加防篡改控制

This commit is contained in:
2026-09-11 18:16:38 +08:00
parent 26cca6a454
commit f2f68e1e6d
21 changed files with 180 additions and 102 deletions

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.7" />
</component>
</project>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-09-10T09:58:20.491101800Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=3A251FDJH000CE" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

4
.idea/encodings.xml generated
View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8" />
</project>

24
.idea/gradle.xml generated
View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/FastBleLib" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/baselibrary" />
<option value="$PROJECT_DIR$/easyPhotos" />
<option value="$PROJECT_DIR$/easysocket" />
</set>
</option>
</GradleProjectSettings>
</option>
<option name="offlineMode" value="true" />
</component>
</project>

4
.idea/misc.xml generated
View File

@@ -1,4 +0,0 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
</project>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -1 +1 @@
#Thu Sep 10 18:18:21 CST 2026 #Fri Sep 11 15:28:30 CST 2026

View File

@@ -502,7 +502,7 @@ abstract class BaseActivity<T : ViewBinding>(val inflater: (inflater: LayoutInfl
} else { } else {
tip.length * (1000L / 10) tip.length * (1000L / 10)
} }
if (showTime < 1000) showTime = 1500 if (showTime < 2000) showTime = 2000
if (showTime > 8000) showTime = 8000 if (showTime > 8000) showTime = 8000
mViewBinding.root.postDelayed({ mViewBinding.root.postDelayed({
if (!isFinishing) { if (!isFinishing) {
@@ -920,7 +920,7 @@ abstract class BaseActivity<T : ViewBinding>(val inflater: (inflater: LayoutInfl
mTranslateView = translateView mTranslateView = translateView
val r = Rect() val r = Rect()
translateView.getWindowVisibleDisplayFrame(r) translateView.getWindowVisibleDisplayFrame(r)
val heightDiff = translateView.getRootView().height - (r.bottom - r.top) val heightDiff = translateView.rootView.height - (r.bottom - r.top)
if (heightDiff > 200) { // 如果高度差大于200,说明键盘是弹出的 if (heightDiff > 200) { // 如果高度差大于200,说明键盘是弹出的
// 进行布局调整 // 进行布局调整
if (!isKeyboardTranslate) { if (!isKeyboardTranslate) {

View File

@@ -95,6 +95,17 @@ interface INetworkService {
// @Url 一定要传入以“/”结尾注解会替换 baseUrl,每次调用可传入完整 URL // @Url 一定要传入以“/”结尾注解会替换 baseUrl,每次调用可传入完整 URL
suspend fun blePassThrough(@Url url: String, @Body body: RequestBody): Response<ResponseBody> suspend fun blePassThrough(@Url url: String, @Body body: RequestBody): Response<ResponseBody>
/**
* 检查apk包状态
*/
@FormUrlEncoded
@POST("user/fetchPackageCheckStatus")
suspend fun checkPackageStatus(
@Field("packageName") packageName: String,
@Field("signSha256") signSha256: String,
@Field("nonce") nonce: String
): BaseResponse<String>
/** /**
* 获取国家代码号 * 获取国家代码号
*/ */

View File

@@ -834,6 +834,14 @@ object NetworkApi : BaseNetworkApi<INetworkService>(INetworkService.BASE_URL) {
*/ */
suspend fun blePassThrough(url: String, body: RequestBody) = service.blePassThrough(url, body) suspend fun blePassThrough(url: String, body: RequestBody) = service.blePassThrough(url, body)
/**
* 检查apk包状态
*/
suspend fun checkPackageStatus(packageName: String, signSha256: String, nonce: String) =
getResult {
service.checkPackageStatus(packageName, signSha256, nonce)
}
/** /**
* 取消自动续费/自动订阅 * 取消自动续费/自动订阅
*/ */

View File

@@ -52,6 +52,7 @@ import com.abbidot.tracker.ui.fragment.pet.PetV2Fragment
import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.Util
import com.abbidot.tracker.util.ViewUtil import com.abbidot.tracker.util.ViewUtil
import com.abbidot.tracker.util.bluetooth.SRBleUtil import com.abbidot.tracker.util.bluetooth.SRBleUtil
import com.abbidot.tracker.vm.AppSetViewModel
import com.abbidot.tracker.vm.ConnectionDeviceViewModel import com.abbidot.tracker.vm.ConnectionDeviceViewModel
import com.abbidot.tracker.vm.CountDownTimerViewModel import com.abbidot.tracker.vm.CountDownTimerViewModel
import com.abbidot.tracker.vm.DataViewModel import com.abbidot.tracker.vm.DataViewModel
@@ -75,6 +76,7 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
private val mAutomaticConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() private val mAutomaticConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels()
private val mLogBleReportViewModel: LogBleReportViewModel by viewModels() private val mLogBleReportViewModel: LogBleReportViewModel by viewModels()
private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels()
private val mAppSetViewModel: AppSetViewModel by viewModels()
// private val mSocketSendMessageViewModel: SocketSendMessageViewModel by viewModels() // private val mSocketSendMessageViewModel: SocketSendMessageViewModel by viewModels()
private val mFamilyViewModel: FamilyViewModel by viewModels() private val mFamilyViewModel: FamilyViewModel by viewModels()
@@ -180,6 +182,7 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
} else { } else {
//清除首次绑定设备信息 //清除首次绑定设备信息
MMKVUtil.putString(MMKVKey.FirstDeviceMac, "") MMKVUtil.putString(MMKVKey.FirstDeviceMac, "")
//监听网络变化
val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback: ConnectivityManager.NetworkCallback = val networkCallback: ConnectivityManager.NetworkCallback =
object : ConnectivityManager.NetworkCallback() { object : ConnectivityManager.NetworkCallback() {
@@ -187,7 +190,10 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
// 当网络可用时调用 // 当网络可用时调用
super.onAvailable(network) super.onAvailable(network)
LogUtil.e("当网络可用时,onAvailable") LogUtil.e("当网络可用时,onAvailable")
if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) { //首次启动APP,没有网络去检验APP合法性,就在网络可用时去检验
if (MMKVUtil.getInt(MMKVKey.isCheckLegalApp) == ConstantInt.Type2) {
mAppSetViewModel.checkPackageStatus(applicationContext)
} else if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) {
mNetworkRequestsFailRetryCount = 0 mNetworkRequestsFailRetryCount = 0
isFirst = false isFirst = false
mDataViewModel.getHomeBindPetList(this@HomeV2Activity) mDataViewModel.getHomeBindPetList(this@HomeV2Activity)
@@ -295,6 +301,22 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
} }
} }
} }
mAppSetViewModel.mCheckPackageStatusLiveData.observe(this) {
if (null == it) {
showToast(R.string.txt_no_legal_app, true)
} else {
if (null == it.getOrNull()) {
showToast(R.string.txt_no_legal_app, true, Gravity.CENTER)
} else {
MMKVUtil.putInt(MMKVKey.isCheckLegalApp, ConstantInt.Type1)
if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) {
mNetworkRequestsFailRetryCount = 0
isFirst = false
mDataViewModel.getHomeBindPetList(this@HomeV2Activity)
}
}
}
}
//退出登录 //退出登录
XEventBus.observe(this, EventName.LogOut) { XEventBus.observe(this, EventName.LogOut) {
finish() finish()

View File

@@ -24,6 +24,7 @@ import com.abbidot.baselibrary.util.MMKVUtil
import com.abbidot.baselibrary.util.Utils import com.abbidot.baselibrary.util.Utils
import com.abbidot.baselibrary.util.rsa.Base64 import com.abbidot.baselibrary.util.rsa.Base64
import com.abbidot.tracker.R import com.abbidot.tracker.R
import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.constant.KeyNames import com.abbidot.tracker.constant.KeyNames
import com.abbidot.tracker.databinding.ActivitySplashBinding import com.abbidot.tracker.databinding.ActivitySplashBinding
import com.abbidot.tracker.dialog.PrivacyPolicyDialog import com.abbidot.tracker.dialog.PrivacyPolicyDialog
@@ -31,10 +32,12 @@ import com.abbidot.tracker.retrofit2.INetworkService
import com.abbidot.tracker.util.ThirdPartyUtil import com.abbidot.tracker.util.ThirdPartyUtil
import com.abbidot.tracker.util.Util import com.abbidot.tracker.util.Util
import com.abbidot.tracker.util.bluetooth.SRBleUtil import com.abbidot.tracker.util.bluetooth.SRBleUtil
import com.abbidot.tracker.vm.AppSetViewModel
import com.abbidot.tracker.vm.CountDownTimerViewModel import com.abbidot.tracker.vm.CountDownTimerViewModel
import com.baidu.location.LocationClient import com.baidu.location.LocationClient
import com.baidu.mapapi.CoordType import com.baidu.mapapi.CoordType
import com.baidu.mapapi.SDKInitializer import com.baidu.mapapi.SDKInitializer
import com.baidu.platform.comapi.util.NetworkUtil
import com.hjq.toast.Toaster import com.hjq.toast.Toaster
import com.qmuiteam.qmui.arch.QMUIActivity import com.qmuiteam.qmui.arch.QMUIActivity
import com.stripe.android.PaymentConfiguration import com.stripe.android.PaymentConfiguration
@@ -57,6 +60,7 @@ class SplashActivity : QMUIActivity() {
private lateinit var mViewBinding: ActivitySplashBinding private lateinit var mViewBinding: ActivitySplashBinding
private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels()
private val mAppSetViewModel: AppSetViewModel by viewModels()
// IWXAPI 是第三方 app 和微信通信的 openApi 接口 // IWXAPI 是第三方 app 和微信通信的 openApi 接口
private lateinit var mApi: IWXAPI private lateinit var mApi: IWXAPI
@@ -114,6 +118,36 @@ class SplashActivity : QMUIActivity() {
mViewBinding.ivSplashDebug.visibility = View.VISIBLE mViewBinding.ivSplashDebug.visibility = View.VISIBLE
} }
if (NetworkUtil.isNetworkAvailable(mContext)) {
mAppSetViewModel.apply {
mCheckPackageStatusLiveData.observe(this@SplashActivity) {
if (null == it) {
showToastAndFinish()
} else {
if (null == it.getOrNull()) {
showToastAndFinish()
} else {
MMKVUtil.putInt(MMKVKey.isCheckLegalApp, ConstantInt.Type1)
legalApp()
}
}
}
}
mAppSetViewModel.checkPackageStatus(applicationContext)
} else {
MMKVUtil.putInt(MMKVKey.isCheckLegalApp, ConstantInt.Type2)
legalApp()
}
}
private fun showToastAndFinish() {
Toaster.show(getString(R.string.txt_no_legal_app))
mViewBinding.root.postDelayed({
finish()
}, 2000)
}
private fun legalApp() {
mCountDownTimerViewModel.mCountDownEndLiveData.observe(this) { mCountDownTimerViewModel.mCountDownEndLiveData.observe(this) {
mViewBinding.splashLottieAnimationView.cancelAnimation() mViewBinding.splashLottieAnimationView.cancelAnimation()
val userId = MMKVUtil.getString(MMKVKey.UserId) val userId = MMKVUtil.getString(MMKVKey.UserId)

View File

@@ -1,13 +1,16 @@
package com.abbidot.tracker.vm package com.abbidot.tracker.vm
import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.abbidot.baselibrary.constant.MMKVKey import com.abbidot.baselibrary.constant.MMKVKey
import com.abbidot.baselibrary.util.AppUtils
import com.abbidot.baselibrary.util.MMKVUtil import com.abbidot.baselibrary.util.MMKVUtil
import com.abbidot.tracker.bean.NotificationBean import com.abbidot.tracker.bean.NotificationBean
import com.abbidot.tracker.retrofit2.NetworkApi import com.abbidot.tracker.retrofit2.NetworkApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
/** /**
*Created by .yzq on 2024/9/26/026. *Created by .yzq on 2024/9/26/026.
@@ -18,6 +21,7 @@ class AppSetViewModel : ViewModel() {
val mGetNotificationLiveData = MutableLiveData<Result<MutableList<NotificationBean>>>() val mGetNotificationLiveData = MutableLiveData<Result<MutableList<NotificationBean>>>()
val mSetNotificationLiveData = MutableLiveData<Result<String>>() val mSetNotificationLiveData = MutableLiveData<Result<String>>()
val mCheckPackageStatusLiveData = MutableLiveData<Result<String>>()
/** /**
* 获取通知设置 * 获取通知设置
@@ -40,4 +44,17 @@ class AppSetViewModel : ViewModel() {
mSetNotificationLiveData.value = result mSetNotificationLiveData.value = result
} }
} }
/**
* 检查apk包状态
*/
fun checkPackageStatus(context: Context) {
viewModelScope.launch {
val packageName = context.packageName
val signSha256 = AppUtils.getApkSignSha256(context)
val nonce = UUID.randomUUID().toString()
val result = NetworkApi.checkPackageStatus(packageName, signSha256, nonce)
mCheckPackageStatusLiveData.value = result
}
}
} }

View File

@@ -948,4 +948,5 @@
<string name="txt_auto_renewal_on">自动续订已开启</string> <string name="txt_auto_renewal_on">自动续订已开启</string>
<string name="txt_remove">移除</string> <string name="txt_remove">移除</string>
<string name="txt_turn_off_remove">关闭并移除</string> <string name="txt_turn_off_remove">关闭并移除</string>
<string name="txt_no_legal_app">非法APP,请从正规渠道下载安装</string>
</resources> </resources>

View File

@@ -999,4 +999,5 @@
<string name="txt_auto_renewal_on">Auto-Renewal Is On</string> <string name="txt_auto_renewal_on">Auto-Renewal Is On</string>
<string name="txt_remove">Remove</string> <string name="txt_remove">Remove</string>
<string name="txt_turn_off_remove">Turn Off &amp; Remove</string> <string name="txt_turn_off_remove">Turn Off &amp; Remove</string>
<string name="txt_no_legal_app">非法APP,请从正规渠道下载安装</string>
</resources> </resources>

View File

@@ -44,6 +44,7 @@ import androidx.annotation.StringDef
MMKVKey.UserGpsLat, MMKVKey.UserGpsLat,
MMKVKey.UserGpsLon, MMKVKey.UserGpsLon,
MMKVKey.UserGpsSpeed, MMKVKey.UserGpsSpeed,
MMKVKey.isCheckLegalApp,
MMKVKey.isFirstCheckBleOpen, MMKVKey.isFirstCheckBleOpen,
MMKVKey.UUID, MMKVKey.UUID,
MMKVKey.Shared MMKVKey.Shared
@@ -66,6 +67,7 @@ annotation class MMKVKey {
const val Gender = "gender" const val Gender = "gender"
const val BirthdayDate = "birthdayDate" const val BirthdayDate = "birthdayDate"
const val CountryCode = "countryCode" const val CountryCode = "countryCode"
const val isCheckLegalApp = "isCheckLegalApp"
//首次绑定流程 //首次绑定流程
const val FirstDeviceId = "firstDeviceId" const val FirstDeviceId = "firstDeviceId"

View File

@@ -75,8 +75,8 @@ class CommonRequestInterceptor : Interceptor {
} }
} }
val uuid = MMKVUtil.getString(MMKVKey.UUID) val uniqueId = MMKVUtil.getString(MMKVKey.UUID)
paramList.add("uuid$uuid") paramList.add("uuid$uniqueId")
val sign = if (paramList.isNotEmpty()) { val sign = if (paramList.isNotEmpty()) {
//按key的字母排序 //按key的字母排序
paramList.sort() paramList.sort()
@@ -87,7 +87,7 @@ class CommonRequestInterceptor : Interceptor {
val token = MMKVUtil.getString(MMKVKey.Token) val token = MMKVUtil.getString(MMKVKey.Token)
builder.addHeader("token", token) builder.addHeader("token", token)
builder.addHeader("uuid", uuid) builder.addHeader("uuid", uniqueId)
builder.addHeader("sign", sign) builder.addHeader("sign", sign)
return chain.proceed(builder.build()) return chain.proceed(builder.build())

View File

@@ -6,9 +6,11 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build
import android.provider.Settings import android.provider.Settings
import android.text.TextUtils import android.text.TextUtils
import com.abbidot.baselibrary.constant.MMKVKey import com.abbidot.baselibrary.constant.MMKVKey
import java.security.MessageDigest
import java.util.Locale import java.util.Locale
import java.util.UUID import java.util.UUID
@@ -115,19 +117,87 @@ class AppUtils {
*/ */
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
fun saveUniqueId(context: Context) { fun saveUniqueId(context: Context) {
var uuid = MMKVUtil.getString(MMKVKey.UUID) var uniqueId = MMKVUtil.getString(MMKVKey.UUID)
if (TextUtils.isEmpty(uuid)) { if (TextUtils.isEmpty(uniqueId)) {
val androidId = val androidId =
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
?: "" ?: ""
uuid = if (TextUtils.isEmpty(androidId)) uniqueId = if (TextUtils.isEmpty(androidId)) UUID.randomUUID().toString()
UUID.randomUUID().toString() else androidId
else MMKVUtil.putString(MMKVKey.UUID, uniqueId)
androidId
MMKVUtil.putString(MMKVKey.UUID, uuid)
} }
} }
/**
*获取apk签名Sha256
*/
fun getApkSignSha2561(context: Context): String {
return try {
val pm = context.packageManager
val pkgName = context.packageName
val pkgInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// API33+ Android13,直接用GET_SIGNING_CERTIFICATES,不需要if判断
val flagLong = PackageManager.GET_SIGNING_CERTIFICATES.toLong()
pm.getPackageInfo(pkgName, PackageManager.PackageInfoFlags.of(flagLong))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// API28 ~ API32
pm.getPackageInfo(pkgName, PackageManager.GET_SIGNING_CERTIFICATES)
} else {
// API24 ~ API27
pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES)
}
val cert = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && pkgInfo.signingInfo != null -> pkgInfo.signingInfo.signingCertificateHistory?.firstOrNull()
else -> pkgInfo.signatures?.firstOrNull()
} ?: return ""
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(cert.toByteArray())
hashBytes.joinToString("") { "%02x".format(Locale.US, it) }
} catch (_: Exception) {
""
}
}
/**
* 获取apk签名Sha256
*/
fun getApkSignSha256(context: Context): String {
return try {
val pm = context.packageManager
val pkgName = context.packageName
val pkgInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// API33+ Android13
val flagLong = PackageManager.GET_SIGNING_CERTIFICATES.toLong()
pm.getPackageInfo(pkgName, PackageManager.PackageInfoFlags.of(flagLong))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// API28 ~ API32
pm.getPackageInfo(pkgName, PackageManager.GET_SIGNING_CERTIFICATES)
} else {
// API24 ~ API27
pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES)
}
val certBytes = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && pkgInfo.signingInfo != null -> {
// 读取【当前生效签名】apkContentsSigners,不是历史证书
val activeCert =
pkgInfo.signingInfo.apkContentsSigners?.firstOrNull() ?: return ""
activeCert.toByteArray()
}
else -> {
val signature = pkgInfo.signatures?.firstOrNull() ?: return ""
signature.toByteArray()
}
}
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(certBytes)
hashBytes.joinToString(separator = "") { "%02x".format(Locale.US, it) }
} catch (_: Exception) {
""
}
}
/** /**
* 获取系统国家地区 * 获取系统国家地区
*/ */