uniapp-x开发Android app常见UTS编译兼容性报错
uniapp-x开发Android app常见UTS编译兼容性报错
UTS Android 编译兼容性速查
uni-app x 使用 UTS 编译器将 TypeScript 转为 Kotlin 字节码。 以下规则适用于 HBuilderX Android 自定义基座 / 发行打包。 凡是违反以下规则的代码,编译必然失败。
1. 模板(Template)规则
1.1 .property 访问返回 Any?
模板中在 UTSJSONObject 上 .property 得到的类型是 Any?(可空)。
<!-- 错误做法 -->
<text>{{ memo.title }}</text> <!-- title 是 Any? -->
<text v-if="memo.content">{{ ... }}</text> <!-- Any? 不能当 boolean -->
<!-- 正确做法 -->
<text>{{ memo.title }}</text> <!-- 纯展示 OK -->
<text v-if="memo.content != null">{{ ... }}</text> <!-- 显式 null 比较 -->
<text>{{ user.is_following ? 'A' : 'B' }}</text> <!-- ❌ Any? 不能做三元条件 -->
1.2 模板中禁止 ! 操作符
<!-- 错误 -->
v-if="!loggedIn"
:class="{ active: !isCollapsed }"
<!-- 正确 -->
v-if="loggedIn == false"
:class="isCollapsed == false ? 'active' : ''"
1.3 模板中禁止 && / || 用在非 boolean
<!-- 错误 -->
v-if="a != null && a.length > 0"
{{ a || 'default' }}
<!-- 正确 -->
v-if="a != null && a.length > 0" <!-- && 两边是 boolean,OK -->
使用三元表达式代替 ||。
1.4 :class 不支持对象字面量
<!-- 错误 -->
:class="{ active: isSelected }"
<!-- 正确 -->
:class="isSelected ? 'active' : ''"
:class="'btn ' + (isSelected ? 'active' : '')"
1.5 v-for 元素类型不可推断
<!-- subscriptions: UTSJSONObject[],sub 在模板中是 Any? -->
v-for="sub in subscriptions"
<!-- 传递给方法时必须用 any | null -->
getSubDisplayName(sub: any | null)
1.6 v-if / 三元条件必须是 boolean
<!-- 错误 -->
v-if="user" <!-- UTSJSONObject 不是 boolean -->
v-if="user.bio" <!-- Any? 不是 boolean -->
{{ user.is_following ? '是' : '否' }} <!-- Any? 不是 boolean -->
<!-- 正确 -->
v-if="user != null"
v-if="user.bio != null"
{{ isFollowing ? '是' : '否' }} <!-- isFollowing 是 computed: boolean -->
1.7 列表模板不要直接用动态对象字段做业务判断
Android list-view/list-item 中,模板直接判断 UTSJSONObject 的动态字段可能出现渲染不稳定,尤其是:
<!-- 不推荐:memo 是 UTSJSONObject,Android 模板里可能不稳定 -->
<text v-if="memo.memo_type === 'web'">链接</text>
<text v-if="memo.url != null">访问链接</text>
更稳的做法是在接口数据标准化时预先计算普通 boolean/string 字段,模板只读简单字段:
const obj = JSON.parse(JSON.stringify(m)) as UTSJSONObject
const rawType = obj['memo_type']
if (rawType != null && (rawType as string) == 'web') {
obj['has_link_type'] = true
}
<text v-if="memo.has_link_type == true">链接</text>
业务字段也要分清职责:
memo_type用于类型指示,例如列表里的“链接/图片” badgeurl用于“是否能访问链接”和实际打开链接- 不要用
memo_type == 'web'去决定能不能打开链接;有些历史 web memo 可能没有url
1.8 列表多行省略优先用 text 的 lines
列表内容需要限制行数时,优先使用 text 组件的 lines 属性,不要套 Web 的 overflow/text-overflow/white-space:
<text class="card-content" lines="10">{{ memo.content }}</text>
详情页不要加 lines,应展示完整内容。
1.9 横向标签栏分平台处理
首页顶部标签栏这类横向短列表,不能简单照搬竖向长列表方案,也不能只靠 Web/CSS 的内容自适应思路。当前已在 Android 和 HarmonyOS 真机确认的做法:
Android/非鸿蒙
<!-- #ifndef APP-HARMONY -->
<list-view class="tag-bar tag-bar-list" :scroll-x="true" :scroll-y="false">
<list-item class="tag-item" :style="{ width: getTagItemWidth('全部') + 'rpx' }">
<text class="tag-pill tag-pill-list">全部</text>
</list-item>
<list-item class="tag-item" v-for="tag in popularTags" :key="tag" :style="{ width: getTagItemWidth(tag) + 'rpx' }">
<text class="tag-pill tag-pill-list">#{{ tag }}</text>
</list-item>
</list-view>
<!-- #endif -->
要点:
- 横向
list-view可以滚动,但list-item不给宽度会默认接近整屏宽,看起来像标签间隔巨大或“一屏一个标签” - 必须显式控制
list-item宽度;这是控制原生 item 占位,不是控制标签margin - Android 分支的 pill 可以单独去掉
margin-right,避免和 item 宽度叠加出大间隔
HarmonyOS
<!-- #ifdef APP-HARMONY -->
<scroll-view class="tag-bar tag-bar-scroll" direction="horizontal" :show-scrollbar="false">
<view class="tag-chip">
<text class="tag-chip-text">全部</text>
</view>
<view class="tag-chip" v-for="tag in popularTags" :key="tag">
<text class="tag-chip-text">#{{ tag }}</text>
</view>
</scroll-view>
<!-- #endif -->
要点:
- 使用
direction="horizontal",并给scroll-view自身样式加flex-direction: row - 标签 chip 直接作为
scroll-view子元素,不要再套内层横向 row - 不要在 HarmonyOS 横向标签栏使用
list-view;项目经验里list-view在鸿蒙端可能影响页面生命周期
配套样式示例:
.tag-bar { flex-shrink: 0; height: 88rpx; background-color: #FFFFFF; }
.tag-bar-scroll { flex-direction: row; }
.tag-chip { flex-shrink: 0; flex-direction: row; align-items: center; padding: 12rpx 28rpx; margin-top: 16rpx; margin-left: 16rpx; border-radius: 999rpx; }
.tag-item { flex-shrink: 0; flex-direction: row; align-items: center; }
.tag-pill { flex-shrink: 0; padding: 12rpx 28rpx; }
2. 脚本(Script)规则
2.1 data() 必须用字面量初始化
// 错误 ❌ — store 引用导致类型推断为 Any?
data() {
return {
loggedIn: authStore.isLoggedIn,
nickname: authStore.user ? getNick(authStore.user) : '',
}
}
// 正确 ✅ — 全部用字面量,onShow 中同步
data() {
return {
loggedIn: false,
nickname: '',
}
},
onShow() {
this.loggedIn = authStore.isLoggedIn
this.nickname = authStore.user ? getNick(authStore.user) : ''
}
2.2 async 生命周期钩子
// 错误 ❌ — onLoad/onShow 必须返回 Unit
async onLoad(options: any) {
const res = await api.get()
}
// 正确 ✅ — 移到 methods,onLoad 中调用
onLoad(options: any) {
this.loadData()
},
methods: {
async loadData() {
const res = await api.get()
}
}
2.3 catch 不能捕获非 Throwable 类型
// 错误 ❌
catch (e: RequestResult) { }
// 正确 ✅
catch (e: any) {
const obj = JSON.parse(JSON.stringify(e)) as UTSJSONObject
const code = obj['statusCode'] as number
}
2.3.1 catch (e: any) 里也不要强转成业务类
Android/UTS 运行时捕获到的错误对象可能是 HolderUTSError,不一定是 Promise reject() 传出的业务类。直接强转会运行时报:
java.lang.ClassCastException: HolderUTSError cannot be cast to RequestResult
// 错误 ❌ — e 可能是 HolderUTSError,不一定是 RequestResult
} catch (e: any) {
const err = e as RequestResult
const statusCode = err.statusCode
}
// 正确 ✅ — 只把错误对象 JSON 化后读必要字段
} catch (e: any) {
var statusCode = 0
try {
const obj = JSON.parse(JSON.stringify(e)) as UTSJSONObject
const rawStatus = obj['statusCode']
if (rawStatus != null) statusCode = rawStatus as number
} catch (err: any) {
console.log('[API] parse error failed:', JSON.stringify(err))
}
}
2.4 if / 条件必须是 boolean
// 错误 ❌
if (this.memo) { }
if (this.currentTag) { }
if (options.id) { }
// 正确 ✅
if (this.memo != null) { }
if (this.currentTag.length > 0) { }
if (rawId != null) { }
2.5 类型收窄(narrowing)不生效
// 错误 ❌ — if 内 res.data 仍然是 UTSJSONObject | null
if (res.data != null) {
this.user = res.data // ❌ 类型不匹配
}
// 正确 ✅ — 用 as 或 !
this.user = res.data as UTSJSONObject
this.user = res.data!
2.6 空数组 / 对象必须标注类型
// 错误 ❌
const arr = []
const obj = {}
// 正确 ✅
const arr: string[] = []
const arr = [] as string[]
const obj = {} as UTSJSONObject
3. any 类型限制
3.1 any 不支持 ['key'] 括号访问
// 错误 ❌ — any 上不能用 bracket
function fn(x: any) {
const v = x['name'] // 编译成 String.get(index: Number),返回 Char
}
// 正确 ✅ — 先转 UTSJSONObject
function fn(x: any | null) {
if (x == null) return
const obj = x as UTSJSONObject
const v = obj['name'] as string
}
3.2 any 不支持 .property 访问
// 错误 ❌
function fn(x: any) {
console.log(x.name)
const paths = res.tempFilePaths
}
// 正确 ✅ — 转 UTSJSONObject + bracket
function fn(x: any) {
const obj = JSON.parse(JSON.stringify(x)) as UTSJSONObject
const name = obj['name']
}
3.3 any 上不能调用方法
// 错误 ❌
if (url.startsWith('http')) { }
// 正确 ✅
if ((url as string).startsWith('http')) { }
3.4 原生回调对象不要直接 cast 成 UTSJSONObject
Android 某些回调参数是原生对象,例如 onBackPress(options) 的 OnBackPressOptions,直接:
const obj = options as UTSJSONObject
会运行时报:
ClassCastException: OnBackPressOptions cannot be cast to UTSJSONObject
应先转成普通 JSON 对象再读字段,并用 try-catch 兜底:
try {
const obj = JSON.parse(JSON.stringify(options)) as UTSJSONObject
const from = obj['from']
} catch (e : any) {
console.log('[Navigation] parse onBackPress options failed:', JSON.stringify(e))
}
4. API / 内置类型限制
4.1 uni.openURL 不可用
Android 平台当前 HBuilderX 版本不支持。
// 错误 ❌
uni.openURL('https://example.com')
// 正确 ✅
uni.setClipboardData({ data: url })
uni.showToast({ title: '链接已复制', icon: 'none' })
4.2 UTSJSONArray 类型不存在
// 错误 ❌
const arr = raw as UTSJSONArray
// 正确 ✅
const arr = raw as any[]
for (let i = 0; i < arr.length; i++) { } // .length 是属性,不是方法
4.3 Number() 是抽象类
// 错误 ❌
const id = Number(str)
// 正确 ✅
const id = parseInt(str)
4.4 String(Int) 不存在
// 错误 ❌
const s = String(42)
// 正确 ✅
const s = (42).toString()
4.5 uni.chooseImage 回调
// 错误 ❌
success: (res: any) => {
const paths = res.tempFilePaths
}
// 正确 ✅
success: (res: any) => {
const json = JSON.parse(JSON.stringify(res)) as UTSJSONObject
const raw = json['tempFilePaths']
if (raw != null) {
const arr = raw as any[]
for (let i = 0; i < arr.length; i++) { paths.push(arr[i] as string) }
}
}
4.6 对象字面量不能构造
// 错误 ❌ — 不能在脚本中构造对象字面量
const headers: UTSJSONObject = {
'Authorization': token
}
// 正确 ✅
const obj = JSON.parse('{}') as UTSJSONObject
obj['Authorization'] = token
// 或直接传 string(uni.request 等框架 API 可以接受调用时字面量)
uni.request({
header: { 'Authorization': token } // 框架 API 参数有类型声明,OK
})
4.7 UTSPromise 构造必须分平台(鸿蒙坑)
UTSPromise 是 uni-app x 在 Android/iOS 的 Promise 封装,但鸿蒙 ArkTS 运行时没有这个全局对象,new UTSPromise(...) 抛 ReferenceError,整个请求封装层崩溃。
// 错误 ❌ — 鸿蒙端 "UTSPromise is not defined"
function request(...) : UTSPromise<RequestResult> {
return new UTSPromise<RequestResult>((resolve, reject) => { ... })
}
// 正确 ✅ — 构造调用分平台,类型标注不动(编译后擦除,两端安全)
function request(...) : UTSPromise<RequestResult> {
// #ifdef APP-HARMONY
return new Promise<RequestResult>((resolve, reject) => {
// #endif
// #ifndef APP-HARMONY
return new UTSPromise<RequestResult>((resolve, reject) => {
// #endif
...
})
}
注意:返回类型标注 UTSPromise<t> 不需要改——类型信息在编译后被擦除,只有 new UTSPromise(...) 构造调用才触发鸿蒙运行时错误。
4.8 methods 内禁用方法提升,必须 this.(鸿蒙坑)
鸿蒙 ArkTS 不支持方法提升(hoisting),methods 内裸调用同类方法会报 xxx is not defined。Android/Kotlin 支持提升所以一直没问题。
// 错误 ❌ — 鸿蒙 "parseDate is not defined"
formatTime(s: string) {
var d = parseDate(s) // parseDate 定义在后面,鸿蒙找不到
}
parseDate(s: string) : Date { ... }
// 正确 ✅ — 通过 this 运行时查找,两端通用
formatTime(s: string) {
var d = this.parseDate(s)
}
parseDate(s: string) : Date { ... }
4.9 uni.request 的 204 No Content 空响应
Android/uni-app x 中,uni.request 默认按 JSON 解析响应时,服务端 204 No Content 的空 body 可能进入失败回调并报:
{"errCode":100001,"errMsg":"invalid json","errSubject":"uni-request"}
这类接口服务端其实可能已经成功执行,例如 DELETE /memos/{id} 返回 204,但前端误走失败分支。请求封装应避免让 uni.request 自动解析空响应:
uni.request({
url,
method,
data,
header,
// 204 空响应不是合法 JSON,统一按 text 接收后手动解析。
dataType: 'text',
success: (res) => {
const sc = res.statusCode
const data = parseResponseData(res.data)
if (sc >= 200 && sc < 300) resolve(new RequestResult(data, sc))
else reject(new RequestResult(data, sc))
},
})
手动解析时只处理项目约定的 JSON 对象;接口不要依赖数组裸返回。列表类接口统一返回 { items, total, page, page_size },前端统一读 res.data['items']。
4.10 系统弹窗/平台 API 不稳定时用页面内 UI
uni.showModal 等平台 API 在 uni-app x 多端可能存在行为差异。关键流程(如删除确认)如果出现“点击后无反应”或平台行为不一致,优先改为页面内自定义确认弹层,并给关键步骤保留日志:
[MemoDetail] confirmDeleteMemo
[MemoDetail] deleteMemo start
[MemoDetail] deleteMemo success
这样可以区分“点击事件未触发”“确认 UI 未显示”“接口失败”“成功后页面切换吞掉 toast”。
5. 鸿蒙端专项检查清单
以下问题在 Android 上都不会暴露,只在鸿蒙端运行时才报错。改完代码务必鸿蒙端实测。
| 问题 | 现象 | 根因 | 修复 |
|---|---|---|---|
new UTSPromise |
所有封装请求 fail,错误 {}(实际是 ReferenceError) |
ArkTS 无 UTSPromise 全局对象 | §4.7 条件编译 |
| 裸调用同类方法 | 渲染崩溃 xxx is not defined |
ArkTS 不支持方法提升 | §4.8 用 this. |
<list-view> 竖向列表 |
切 tabBar 回来 onShow 不触发 | list-view 在鸿蒙异常 | 模板条件编译用 scroll-view |
错误对象 {} |
JSON.stringify(err) 显示空 |
鸿蒙错误对象不可枚举 | 分字段打印 errMsg/errno 或直接 console.log |
5. Store / Pinia 注意事项
| 场景 | 做法 |
|---|---|
| 模板访问 store 属性 | ❌ 不直接访问 authStore.isLoggedIn。拷贝到本地 data() boolean |
| 方法中访问 store | ✅ authStore.isLoggedIn 直接使用 |
data() 初始化用 store 值 |
❌ 类型会变 Any?。用字面量,onShow 同步 |
computed 中访问 store |
✅ 如 isMe() { return authStore.user != null && ... } |
6. .then() 限制
// 错误 ❌
promise.then(onSuccess, onError) // 两个回调的重载解析失败
// 正确 ✅ — 用 await + try/catch
try {
const r = await promise
} catch (e: any) { }
// 或单回调 .then()
promise.then(() => { ... })
7. 比较操作符
7.1 === 在 Number vs Int 上不稳定
// 错误 ❌ — === 是 Kotlin 引用比较,装箱的 Number 和 Int 可能指向不同对象
if (res.statusCode === 200) { }
// 正确 ✅ — == 是值比较
if (res.statusCode == 200) { }
if (sc == 401) { }
8. 字符串/日期操作
// 日期格式化 — 用 .toString()
return date.getHours().toString().padStart(2, '0')
// ❌ 不能用 String(date.getHours()).padStart(...)
9. 常见编译错误速查
| 错误信息 | 原因 | 修复 |
|---|---|---|
找不到名称"XXX" |
模板引用了未定义的属性/方法,或类型不匹配导致方法不可见 | 检查方法名、参数类型 |
参数类型不匹配:实际类型为 'Char',预期类型为 'String' |
用 ['key'] 在 any/String 上 |
先 as UTSJSONObject |
Throwable type mismatch |
catch (e: 非Throwable) |
改为 catch (e: any) |
Condition type mismatch: Any? → Boolean |
if / v-if / 三元用了非 boolean |
加 != null、== false |
Cannot create an instance of an abstract class |
Number() |
用 parseInt() |
Cannot infer type for this parameter |
空数组 [] 无类型 |
[] as string[] / [] as any[] |
Assignment type mismatch: UTSJSONObject? → UTSJSONObject |
可空值赋给非空变量 | as UTSJSONObject 或 ! |
找不到名称'unreadCount' / 'notifications' |
模板访问 store 属性 | 拷贝到本地 data |
请检查 uni.openURL 的拼写是否正确 |
API 不可用 | 改用 clipboard |
.then(onFulfilled, onRejected) 编译挂掉 |
双回调重载无法解析 | 用 try/catch await |
HolderUTSError cannot be cast to RequestResult |
catch (e:any) 后强转业务类 |
JSON 化后安全读取字段,不要强转 |
uni-request invalid json 且服务端是 204 No Content |
空响应体被自动按 JSON 解析 | uni.request 设置 dataType:'text',手动解析响应 |
9. 构建流程须知
- 差量编译 — HBuilderX 自动修复(AI修复)可能会覆写你的编辑,导致文件被 revert。
- 清除缓存 — 如果磁盘文件正确但编译报错仍显示旧代码,需退出 HBuilderX 重开并清除编译缓存。
- Server
.env独立 — 服务端 JWTSECRET_KEY与本地不同;必须登录服务端。 - Release 构建 — 标准 debug 基座不含 uni-push 模块;推送功能需要 release 自定义基座。
- 分类:
- Android
相关文章
转载“牛叉的goophone 4GS”
“iphone”还可以运行android啊 Original resource: Click here 阅读更多…
android手机QQ2012很给力
前几天我更新了android手机QQ2012,感觉很给力哦。 特别是那个多终端同时在线,感觉很牛x。我还是今天猜发现的哦。 今天我在笔记本上用eclipse做小项目,电脑上同时登着QQ呢,我的手机 阅读更多…
给android模拟器安装apk
1、运行Android模拟器,启动你的Android手机系统,准备好你需要安装apk软件 例如,我把UC浏览器的APK放到D盘,文件名为:ucweb-7.2.2.54-999-139 阅读更多…
真的可以直接在Windows中运行原生Android4.0系统,
看到的第一印象想到了bluestacks吧,呵呵,那个知道的人已经不少了哦,这次说的可是近日国内的创业公司绍奇科技(SocketeQ)推出了一款名为WindowsAndroid的程序,可以直接在Win 阅读更多…
Software being installed: Android Development Tools 16.0.1.v201112150204-238534 (com.android.ide.eclipse.adt.feature.group 16.0.1.v201112150204-238534)
怎么解决呢 官方的解决方案 During installation, there's an error about requiring org.eclipse.wst.sse.ui. How d 阅读更多…
在电脑上安装Android模拟器Emulator
如果你还没有Android手机,那么可以先在电脑上安装一个Android模拟器,因为它可以在电脑上模拟出Android手机系统,让你也能体验一下它强大的魅力。 一、 在电脑上安装Andr 阅读更多…