Integrate Umeng analytics and APM

This commit is contained in:
2026-07-07 14:20:04 +08:00
parent 9899a2ccb3
commit 6bdabadc3b
9 changed files with 155 additions and 2 deletions

View File

@ -5,6 +5,9 @@ target 'suixinkan' do
pod 'AMapLocation-NO-IDFA'
pod 'AMap3DMap-NO-IDFA'
pod 'AMapSearch-NO-IDFA'
pod 'UMCommon'
pod 'UMDevice'
pod 'UMAPM'
end
target 'suixinkanTests' do

View File

@ -6,11 +6,19 @@ PODS:
- AMapFoundation-NO-IDFA (>= 1.9.0)
- AMapSearch-NO-IDFA (9.8.0):
- AMapFoundation-NO-IDFA (>= 1.9.0)
- UMAPM (2.0.7):
- UMCommon
- UMCommon (7.5.11):
- UMDevice
- UMDevice (3.6.0)
DEPENDENCIES:
- AMap3DMap-NO-IDFA
- AMapLocation-NO-IDFA
- AMapSearch-NO-IDFA
- UMAPM
- UMCommon
- UMDevice
SPEC REPOS:
trunk:
@ -18,13 +26,19 @@ SPEC REPOS:
- AMapFoundation-NO-IDFA
- AMapLocation-NO-IDFA
- AMapSearch-NO-IDFA
- UMAPM
- UMCommon
- UMDevice
SPEC CHECKSUMS:
AMap3DMap-NO-IDFA: f53ee0cb33db83c1a08856b5995e53a0c2096158
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
PODFILE CHECKSUM: bf22a001ae02e51eb203f1eae5556a33b831ada7
PODFILE CHECKSUM: b2f49c71df3a1aca1cf08d192e467a8be772283c
COCOAPODS: 1.16.2

View File

@ -364,6 +364,10 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@ -403,6 +407,10 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;

View File

@ -13,6 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UmengBootstrap.configureIfNeeded()
if AppStore.shared.privacyAgreementAccepted, !AppStore.shared.token.isEmpty {
AMapBootstrap.configureIfNeeded()
}
@ -35,4 +36,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

View File

@ -0,0 +1,51 @@
//
// UmengBootstrap.swift
// suixinkan
//
import Foundation
/// SDK
enum UmengBootstrap {
/// SDK
private(set) static var isConfigured = false
private static let lock = NSLock()
private static var configureHandler: (_ appKey: String, _ channel: String) -> Void = { appKey, channel in
UMConfigure.setLogEnabled(false)
UMConfigure.initWithAppkey(appKey, channel: channel)
}
/// App SDK
@discardableResult
static func configureIfNeeded(appStore: AppStore = .shared) -> Bool {
guard appStore.privacyAgreementAccepted else { return false }
lock.lock()
defer { lock.unlock() }
guard !isConfigured else { return true }
configureHandler(UmengConfig.appKey, UmengConfig.channel)
isConfigured = true
return true
}
/// SDK
static func setConfigureHandlerForTesting(_ handler: @escaping (_ appKey: String, _ channel: String) -> Void) {
lock.lock()
defer { lock.unlock() }
configureHandler = handler
}
///
static func resetForTesting() {
lock.lock()
defer { lock.unlock() }
isConfigured = false
configureHandler = { appKey, channel in
UMConfigure.setLogEnabled(false)
UMConfigure.initWithAppkey(appKey, channel: channel)
}
}
}

View File

@ -0,0 +1,15 @@
//
// UmengConfig.swift
// suixinkan
//
import Foundation
/// SDK
enum UmengConfig {
/// iOS AppKey
nonisolated static let appKey = "6a470b81cbfa6959516c34d0"
/// 使 App Store
nonisolated static let channel = "App Store"
}

View File

@ -25,6 +25,7 @@ enum AuthSessionHelper {
if privacyAgreementAccepted {
AMapBootstrap.configureIfNeeded()
UmengBootstrap.configureIfNeeded()
}
NotificationCenter.default.post(name: NotificationName.userDidLogin, object: nil)

View File

@ -7,3 +7,10 @@
#import <AMapLocationKit/AMapLocationKit.h>
#import <MAMapKit/MAMapKit.h>
#import <AMapSearchKit/AMapSearchKit.h>
#import <UMCommon/UMCommon.h>
#if __has_include(<UMAPM/UMAPM.h>)
#import <UMAPM/UMAPM.h>
#elif __has_include(<UMAPM/UMCrashConfigure.h>)
#import <UMAPM/UMCrashConfigure.h>
#endif

View File

@ -0,0 +1,54 @@
//
// UmengBootstrapTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
/// SDK gate
final class UmengBootstrapTests: XCTestCase {
private var defaults: UserDefaults!
private var appStore: AppStore!
override func setUp() {
super.setUp()
UmengBootstrap.resetForTesting()
defaults = UserDefaults(suiteName: "UmengBootstrapTests")!
defaults.removePersistentDomain(forName: "UmengBootstrapTests")
appStore = AppStore(defaults: defaults)
}
override func tearDown() {
UmengBootstrap.resetForTesting()
super.tearDown()
}
func testConfigureReturnsFalseWhenPrivacyNotAccepted() {
var callCount = 0
UmengBootstrap.setConfigureHandlerForTesting { _, _ in
callCount += 1
}
appStore.privacyAgreementAccepted = false
XCTAssertFalse(UmengBootstrap.configureIfNeeded(appStore: appStore))
XCTAssertFalse(UmengBootstrap.isConfigured)
XCTAssertEqual(callCount, 0)
}
func testConfigureIsIdempotentWhenPrivacyAccepted() {
var calls: [(appKey: String, channel: String)] = []
UmengBootstrap.setConfigureHandlerForTesting { appKey, channel in
calls.append((appKey, channel))
}
appStore.privacyAgreementAccepted = true
XCTAssertTrue(UmengBootstrap.configureIfNeeded(appStore: appStore))
XCTAssertTrue(UmengBootstrap.isConfigured)
XCTAssertTrue(UmengBootstrap.configureIfNeeded(appStore: appStore))
XCTAssertEqual(calls.count, 1)
XCTAssertEqual(calls.first?.appKey, UmengConfig.appKey)
XCTAssertEqual(calls.first?.channel, UmengConfig.channel)
}
}