Uniapp 引入 Android aar 包 和 Android 离线打包
需求:
原生安卓 apk 要求嵌入到 uniapp 中,并通过 uniapp 前端调起 app 的相关组件。
下面手把手教你,从 apk 到 aar,以及打包冲突到如何运行,期间我所遇到的问题都会 一 一 进行说明,相关版本以我文章内为例子,其他版本如果存在差异请自行解决(不过一般也不会有很大差别啦)
官方文档传送门:uni原生插件开发教程
一、uniapp(这里以新建为主演示)
直接在 HBuilder 中新建即可
新建完成后,uniapp 工作台会创建对应的项目,等会生成 appkey 会用到(没有 dcloud 账号就创建一个)
二、Android环境(离线基座项目)搭建
因为我们是 Android 开发的,所以我们只看 Android 部分就可以了,开发环境如下:
首先下载对应 HBuilderX 对应版本的 SDK
1. 查看我们当前 HBuilder 的版本(建议升级到最新):
2. 点击以下红框部分,跳转到查下 Android sdk 界面,选择对应的版本下载,如果HBuilder 不是最新的,就到历史版本中下载对应的即可
3. 在 Android Studio 中,新建项目(选 java,当前项目是我们离线基座的apk,不是我们的项目的),记录下包名(以下我的相关配置):
4. 添加相关配置:
app 下 build.gradle:
implementation 'androidx.appcompat:appcompat:1.0.0'implementation 'androidx.legacy:legacy-support-v4:1.0.0'implementation 'com.alibaba:fastjson:1.2.83'implementation 'androidx.webkit:webkit:1.3.0'implementation 'com.facebook.fresco:fresco:2.5.0'implementation "com.facebook.fresco:animated-gif:2.5.0"implementation 'androidx.recyclerview:recyclerview:1.0.0'implementation 'com.squareup.okio:okio:1.15.0'implementation 'com.squareup.okhttp3:okhttp:3.12.12'implementation 'com.github.bumptech.glide:glide:4.9.0'
下载后的sdk放进来(我们自己的aar包打包后直接放进来,然后同步即可):
可以看到,这个基座是一个 androidX 的项目 ,如果我们要引进来的项目是 support 的也没关系(我的就是 support 的)
5. 搞一个签名,签名网上有很多教程,这里我就不贴出来了,可以自行搜索,这里我贴一下我的配置,这个签名的作用,是为后面我们打包 apk 和申请 appkey 的时候用到的(离线打包自签名,咋搞都可以,只要签名能用):
6. AndroidManifest.xml
注意点:
(1)application 添加:tools:replace=“android:icon,android:allowBackup”
(2)provider 的 authorities需要改成你当前的 包名…dc.fileprovider,例如:android:authorities=“com.android.myapplication2.dc.fileprovider”
(3) 增加 dcloud_appkey,value 就是等会我们要去 uniapp 工作台申请的 appkey
<meta-dataandroid:name="dcloud_appkey"android:value="xxx" />
完整示例如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.android.myapplication2"><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission><uses-permission android:name="zy.permission.OUT_ENTER" /><applicationandroid:allowBackup="true"android:icon="@drawable/icon"android:label="@string/app_name"android:supportsRtl="true"tools:replace="android:icon,android:allowBackup"><activityandroid:name="io.dcloud.PandoraEntry"android:configChanges="orientation|keyboardHidden|keyboard|navigation"android:label="@string/app_name"android:launchMode="singleTask"android:hardwareAccelerated="true"android:theme="@style/TranslucentTheme"android:screenOrientation="user"android:windowSoftInputMode="adjustResize" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><intent-filter><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><action android:name="android.intent.action.VIEW" /><data android:scheme=" " /></intent-filter></activity><activityandroid:name="io.dcloud.PandoraEntryActivity"android:launchMode="singleTask"android:configChanges="orientation|keyboardHidden|screenSize|mcc|mnc|fontScale|keyboard|smallestScreenSize|screenLayout|screenSize|uiMode"android:hardwareAccelerated="true"android:permission="com.miui.securitycenter.permission.AppPermissionsEditor"android:screenOrientation="user"android:theme="@style/DCloudTheme"android:windowSoftInputMode="adjustResize"></activity><providerandroid:name="io.dcloud.common.util.DCloud_FileProvider"android:authorities="com.android.myapplication2.dc.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/dcloud_file_provider" /></provider><meta-dataandroid:name="dcloud_appkey"android:value="xxx" /></application></manifest>
三、申请离线打包的 appkey
1. 登录 dcloud 后,进入工作台,在“应用管理——我的应用”中,找到我们在 HBuilder 中创建的项目
2. 点击 “应用名称” 后,跳转到应用信息,选择 “各平台信息”,点击 “新增” 按钮
3. 将我们刚才创建的基座 Android 项目的包名,输入到 “包名” 文本框中,对于 SHA1 和 SHA256 获取,回到我们的 Android Studio,右键我们的签名,然后选择 Terminal 打开
输入命令:keytool -list -v -keystore 签名文件(注意后缀也需要) -storepass 密码
这样就得到了我们的 sha1 和 sha256
最终平台信息如下
4. 上面平台信息填写完成后,点击提交,平台信息就会变化了
5. 这里我已经创建了离线打包 key,没有创建显示的是创建按钮,点击创建生成我们的 appkey,至此,appkey 创建完成
6. 将 appkey,配置到我们刚才 AndroidManifest.xml 中的 dcloud_appkey
四、基座 Android 项目的完善
1. 回到 HBuilder,打开我们的项目,在项目根下,创建以下目录结构(dir1看package.json 的配置):
其中,package.json 配置结构如下,建议红框中的三个部分和dir1都填写一样(插件名称),避免出错,而 class 部分,是用于暴露给 uniapp 调用的类路径,该路径是你 aar 下的,不是签名的基座的(关于 aar 文件,参考后面 aar 打包部分)
{"id": "插件名称","name": "插件名称","version": "1.0.0","description": "","_dp_type": "nativeplugin","_dp_nativeplugin": {"android": {"plugins": [{"type": "module","name": "插件名称","class": "aar中提供的类路径"}],"integrateType": "aar"}}
}
2. 这里配置正确后,打开 manifest.json,找到 App 原生插件配置,选择本地插件
前面配置正确的话,弹出框就能看到我们的插件了
3. 添加调起我们 aar 的方法(需要和 Android 端约定方法名和参数)
插件名称,参考前面 package.json 的配置,里面 dir1 部分,这里 startActivity(obj),是调起 pageckage.json 中配置的 class 对应类的方法,具体可以看后面打包部分,关于该类的创建和方法定义
4. 在 HBuilder 中,将我们的 uniapp 项目打包成静态资源出来
打包完成后,会在项目根下生成 unpackage下,生成 resources 目录,resources 目录下的目录,存放的就是我们需要的静态资源,该目录名称就是我们项目的应用标识,复制该目录,粘贴到基座 app 的 assets/app 下,该资源目录不存在就创建一个
5. 在 assets 资源目录下,创建 data 目录,然后将 sdk 中提供的三个 dcloud_ 文件放进来,打开 dcloud_control.xml,将其改下面的样子,其中,appid 就是你 uniapp 项目的应用标识,注意要给 hbuilder 添加 debug 和 syncDebug
<hbuilder debug="true" syncDebug="true"><apps><app appid="__UNI__XXXX" appver="1.0.0"/></apps>
</hbuilder>
6. 将我们的的目标 aar 放到基座项目的 libs 下,
前面我们依赖已经配置了 implementation fileTree(dir: ‘libs’, include: [‘.aar’, '.jar’]) ,所以手动同步一下
7. 接下来就可以直接运行到手机了,而安装在手机的,就是离线基座了,或者打包成 apk,放在 uniapp unpackage/debug 目录下,基座apk名称修改成 android_debug.apk (一定要这个名字),Hbuilder 选择运行到设备的时候,选择自定义基座也是可以的
五、AAR 打包
PS:以下打包的过程,都是你自己项目的,跟基座项目没关系
PPS:我的项目是 kotlin ,且是 support,可以正常打包和调起的
1、添加打包工具
在项目的 build.gradle 中添加 fat-aar 打包工具:
classpath ‘com.github.kezong:fat-aar:1.3.8’
2、修改应用 build.gradle配置
将 ‘com.android.application’ 修改成 ‘com.android.library’
添加 apply plugin: ‘com.kezong.fat-aar’
将所有依赖改成 embed 方式,例如:
如果项目有依赖内部的其他 module 模块,那么也是需要修改成 embed,但是,该模块内的依赖的是不会被打包进去的,为了更好的调整,建议将该模块下的所有依赖,搬到当前应用的依赖下,然后修改成 embed,方便后面修改,也不用去修改所依赖内部模块的内容
引入 uniapp 依赖包,用 compileOnly,该模块用于创建我们前面 package.json 中的 class,该 class 依赖 uniapp 包中的类:
以下是我的项目的部分依赖例子(关于我遇到的包冲突部分,在后面):
3、添加 dcloud_uniplugins.json 配置
在 assets 中,创建 dcloud_uniplugins.json,添加如下配置:
其中,name和class 和前面 package.json 中的保持一致,类名可以自己定义
{"nativePlugins": [{"plugins": [{"type": "module","name": "插件名称","class": "包名.ZyModule"}]}]
}
4、创建我们的入口类 ZyModule
这里我只暴露了一个方法 startActivity,且可以传入一个参数,具体根据自己需求修改,我这里方法用于调起我的 MyActivity,这里定义好后,就可以将方法名和 uniapp 的 package.json 中进行配置和调用了
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.common.UniDestroyableModule;public class ZyModule extends UniDestroyableModule {private static final String TAG = "ZyModule";@UniJSMethod(uiThread = true)public void startActivity(Object data) {Log.e(TAG, "传递参数:" + (JSONObject) data);Context context = mUniSDKInstance.getContext();try {Intent intent = new Intent(context, MyActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);Log.d(TAG, "启动成功");} catch (Exception e) {Log.e(TAG, "启动失败: " + e.getMessage());}}@Overridepublic void destroy() {}
}
5、AndroidManifest.xml 的修改
- 去掉 application 中的 name
- 去掉 mian 入口的配置
- FileProvider 一定要写完整的包名,不能用 ${applicationId},否则会调起不起我们的类
- 点击 Android Studio 的 gradle(一般在右边),然后找到我们的项目,在 Tasks/ build 或者 other 或者某个目录下,找到 assembleDebug 或者 assembleRelease,双击运行即可生成 aar 包
- 将 aar 复制到两个地方(aar包名名称不用管,但最好修改成统一的格式)
(1)uniapp插件中:nativeplugins\插件名称\android
(2)直接放在基座的libs目录下,然后同步一下即可
六、问题
1、appkey或者配置无效问题
在申请 appkey 时,其实就是新增平台信息的时候,填错包名或者 SHA1、SHA256,然后重新回来修改成正确的,接着如果去重新生成 appkey,但是发现 appkey 没有改变(如果有改变就用这个新的试试看,我遇到的是没有改变),就重新创建一条平台,原先这条就可以删掉了。
我是一开始包名写错,然后修改成正确的包名后,发现 appkey 没有变化,使用该 appkey 总是包标题错误,就删掉了重新生成才可以的。
2、基座打包冲突和缺包处理
首先,建议直接离线打包,在线打包每个账号每天有次数限制,且需要排队,很慢
其次,包冲突可以在本地快速处理
在打包过程中,有几个包是需要我们自己过滤的,我们的 aar 项目需要改成 compileOnly:
- fastjson
- zip4j
- glide
- com.squareup.okio:okio:1.15.0 和 com.squareup.okhttp3:okhttp:3.12.12
这几个包在在线打包的时候都会出现包冲突报错的,所以我们不能打包进我们的 aar 中,当然,离线下我们需要引入,具体看一看前面基座项目应用的依赖配置
zip4j在基座的libs下aar中有
fastjson需要基座申明依赖
glide 需要的版本是 4.9.0,如果是其他版本,请将自己的项目配置成该版本,然后自行修改代码
缺包和部分冲突
关于缺包,是因为 embed 的依赖方式,是不会将第三方依赖中的依赖打包进去的,这个需要自己在 External Libraries 中找到相关的包添加到你 aar 应用的依赖中,例如,我这里用到的 rx:
或者我项目本身 kotlin 部分:
当然,在线打包,如果我们的是 kotlin 项目,那么我们需要去掉以下三个包:
其他包冲突和缺包都可以按照上面的方式来处理,建议先离线来处理,一般会把所有冲突先罗列出来,运行的时候才会报缺包,缺包就缺啥补啥即可
3、aar 已经放在 uniapp 项目下,为啥运行的时候,会报插件 undefined
原因是没有制作基座!!!
一定要有基座,基座其实就是将 aar 打包进 apk,运行的时候才会被调用到,基座 apk 路径可以参考前面
七、测试
uniapp 要调起 aar,无论是在线打包还是离线打包,都是生成自定义基座:项目/unpackage/debug/android_debug.apk,然后运行的时候选择自定义基座的,如果没有这个基座,而是默认标准基座,那么 aar 是掉不起来的
相关文章:

Uniapp 引入 Android aar 包 和 Android 离线打包
需求: 原生安卓 apk 要求嵌入到 uniapp 中,并通过 uniapp 前端调起 app 的相关组件。 下面手把手教你,从 apk 到 aar,以及打包冲突到如何运行,期间我所遇到的问题都会 一 一 进行说明,相关版本以我文章内为…...

10款高效音频剪辑工具,让声音编辑更上一层楼。
音频剪辑在音频,视频,广告制作,游戏开发,广播等领域中都有广泛的应用。通过音频剪辑,创作者可以通将不同的音频片段进行剪切、拼接、混音等操作,创作出风格各异的音乐作品。如果你也正在为音频创作而努力的…...

Javascript——设计模式(一)
Javascript常见设计模式-CSDN博客 设计模式专栏内容总结-CSDN博客 C#编程思想——设计模式-CSDN博客 设计模式概述及其作用 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验的总结。使用设计模式的主要目的是为…...

Hybird和WebView
在移动端Hybrid开发模式下,iOS和Android应用都可以通过一种共享代码的方式,利用Web技术(HTML、CSS、JavaScript)和原生应用的功能进行开发。这种方式的主要优点是减少了开发成本,因为大部分代码可以共享,同…...

c++实现中缀表达式 转换为后缀表达式
使用栈来计算后缀表达式的值: 9(3 - 1)*310/2; 后缀表达式:所有的符号都是在运算数字的后面出现: 9 3 1 – 3 * 10 2 / 规则: 中缀表达式转后缀表达式: 1.从左到右遍历中缀表达式的每个数字和符号,若是数字就打印同时入栈数…...

Cisco FMC重置SmartLicense到Evaluatin mode步骤
1 科普: what is FMC full name is Firepower Management Center, 是思科FirePower防火墙的统一管理平台. 能管理ASA不? no,只能管理FTD模式的墙。这里的FTD包括物理机firepower系列运行的FTD,以及FTDv(虚拟化版本&a…...

多表查询综合归纳
目录 1. 多表关系 1.1 一对多(多对一) 1.2 多对多 1.3 一对一 2. 多表查询概述 2.1 熟悉表 2.2 笛卡尔积 2.3 消除笛卡尔积 2.4 多表查询分类 3. 内连接 3.1 隐式内连接 3.2 显式内连接 4. 外连接 4.1 左外连接 4.2 右外连接 5. 自连接 …...

【5.线性表-链式表示-王道课后算法题】
王道数据结构-第二章-链式表示算法题 1.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。2. 试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设该结点唯一…...

存储过程及练习
1.存储过程 📖什么是存储过程? 存储过程和函数是事先经过编译并存储在数据库中的一段sql语句集合,调用存储过程函数可以简 化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的 效率…...

【在Linux世界中追寻伟大的One Piece】多路转接epoll
目录 1 -> I/O多路转接之poll 1.1 -> poll函数接口 1.2 -> poll的优点 1.3 -> poll的缺点 1.4 -> poll示例 1.4.1 -> 使用poll监控标准输入 2 -> I/O多路转接之epoll 2.1 -> 初识epoll 2.2 -> epoll的相关系统调用 2.2.1 -> epoll_cre…...

设计模式-参考的雷丰阳老师直播课
一般开发中使用的模式为模版模式策略模式组合,模版用来定义骨架,策略用来实现细节。 模版模式 策略模式 与模版模式特别像,模版模式会定义好步骤定义好框架,策略模式定义小细节 入口类 使用模版模式策略模式开发支付 以上使用…...

Python +Pyqt5 简单视频爬取学习(一)
文章目录 前言 一、演示 二、查找网页视频流的索引文件 三、分析视频流的url和视频流索引文件的差异性 四、判断视频数据是否需要转化为ts 五、判断视频是否被加密,如若被加密,需要先解密 六、合并所有的ts视频,以MP4模式输出完整视频 总结 前…...

Python Requests模块全面教程
Python Requests模块全面教程 在现代软件开发中,网络请求是一个不可或缺的部分。无论是获取网页数据、调用API接口,还是进行数据交互,都会涉及到HTTP请求。Python的Requests模块是一个非常强大的库,能够让我们轻松地发送HTTP请求…...

PyQt入门指南六十 与Python其他库的集成方法
PyQt是一个强大的GUI库,它可以与Python的其他库无缝集成,以实现更复杂的功能。以下是一些常见的集成方法和示例: 1. NumPy NumPy是Python中用于科学计算的基础库。您可以在PyQt应用程序中使用NumPy来处理数据和进行数值计算。 import sys …...

Android15之解决:Dex checksum does not match for dex:framework.jar问题(二百三十九)
简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【…...

车企自动驾驶功能策略 --- 硬件预埋(卷传感器配置)
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...

【已为网站上传证书,却显示不安全】
已为网站上传证书,却显示不安全 错误显示解决办法分析原因 错误显示 此站点有一个由受信任的颁发机构颁发的有效证书但是网站的某些部分不安全 解决办法 删除浏览器所有历史记录, 如果是Edge浏览器显示不安全,那就删除Edge浏览器的所有历史记录; 如果是Google Chrome浏览器显…...

docker busybox作为initContainers
一、上传到私有仓储 docker pull busybox:1.33.1 docker tag busybox:1.33.1 192.168.31.185/public/busybox:1.33.1 docker push 192.168.31.185/public/busybox:1.33.1 --- apiVersion: apps/v1 kind: Deployment metadata:annotations: {}labels: {}name: saas-ali-apiname…...

20.UE5UI预构造,开始菜单
2-22 开始菜单、事件分发器、UI预构造_哔哩哔哩_bilibili 目录 1.UI预构造 2.开始菜单和开始关卡 2.1开始菜单 2.2开始关卡 2.3将开始菜单展示到开始关卡 3.事件分发器 1.UI预构造 如果我们直接再画布上设计我们的按钮,我们需要为每一个按钮进行编辑&#x…...

Electron教程1-初学入门
玩转Electron Electron 是什么注意事项环境安装安装 vscode安装 git 第一个实例第二个实例第二个实例解读 总结问题解答 Electron 是什么 Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个…...

从北美火到中国,大数据洞察品牌“STANLEY”的突围之路
保守直筒大头的“硬汉”外形,以百变颜色踩中时尚命脉,与各路大牌“梦幻联动”,不少时尚弄潮儿没能逃过其“真香”诱惑。 这就是今年以来从北美火到中国的STANLEY,在“巨无霸”水杯中突围出属于自己的一条路。 最近STANLEY又整活…...

深度学习之GAN应用
1 GAN的应用(文本生成) 1.1 GAN为什么不适合文本任务? GAN在2014年被提出之后,在图像生成领域取得了广泛的研究应用。然后在文本领域却一直没有很惊艳的效果。主要在于文本数据是离散数据,而GAN在应用于离散数据时…...

鸿蒙生态下的安全隐私保护:打造用户信任的应用体验
鸿蒙生态下的安全隐私保护:打造用户信任的应用体验 随着华为鸿蒙系统的快速发展,越来越多的设备开始支持这一操作系统,不仅限于智能手机,还包括智能穿戴设备、智能家居产品等。作为开发者,在享受鸿蒙生态系统带来的广…...

用pandoc工具实现ipynb,md,word,pdf之间的转化
Pandoc 是一个强大的工具,可以实现多种文件格式之间的转换,包括 Jupyter Notebook (.ipynb)、Markdown (.md)、Word (.docx)、PDF 等格式。以下是具体的实现方法: 1. 安装 Pandoc 确保已安装 Pandoc: Linux: sudo apt install p…...

第三十一天|贪心算法| 56. 合并区间,738.单调递增的数字 , 968.监控二叉树
目录 56. 合并区间 方法1:fff 看方法2:fff优化版 方法3: 738.单调递增的数字 968.监控二叉树(贪心二叉树) 56. 合并区间 判断重叠区间问题,与452和435是一个套路 方法1:fff 看方法2&am…...

力扣 最长公共前缀-14
最长公共前缀-14 class Solution { public:string longestCommonPrefix(vector<string>& strs) {//定义一个字符数组,用于存储strs字符串数组第一个字符串,方便与后面的字符串进行比较判断char s[200];//定义一个字符数组,用来返回…...

IDEA调整警告级别【IntelliJ IDEA 2024.2.0.1】
文章目录 目前现状鼠标悬停,选择配置筛选 > 取消选择OK效果 目前现状 需要把提示改成只要显示error的5个 鼠标悬停,选择配置 筛选 > 取消选择 OK 效果...

Vulnhub靶场 Billu_b0x 练习
目录 0x00 准备0x01 主机信息收集0x02 站点信息收集0x03 漏洞查找与利用1. 文件包含2. SQL注入3. 文件上传4. 反弹shell5. 提权(思路1:ssh)6. 提权(思路2:内核)7. 补充 0x04 总结 0x00 准备 下载链接&#…...

Essential Cell Biology--Fifth Edition--Chapter one (6)
1.1.4.4 Internal Membranes Create Intracellular Compartments with Different Functions [细胞膜形成具有不同功能的细胞内隔室] 细胞核、线粒体和叶绿体并不是真核细胞中唯一的膜包围细胞器。细胞质中含有大量的[ a profusion of]其他细胞器,这些细胞器被单层膜…...

Jupyter Book 快捷键总结大全
快捷键完整分类与功能 1. 模式切换 在 jb 中,您可以通过快捷键快速切换编辑模式和命令模式: 快捷键功能Esc切换到命令模式Enter切换到编辑模式 2. 单元格操作 单元格是 jb 的基本操作单位,以下快捷键可以帮助您快速编辑和管理单元格&…...