├── .gitignore ├── .npmignore ├── HeroUmengAnalytics.podspec ├── README.md ├── android ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── github │ └── reactnativehero │ └── umenganalytics │ ├── RNTUmengAnalyticsModule.kt │ └── RNTUmengAnalyticsPackage.kt ├── index.js ├── ios ├── RNTUmengAnalytics.xcodeproj │ └── project.pbxproj └── RNTUmengAnalytics │ ├── RNTUmengAnalytics.h │ └── RNTUmengAnalytics.m └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | 40 | Podfile.lock 41 | Pods/ 42 | package-lock.json 43 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | 40 | Podfile.lock 41 | Pods/ 42 | example/ 43 | package-lock.json 44 | yarn.lock -------------------------------------------------------------------------------- /HeroUmengAnalytics.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "HeroUmengAnalytics" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | s.platform = :ios, "9.0" 14 | 15 | s.source = { :git => "https://github.com/react-native-hero/umeng-analytics.git", :tag => "v#{s.version}" } 16 | s.source_files = "ios/**/*.{h,m}" 17 | 18 | s.dependency 'React' 19 | s.dependency 'UMCommon' 20 | s.dependency 'UMDevice' 21 | end 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @react-native-hero/umeng-analytics 2 | 3 | ## Getting started 4 | 5 | Install the library using either Yarn: 6 | 7 | ``` 8 | yarn add @react-native-hero/umeng-analytics 9 | ``` 10 | 11 | or npm: 12 | 13 | ``` 14 | npm install --save @react-native-hero/umeng-analytics 15 | ``` 16 | 17 | ## Link 18 | 19 | - React Native v0.60+ 20 | 21 | For iOS, use `cocoapods` to link the package. 22 | 23 | run the following command: 24 | 25 | ``` 26 | $ cd ios && pod install 27 | ``` 28 | 29 | For android, the package will be linked automatically on build. 30 | 31 | - React Native <= 0.59 32 | 33 | run the following command to link the package: 34 | 35 | ``` 36 | $ react-native link @react-native-hero/umeng-analytics 37 | ``` 38 | 39 | ## Setup 40 | 41 | ![image](https://user-images.githubusercontent.com/2732303/77606227-ded8b680-6f51-11ea-9aa4-0378e79deaa7.png) 42 | 43 | 打开应用信息页面,安卓推送有 `Appkey` 和 `Umeng Message Secret` 两个字段,iOS 只有 `Appkey` 字段,后面将用这些字段初始化友盟。 44 | 45 | ### iOS 46 | 47 | 修改 `AppDelegate.m`,如下 48 | 49 | ```oc 50 | #import 51 | 52 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 53 | { 54 | ... 55 | // 初始化友盟基础库 56 | // channel 一般填 App Store,如果有测试环境,可按需填写 57 | // debug 表示是否打印调试信息 58 | [RNTUmengAnalytics init:@"appKey" channel:@"App Store" debug:false]; 59 | return YES; 60 | } 61 | ``` 62 | 63 | ### Android 64 | 65 | 修改 `android/build.gradle`,如下: 66 | 67 | ``` 68 | allprojects { 69 | repositories { 70 | // 确保添加了友盟仓库 71 | maven { url 'https://repo1.maven.org/maven2/' } 72 | } 73 | } 74 | ``` 75 | 76 | `android/app/build.gradle` 根据不同的包填写不同的配置,如下: 77 | 78 | ``` 79 | buildTypes { 80 | debug { 81 | manifestPlaceholders = [ 82 | UMENG_APP_KEY: '', 83 | UMENG_PUSH_SECRET: '', 84 | UMENG_CHANNEL: '', 85 | ] 86 | } 87 | release { 88 | manifestPlaceholders = [ 89 | UMENG_APP_KEY: '', 90 | UMENG_PUSH_SECRET: '', 91 | UMENG_CHANNEL: '', 92 | ] 93 | } 94 | } 95 | ``` 96 | 97 | 在 `MainApplication` 的 `onCreate` 方法进行初始化,如下: 98 | 99 | Kotlin 版本 100 | 101 | ```kotlin 102 | override fun onCreate() { 103 | val metaData = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA).metaData 104 | 105 | // 初始化友盟基础库 106 | // 第三个参数表示是否显示调试信息 107 | RNTUmengAnalyticsModule.init(this, metaData, false) 108 | } 109 | ``` 110 | 111 | Java 版本 112 | 113 | ```java 114 | // onCreate 方法体换成下面这段 115 | Bundle metaData = this.getPackageManager().getApplicationInfo( 116 | this.getPackageName(), PackageManager.GET_META_DATA 117 | ).metaData; 118 | RNTUmengAnalyticsModule.init(this, metaData, false); 119 | ``` 120 | 121 | ### 配置混淆规则 122 | 123 | 在 `android/app/proguard-rules.pro` 添加以下混淆规则,注意替换自己的包名,并且删掉 `[` 和 `]`。 124 | 125 | ``` 126 | -keep public class [您的应用包名].R$*{ 127 | public static final int *; 128 | } 129 | ``` 130 | 131 | ## Usage 132 | 133 | ```js 134 | import { 135 | // 初始化友盟时传入的 channel 参数 136 | CHANNEL, 137 | 138 | init, 139 | getDeviceInfo, 140 | getPhoneNumber, 141 | signIn, 142 | signOut, 143 | exitApp, 144 | enterPage, 145 | leavePage, 146 | sendEvent, 147 | sendEventLabel, 148 | sendEventData, 149 | sendEventCounter, 150 | } from '@react-native-hero/umeng-analytics' 151 | 152 | // 对于安卓来说,需要等用户同意隐私政策后,再调用 init,js 的 init 才是真正的初始化 153 | // https://developer.umeng.com/docs/119267/detail/182050 154 | init().then(() => { 155 | // 初始化完成 156 | }) 157 | 158 | // 提供一个退出 app 的方法 159 | // 好像 RN 官方也没提供此方法,单个方法不好写一个库,就放在这个库了 160 | exitApp() 161 | 162 | // 以下方法必须等 init() 调用结束后才能调用,否则会抛出错误 163 | 164 | getDeviceInfo().then(data => { 165 | data.deviceId 166 | data.deviceType 167 | data.brand 168 | data.bundleId 169 | }) 170 | 171 | getUserAgent().then(data => { 172 | data.userAgent 173 | }) 174 | 175 | getPhoneNumber().then(data => { 176 | // 只有安卓有希望读取出本机的手机号码,前提是已获得 READ_PHONE_STATE 权限 177 | data.phoneNumber 178 | }) 179 | 180 | // 帐号统计 181 | signIn('userId') 182 | // provider 不能以下划线开头,使用大写字母和数字标识 183 | // 如果是上市公司,建议使用股票代码,比如 WB 184 | signIn('userId', 'provider') 185 | signOut() 186 | 187 | // 页面统计,注意要配对调用 188 | // 不能连续调用 enterPage,也不能连续调用 leavePage 189 | enterPage('pageName') 190 | leavePage('pageName') 191 | 192 | // 自定义事件,eventId 需先在友盟后台注册之后才可以统计 193 | sendEvent('eventId') 194 | sendEventLabel('eventId', 'label') 195 | sendEventData('eventId', { key1: 'value1', key2: 'value2' }) 196 | sendEventCounter('eventId', { key1: 'value1', key2: 'value2' }, 1) 197 | ``` 198 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // Buildscript is evaluated before everything else so we can't use safeExtGet 3 | def kotlinVersion = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : '1.7.20' 4 | 5 | repositories { 6 | mavenCentral() 7 | google() 8 | maven { url 'https://repo1.maven.org/maven2/' } 9 | } 10 | 11 | dependencies { 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 13 | } 14 | } 15 | 16 | def safeExtGet(prop, fallback) { 17 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 18 | } 19 | 20 | apply plugin: 'com.android.library' 21 | 22 | android { 23 | compileSdkVersion safeExtGet('compileSdkVersion', 28) 24 | buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') 25 | 26 | defaultConfig { 27 | minSdkVersion safeExtGet('minSdkVersion', 16) 28 | targetSdkVersion safeExtGet('targetSdkVersion', 28) 29 | versionCode 1 30 | versionName "1.0" 31 | consumerProguardFiles 'proguard-rules.pro' 32 | } 33 | lintOptions { 34 | abortOnError false 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.7.20')}" 41 | 42 | implementation 'com.umeng.umsdk:common:+'//必选 43 | implementation 'com.umeng.umsdk:asms:+'//必选 44 | } 45 | 46 | apply plugin: 'kotlin-android' 47 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class com.umeng.** { *; } 2 | -keep class com.uc.** { *; } 3 | -keep class com.efs.** { *; } 4 | 5 | -keepclassmembers class * { 6 | public (org.json.JSONObject); 7 | } 8 | 9 | -keepclassmembers enum * { 10 | public static **[] values(); 11 | public static ** valueOf(java.lang.String); 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/src/main/java/com/github/reactnativehero/umenganalytics/RNTUmengAnalyticsModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.reactnativehero.umenganalytics 2 | 3 | import android.Manifest 4 | import android.app.Application 5 | import android.content.Context 6 | import android.content.pm.PackageManager 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.provider.Settings.Secure 10 | import android.telephony.TelephonyManager 11 | import android.webkit.WebSettings 12 | import androidx.core.app.ActivityCompat 13 | import com.facebook.react.bridge.* 14 | import com.umeng.analytics.MobclickAgent 15 | import com.umeng.commonsdk.UMConfigure 16 | import com.umeng.commonsdk.statistics.common.DeviceConfig 17 | import java.util.* 18 | import java.util.concurrent.Executors 19 | import java.util.concurrent.TimeUnit 20 | import kotlin.collections.HashMap 21 | import androidx.core.content.edit 22 | 23 | 24 | class RNTUmengAnalyticsModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener { 25 | 26 | companion object { 27 | 28 | private var appKey = "" 29 | private var pushSecret = "" 30 | private var channel = "" 31 | 32 | // 初始化友盟基础库 33 | @JvmStatic fun init(app: Application, metaData: Bundle, debug: Boolean) { 34 | 35 | appKey = metaData.getString("UMENG_APP_KEY", "").trim() 36 | pushSecret = metaData.getString("UMENG_PUSH_SECRET", "").trim() 37 | channel = metaData.getString("UMENG_CHANNEL", "").trim() 38 | 39 | UMConfigure.setLogEnabled(debug) 40 | UMConfigure.preInit(app, appKey, channel) 41 | 42 | } 43 | 44 | } 45 | 46 | init { 47 | reactContext.addLifecycleEventListener(this) 48 | } 49 | 50 | private var isReady = false 51 | 52 | override fun getName(): String { 53 | return "RNTUmengAnalytics" 54 | } 55 | 56 | override fun getConstants(): Map { 57 | 58 | val constants: MutableMap = HashMap() 59 | 60 | constants["CHANNEL"] = channel 61 | 62 | return constants 63 | 64 | } 65 | 66 | @ReactMethod 67 | fun init(promise: Promise) { 68 | 69 | UMConfigure.submitPolicyGrantResult(reactContext, true) 70 | UMConfigure.init(reactContext, appKey, channel, UMConfigure.DEVICE_TYPE_PHONE, pushSecret) 71 | 72 | // 手动采集 73 | MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.MANUAL) 74 | 75 | isReady = true 76 | 77 | promise.resolve(Arguments.createMap()) 78 | 79 | } 80 | 81 | @ReactMethod 82 | fun getDeviceInfo(promise: Promise) { 83 | 84 | if (!checkReady(promise)) { 85 | return 86 | } 87 | 88 | fun tryDeviceInfo() { 89 | val map = this.getDeviceInfoMap() 90 | if (map.getString("deviceId").isNullOrEmpty()) { 91 | Executors.newSingleThreadScheduledExecutor().schedule({ 92 | tryDeviceInfo() 93 | }, 50, TimeUnit.MILLISECONDS) 94 | } 95 | else { 96 | promise.resolve(map) 97 | } 98 | } 99 | tryDeviceInfo() 100 | 101 | } 102 | 103 | @ReactMethod 104 | fun getUserAgent(promise: Promise) { 105 | 106 | if (!checkReady(promise)) { 107 | return 108 | } 109 | 110 | val map = Arguments.createMap() 111 | 112 | try { 113 | val userAgent = WebSettings.getDefaultUserAgent(reactContext) 114 | map.putString("userAgent", userAgent) 115 | } catch (e: RuntimeException) { 116 | val userAgent = System.getProperty("http.agent") 117 | if (userAgent != null && userAgent.isNotEmpty()) { 118 | map.putString("userAgent", userAgent) 119 | } 120 | else { 121 | map.putString("error", e.localizedMessage) 122 | } 123 | } 124 | 125 | promise.resolve(map) 126 | 127 | } 128 | 129 | @ReactMethod 130 | fun getPhoneNumber(promise: Promise) { 131 | 132 | if (!checkReady(promise)) { 133 | return 134 | } 135 | 136 | var hasPermission = true 137 | 138 | val readPhoneStatePermission: Int = ActivityCompat.checkSelfPermission(reactContext, Manifest.permission.READ_PHONE_STATE) 139 | if (readPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { 140 | hasPermission = false 141 | } 142 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 143 | val readPhoneNumbersPermission: Int = ActivityCompat.checkSelfPermission(reactContext, Manifest.permission.READ_PHONE_NUMBERS) 144 | if (readPhoneNumbersPermission != PackageManager.PERMISSION_GRANTED) { 145 | hasPermission = false 146 | } 147 | } 148 | 149 | val map = Arguments.createMap() 150 | 151 | if (hasPermission) { 152 | try { 153 | val manager = reactContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager 154 | map.putString("phoneNumber", manager.line1Number) 155 | } 156 | catch (e: Exception) { 157 | map.putString("error", e.localizedMessage) 158 | } 159 | } 160 | else { 161 | map.putString("error", "no permission") 162 | } 163 | 164 | promise.resolve(map) 165 | 166 | } 167 | 168 | @ReactMethod 169 | fun exitApp() { 170 | android.os.Process.killProcess(android.os.Process.myPid()) 171 | } 172 | 173 | @ReactMethod 174 | fun signIn(name: String, provider: String?) { 175 | 176 | if (!checkReady(null)) { 177 | return 178 | } 179 | 180 | val hasProvider = provider?.isNotEmpty() ?: false 181 | if (hasProvider) { 182 | MobclickAgent.onProfileSignIn(provider, name) 183 | } 184 | else { 185 | MobclickAgent.onProfileSignIn(name) 186 | } 187 | } 188 | 189 | @ReactMethod 190 | fun signOut() { 191 | if (!checkReady(null)) { 192 | return 193 | } 194 | MobclickAgent.onProfileSignOff() 195 | } 196 | 197 | @ReactMethod 198 | fun enterPage(pageName: String) { 199 | if (!checkReady(null)) { 200 | return 201 | } 202 | MobclickAgent.onPageStart(pageName) 203 | } 204 | 205 | @ReactMethod 206 | fun leavePage(pageName: String) { 207 | if (!checkReady(null)) { 208 | return 209 | } 210 | MobclickAgent.onPageEnd(pageName) 211 | } 212 | 213 | @ReactMethod 214 | fun sendEvent(eventId: String) { 215 | if (!checkReady(null)) { 216 | return 217 | } 218 | MobclickAgent.onEvent(reactContext, eventId) 219 | } 220 | 221 | @ReactMethod 222 | fun sendEventLabel(eventId: String, label: String) { 223 | if (!checkReady(null)) { 224 | return 225 | } 226 | MobclickAgent.onEvent(reactContext, eventId, label) 227 | } 228 | 229 | @ReactMethod 230 | fun sendEventData(eventId: String, data: ReadableMap) { 231 | if (!checkReady(null)) { 232 | return 233 | } 234 | val map = data.toHashMap() 235 | MobclickAgent.onEventObject(reactContext, eventId, map) 236 | } 237 | 238 | @ReactMethod 239 | fun sendEventCounter(eventId: String, data: ReadableMap, counter: Int) { 240 | if (!checkReady(null)) { 241 | return 242 | } 243 | val map = HashMap() 244 | for ((key, value) in data.toHashMap()) { 245 | map[key] = value as String 246 | } 247 | MobclickAgent.onEventValue(reactContext, eventId, map, counter) 248 | } 249 | 250 | @ReactMethod 251 | fun sendError(error: String) { 252 | if (!checkReady(null)) { 253 | return 254 | } 255 | MobclickAgent.reportError(reactContext, error) 256 | } 257 | 258 | private fun checkReady(promise: Promise?): Boolean { 259 | if (!isReady) { 260 | promise?.reject("-1", "umeng sdk is not ready.") 261 | return false 262 | } 263 | return true 264 | } 265 | 266 | private fun getDeviceInfoMap(): WritableMap { 267 | // 获取 deviceId 改为三次尝试 268 | var deviceId = DeviceConfig.getDeviceIdForGeneral(reactContext) 269 | if (deviceId.isEmpty()) { 270 | deviceId = Secure.getString(reactContext.contentResolver, Secure.ANDROID_ID) 271 | } 272 | if (deviceId.isEmpty()) { 273 | deviceId = getUUID() 274 | } 275 | val deviceType = DeviceConfig.getDeviceType(reactContext) 276 | val brand = Build.BRAND 277 | val bundleId = DeviceConfig.getPackageName(reactContext) 278 | 279 | val map = Arguments.createMap() 280 | map.putString("deviceId", deviceId.lowercase(Locale.ROOT)) 281 | map.putString("deviceType", deviceType.lowercase(Locale.ROOT)) 282 | map.putString("brand", brand.lowercase(Locale.ROOT)) 283 | map.putString("bundleId", bundleId) 284 | 285 | return map 286 | } 287 | 288 | private fun getUUID(): String { 289 | val sharedPref = reactContext.currentActivity?.getPreferences(Context.MODE_PRIVATE) 290 | ?: return "" 291 | 292 | val key = "umeng_analytics_uuid" 293 | 294 | var uuid = sharedPref.getString(key, "") 295 | if (uuid.isNullOrEmpty()) { 296 | uuid = UUID.randomUUID().toString() 297 | sharedPref.edit { 298 | putString(key, uuid) 299 | } 300 | } 301 | 302 | return uuid 303 | } 304 | 305 | override fun onHostResume() { 306 | MobclickAgent.onResume(reactContext.currentActivity) 307 | } 308 | 309 | override fun onHostPause() { 310 | MobclickAgent.onPause(reactContext.currentActivity) 311 | } 312 | 313 | override fun onHostDestroy() { 314 | 315 | } 316 | 317 | } 318 | -------------------------------------------------------------------------------- /android/src/main/java/com/github/reactnativehero/umenganalytics/RNTUmengAnalyticsPackage.kt: -------------------------------------------------------------------------------- 1 | package com.github.reactnativehero.umenganalytics 2 | 3 | import com.facebook.react.ReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.uimanager.ViewManager 7 | 8 | class RNTUmengAnalyticsPackage : ReactPackage { 9 | 10 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 11 | return listOf(RNTUmengAnalyticsModule(reactContext)) 12 | } 13 | 14 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 15 | return emptyList() 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | import { NativeModules } from 'react-native' 3 | 4 | const { RNTUmengAnalytics } = NativeModules 5 | 6 | // 初始化时配置的渠道 7 | export const CHANNEL = RNTUmengAnalytics.CHANNEL 8 | 9 | export function init() { 10 | return RNTUmengAnalytics.init() 11 | } 12 | 13 | export function getDeviceInfo() { 14 | return RNTUmengAnalytics.getDeviceInfo() 15 | } 16 | 17 | export function getUserAgent() { 18 | return RNTUmengAnalytics.getUserAgent() 19 | } 20 | 21 | export function getPhoneNumber() { 22 | return RNTUmengAnalytics.getPhoneNumber() 23 | } 24 | 25 | export function signIn(userId, provider) { 26 | RNTUmengAnalytics.signIn(userId, provider) 27 | } 28 | 29 | export function signOut() { 30 | RNTUmengAnalytics.signOut() 31 | } 32 | 33 | export function exitApp() { 34 | RNTUmengAnalytics.exitApp() 35 | } 36 | 37 | // enterPage 和 leavePage 必须对称调用 38 | // 在这里做一层保证 39 | let currentPage 40 | 41 | export function enterPage(pageName) { 42 | if (!currentPage) { 43 | RNTUmengAnalytics.enterPage(pageName) 44 | currentPage = pageName 45 | } 46 | } 47 | 48 | export function leavePage(pageName) { 49 | if (currentPage === pageName) { 50 | RNTUmengAnalytics.leavePage(pageName) 51 | currentPage = undefined 52 | } 53 | } 54 | 55 | // 友盟文档规定:id,ts,du 是保留字段,不能作为 event id 及 key 的名称。 56 | const bannedKeys = { 57 | id: true, 58 | ts: true, 59 | du: true, 60 | } 61 | 62 | function checkEventId(eventId) { 63 | if (bannedKeys[eventId]) { 64 | throw new Error(`[${eventId}] 是保留字段,不能作为 event id.`) 65 | } 66 | } 67 | function checkEventDataKey(data) { 68 | for (let key in data) { 69 | if (bannedKeys[key]) { 70 | throw new Error(`${key} 是保留字段,不能作为 event data 的 key.`) 71 | } 72 | } 73 | } 74 | 75 | export function sendEvent(eventId) { 76 | checkEventId(eventId) 77 | RNTUmengAnalytics.sendEvent(eventId) 78 | } 79 | 80 | export function sendEventLabel(eventId, label) { 81 | checkEventId(eventId) 82 | RNTUmengAnalytics.sendEventLabel(eventId, label) 83 | } 84 | 85 | export function sendEventData(eventId, data) { 86 | checkEventId(eventId) 87 | checkEventDataKey(data) 88 | RNTUmengAnalytics.sendEventData(eventId, data) 89 | } 90 | 91 | export function sendEventCounter(eventId, data, counter) { 92 | checkEventId(eventId) 93 | checkEventDataKey(data) 94 | RNTUmengAnalytics.sendEventCounter(eventId, data, counter) 95 | } 96 | 97 | export function sendError(error) { 98 | // 安卓才有这个接口 99 | if (RNTUmengAnalytics.sendError) { 100 | RNTUmengAnalytics.sendError(error) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ios/RNTUmengAnalytics.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1EC4B2FB24B8A80A00192AB7 /* RNTUmengAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC4B2FA24B8A80A00192AB7 /* RNTUmengAnalytics.m */; }; 11 | 1EC4B2FC24B8A80A00192AB7 /* RNTUmengAnalytics.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1EC4B2F924B8A80A00192AB7 /* RNTUmengAnalytics.h */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | 1EC4B2F424B8A80A00192AB7 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = "include/$(PRODUCT_NAME)"; 19 | dstSubfolderSpec = 16; 20 | files = ( 21 | 1EC4B2FC24B8A80A00192AB7 /* RNTUmengAnalytics.h in CopyFiles */, 22 | ); 23 | runOnlyForDeploymentPostprocessing = 0; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 1EC4B2F624B8A80A00192AB7 /* libRNTUmengAnalytics.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNTUmengAnalytics.a; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 1EC4B2F924B8A80A00192AB7 /* RNTUmengAnalytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNTUmengAnalytics.h; sourceTree = ""; }; 30 | 1EC4B2FA24B8A80A00192AB7 /* RNTUmengAnalytics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNTUmengAnalytics.m; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 1EC4B2F324B8A80A00192AB7 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 1EC4B2ED24B8A80A00192AB7 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 1EC4B2F824B8A80A00192AB7 /* RNTUmengAnalytics */, 48 | 1EC4B2F724B8A80A00192AB7 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 1EC4B2F724B8A80A00192AB7 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 1EC4B2F624B8A80A00192AB7 /* libRNTUmengAnalytics.a */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 1EC4B2F824B8A80A00192AB7 /* RNTUmengAnalytics */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 1EC4B2F924B8A80A00192AB7 /* RNTUmengAnalytics.h */, 64 | 1EC4B2FA24B8A80A00192AB7 /* RNTUmengAnalytics.m */, 65 | ); 66 | path = RNTUmengAnalytics; 67 | sourceTree = ""; 68 | }; 69 | /* End PBXGroup section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 1EC4B2F524B8A80A00192AB7 /* RNTUmengAnalytics */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 1EC4B2FF24B8A80A00192AB7 /* Build configuration list for PBXNativeTarget "RNTUmengAnalytics" */; 75 | buildPhases = ( 76 | 1EC4B2F224B8A80A00192AB7 /* Sources */, 77 | 1EC4B2F324B8A80A00192AB7 /* Frameworks */, 78 | 1EC4B2F424B8A80A00192AB7 /* CopyFiles */, 79 | ); 80 | buildRules = ( 81 | ); 82 | dependencies = ( 83 | ); 84 | name = RNTUmengAnalytics; 85 | productName = RNTUmengAnalytics; 86 | productReference = 1EC4B2F624B8A80A00192AB7 /* libRNTUmengAnalytics.a */; 87 | productType = "com.apple.product-type.library.static"; 88 | }; 89 | /* End PBXNativeTarget section */ 90 | 91 | /* Begin PBXProject section */ 92 | 1EC4B2EE24B8A80A00192AB7 /* Project object */ = { 93 | isa = PBXProject; 94 | attributes = { 95 | LastUpgradeCheck = 1150; 96 | ORGANIZATIONNAME = musicode; 97 | TargetAttributes = { 98 | 1EC4B2F524B8A80A00192AB7 = { 99 | CreatedOnToolsVersion = 11.5; 100 | }; 101 | }; 102 | }; 103 | buildConfigurationList = 1EC4B2F124B8A80A00192AB7 /* Build configuration list for PBXProject "RNTUmengAnalytics" */; 104 | compatibilityVersion = "Xcode 9.3"; 105 | developmentRegion = en; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | en, 109 | Base, 110 | ); 111 | mainGroup = 1EC4B2ED24B8A80A00192AB7; 112 | productRefGroup = 1EC4B2F724B8A80A00192AB7 /* Products */; 113 | projectDirPath = ""; 114 | projectRoot = ""; 115 | targets = ( 116 | 1EC4B2F524B8A80A00192AB7 /* RNTUmengAnalytics */, 117 | ); 118 | }; 119 | /* End PBXProject section */ 120 | 121 | /* Begin PBXSourcesBuildPhase section */ 122 | 1EC4B2F224B8A80A00192AB7 /* Sources */ = { 123 | isa = PBXSourcesBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | 1EC4B2FB24B8A80A00192AB7 /* RNTUmengAnalytics.m in Sources */, 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXSourcesBuildPhase section */ 131 | 132 | /* Begin XCBuildConfiguration section */ 133 | 1EC4B2FD24B8A80A00192AB7 /* Debug */ = { 134 | isa = XCBuildConfiguration; 135 | buildSettings = { 136 | ALWAYS_SEARCH_USER_PATHS = NO; 137 | CLANG_ANALYZER_NONNULL = YES; 138 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 139 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 140 | CLANG_CXX_LIBRARY = "libc++"; 141 | CLANG_ENABLE_MODULES = YES; 142 | CLANG_ENABLE_OBJC_ARC = YES; 143 | CLANG_ENABLE_OBJC_WEAK = YES; 144 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 145 | CLANG_WARN_BOOL_CONVERSION = YES; 146 | CLANG_WARN_COMMA = YES; 147 | CLANG_WARN_CONSTANT_CONVERSION = YES; 148 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 149 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 150 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 151 | CLANG_WARN_EMPTY_BODY = YES; 152 | CLANG_WARN_ENUM_CONVERSION = YES; 153 | CLANG_WARN_INFINITE_RECURSION = YES; 154 | CLANG_WARN_INT_CONVERSION = YES; 155 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 156 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 157 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 158 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 159 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 160 | CLANG_WARN_STRICT_PROTOTYPES = YES; 161 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 162 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 163 | CLANG_WARN_UNREACHABLE_CODE = YES; 164 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 165 | COPY_PHASE_STRIP = NO; 166 | DEBUG_INFORMATION_FORMAT = dwarf; 167 | ENABLE_STRICT_OBJC_MSGSEND = YES; 168 | ENABLE_TESTABILITY = YES; 169 | GCC_C_LANGUAGE_STANDARD = gnu11; 170 | GCC_DYNAMIC_NO_PIC = NO; 171 | GCC_NO_COMMON_BLOCKS = YES; 172 | GCC_OPTIMIZATION_LEVEL = 0; 173 | GCC_PREPROCESSOR_DEFINITIONS = ( 174 | "DEBUG=1", 175 | "$(inherited)", 176 | ); 177 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 178 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 179 | GCC_WARN_UNDECLARED_SELECTOR = YES; 180 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 181 | GCC_WARN_UNUSED_FUNCTION = YES; 182 | GCC_WARN_UNUSED_VARIABLE = YES; 183 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 184 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 185 | MTL_FAST_MATH = YES; 186 | ONLY_ACTIVE_ARCH = YES; 187 | SDKROOT = iphoneos; 188 | }; 189 | name = Debug; 190 | }; 191 | 1EC4B2FE24B8A80A00192AB7 /* Release */ = { 192 | isa = XCBuildConfiguration; 193 | buildSettings = { 194 | ALWAYS_SEARCH_USER_PATHS = NO; 195 | CLANG_ANALYZER_NONNULL = YES; 196 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 197 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 198 | CLANG_CXX_LIBRARY = "libc++"; 199 | CLANG_ENABLE_MODULES = YES; 200 | CLANG_ENABLE_OBJC_ARC = YES; 201 | CLANG_ENABLE_OBJC_WEAK = YES; 202 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 203 | CLANG_WARN_BOOL_CONVERSION = YES; 204 | CLANG_WARN_COMMA = YES; 205 | CLANG_WARN_CONSTANT_CONVERSION = YES; 206 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 207 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 208 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 209 | CLANG_WARN_EMPTY_BODY = YES; 210 | CLANG_WARN_ENUM_CONVERSION = YES; 211 | CLANG_WARN_INFINITE_RECURSION = YES; 212 | CLANG_WARN_INT_CONVERSION = YES; 213 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 214 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 215 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 217 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 218 | CLANG_WARN_STRICT_PROTOTYPES = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | COPY_PHASE_STRIP = NO; 224 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 225 | ENABLE_NS_ASSERTIONS = NO; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu11; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 236 | MTL_ENABLE_DEBUG_INFO = NO; 237 | MTL_FAST_MATH = YES; 238 | SDKROOT = iphoneos; 239 | VALIDATE_PRODUCT = YES; 240 | }; 241 | name = Release; 242 | }; 243 | 1EC4B30024B8A80A00192AB7 /* Debug */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | CODE_SIGN_STYLE = Automatic; 247 | DEVELOPMENT_TEAM = N276SVAZ33; 248 | HEADER_SEARCH_PATHS = "$(SRCROOT)/../../react-native/React/**"; 249 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 250 | OTHER_LDFLAGS = "-ObjC"; 251 | PRODUCT_NAME = "$(TARGET_NAME)"; 252 | SKIP_INSTALL = YES; 253 | TARGETED_DEVICE_FAMILY = "1,2"; 254 | }; 255 | name = Debug; 256 | }; 257 | 1EC4B30124B8A80A00192AB7 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | CODE_SIGN_STYLE = Automatic; 261 | DEVELOPMENT_TEAM = N276SVAZ33; 262 | HEADER_SEARCH_PATHS = "$(SRCROOT)/../../react-native/React/**"; 263 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 264 | OTHER_LDFLAGS = "-ObjC"; 265 | PRODUCT_NAME = "$(TARGET_NAME)"; 266 | SKIP_INSTALL = YES; 267 | TARGETED_DEVICE_FAMILY = "1,2"; 268 | }; 269 | name = Release; 270 | }; 271 | /* End XCBuildConfiguration section */ 272 | 273 | /* Begin XCConfigurationList section */ 274 | 1EC4B2F124B8A80A00192AB7 /* Build configuration list for PBXProject "RNTUmengAnalytics" */ = { 275 | isa = XCConfigurationList; 276 | buildConfigurations = ( 277 | 1EC4B2FD24B8A80A00192AB7 /* Debug */, 278 | 1EC4B2FE24B8A80A00192AB7 /* Release */, 279 | ); 280 | defaultConfigurationIsVisible = 0; 281 | defaultConfigurationName = Release; 282 | }; 283 | 1EC4B2FF24B8A80A00192AB7 /* Build configuration list for PBXNativeTarget "RNTUmengAnalytics" */ = { 284 | isa = XCConfigurationList; 285 | buildConfigurations = ( 286 | 1EC4B30024B8A80A00192AB7 /* Debug */, 287 | 1EC4B30124B8A80A00192AB7 /* Release */, 288 | ); 289 | defaultConfigurationIsVisible = 0; 290 | defaultConfigurationName = Release; 291 | }; 292 | /* End XCConfigurationList section */ 293 | }; 294 | rootObject = 1EC4B2EE24B8A80A00192AB7 /* Project object */; 295 | } 296 | -------------------------------------------------------------------------------- /ios/RNTUmengAnalytics/RNTUmengAnalytics.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RNTUmengAnalytics : NSObject 4 | 5 | + (void)init:(NSString *)appKey channel:(NSString *)channel debug:(BOOL)debug; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/RNTUmengAnalytics/RNTUmengAnalytics.m: -------------------------------------------------------------------------------- 1 | #import "RNTUmengAnalytics.h" 2 | 3 | #import 4 | #import 5 | #import 6 | 7 | @implementation RNTUmengAnalytics 8 | 9 | static NSString *CHANNEL = @""; 10 | 11 | + (void)init:(NSString *)appKey channel:(NSString *)channel debug:(BOOL)debug { 12 | 13 | CHANNEL = channel; 14 | 15 | [UMConfigure initWithAppkey:appKey channel:channel]; 16 | [UMConfigure setLogEnabled:debug]; 17 | 18 | // 手动采集 19 | [MobClick setAutoPageEnabled:NO]; 20 | 21 | } 22 | 23 | + (BOOL)requiresMainQueueSetup { 24 | return YES; 25 | } 26 | 27 | - (dispatch_queue_t)methodQueue { 28 | return dispatch_queue_create("com.github.reactnativehero.umenganalytics", DISPATCH_QUEUE_SERIAL); 29 | } 30 | 31 | - (NSDictionary *)constantsToExport { 32 | return @{ 33 | @"CHANNEL": CHANNEL, 34 | }; 35 | } 36 | 37 | RCT_EXPORT_MODULE(RNTUmengAnalytics); 38 | 39 | RCT_EXPORT_METHOD(init:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { 40 | 41 | resolve(@{}); 42 | 43 | } 44 | 45 | // 获得设备信息,用于集成测试 46 | RCT_EXPORT_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { 47 | 48 | NSDictionary *deviceInfo = [self getDeviceInfo]; 49 | 50 | resolve(deviceInfo); 51 | 52 | } 53 | 54 | RCT_EXPORT_METHOD(getUserAgent:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 55 | __weak RNTUmengAnalytics *weakSelf = self; 56 | dispatch_async(dispatch_get_main_queue(), ^{ 57 | __strong RNTUmengAnalytics *strongSelf = weakSelf; 58 | if (strongSelf) { 59 | // Save WKWebView (it might deallocate before we ask for user Agent) 60 | __block WKWebView *webView = [[WKWebView alloc] init]; 61 | 62 | [webView evaluateJavaScript:@"window.navigator.userAgent;" completionHandler:^(id _Nullable result, NSError * _Nullable error) { 63 | if (error) { 64 | reject(@"error", error.localizedDescription, nil); 65 | } 66 | else { 67 | resolve(@{ 68 | @"userAgent": [NSString stringWithFormat:@"%@", result], 69 | }); 70 | } 71 | // Destroy the WKWebView after task is complete 72 | webView = nil; 73 | }]; 74 | } 75 | }); 76 | } 77 | 78 | RCT_EXPORT_METHOD(getPhoneNumber:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { 79 | resolve(@{ 80 | @"phoneNumber": @"", 81 | }); 82 | } 83 | 84 | // 账号统计 85 | RCT_EXPORT_METHOD(signIn:(NSString *)userId provider:(NSString *)provider) { 86 | if (provider && provider.length > 0) { 87 | [MobClick profileSignInWithPUID:userId provider:provider]; 88 | } 89 | else { 90 | [MobClick profileSignInWithPUID:userId]; 91 | } 92 | } 93 | 94 | RCT_EXPORT_METHOD(signOut) { 95 | [MobClick profileSignOff]; 96 | } 97 | 98 | RCT_EXPORT_METHOD(exitApp) { 99 | exit(0); 100 | } 101 | 102 | // 必须配对调用 beginLogPageView 和 endLogPageView 两个函数来完成自动统计 103 | // 若只调用某一个函数不会生成有效数据 104 | RCT_EXPORT_METHOD(enterPage:(NSString *)pageName) { 105 | [MobClick beginLogPageView:pageName]; 106 | } 107 | 108 | RCT_EXPORT_METHOD(leavePage:(NSString *)pageName) { 109 | [MobClick endLogPageView:pageName]; 110 | } 111 | 112 | // 自定义事件 113 | RCT_EXPORT_METHOD(sendEvent:(NSString *)eventId) { 114 | [MobClick event:eventId]; 115 | } 116 | 117 | RCT_EXPORT_METHOD(sendEventLabel:(NSString *)eventId label:(NSString *)label) { 118 | [MobClick event:eventId label:label]; 119 | } 120 | 121 | RCT_EXPORT_METHOD(sendEventData:(NSString *)eventId data:(NSDictionary *)data) { 122 | [MobClick event:eventId attributes:data]; 123 | } 124 | 125 | RCT_EXPORT_METHOD(sendEventCounter:(NSString *)eventId data:(NSDictionary *)data counter:(int)counter) { 126 | [MobClick event:eventId attributes:data counter:counter]; 127 | } 128 | 129 | - (NSDictionary *) getDeviceInfo { 130 | 131 | NSString *deviceId =[UMConfigure deviceIDForIntegration]; 132 | NSString *deviceType = [self getDeviceType]; 133 | NSString *brand = @"apple"; 134 | NSString *bundleId = [self getBundleId]; 135 | 136 | return @{ 137 | @"deviceId": [deviceId lowercaseString], 138 | @"deviceType": [deviceType lowercaseString], 139 | @"brand": [brand lowercaseString], 140 | @"bundleId": bundleId, 141 | }; 142 | 143 | } 144 | 145 | - (NSString *) getBundleId { 146 | return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; 147 | } 148 | 149 | - (NSString *) getDeviceType { 150 | switch ([[UIDevice currentDevice] userInterfaceIdiom]) { 151 | case UIUserInterfaceIdiomPhone: return @"Phone"; 152 | case UIUserInterfaceIdiomPad: 153 | if (TARGET_OS_MACCATALYST) { 154 | return @"Desktop"; 155 | } 156 | if (@available(iOS 14.0, *)) { 157 | if ([NSProcessInfo processInfo].isiOSAppOnMac) { 158 | return @"Desktop"; 159 | } 160 | } 161 | return @"Tablet"; 162 | case UIUserInterfaceIdiomTV: return @"Tv"; 163 | case UIUserInterfaceIdiomMac: return @"Desktop"; 164 | default: return @"Unknown"; 165 | } 166 | } 167 | 168 | @end 169 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-native-hero/umeng-analytics", 3 | "version": "0.3.3", 4 | "description": "react native umeng analytics", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/react-native-hero/umeng-analytics.git" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "native", 16 | "umeng analytics" 17 | ], 18 | "author": "musicode", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/react-native-hero/umeng-analytics/issues" 22 | }, 23 | "homepage": "https://github.com/react-native-hero/umeng-analytics#readme" 24 | } --------------------------------------------------------------------------------