|
@@ -0,0 +1,334 @@
|
|
|
|
|
+/** 本地存储:为 true 时用系统浏览器打开链接,否则用内置 WebView */
|
|
|
|
|
+export const OPEN_URL_IN_SYSTEM_BROWSER_KEY = 'open_url_in_system_browser'
|
|
|
|
|
+
|
|
|
|
|
+/** 是否已做过「首次启动弱机默认」检测(全应用仅一次) */
|
|
|
|
|
+export const OPEN_URL_DEVICE_DEFAULT_APPLIED_KEY = 'open_url_device_default_applied'
|
|
|
|
|
+
|
|
|
|
|
+/** 用户在「应用设置」里手动改过开关后为 true,不再自动覆盖 */
|
|
|
|
|
+export const OPEN_URL_USER_EXPLICIT_SET_KEY = 'open_url_user_explicit_set'
|
|
|
|
|
+
|
|
|
|
|
+/** 首次/刷新时写入的检测结果(JSON) */
|
|
|
|
|
+export const OPEN_URL_DEVICE_SNAPSHOT_KEY = 'open_url_device_snapshot'
|
|
|
|
|
+
|
|
|
|
|
+/** Android 12 对应 API 31 */
|
|
|
|
|
+const ANDROID_12_API_LEVEL = 31
|
|
|
|
|
+/** 物理内存低于此值(GB)视为弱机,默认用系统浏览器打开链接 */
|
|
|
|
|
+const WEAK_DEVICE_RAM_GB = 8
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Java Long / 任意桥接值为字节数
|
|
|
|
|
+ * @param {*} v
|
|
|
|
|
+ * @returns {number}
|
|
|
|
|
+ */
|
|
|
|
|
+function javaLongishToNumber(v) {
|
|
|
|
|
+ if (v == null || v === undefined) return 0
|
|
|
|
|
+ if (typeof v === 'number' && Number.isFinite(v)) return v
|
|
|
|
|
+ if (typeof v === 'string') {
|
|
|
|
|
+ const n = parseFloat(v)
|
|
|
|
|
+ return Number.isFinite(n) ? n : 0
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof plus !== 'undefined' && plus.android && plus.android.invoke) {
|
|
|
|
|
+ const lv = plus.android.invoke(v, 'longValue')
|
|
|
|
|
+ if (typeof lv === 'number' && Number.isFinite(lv)) return lv
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ try {
|
|
|
|
|
+ const n = Number(v)
|
|
|
|
|
+ return Number.isFinite(n) ? n : 0
|
|
|
|
|
+ } catch (e2) {}
|
|
|
|
|
+ return 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 读取 /proc/meminfo 中 MemTotal(kB)换算为字节(多数机型可用,作 ActivityManager 失败时的回退)
|
|
|
|
|
+ * @returns {number}
|
|
|
|
|
+ */
|
|
|
|
|
+function readMemTotalBytesFromProc() {
|
|
|
|
|
+ // #ifdef APP-PLUS
|
|
|
|
|
+ let br = null
|
|
|
|
|
+ try {
|
|
|
|
|
+ const FileReader = plus.android.importClass('java.io.FileReader')
|
|
|
|
|
+ const BufferedReader = plus.android.importClass('java.io.BufferedReader')
|
|
|
|
|
+ const fr = new FileReader('/proc/meminfo')
|
|
|
|
|
+ br = new BufferedReader(fr)
|
|
|
|
|
+ let line = br.readLine()
|
|
|
|
|
+ let guard = 0
|
|
|
|
|
+ while (line != null && guard < 400) {
|
|
|
|
|
+ guard++
|
|
|
|
|
+ const s = String(line)
|
|
|
|
|
+ if (s.indexOf('MemTotal:') === 0) {
|
|
|
|
|
+ const parts = s.trim().split(/\s+/)
|
|
|
|
|
+ const kb = parseInt(parts[1], 10)
|
|
|
|
|
+ if (Number.isFinite(kb) && kb > 0) return kb * 1024
|
|
|
|
|
+ return 0
|
|
|
|
|
+ }
|
|
|
|
|
+ line = br.readLine()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (br) br.close()
|
|
|
|
|
+ } catch (e2) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ // #endif
|
|
|
|
|
+ return 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 物理内存总字节数(Android App)
|
|
|
|
|
+ * @returns {{ bytes: number, source: string }}
|
|
|
|
|
+ */
|
|
|
|
|
+function readAndroidTotalRamBytes() {
|
|
|
|
|
+ const empty = { bytes: 0, source: '' }
|
|
|
|
|
+ // #ifdef APP-PLUS
|
|
|
|
|
+ try {
|
|
|
|
|
+ const main = plus.android.runtimeMainActivity()
|
|
|
|
|
+ const ActivityManager = plus.android.importClass('android.app.ActivityManager')
|
|
|
|
|
+ const Context = plus.android.importClass('android.content.Context')
|
|
|
|
|
+ const MemoryInfo = plus.android.importClass('android.app.ActivityManager$MemoryInfo')
|
|
|
|
|
+ const am = main.getSystemService(Context.ACTIVITY_SERVICE)
|
|
|
|
|
+ const memInfo = new MemoryInfo()
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ plus.android.invoke(am, 'getMemoryInfo', memInfo)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ am.getMemoryInfo(memInfo)
|
|
|
|
|
+ } catch (e2) {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let raw = null
|
|
|
|
|
+ try {
|
|
|
|
|
+ raw = plus.android.invoke(memInfo, 'totalMem')
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ if (raw == null || raw === undefined) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ raw = memInfo.totalMem
|
|
|
|
|
+ } catch (e2) {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const fromAm = javaLongishToNumber(raw)
|
|
|
|
|
+ if (fromAm > 0) return { bytes: fromAm, source: 'ActivityManager' }
|
|
|
|
|
+
|
|
|
|
|
+ const fromProc = readMemTotalBytesFromProc()
|
|
|
|
|
+ if (fromProc > 0) return { bytes: fromProc, source: '/proc/meminfo' }
|
|
|
|
|
+
|
|
|
|
|
+ return empty
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ const fromProc = readMemTotalBytesFromProc()
|
|
|
|
|
+ if (fromProc > 0) return { bytes: fromProc, source: '/proc/meminfo' }
|
|
|
|
|
+ return empty
|
|
|
|
|
+ }
|
|
|
|
|
+ // #endif
|
|
|
|
|
+ return empty
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 读取 Android 设备信息(App 端)。用于快照与弱机判断。
|
|
|
|
|
+ * @returns {object|null}
|
|
|
|
|
+ */
|
|
|
|
|
+function collectAndroidSnapshot() {
|
|
|
|
|
+ // #ifdef APP-PLUS
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (uni.getSystemInfoSync().platform !== 'android') return null
|
|
|
|
|
+
|
|
|
|
|
+ const out = {
|
|
|
|
|
+ platform: 'android',
|
|
|
|
|
+ at: Date.now(),
|
|
|
|
|
+ androidSdkInt: null,
|
|
|
|
|
+ androidRelease: '',
|
|
|
|
|
+ totalRamBytes: null,
|
|
|
|
|
+ totalRamGb: null,
|
|
|
|
|
+ ramSource: '',
|
|
|
|
|
+ weakOs: false,
|
|
|
|
|
+ weakRam: false,
|
|
|
|
|
+ weakDevice: false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const VERSION = plus.android.importClass('android.os.Build$VERSION')
|
|
|
|
|
+ out.androidSdkInt = Number(VERSION.SDK_INT)
|
|
|
|
|
+ out.androidRelease = String(VERSION.RELEASE || '')
|
|
|
|
|
+ out.weakOs = out.androidSdkInt < ANDROID_12_API_LEVEL
|
|
|
|
|
+
|
|
|
|
|
+ const ram = readAndroidTotalRamBytes()
|
|
|
|
|
+ if (ram.bytes > 0) {
|
|
|
|
|
+ out.totalRamBytes = ram.bytes
|
|
|
|
|
+ out.totalRamGb = Math.round((ram.bytes / (1024 * 1024 * 1024)) * 100) / 100
|
|
|
|
|
+ out.weakRam = out.totalRamGb < WEAK_DEVICE_RAM_GB
|
|
|
|
|
+ out.ramSource = ram.source || ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ out.weakDevice = !!(out.weakOs || out.weakRam)
|
|
|
|
|
+ return out
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ platform: 'android',
|
|
|
|
|
+ at: Date.now(),
|
|
|
|
|
+ error: String((e && e.message) || e)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // #endif
|
|
|
|
|
+ return null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function persistSnapshot(snap) {
|
|
|
|
|
+ if (!snap) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_DEVICE_SNAPSHOT_KEY, JSON.stringify(snap))
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 供应用设置页展示多行说明
|
|
|
|
|
+ * @param {object|null} s getOpenUrlDeviceSnapshot() 返回值
|
|
|
|
|
+ * @returns {string[]}
|
|
|
|
|
+ */
|
|
|
|
|
+export function formatOpenUrlDeviceSnapshotLines(s) {
|
|
|
|
|
+ if (!s || typeof s !== 'object') return []
|
|
|
|
|
+ if (s.platform === 'android') {
|
|
|
|
|
+ const lines = ['本机检测(已保存;进入本页会刷新内存与系统信息)']
|
|
|
|
|
+ if (s.androidSdkInt != null) {
|
|
|
|
|
+ lines.push(`Android API:${s.androidSdkInt}(低于 31 视为系统版本偏低)`)
|
|
|
|
|
+ }
|
|
|
|
|
+ lines.push(`Android 版本:${s.androidRelease || '—'}`)
|
|
|
|
|
+ if (s.totalRamGb != null) {
|
|
|
|
|
+ const src = s.ramSource ? `,来源:${s.ramSource}` : ''
|
|
|
|
|
+ lines.push(`物理内存:约 ${s.totalRamGb} GB(低于 8 GB 视为内存偏低)${src}`)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ lines.push('物理内存:未能读取(ActivityManager 与 /proc/meminfo 均失败)')
|
|
|
|
|
+ }
|
|
|
|
|
+ lines.push(`弱机判定:${s.weakDevice ? '是' : '否'}(系统偏低或内存偏低)`)
|
|
|
|
|
+ if (s.error) lines.push(`读取异常:${s.error}`)
|
|
|
|
|
+ return lines
|
|
|
|
|
+ }
|
|
|
|
|
+ if (s.note) return [String(s.note)]
|
|
|
|
|
+ return []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 进入应用设置等页面时刷新并保存检测快照(不改变首次默认逻辑)。
|
|
|
|
|
+ */
|
|
|
|
|
+export function refreshAndroidOpenUrlDeviceSnapshot() {
|
|
|
|
|
+ // #ifdef APP-PLUS
|
|
|
|
|
+ const snap = collectAndroidSnapshot()
|
|
|
|
|
+ persistSnapshot(snap)
|
|
|
|
|
+ // #endif
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @returns {object|null}
|
|
|
|
|
+ */
|
|
|
|
|
+export function getOpenUrlDeviceSnapshot() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const raw = uni.getStorageSync(OPEN_URL_DEVICE_SNAPSHOT_KEY)
|
|
|
|
|
+ if (raw == null || raw === '') return null
|
|
|
|
|
+ if (typeof raw === 'object') return raw
|
|
|
|
|
+ return JSON.parse(String(raw))
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function userHasExplicitOpenUrlPreference() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return !!uni.getStorageSync(OPEN_URL_USER_EXPLICIT_SET_KEY)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 应用首次启动执行一次(见 App.vue onLaunch):
|
|
|
|
|
+ * 仅 Android:系统版本低于 12 或物理内存低于 8GB 时,若用户未在设置里手动改过,则默认开启「用手机浏览器打开」。
|
|
|
|
|
+ */
|
|
|
|
|
+export function maybeApplyAndroidWeakDeviceOpenUrlDefault() {
|
|
|
|
|
+ // #ifdef APP-PLUS
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (uni.getStorageSync(OPEN_URL_DEVICE_DEFAULT_APPLIED_KEY)) return
|
|
|
|
|
+
|
|
|
|
|
+ const platform = uni.getSystemInfoSync().platform
|
|
|
|
|
+ if (platform !== 'android') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_DEVICE_SNAPSHOT_KEY, JSON.stringify({
|
|
|
|
|
+ platform,
|
|
|
|
|
+ at: Date.now(),
|
|
|
|
|
+ note: '弱机默认仅 Android 检测'
|
|
|
|
|
+ }))
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_DEVICE_DEFAULT_APPLIED_KEY, true)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const snap = collectAndroidSnapshot()
|
|
|
|
|
+ persistSnapshot(snap)
|
|
|
|
|
+
|
|
|
|
|
+ const weak =
|
|
|
|
|
+ snap &&
|
|
|
|
|
+ !snap.error &&
|
|
|
|
|
+ (snap.weakOs || snap.weakRam)
|
|
|
|
|
+
|
|
|
|
|
+ if (weak && !userHasExplicitOpenUrlPreference()) {
|
|
|
|
|
+ setOpenUrlInSystemBrowser(true)
|
|
|
|
|
+ }
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_DEVICE_DEFAULT_APPLIED_KEY, true)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_DEVICE_DEFAULT_APPLIED_KEY, true)
|
|
|
|
|
+ } catch (e2) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ // #endif
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function getOpenUrlInSystemBrowser() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const v = uni.getStorageSync(OPEN_URL_IN_SYSTEM_BROWSER_KEY)
|
|
|
|
|
+ return v === true || v === 'true' || v === 1
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @param {boolean} value
|
|
|
|
|
+ * @param {{ fromUser?: boolean }} [options] fromUser 为 true 时表示用户在应用设置中操作,之后不再被弱机默认覆盖
|
|
|
|
|
+ */
|
|
|
|
|
+export function setOpenUrlInSystemBrowser(value, options) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_IN_SYSTEM_BROWSER_KEY, !!value)
|
|
|
|
|
+ if (options && options.fromUser) {
|
|
|
|
|
+ uni.setStorageSync(OPEN_URL_USER_EXPLICIT_SET_KEY, true)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 按用户偏好:系统浏览器或内置 WebView。
|
|
|
|
|
+ * @param {string} url
|
|
|
|
|
+ * @param {string} [title] 内置 WebView 标题
|
|
|
|
|
+ */
|
|
|
|
|
+export function openEmbeddedOrSystemBrowser(url, title) {
|
|
|
|
|
+ const u = String(url || '').trim()
|
|
|
|
|
+ if (!u) return
|
|
|
|
|
+ const encTitle = encodeURIComponent(title || '页面')
|
|
|
|
|
+ const pageUrl =
|
|
|
|
|
+ '/pages/webview/index?url=' + encodeURIComponent(u) + '&title=' + encTitle
|
|
|
|
|
+
|
|
|
|
|
+ if (getOpenUrlInSystemBrowser()) {
|
|
|
|
|
+ // #ifdef APP-PLUS
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (typeof plus !== 'undefined' && plus.runtime && typeof plus.runtime.openURL === 'function') {
|
|
|
|
|
+ plus.runtime.openURL(u)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ // #endif
|
|
|
|
|
+ // #ifdef H5
|
|
|
|
|
+ if (typeof window !== 'undefined' && typeof window.open === 'function') {
|
|
|
|
|
+ window.open(u, '_blank')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // #endif
|
|
|
|
|
+ }
|
|
|
|
|
+ uni.navigateTo({ url: pageUrl })
|
|
|
|
|
+}
|