1. 项目中使用arr原生插件
2. 离线打包使用arr原生插件
2.1
根据官网文档,配置arr
2.2
2.3
2.4
2.5
查看插件原生AndroidManifest.xml, 如果有单独设置meta-data和权限,本地SDK的AndroidManifest.xml也要加上
2.6
手机运行,没问题就打包APK
2.7
注意: 要把签名文件放到F:\companyItem2\32_daijia\Android-SDK@4.66.82418_20250520\Android-SDK@4.66.82418_20250520\HBuilder-Integrate-AS\simpleDemo\ceshi.keystore.jks 才能在AS跑起来
3.热更新核心代码
- 获取服务器的版本号
- 获取本地的版本号( uni.getSystemInfoSync()的appWgtVersion, 这个是wgt版本号 )
- 判断两个版本号,进行APK更新或者WGT更新
// 下载APK
function downloadAndInstallApk (response: any) {
uni.showLoading({ title: '下载新版应用中...', mask: true }).then()
// 创建下载任务
const downloadTask = plus.downloader.createDownload(
response.data.url,
{ method: 'GET' },
(task, status) => {
if (status === 200) {
downloadScope.value.completed = true
downloadScope.value.filename = task.filename ?? ''
return installApp()
} else {
uni.hideLoading()
uni.showToast({ title: '下载新版应用失败', duration: 1500, icon: 'none' }).then()
}
}
)
// 监听下载进度
downloadTask.addEventListener('statechanged', (task) => {
// 已接收到数据
if (task.state === 3) {
downloadScope.value.loaded = task.downloadedSize ?? 0
downloadScope.value.total = task.totalSize ?? 0
}
})
// 开始下载
downloadTask.start()
}
// 安装APK
function installApp () {
// Apk文件
const fileName = downloadScope.value.filename
// 注册广播监听app安装情况
// onInstallListening(callBack)
try {
const newPath = plus.io.convertLocalFileSystemURL(fileName)
plus.runtime.install(newPath, { force: true }, () => {
// 成功跳转到安装界面
uni.hideLoading()
}, (err) => {
uni.hideLoading()
uni.showToast({ title: `安装新版应用失败 ${newPath} ${JSON.stringify(err)}`, duration: 1500, icon: 'none' }).then()
console.error(`安装新版应用失败:${newPath} ${JSON.stringify(err)}`)
})
} catch (err) {
uni.hideLoading()
uni.showToast({ title: `安装新版应用失败:${JSON.stringify(err)}`, duration: 1500, icon: 'none' }).then()
console.error(`安装新版应用失败:${JSON.stringify(err)}`)
}
}
// 下载并安装Wgt
function downloadAndInstallWgt (response: any) {
uni.showLoading({ title: '下载新版文件中...', mask: true })
uni.downloadFile({
url: response.data.url,
success (downloadResult) {
console.debug(`下载新版文件结果:${JSON.stringify(downloadResult)}`)
if (downloadResult.statusCode === 200) {
// true表示强制安装,不进行manifest.json版本号的校验;false则需要版本号校验
plus.runtime.install(downloadResult.tempFilePath, {
force: true
}, () => {
uni.hideLoading()
console.debug('加载新版文件成功')
uni.showToast({ title: '加载新版文件成功,即将重启应用', duration: 1500, icon: 'none' })
setTimeout(() => {
uni.hideLoading()
plus.runtime.restart()
}, 2000)
}, (err) => {
uni.hideLoading()
uni.showToast({ title: `加载新版文件失败: ${JSON.stringify(err)}`, icon: 'none' })
console.error(`加载新版文件失败: ${JSON.stringify(err)}`)
})
} else {
uni.hideLoading()
uni.showToast({ title: `下载新版文件失败 ${downloadResult.statusCode}`, duration: 1500, icon: 'none' })
console.error(`下载新版文件失败:${downloadResult.statusCode}`)
}
},
fail (err) {
uni.hideLoading()
uni.showToast({ title: `下载新版文件失败 ${err}`, duration: 1500, icon: 'none' })
console.error(`下载新版文件失败:${err}`)
}
})
}
4.权限相关
4.1 权限列表
[
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
'<uses-permission android:name="android.permission.VIBRATE"/>',
'<uses-permission android:name="android.permission.READ_LOGS"/>',
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
/* 定位权限 */
'<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>',
'<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>',
'<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>',
/* 麦克风 */
'<uses-feature android:name="android.permission.RECORD_AUDIO"/>',
/* 相机 */
'<uses-feature android:name="android.hardware.camera"/>',
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
'<uses-permission android:name="android.permission.CAMERA"/>',
/* 存储读写 */
'<uses-feature android:name="android.permission.READ_EXTERNAL_STORAGE"/>',
'<uses-feature android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>',
'<uses-feature android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>',
/* 打电话 */
'<uses-permission android:name="android.permission.CALL_PHONE"/>',
/* 安装应用权限 */
'<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>',
'<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>',
/* 电池优化,防止后台杀进程 */
'<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>',
'<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>',
]
4.2 权限检测
4.2.1 判断有没权限 并 请求权限
使用 plus.android.requestPermissions
注意:
- “安装其它应用、忽略电池优化” 这些按照原生来写就好。(原生的实例对象的方法通过
plus.android.invoke
调用,静态方法通过.xxx调用)
4.2.2 跳转权限设置
const APP_ACTION_LIST = [
'ACTION_APPLICATION_DETAILS_SETTINGS',
'ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS',
]
const SYSTEM_ACTION_LIST = [
'ACTION_MANAGE_UNKNOWN_APP_SOURCES',
'ACTION_LOCATION_SOURCE_SETTINGS',
'ACTION_APPLICATION_SETTINGS',
'ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION',
]
export function goToPermissionSettings (action = 'ACTION_APPLICATION_DETAILS_SETTINGS') {
uni.showLoading({ title: '前往设置页面', mask: true })
const Intent = plus.android.importClass("android.content.Intent")
const Settings = plus.android.importClass("android.provider.Settings")
const mainActivity = plus.android.runtimeMainActivity()
const intent = new Intent()
intent.setAction(Settings[action])
if (APP_ACTION_LIST.includes(action)) {
const Uri = plus.android.importClass("android.net.Uri")
const uri = Uri.fromParts("package", mainActivity.getPackageName(), null)
intent.setData(uri)
}
uni.hideLoading()
mainActivity.startActivity(intent)
}
5.AS调试原生代码
开发原生插件: https://nativesupport.dcloud.net.cn/NativePlugin/course/android.html
开发原生插件的时候,默认是在离线SDK的
UniPlugin-Hello-AS
,此时就需要使用AS的调试
- 项目直接调用
uni.requireNativePlugin
并使用插件 - HBuildX打包项目
- 打包项目放入到
UniPlugin-Hello-AS
- 下图方式调试插件
6.ADB调试闪退等问题
adb查看内存
adb shell cat /proc/meminfo
adb shell top -n 1
adb排查闪退
adb logcat -v time > crash_log.txt
adb logcat -v time *:E > crash_log.txt
adb查看系统日志
adb shell dumpsys dropbox --print >>./crash9.log
adb查看安卓版本
adb shell getprop ro.build.version.release
adb查看安卓id
adb shell settings get secure android_id
adb查看APP进程id
adb shell ps -A | grep your.package.name
部分总结:
- 内存满了会导致APP闪退和打不开
- 刷过系统的,如果是谷歌定位,国内无法使用
- 无法保活:给APP添加自启动、允许后台运行、锁定应用、添加前台服务(保活)、定时发送通知
7.其它
7.1 读写权限
Android11 以上是
“android.permission.MANAGE_EXTERNAL_STORAGE”
Android11 以下是
“android.permission.READ_EXTERNAL_STORAGE”, // 读外部存储
“android.permission.WRITE_EXTERNAL_STORAGE” // 写外部存储
7.2 部分插件可能不支持x86
比如Android高德内置导航,只能真机调试;且建议AS直接删掉x86;
7.3 设置允许后台运行
暂时无法跑通,使用adb也跳不过去设置页面,报错没有权限
7.4 原生插件是R获取不到,导致无法打包
清一下编译缓存,然后重新在 settings.gradle
和 build.gradle
里面导入项目
7.5 原生插件打包ARR
进入根目录
// Debug构建
./gradlew :mylibrary:assembleDebug
// Release 构建
./gradlew :mylibrary:assembleRelease
7.6 云打包的AndroidManifest.xml解密
java -jar AXMLPrinter2.jar AndroidManifest.xml > manifest.txt
7.7 跳转其它原生页面,通过APP点击进入,返回了主活动页
uniapp的离线打包壳有问题
AndroidManifest.xml 的 io.dcloud.PandoraEntry
activity,删除 android:launchMode="singleTask"
7.8 adb排查闪退
adb更新手机坐标
adb查看内存:adb shell cat /proc/meminfo ; adb shell top -n 1
adb查看内存占用详情:adb shell dumpsys meminfo
adb查看安卓版本:adb shell getprop ro.build.version.release
adb查看安卓id:adb shell settings get secure android_id
adb排查闪退:adb logcat -v time > crash_log.txt ; adb logcat -v time *:E > crash_log.log
adb查看APP进程id: adb shell ps -A | grep your.package.name
adb查看系统日志: adb shell dumpsys dropbox --print >>./crash9.log
adb获取当前页面:adb shell "dumpsys window | grep mCurrentFocus"
adb启动页面: adb shell am start -n com.example.app/.MainActivity
7.9 灰屏闪退问题
场景BUG
:部分手机,上划退出APP,重新进入APP,灰屏闪退重现条件
:保活+允许自启动+关闭电池优化
排查方式
:排查1: 想办法百分百重现BUG(最小Demo形式)
1.一开始测试确定只要“关闭自启动”,就不会出现灰屏闪退,所以推断 “保活+允许自启动” 必重现BUG
2.尝试精简代码,重新拉取一个uniapp vue3的脚手架,只保留一个页面调用保活插件,然后允许自启动 (无法重现)
3.无法重现后,猜测与权限有关,把权限都加上(保活关闭电池优化)
4.暂时确定重现条件为:保活+允许自启动+关闭电池优化+uniapp的Activity启动异常(可能)
5.排查Activity,精简代码打包好,拉到AS,然后使用uniapp插件壳,直接跑调试,发现BUG无法重现
6.第五步更怀疑是Activity的配置问题
7.尝试直接用uniapp插件壳打包,发现BUG又出现了(至此,确定是打包后出现的,与Activity的配置无关)
排查2: 开始定位具体问题
8.用打包后的APP,在AS跑logcat,发现明显空指针异常(之前一直使用adb logcat完全没这个报错,他喵的。还是AS牛逼)
9.按照空指针报错,将return START_STICKY; 改为return START_REDELIVER_INTENT;
10.重新打包,问题不出现
11.打包新的arr,使用原本项目打包,对应离线壳也更新arr,至此问题解决