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