uniapp小程序改安卓app笔记

392 阅读13分钟

app 端 uniapp 是默认开启 scoped 的!!


image.png

什么叫做基座呢?现在你制作好了一个 app 的代码,要运行到手机上,而且想要实现一个效果,就是电脑端这边的代码一改,手机上显示的页面马上变化,那你说这个功能谁来提供呢?就是一个叫做安卓基座的东西,以下详细介绍:

  1. 基座的本质 基座是一个​​预编译的安卓原生应用框架​​(APK),包含以下核心组件:

    • WebView 引擎:用于渲染 HTML/CSS/JS 构成的 uni-app 页面。
    • JavaScript 引擎(如 V8):解析执行 uni-app 的业务逻辑代码。
    • 原生插件桥接层:通过 uni.requireNativePlugin 调用原生功能(如相机、蓝牙)。
    • 热更新模块:支持代码修改后实时同步到设备,无需重新安装 APK。
  2. 与完整 APK 的区别

    • 基座是非完整应用,仅提供运行环境,不包含业务代码。
    • 业务代码(Vue/JS 文件)通过热更新动态注入基座,实现快速调试。

image.png

首先要指定 adb 路径,什么是 adb

image.png

本质就是一个让电脑可以使用某种协议和手机连接,电脑可以传输命令给手机,手机端可以执行命令的工具,举个例子:

典型工作流程

  1. 用户输入命令 adb install app.apk
  2. ADB Client 连接本机 ADB Server(端口 5037)。
  3. Server 检测目标设备,将命令转发至设备的 adbd 进程。
  4. adbd 执行 APK 安装操作,返回结果给 Server。
  5. Server 将结果传回 Client,终端显示安装成功与否。

好,介绍完 adb 是什么,刚刚的图片中提到了: image.png

adb 默认路径就是你电脑里安装的 adb,在环境变量里面全局安装的那一个,当然你也可以指定,安卓模拟器的安装目录录也是自带一个 adb 的。


好,那接着来介绍:image.png是什么?以 mumu 模拟器举例:

  • MuMu主程序端口:模拟器作为Windows应用运行时占用的端口,用于管理窗口/输入/虚拟机等自身功能。
  • ADB调试端口:模拟器内部Android系统的调试入口,专供 adb 命令连接虚拟设备。

一个安卓模拟器会有两个重要的端口,一个就是安卓模拟器这个程序在 Windows 里需要占据一个端口来进行运行,另外一个是 adb 调试端口,怎么理解?安卓模拟器本质上是一个 Windows 桌面应用然后内部包了一个安卓虚拟机,这个安卓虚拟机这是一个沙箱构造,就是里面的内容是不能出去的,这也保证安卓模拟机外部是安全的,就算有病毒,也只会在安卓虚拟机内部感染,那现在其实就是你外部要怎么和内部的这个安卓虚拟机进行通信,那其实已经显而易见,对不对?就是 adb 调试端口。

所以图片这里要让你指定一个安卓模拟器端口,指定的是什么?指定的是这个安卓虚拟机的端口,也就是 adb 调试端口。


image.png

这个问题是怎么发生的呢?基座版本(4.66)和编译时使用的SDK版本(3.8.12)不匹配,正是造成弹窗警告的根本原因。还记得基座是什么吗?一个存放业务代码的容器,对不对?那这个基座是在什么时候进行下载的呢?在你的安卓模拟器上第一次运行 app 程序的时候,会自动下载这个安卓基座,这个安卓基座里面其实就包含了,比如调用安卓原生系统的照相机,蓝牙等功能,此时 hbuildx 就会下载对应版本的安卓基座,好,那现在容器有了你要往里面放业务代码对不对?那你是不是得先编译你原来的 vue 代码?

"@dcloudio/uni-app": "3.0.0-alpha-3081220230802001",

使用这个包进行编译代码,他的版本是 30812 也就是 3.8.12,那就刚好对上了,那此时要怎么解决这个问题呢?那你就更新一下这个包,把它更新到4.66版本,是不是问题就解决了?或者想办法让我们的基座版本降级。

  • 更新到最新正式版

    npx @dcloudio/uvm@latest
    
  • 更新到最新 Alpha 版

    npx @dcloudio/uvm@latest alpha
    
  • 更新到正式版指定版本

    npx @dcloudio/uvm@latest 3.2.0
    
  • 更新到 Alpha 版指定版本

    npx @dcloudio/uvm@latest 3.2.0-alpha
    

我发现使用 @dcloudio/uvm 这个包可以直接更新项目里的多个依赖版本。就比如我现在有5个依赖,5个依赖都会同时更新,原理是什么?怎么实现的呢?最简单的方式就是你每次调用这个包,就修改 package.json 中的依赖为最新或者指定版本号,然后再执行 npm i 或者 pnpm i

但是我发现 "@dcloudio/uni-app" 这个包根本就没有 4.66 版本,也就是说,基座版本和代码 sdk 版本一定是不匹配的,那只能屏蔽掉这个兼容性检查弹窗了:

"app-plus": {
    "compatible": {             //可选,JSON对象,uni-app兼容模式
        "ignoreVersion": false, //可选,Boolean类型,是否忽略版本兼容检查提示
    }
}

通过在 manifest.json 内部添加这么一个配置来直接忽略这个版本兼容检查提示。


image.png

这个按钮是打开 app 调试控制台的,但是在当前 4.66 版本会有 bug,点几次 hbuildx 直接闪退。但是没办法,目前只能使用这种方式调试 app 页面。


编译前原始状态(小程序)

<navigator
  class="category-item"
  hover-class="none"
  open-type="switchTab"
  url="/pages/category/category"
>
  <!-- 内部内容 -->
</navigator>

编译后转换结果(App)

<uni-navigator class="category-item" data-v-44ae5a81="">
  <a class="navigator-wrap" href="/pages/category/category"></a>
  <!-- 内部内容转换结果 -->
</uni-navigator>

这个 a 标签其实是多余的,点击 uni-navigator 内部调用 uni.switchTab 这个 api 进行跳转,这非常好,你的样式作用在 <uni-navigator> 上,也不会因为内部多了一个隐藏的 <a> 标签而错乱。


其他的元素和样式转换规则,其实和 uniapp 小程序转 H5 是一模一样,我在那边的笔记都有记录,去那边看就行,这里着重提一点,就是因为我们的 App 和 H5 几乎一模一样,只是页面容器有区别,还有这个 navgitor 标签有区别,APP 多了原生的导航栏和 tabBar,其他的几乎一模一样,那同样也会出现一个问题,就比如我现在这一个页面有 200 张图片,如果我没有对图片进行懒加载的话,200 个请求一下子就出去了,所以呢使用条件编译配合 img 标签实现懒加载:

<!-- #ifdef H5 || APP-PLUS -->
<img :src="item.picture" alt="" class="picture" loading="lazy" />
<!-- #endif -->

有同学会问,为什么不用 uniapp 提供的 image 标签实现懒加载呢?因为 image 标签的懒加载功能在我们的 App 端是实现不了的呢。


image.png

为什么会出现这个问题,就是 .viewport 底部的 margin-bottom 失效了?这个问题解决了:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    html, body {
      height: 100%;
      margin: 0;
      padding: 0;
    }
​
    .main {
      width: 500px;
      height: 100%;
      background-color: red;
      overflow: hidden;
    }
​
    .box {
      height: 100%;
      background-color: blue;
      margin-bottom: 50px;
    }
  </style>
</head>
<body>
<div class="main">
  <div class="box"></div>
</div>
</body>
</html>

以上代码展示的效果是:

image.png

可以注意到 margin-bottom 完美隐身,和我们之前遇到的情况一样,因为 .main 占据视口高度,.box 也是占据视口高度,此时的 margin-bottom 被 overflow: hidden; 隐藏掉了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    html, body {
      height: 100%;
      margin: 0;
      padding: 0;
    }
​
    .main {
      display: flex; // 新添加的
      flex-direction: column; // 新添加的
      width: 500px;
      height: 100%;
      background-color: red;
      overflow: hidden;
    }
​
    .box {
      height: 100%;
      background-color: blue;
      margin-bottom: 50px;
    }
  </style>
</head>
<body>
<div class="main">
  <div class="box"></div>
</div>
</body>
</html>

新的效果是:

image.png

此时可以看见 margin-bottom 又回来了,我猜测此时的 height: 100% 其实相当于一个 flex: 1 的效果,所以可以看见 margin-bottom,那上面这个 bug 其实就已经解决掉了。


image.png

所以我们就知道了,在 app 端,你要保持和小程序端一样的布局,小程序端的页面容器是 page,app 端的页面容器是 #app 元素,在 app 中通过在全局文件中进行条件编译来设置页面容器的样式:

/* #ifdef APP-PLUS */
#app {
	height: 100%;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background-color: #f7f7f8;
}
/* #endif */

那你肯定还会有一个疑惑,导航栏和 tabBar 为什么没有在这个 webview 调试控制台显示出来呢?

  1. 原生组件渲染机制

    • UniApp 在编译为 App 时,导航栏(NavigationBar)和 TabBar 是由原生系统(Android/iOS)渲染的,而非通过 WebView。
    • 您的页面内容(Vue 组件)则运行在 WebView 内部,因此调试工具只能捕获 WebView 中的内容。
  2. 调试工具的局限性

    • WebView 调试工具只能访问 WebView 内部的 DOM 结构
    • 原生导航栏和 TabBar 是覆盖在 WebView 上层的独立控件,不在 WebView 的 DOM 树中,因此不可见。
|-------------------------------|
|    导航栏                      | → 系统原生渲染(状态栏)
|-------------------------------|
|                               |
|   WebView 页面内容区域          | → 您的 Vue 页面(可被调试工具捕获)
|                               |
|-------------------------------|
| TabBar (Native TabBar)        | → UniApp 通过原生代码渲染
|-------------------------------|

这和 uniapp 生成的网页端很不一样,因为 uniapp 生成的网页端,导航栏和 TabBar 都是使用 dom 元素生成的。

image.png


小程序特有功能,比如微信登录,又比如微信专属的动画效果:

image.png

还有微信特有的按钮:

image.png image-20250604084717568转存失败,建议直接上传图片文件

这些都得用条件编译给他去掉。


uniapp 打包的 app 实现了和微信小程序一样的缓存页面栈,就是会自动缓存已经访问并且没有销毁的页面,核心原理是什么?

image.png


image.png

接下来解决 H5 端和 app 端骨架屏失效的问题,为什么微信小程序没问题,在这边就会有问题呢?先来介绍微信小程序的骨架屏为什么可以正常显示:

image.png

接下来再来介绍为什么 H5 端跟 App 端不能正常显示骨架屏:

image.png

/* #ifdef H5 || APP-PLUS */
/* 引入骨架屏宽高样式 */
@import './style/CategoryPanel.scss';
@import './style/HotPanel.scss';
@import '@/components/style/XtxSwiper.scss';
@import '@/components/style/XtxGuess.scss';
/* #endif */

在骨架屏组件内部使用这种方式来引入骨架屏的宽高样式,记住啊,这里后缀要加 .scss,不然会找不到这个文件,因为 vite 默认不会解析 .scss 后缀文件,

*// Vite 默认的解析器会尝试查找这些扩展名* const defaultResolveExtensions = [  '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json',  '.vue', '.svelte', '.marko', '.astro'  *// 框架文件扩展* ];

也可以在 vite 中配置:

image.png

这样就可以正确识别了,不过最简单的还是把路径写全。


为什么打包的时候需要提供一个 App 证书?明明就是使用 uniapp 提供的安卓基座在手机上是可以运行这么一个 App 的。这个时候为什么不用证书呢?

adbUSB 传输 正是开发阶段无需手动处理证书的关键。下面详解这背后的工作原理和限制:

🔧 一、adb 调试的本质:临时特权通道

当你通过 USB 连接手机并运行 adb install app-debug.apk 时:

  1. 设备层面:

    • 手机需开启 USB 调试模式(开发者选项内)。
    • 首次连接时需授权电脑 调试权限(弹出“允许 USB 调试吗?”)。
  2. 系统行为:

    • adb 进程获得临时特权 → 可绕过常规安装器的证书严格校验。
    • 自动使用 调试证书(如 debug.keystore)签名并静默安装。

这就是为什么你“点一下就能装” ——本质是系统为开发者开了“后门”!


🚫 二、USB 调试的局限性

1. 无法分发给他人

  • 好友手机需同时满足:

    • 开启 USB 调试(普通用户不会开)
    • 用数据线连接你的电脑(物理隔离)
  • 微信/QQ 发送 APK → 安装时走 普通安装流程,无证书即被拦截。

2. 调试包无法上架商店

  • debug.keystore 签名的 APK:

    • 包名强制包含 .debug 后缀
    • 密钥有效期仅 365 天 → 过期后无法更新

3. 设备管理麻烦

  • 每台测试手机都需连接电脑执行 adb install → 不适用于大规模测试。

生成安卓证书是应用上架的关键步骤,以下是开发者自己生成证书的几种方案:


方案一:Android Studio 生成

不使用,所以就不详细描述了。


方案二:通过 uni-app 本地生成(HBuilderX

步骤

1.使用云打包,其实就是 uniapp 官方帮你打包 apk

image.png

2.生成应用标识和应用名称:

image.png

应用标识必须是你要做一些操作之前,你必须登录嘛,这个应用标识只有你登录了 hbuildx 的账号之后才能获取,它就防止你游客可以使用云打包功能。

应用名称就是 App 名称。

3.设置应用图标:

image.png

4.进行云打包配置:

image.png

5.生成云端证书,点击 使用云端证书 右边的 详情,进入 uniapp 的开发者中心

image.png

image.png

image.png

此时云端证书就生成好了。

6.选择快速安心打包:

image.png

设置隐私弹窗,隐私弹框配置如下:

{
    "version": "1", // 隐私政策版本号(必填)。应用升级时需提高版本号以重新弹出协议框
    "prompt": "template", // 是否使用原生隐私弹框:'template'-启用模板样式,'none'-不启用
    "title": "服务协议和隐私政策", // 主弹框标题文本内容(必填)
    "message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款...", // 主弹框富文本内容(必填),支持a/font/br等HTML标签
    "buttonAccept": "同意并接受", // 主弹框同意按钮文本(默认值"同意")
    "buttonRefuse": "暂不同意", // 主弹框拒绝按钮文本(留空则不显示此按钮)
    "hrefLoader": "system", // 链接加载方式:'system'-系统浏览器(不支持项目内路径),'default'-应用内置Webview(默认)
    "backToExit": "false", // 点返回键是否退出应用:true-允许退出,false-禁止退出(默认)。应用市场审核敏感项
    "second": { // 二次确认弹窗配置(message不为空时触发)
        "title": "确认提示", // 二次弹窗标题
        "message": "  进入应用前,你需先同意...", // 二次弹窗富文本内容
        "buttonAccept": "同意并继续", // 二次弹窗同意按钮文本
        "buttonRefuse": "退出应用" // 二次弹窗拒绝按钮文本
    },
    "disagreeMode": { // 用户拒绝协议的响应配置
        "support": false, // 是否启用游客模式:true-拒绝后进游客态,false-直接退出(默认)
        "loadNativePlugins": false, // 拒绝时是否加载原生插件:true-加载(默认),false-禁用插件
        "visitorEntry": false, // 是否显示游客按钮(HBuilderX 3.6.7+):true-显示额外游客按钮
        "showAlways": true // 拒绝后下次启动是否再弹:true-持续弹出,false-仅弹一次(默认)
    },
    "styles": { // 视觉样式定制
        "backgroundColor": "#FFFFFF", // 弹框背景色(#RRGGBB格式)
        "borderRadius": "12px", // 弹框圆角值(逻辑像素单位px)
        "title": { // 标题样式
            "color": "#1A1A1A" // 标题文本颜色
        },
        "buttonAccept": { // 同意按钮样式
            "color": "#4285F4" // 按钮文本颜色
        },
        "buttonRefuse": { // 拒绝按钮样式
            "color": "#5F6368" // 按钮文本颜色
        },
        // HBuilderX 3.6.7+ 新增
        "buttonVisitor": { // 游客按钮样式(需配合disagreeMode.visitorEntry使用)
            "color": "#FF9800" // 游客按钮文本颜色
        }
    }
}

没有在上面写出来的属性都是完全不支持的,

image.png

这里说是这么说,经过我的测试,你这个属性不管你怎么设置?你拒绝协议后,下次默认还是继续弹出的。

over。