/** 本地存储:为 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 }) }