转载自: UniApp 踩坑日记

前言

UniApp 可以实现跨平台的打包和编辑,支持小程序和 App,方便开发的同时也带来了一些系列兼容性的问题,曾和 UniApp 官方的开发人员有过沟通,对于兼容性的问题很多时候官方也没有办法去解决,我们只能通过产品的角度去寻求另外的一种解决方式。

不过对于简单的应用,也不用同时兼容太多的平台,UniApp 也是企业和开发者的选择之一,今天这篇文章主要记录了我在使用 UniApp 的过程中遇到了一些问题和解决方案。

iOS 底部安全距离

/\*兼容 IOS<11.2\*/  
padding-bottom: constant(safe-area-inset-bottom);  
/\*兼容 IOS>11.2\*/  
padding-bottom: env(safe-area-inset-bottom);  

App 状态栏距离

<view class="page">  
  <view class="status\_bar"></view>  
</view>  

<style scoped>  
.status\_bar {  
 height: var(--status-bar-height);  
 width: 100%;  
}  
</style>  

textarea 组件的层级问题

  • 使用原生组件 cover-view 覆盖掉样式
  • 动态渲染原生组件

textarea 属于原生组件,层级高于其他组件,在小程序端处出现层级穿透的问题,对此官方提供了 cover-view 原生组件,不过非常鸡肋。

在这里我们更加推荐使用 动态渲染组件的形式。

<template>  
 <view>  
  <textarea placeholder="这是一个多行文本域" v-if="showPopup" />  
  <uni-popup ref="popup" @change="handleChange">  
      这是一个弹出框  
    </uni-popup>  
 </view>  
</template>  

<script>  
 export default {  
  data() {  
   return {  
    showPopup: false  
   }  
  },  
  onLoad() {  
      this.$nextTick(() => {  
        this.$refs.popup.open('top')  
      })  
  },  
  methods: {  
      handleChange(e) {  
        this.showPopup = e.show  
      }  
  }  
 }  
</script>  

App 实现热更新

App 热更新是 App 端常用的升级手段,但是因为 iOS 不允许 App 热更新上架应用市场,这里值介绍了 Android 端的使用。

同时需要注意的是如果热更新的代码包需要与 uniapp 云服务打包的版本号相同,否则会出现不兼容的问题。


var dtask = plus.downloader.createDownload(this.upgradeUrl, {}, (d, status) => {  
  if(status === 200) {  
    plus.runtime.install(d.filename, {}, () => {  
      plus.nativeUI.closeWaiting();  
      console.log("安装wgt文件成功!", this.isforce);  
      // 重启App  
      plus.runtime.restart();  
    }, (e) => {  
      console.log("安装wgt文件失败["+e.code+"]:"+e.message);  
      plus.nativeUI.alert("安装wgt文件失败["+e.code+"]:"+e.message);  
    });  
  } else {  
    console.log('网络异常');  
    plus.downloader.clear(dtask);  
  }  
})  

// 监听下载进度  
dtask.addEventListener('statechanged', (task) => {  
  let { totalSize, downloadedSize } = task;  
  console.log('下载进度', Math.floor(downloadedSize / totalSize * 100) || 0)  
})  

App 被第三方调用

打开 manifest.json => App常用其它配置 => UrlSchemes; 设置 App 的 UrlSchemes

  • H5 打开 App
<a href="test://dl">打开App<a>  

  • App 监听打开的参数
onLaunch: () => {  
  // URLSchemes  
  this.checkSchemes();  
  // 重点是以下: 一定要监听后台恢复 !一定要  
  plus.globalEvent.addEventListener("newintent", () => {  
    this.checkSchemes();  
  });  
}  
methods: {  
  // APP 检查 URLSchemes  
  checkSchemes() {  
    let args = plus.runtime.arguments;  
    console.log('获取到的参数', args)  
  }  
}  

App 打开小程序

plus.share.getServices(res => {  
  var sweixin = null;  
  for (var i = 0; i < res.length; i++) {  
    var t = res[i];  
    if (t.id == 'weixin') {  
      sweixin = t;  
    }  
  }  
  if (sweixin) {  
    sweixin.launchMiniProgram({  
      id: 'xxxx', // 小程序原始id  
      path: 'xxxxx', // 小程序路径  
      type: 0 //  0-正式版; 1-测试版; 2-体验版。  
    })  
  }  
})  

跨组件通信

// componentA  
// 添加监听关闭 splash 事件  
uni.$on("closeSplashscreen", () => {  
  plus.navigator.closeSplashscreen();  
});  

// componentB  
uni.$emit("closeSplashscreen");  

页面跳转通信

// pageA 监听被打开页面的事件  
uni.navigateTo({  
  url: 'xxxx',  
  events: {  
    event: () => {  
      console.log('enent')  
    }  
  }  
})  

// pageB 触发事件  
const eventChannel = this.getOpenerEventChannel();  
eventChannel.emit && eventChannel.emit('event');  

input 最大长度问题

在 iOS input 输入文字时的拼音的长度大于最大的长度无法输入的问题,当你遇见这个问题的时候,恭喜你无法解决,你只能通过修改产品方案,不限制用户输入的长度,在提交表单时在进行校验即可。

下拉刷新时数据重复的问题

这个问题是因为我们在对组件进行 for 循环时的 key 值问题,具体原因可能还是 uniapp 编译机制的问题吧,如果你出现了这个问题,建议通过 uid 来作为 for 循环的 key 值。


<template>  
  <view>  
    <view v-for="item in list" :key="item.\_uid"></view>  
  </view>  
</template>  

<script>  
  export default {  
    data(){  
      return {  
        list: []  
      }  
    },  
    onLoad() {  
      // 模拟请求后的数据  
      this.list = [a,b,c].map((item) => {  
        // 设置uid  
        return { ...item, \_uid: this.guid() }  
      })  
    },  
    methods: {  
      guid(len = 32, firstU = true, radix = null) {  
        const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");  
        const uuid = [];  
        radix = radix || chars.length;  

        if (len) {  
          // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位  
          for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() \* radix)];  
        } else {  
          let r;  
          // rfc4122标准要求返回的uuid中,某些位为固定的字符  
          uuid[8] = uuid[13] = uuid[18] = uuid[23] = "-";  
          uuid[14] = "4";  

          for (let i = 0; i < 36; i++) {  
            if (!uuid[i]) {  
              r = 0 | (Math.random() \* 16);  
              uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];  
            }  
          }  
        }  
        // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class  
        if (firstU) {  
          uuid.shift();  
          return `u${uuid.join("")}`;  
        }  
        return uuid.join("");  
      }  
    }  
  }  
</script>  

<style>  
  .content {  
    display: flex;  
    flex-direction: column;  
    align-items: center;  
    justify-content: center;  
  }  

  .logo {  
    height: 200rpx;  
    width: 200rpx;  
    margin-top: 200rpx;  
    margin-left: auto;  
    margin-right: auto;  
    margin-bottom: 50rpx;  
  }  

  .text-area {  
    display: flex;  
    justify-content: center;  
  }  

  .title {  
    font-size: 36rpx;  
    color: #8f8f94;  
  }  
</style>  

隐私合规问题

如果你的 App 需要上架应用市场,就必须的注重 App 申请权限的问题。对此我们需要注意一下几点:

  • 使用原生隐私政策提示框

打开项目的 manifest.json 文件,切换到“App 启动界面配置”,在“Android 启动界面样式”中勾选“使用原生隐私政策提示框”

{  
    "version" : "1",  
    "prompt" : "template",  
    "title" : "个人信息保护说明",  
    "message" : "<font color='#000000' size='2'>\t\t\t\t欢迎您使用xxx平台,为帮助您注册使用,浏览推荐,发布投诉,互动交流,我们将手机您的部分必要信息;<br/><br/>\t\t\t\t我们手机的相关信息,未经您的同意,我们不会从第三方获取、共享或提供您的信息;<br/><br/>\t\t\t\t本协议有您与平台的经营者共同缔结,本协议具有合同效力。<br/><br/>\t\t\t\t请查看<a href=\"https://xxxx\">《用户服务协议》</a>及<a href=\"https://xxxx\">《隐私政策》</a></font>",  
    "buttonAccept" : "同意并接受",  
    "buttonRefuse" : "暂不同意",  
    "styles" : {  
        "borderRadius" : "5px",  
        "buttonAccept" : {  
            "color" : "#2cbe9a"  
        }  
    }  
}  

  • 详细的描述引用了什么 SDK 以及作用和时机

友盟统计(com.umeng)使用场景:统计 APP 下载量;使用目的:用于数据统计分析;涉及个人信息:设备信息(IMEI、ANDROID_ID、DEVICE_ID、IMSI)、应用已安装列表、网络信息;收集规则:APP 前台运行时获取 SIM 序列号、SD 卡数据和设备序列号用于数据统计分析;个人信息处理规则: http://xxxx

  • 关闭 App 在首次启动时申请存储、照片和拨打电话的权限
// 在"app-plus" -> "distribute" -> "android" 节点下添加 permissionExternalStorage 节点  
// 禁止App在启动时存储、照片的权限  
"permissionExternalStorage": {  
    "request": "always",  
    "prompt": "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。"  
 }  

 // 禁止App在启动时获取拨打电话的权限  
 "permissionPhoneState" : {  
    "request" : "none",  
    "prompt" : "为保证您正常、安全地使用,需要获取设备识别码(部分手机提示为获取手机号码)使用权限,请允许。"  
}  

键盘遮挡样式的问题

// pages.json 设置键盘的弹出模式  
"app-plus":{  
    "softinputMode": "adjustResize"  
}  


打包体积过大

在 Uniapp 中 App 云打包有体积的限制,而在小程序中也存在体积大小的限制。

  • 小程序分包加载

"subPackages": [{  
    "root": "packageA",  
    "pages": [{  
        "path": "pages/index",  
    }]  
  }, {  
    "root": "packageB",  
    "pages": [{  
        "path": "pages/index",  
    }]  
  }  
]  


  • 静态资源 CDN

占用我们资源包大小的莫过于图片了,我们可以将图片上 CDN 存储减少打包体积大小。

当然在开发过程中为了方便,我们可以在本地建立服务,通过 ip 来访问图片。

<template>  
  <image :src="getImageUrl('/public/logo.png')" mode=""></image>  
</template>  

<script>  
  export default {  
    methods: {  
      getImageUrl(path) {  
        return process.env.NODE\_ENV === 'production' ? `http://CDN地址 ${path}` : `http://本地服务地址 ${path}`  
      }  
    }  
  }  
</script>  

总结

如果你决定了使用 UniApp 做为当前项目的框架并且适配多平台,那么需要开始处理让人头疼的兼容性问题。

很多时候我们依赖一些第三方的云插件,云插件的更新或者云插件的 Bug 可能会影响你的 App 打包失败。

很多时候这些兼容性的问题不是必现的,出现的可能有很多的因素,比如 UniApp 更新了版本。

最后

感谢你的阅读~

如果你有任何的疑问欢迎您在后台私信,我们一同探讨学习!

如果觉得这篇文章对你有所帮助,点赞、在看是最大的支持!