uniapp开发app进阶


1. 项目中使用arr原生插件

本地使用arr1

本地使用arr2

本地使用arr3

本地使用arr4

本地使用arr5

本地使用arr6

2. 离线打包使用arr原生插件

2.1

根据官网文档,配置arr

2.2

离线打包使用arr1

2.3

离线打包使用arr2

2.4

离线打包使用arr3

2.5

查看插件原生AndroidManifest.xml, 如果有单独设置meta-data和权限,本地SDK的AndroidManifest.xml也要加上

2.6

手机运行,没问题就打包APK
离线打包使用arr4

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.热更新核心代码

  1. 获取服务器的版本号
  2. 获取本地的版本号( uni.getSystemInfoSync()的appWgtVersion, 这个是wgt版本号 )
  3. 判断两个版本号,进行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

注意:

  1. “安装其它应用、忽略电池优化” 这些按照原生来写就好。(原生的实例对象的方法通过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调试原生代码

  1. 项目直接调用uni.requireNativePlugin并使用插件
  2. HBuildX打包项目
  3. 打包项目放入到UniPlugin-Hello-AS
  4. 下图方式调试插件

调试插件1

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

部分总结:

  1. 内存满了会导致APP闪退和打不开
  2. 刷过系统的,如果是谷歌定位,国内无法使用
  3. 无法保活:给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.gradlebuild.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牛逼)
调试插件1
9.按照空指针报错,将return START_STICKY; 改为return START_REDELIVER_INTENT;
10.重新打包,问题不出现
11.打包新的arr,使用原本项目打包,对应离线壳也更新arr,至此问题解决


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录