news 2026/10/1 20:45:10

uni-app Android原生插件开发全攻略:从环境搭建到打包上架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uni-app Android原生插件开发全攻略:从环境搭建到打包上架

做 uni-app 项目的朋友应该都有过这种体验:JS 层逻辑写得好好的,一碰到蓝牙、NFC、身份证读卡器这类硬件能力,或者要接入某个只有原生 SDK 的厂商服务,瞬间就抓瞎了。我自己第一次在 uni-app 项目里对接一脸谱人脸识别 SDK 时,就卡了将近两周,后来老老实实把安卓原生插件这套东西啃下来,才算是把路走通。这篇博文就是把我摸索过程中踩过的坑、验证过的方案,以及最终沉淀下来的标准操作流程,一次性整理出来,给正准备入坑或者已经被坑得焦头烂额的同学一个完整参考。

原生插件这词听起来很玄乎,但本质上就是把安卓的 Java/Kotlin 代码包成一个 uni-app 能调用的模块。你平时在 HBuilderX 里写着 vue 文件,突然要调一个安卓独有的系统接口,这时候 JS 桥接层够不着,原生插件就派上用场了。它能帮你做的事远超想象:调用系统级 API、集成第三方商用 SDK、处理大数据量计算、甚至自己画原生 UI 组件再嵌进页面。只要你的 uni-app 项目跑在安卓端,这就是帮你突破 JS 能力边界的最直接武器。

我写这套内容前专门翻了翻最近大家在群里问得最多的问题,发现大多数人不是不想学,而是卡在“不知道从哪儿下手”。网上资料要么只讲了某一个环节,要么版本偏老,跟着做一遍根本跑不起来。所以这篇文章我会从开发环境搭建一直写到插件打包上架,全程按我正在用的这套稳定方案来,尽量做到让一个没写过原生代码的 uni-app 开发者,也能照着把插件跑通。

1. 原生插件到底是什么,以及你什么时候需要它

1.1 为什么 uni-app 应用会需要原生插件

uni-app 能一套代码跑三端,靠的是对 JS 层的封装,但封装是有边界的。你写uni.scanCode这类内置 API 时很爽,因为框架把原生实现帮你藏起来了;可一旦你想调用某个传感器、某个系统服务,或者集成第三方厂商的 SDK,框架就会告诉你“此 API 仅支持 App 端”。说得直白点,uni-app 的 JS 运行环境被关在一个盒子里,盒子的围墙能开多少窗户,取决于官方帮你封了多少原生能力。

以我实际项目为例,当时客户要求 App 内嵌入一个证件识别功能,供应商给的是带 so 库和 aar 的安卓 SDK,iOS 端也只有对应的 framework。这种商业 SDK 又不可能有人专门给你做 uni-app 适配,你没有别的选择,只能通过原生插件把这层桥搭起来。类似场景还包括:对接打印机、读取身份证、使用高精定位算法、播放特殊格式的音视频流,这些都是官方 API 覆盖不到的地方。

值得注意的是,不要一遇到问题就想着上原生插件。插件开发有维护成本,而且每次 HBuilderX 升级、安卓 API 变化,都有可能影响插件运行。能用uni-app内置 API 或现有市场插件解决的,优先用现成的;只有当需求已经明确超出 JS 层能力范围,或者性能表现完全无法接受时,才是你打开 Android Studio 写原生代码的最佳时机。

1.2 插件的三种形态:Module、Component 与 JS 插件

uni-app 安卓原生插件从承载方式上看,分三类。第一种是Module 插件,最常见也最实用,它只封装逻辑、不涉及 UI,你的 App 里某个页面某个按钮点击后,去调用一个原生方法,这个方法内部做了什么事,页面感知不到。蓝牙连接、数据加解密、通讯协议解析这类都适合做成 Module 类型,实现就是写一个继承自UniModule的 Java 类,暴露方法给 JS 层调用。

第二种是Component 插件,这种是用来提供原生控件的,比如一个自定义的扫码取景框、一个视频播放器内核,它直接作为页面里的一个组件来使用,写法上类似<module-file-system>。Component 插件开发难度比 Module 高一些,因为要处理组件生命周期、属性同步、事件回调,非必要不建议新手先碰。

第三种是JS 插件,这个严格来说不是原生插件,而是纯 JS 封装的公用代码模块,适合封装网络请求、工具函数等跨端统一逻辑,不需要原生参与。我在实际项目中通常把公共的 JS 工具、请求封装做成这类插件放到市场里,方便多个项目复用。

如果你是第一次接触 uni-app 原生插件,我的建议很直接:从 Module 类型开始,把环境、打包、调试这条路跑通,再考虑 Component。Module 插件的开发链路最短,能最快验证你的工具链是不是通的,也能帮你建立对 uni-app 原生通信机制的整体理解。

1.3 本地插件和云端插件的运行机制差异

插件开发分两套使用方式,一个是本地直接导入 HBuilderX 工程里用,一个是上传到 uni-app 插件市场再云打包时拉取。本地插件适合你个人或者公司内部项目自用,直接把 android 目录放到项目nativeplugins文件夹下就能识别;云端插件适合发布给其他开发者使用,你需要在插件市场完成上架,别人集成时在 manifest.json 里点选一下就能打包。

很多人第一次用本地插件时,改完原生代码重新运行自定义基座,发现怎么调都是旧逻辑,这就是没搞清楚运行机制。本地插件模式下的生效过程是:HBuilderX 在打自定义调试基座(自定义基座)时,会把nativeplugins下的插件代码一并编译进去,基座就是你的调试环境。所以只要你改了原生代码,必须重新制作自定义基座,等编译完成后基座下载安装到手机,然后再运行 uni-app 项目,新逻辑才会真正生效。这个流程搞不清楚,后面你连 debug 都会一头雾水。

云端插件则依赖插件市场的在线打包服务,你在 manifest.json 里勾选了某个付费或免费插件,云打包服务器会自动下载对应的 aar 或源码合并进 APK。好处是不用本地配环境,但相对的,调试时你不可能每次都走云打包,开发阶段建议先把插件做成本地形式,跑通了再上传到市场做云端发布。

2. 开发环境准备与工程搭建

2.1 开发工具和版本选择

做安卓原生插件开发,两样工具跑不掉:Android Studio和HBuilderX。Android Studio 负责编写和调试原生代码,HBuilderX 负责跑 uni-app 项目、制作自定义基座、最终打正式包。版本方面我个人的推荐配置是:Android Studio 用较新稳定版(我目前用的是 Electric Eel 之后的版本),Gradle 版本不用刻意去追最新,用模板默认的就行。

另外需要注意,uni-app 开发原生插件有专门的Android 离线 SDK,里面包含了 uni-app 的运行时库和一些示例工程。这里有个容易踩的坑:很多人直接去官网下载最新版离线 SDK,然后发现跟自己的 HBuilderX 版本不匹配,编译报一堆错。我的做法是在 HBuilderX 的帮助菜单里点“查看版本”,记下完整的版本号比如 3.99.xx,再去找对应版本的离线 SDK 下载。版本匹配这件事,比用什么 Android Studio 更重要,因为 uni-app 的运行时 API 是跟着主版本走的,不一致就是无解。

还有个小细节:Java 环境。新版 Android Studio 自带 JBR 没问题,但如果你在命令行里跑 Gradle 打包,确保JAVA_HOME指向 JDK 17 或以上。模板工程如果默认配了 JDK 8,你一定得手动改成 17,否则 Gradle 同步那一步就会卡住。

2.2 下载官方 uniplugin 模板工程

uni-app 官方提供了一个叫uniplugin_android的 GitHub 模板工程,这是目前最省事的起点。你不需要从零去创建安卓工程,直接 clone 下来改一改就能用。这个模板里已经写好了 Module 和 Component 的示例代码,还配好了打包脚本和 manifest 配置,你只需要在此基础上添加自己的类和方法。

模板的核心目录结构是这样的:

  • app目录:示例 app,可以单独运行起来调试插件
  • uniplugin_module:Module 插件示例代码所在 module
  • uniplugin_component:Component 插件示例代码
  • libs目录:放置你自己要集成的 aar、jar、so 文件

克隆下来之后,先用 Android Studio 打开,等待 Gradle 同步完成,跑一遍示例 app,确认环境没问题再做修改。如果同步失败,优先检查 Gradle 版本和网络,另外把compileSdk和targetSdk版本调到你手机上能支持的范围。

2.3 创建独立的插件 Module

我第一次做插件时犯过一个大错:直接在主 app 里写插件代码,结果打自定义基座时怎么都不识别。正确的做法是,在 Android Studio 里创建一个独立的Android Library Module,让它承载插件代码,编译产物是 aar,这样 HBuilderX 才能按约定的格式识别和集成。

在模板工程里,uniplugin_module就是一个现成的 Library Module,它的build.gradle开头是com.android.library,并且没有任何 applicationId。如果你要新建自己的 Module,照着它的样子复制一个再改名就行。就算你只有一个插件类,也建议拆成独立 Module,以后插件多了、相互依赖了,这个结构能帮你省很多事。

配置好之后,在 Module 的src/main/assets下创建dcloud_uniplugins.json文件,这个文件就是插件注册表,uni-app 运行时就是靠读它来把 JS 调用映射到原生类上的。格式大致是:

{ "nativePlugins": [ { "plugins": [ { "type": "module", "name": "MyPlugin", "class": "com.example.myplugin.MyPluginModule" } ] } ] }

这里的name就是你在 JS 层uni.requireNativePlugin('MyPlugin')时传入的名字,一定要保持一致。class是完整的类路径,包名写错一个字符,运行时就报找不到插件。

2.4 把 Module 集成到 HBuilderX 工程

原生代码这边准备好之后,回到 HBuilderX,在你项目的根目录下创建nativeplugins文件夹,里面再建一个以插件名命名的子目录(比如MyPlugin),然后把你在 Android Studio 里写好的源码、libs、以及dcloud_uniplugins.json按照官方要求的目录结构放进去。

结构大概长这样:

nativeplugins/ └── MyPlugin/ ├── android/ │ ├── libs/ │ ├── src/ │ └── build.gradle └── package.json

package.json里填插件的基本信息,包括插件标识、名称、版本号、插件类型、平台标识等。注意这个标识在 HBuilderX 里显示用,不能跟其他插件重名。我遇到过一次因为 package.json 格式写错,HBuilderX 直接拒绝识别插件,报错信息还特别隐晦,后来才排查到是 JSON 里多个逗号。

做完这些,在 HBuilderX 的 manifest.json 里切到“App 原生插件配置”,点“本地插件”,就能看到你刚放进来的插件了。勾选启用,然后重新制作自定义基座,集成就算完成。

3. 核心开发流程与实操实现

3.1 编写 UniModule 子类与核心注解

插件的核心就是你写的那个继承UniModule的 Java 类。你在这个类里写的方法,就是 JS 层能直接调用的原生接口。模板的UniModule类自带了一个onCreate生命周期,这里可以做一些插件初始化的工作,比如获取上下文、初始化 SDK 实例。

方法定义有几个关键约束,第一,方法必须是 public 的;第二,必须用@UniJSMethod注解标注,运行时才能识别;第三,方法必须接收一个UniJSCallback参数,用来把结果异步回传给 JS。下面是一个简洁的示例:

public class MyPluginModule extends UniModule { private static final String TAG = "MyPluginModule"; @UniJSMethod(uiThread = false) public void doSomething(String param, UniJSCallback callback) { JSONObject result = new JSONObject(); try { result.put("code", 0); result.put("data", "Hello from native, param=" + param); callback.invoke(result); } catch (Exception e) { result.put("code", -1); result.put("error", e.getMessage()); callback.invoke(result); } } }

也许你注意到了uiThread = false这个设置,它表示这个方法会跑在非 UI 线程上。如果你的原生逻辑是耗时操作,比如网络请求、文件读取,建议设成 false 避免阻塞主线程;如果只是简单的数据查询、页面跳转、Toast 提示,设成 true 更省事。需要注意的是,如果指定了uiThread = false,你在方法里操作 UI 就必须要切回主线程,否则会崩溃。

3.2 JS 层调用插件方法与参数传递细节

代码写完之后,在 uni-app 的 vue 页面里调用。第一步是用uni.requireNativePlugin('MyPlugin')拿到插件实例,注意这一步要在插件准备好之后做,也就是页面onLoad或之后,别在全局声明时直接调用,否则可能拿到空值。

const plugin = uni.requireNativePlugin('MyPlugin') plugin.doSomething('hello', (res) => { console.log('result = ' + JSON.stringify(res)) })

参数传递这块有几个容易踩的坑。原生方法如果接收 String 参数,直接传就行;接收 int、float 也没问题,uniapp 会帮你做类型转换;但如果要传复杂对象,不能直接传一个 JS 对象,JS 那边的对象到原生这边会变成一个 JSONObject,原生这边方法签名最好写JSONObject或JSONArray,用optString、optInt这类安全取值方法,不要用getString直接拿,因为 key 不存在时会抛异常而不是返回空值。

回调方面,UniJSCallback的invoke方法可以多次调用。每次调用 JS 这边的回调函数都会收到一次消息。利用这个特性,你可以实现进度上报、原生日志输出到页面等场景。但要注意,多次回调是有性能开销的,别拿它当高频数据通道,如果每秒要推几十次数据给 JS,建议走事件通道而不是回调。

3.3 事件发送:让原生主动通知 JS 层

有一种场景是原生在后台收到了系统广播、长连接消息,这时候需要主动把数据推给 JS 层,而不是等 JS 来调用。为此,uni-app 给插件模块准备了事件机制。在 Module 里可以用mUniSDKInstance.fireGlobalEventCallback向上层广播事件。

mUniSDKInstance.fireGlobalEventCallback("onMessageReceived", jsonObject);

JS 层用uni.$on('onMessageReceived', callback)监听即可。这里要注意事件名的全局唯一性,因为你不知道项目里还有没有别的插件用了同样的名字,最好加上插件前缀,比如myplugin_onMessage。另外记得在页面onUnload或组件销毁时调用uni.$off取消监听,避免内存泄漏和重复回调。

事件机制还有一个妙用:用它把大型数据从原生分片传给 JS。比如一个几兆的日志文件,一次 callback 传过大的 JSON 会导致页面卡顿甚至内存溢出,切成小块用事件循环推,页面就不卡了。

3.4 自定义调试基座与真机运行流程

写完代码不是直接跑 uni-app 就能生效的,前面说过,必须重新制作自定义基座。流程很简单,HBuilderX 菜单栏点“运行”->“运行到手机或模拟器”->“制作自定义调试基座”,等编译结束后就变成了一个带原生插件的 App。

基座制作完成后,在手机和电脑用数据线连好,确保手机开启 USB 调试,然后选择“运行到手机或模拟器”里刚才制作的那个自定义基座。这时候启动的是打包好原生代码的调试壳子,才能真正调用到你的插件。

调试时有个很有用的技巧:在原生代码的各个关键节点打上日志,然后用 Android Studio 的 Logcat 查看。HBuilderX 控制台只能看到前端 JS 的报错,原生层的异常很多时候是静默崩溃。记得先通过adb把设备连上 Android Studio,再运行基座,这样 Logcat 里能实时看到插件打出来的所有日志。

3.5 开发环境在 Android Studio 里的调试方法

原生插件有个比较麻烦的地方,uni-app 项目的调试实际上分两层。前端层调试可以用 HBuilderX 的调试工具,原生层的断点调试需要一点额外手段。我常用的场景是,单独把 Android Studio 工程跑起来,应用初始化时加载一个测试页面,直接在这个测试页面里调用插件方法,这样能在原生代码里打断点、看变量、查调用栈,效率比在黑盒里猜高得多。

具体做法是,模板工程里本身带了一个示例 app,你可以在这个 app 的启动 Activity 里写几行代码模拟 uni-app 的调用逻辑,比如直接创建MyPluginModule实例并调用doSomething方法,然后正常 debug。这个方法不涉及真机上的 uni-app 框架,主要在原生上下文里验证 SD 卡路径、SDK 初始化、so 库加载等容易出问题的环节。

如果你非要边跑 uni-app 边断点,也可以稍微绕一下:在代码里用android.os.Debug.waitForDebugger(),然后以调试模式启动基座。但这种方式经常不稳定,不如我前面说的“独立调试”来得顺手。经验之谈,原生逻辑复杂时,先脱离 uni-app 环境跑通一遍,再回到集成环境验证。

4. 插件打包、签名与发布上架

4.1 本地插件打包成 aar 的两种方式

插件开发完成后,你最终要面对两件事,一是自己项目里要打正式包,二是上传到插件市场让别人用。如果你只是自己项目用,最简单的方式就是把 Module 的源码直接放在 HBuilderX 的nativeplugins目录,云打包时服务器会自动编译集成,你不用手动出 aar。

但如果你要上传到插件市场,或者公司内网有一个人维护插件、多个项目使用,这时候就需要把插件 Module 导出成 aar 文件交给使用者。导出方式有两种,一种是在 Android Studio 里选中你的 Module,执行 Gradle 面板里的assembleRelease任务,然后在build/outputs/aar目录下找到产出的 aar;另一种是用命令行,在gradlew所在目录执行:

./gradlew :uniplugin_module:assembleRelease

生成 aar 之后,把它连同依赖的第三方 aar、jar、so 一起按照官方要求的目录放到插件的android/libs下。这里有个血泪教训:如果 Module 依赖了另外一个本地 Library Module,你光导出当前 Module 的 aar 是不够的,还要把依赖的 Library 也一起导出,并且手动关联到插件工程里,否则别人集成时会发现一堆类找不到。

4.2 插件市场发布流程与审核要点

上传插件市场之前,你需要在插件市场后台用开发者账号登录,创建一个新插件,填写插件标识、名称、简介、版本号等信息,然后上传一个 zip 包。这个 zip 包的内容就是你本地nativeplugins里那个插件目录的完整复制,确保压缩包的根目录直接就是android、package.json等。

审核阶段最容易被驳回的几个问题我帮你提前避掉。第一,包名不能和 uni-app 官方内置模块冲突;第二,插件内不能存在明显的调试日志输出,尤其是不能把密钥、token 打出来;第三,如果插件涉及隐私权限(定位、相机、读取设备信息),必须要在简介和隐私协议里说明用途;第四,不能包含任何诱导用户前往第三方下载链接的代码。

关于公共插件和私有插件,上传市场时可以设置。公共插件所有人可见可下载,一般配合收费或免费使用;私有插件只有你指定的小组或账号可见,适合公司内部多项目共享。我的建议是,公司内部的项目插件走私有,除非你要对外输出技术能力,否则没必要开源出来给自己找维护负担。

4.3 云端打包与 DCloud 证书签名配置

在 HBuilderX 里点“发行”->“原生 App 云打包”,会要求你配置安卓证书。证书的生成方式网上很多,但我要提醒的是公私钥库之间的关系。云端打包时你只需要把 keystore 文件、别名、两个密码配置好,DCloud 的服务器会用你的证书对 APK 进行正式签名。

这里有个很关键的坑:如果你的 App 之前已经用某个证书签名上架过,后续的所有版本必须用同一把证书签名,否则 Android 系统会认为这是两个不同的 App,用户无法覆盖安装,应用商店也无法更新。我见过不止一个项目因为证书丢失,被迫换包名重新上架的惨案。所以拿到证书后,至少备份三份,一份本地磁盘、一份公司服务器、一份加密网盘。

如果你用的插件包含原生代码,云打包时务必选择“使用云端证书”并配置好证书信息,不要用默认测试证书。测试证书打出来的包也能装,但一上架就会被市场拒绝,而且没法和正式证书互相覆盖升级。

4.4 离线打包场景下如何集成原生插件

有些企业客户要求必须在本地出包,不能把代码交给云端,这时就要走离线打包路线。离线打包的意思是你在本地用 Android Studio 打开官方离线 SDK,把自己的 uni-app 前端资源包(wgt 或 apk 资源)塞进去,配合原生插件代码一起编译出 APK。

离线打包时的原生插件集成方式和 HBuilderX 云打包略有区别。你需要手动在 App 工程的assets目录下配置dcloud_uniplugins.json,同时把插件的 aar 或源码作为依赖引入 App 工程,并在 App 的build.gradle里声明依赖关系。别少看这一步,漏了 json 配置,插件就像没装一样,JS 那端调了直接报 “plugin not found”。

离线打包对开发者的安卓工程能力要求更高,你要自己管理 Gradle 依赖、so 库适配、混淆规则,但换来的是打包流程完全可控,适合对包体大小、签名、渠道包数量有严格要求的场景。

5. 常见问题、踩坑记录与效率提升技巧

5.1 高频报错信息与处理对照

插件的报错信息往往很简短,但背后原因千差万别。我把这两年碰到的高频错误汇总成了表格,方便你按图索骥排查。

报错信息可能原因处理方式
plugin not founddcloud_uniplugins.json 配置错误或未生效检查 json 格式、类路径、插件名称是否一致
ModuleNotFoundError插件的 Module 没有编译进基座重新制作自定义基座,确认勾选了插件
ClassNotFoundExceptionaar 未正确引入,或依赖 Library 缺失检查 libs 目录、build.gradle 依赖
UnsatisfiedLinkErrorso 库缺失或架构不匹配补齐 armeabi-v7a、arm64-v8a 对应的 so
The callback is not exist回调创建时机不对或已销毁避免在页面关闭后再调用 callback
java.lang.SecurityException缺少动态权限或权限未申请在原生代码中用 requestPermissions 动态申请
JSONException参数 key 不存在或类型转换失败原生用 optXxx 方法,JS 端传 JSON 对象时要 stringify

这张表不是万能的,但覆盖了八成新手接插件时的报错情况。遇到任何问题,先别着急改代码,打开 Logcat 定位到具体异常堆栈,再对照表里的方向排查,效率会高很多。

5.2 架构、so 库与混淆相关的经典大坑

安卓设备 CPU 架构主要分 armeabi-v7a 和 arm64-v8a,现在的 64 位手机如果只放了 32 位 so,运行时会报Unable to load library。解决方式是在插件 Module 的build.gradle里用ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }指定需要的架构,同时确保 so 文件都放在了src/main/jniLibs/armeabi-v7a和src/main/jniLibs/arm64-v8a对应的目录下。

另外一个隐蔽问题是混淆。如果你在打正式 release 包时开了混淆,而插件类混淆后被改了名字,uni-app 运行时按原类名找就找不到了。所以插件 Module 的混淆规则必须加上一到两条 keep:

-keep class com.example.myplugin.** { *; }

同时还要 keep 住所有实现UniModule、UniComponent的子类,以及dcloud_uniplugins.json里声明的类。这不是细节,这是 release 包能不能跑的生死线。

5.3 内存、线程与回调安全的实操建议

原生插件和 JS 进行大量数据交互时,最常见的问题就是 UI 卡顿和内存飙升。我写过一次很蠢的代码,在uiThread = false的方法里循环几千次调用callback.invoke,结果页面直接卡死。后来改成用事件通道分片推送,并在每批之间做 10 毫秒延时,问题才缓解。

另一个安全问题是,在异步线程里回调 JS 后,组件可能已经被销毁,再操作就会崩溃。处理方式是在原生代码里判断mUniSDKInstance是否仍然有效,或者捕获IllegalStateException,别让异常向上抛。前端那边也尽量做到组件销毁时uni.$off清理监听。

多线程改写还有个要点,如果你用了 Executor 或子线程,一定要保证callback.invoke是在同一个线程里按顺序调用。市面上一些网上找的代码直接用多个线程并发调同一个 callback,结果前端拿到数据的顺序是乱的,逻辑立刻出错。

5.4 我的开发效率提速心得

最后分享几个我这两年用下来特别提效的小习惯。第一,单独维护一个“插件调试专用 uni-app 项目”,项目里只有测试页面,用来集中验证所有插件的边界情况,不用把插件塞进正式的大项目里反复打包,节省大量时间。

第二,原生代码尽量做日志开关,用一个静态变量控制日志输出级别,开发时全量打,发正式版时一键关掉。这样既不影响调试,又不会把冗余日志带到线上包。

第三,插件里的业务逻辑要尽量薄,尽量把数据处理放在前端,原生只做“能力提供”。这看起来多了一层通信开销,但维护和排错会舒服很多。实践下来,插件越薄,bug 越少。

第四,每次更新插件版本的时候,同步维护一个 CHANGELOG,哪怕只是两三行。因为插件的使用方往往不止你一个人,别人更新后出了问题,看变更记录能少吵很多架。

结语:一点实际的建议

原生插件这条路,说穿了就是给 uni-app 装上第三只手的活儿。刚开始你会觉得配置繁琐、坑很多,但把模板工程和打包流程吃透之后,再开发第二个插件,速度会快得超乎你想象。我个人比较推荐的成长路径是:先拿一个 Module 类型的小功能练手,比如让原生返回一个设备唯一标识,从环境搭建到打出正式包完整走一遍;然后尝试接入一个真实的第三方 SDK,感受一下 aar 依赖和混淆的威力;最后再碰 Component 类型,去做一个自定义的原生 UI 控件。每一步都走扎实了,你在 uni-app 项目里的“能力天花板”就算彻底打开了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 20:44:07

Streama私人家用视频站搭建指南:Ubuntu+Java8+systemd全栈部署

1. 项目概述&#xff1a;为什么一个“私人家用视频网站”值得花三小时认真搭一次Streama 是我过去三年里反复重装、迁移、优化过至少七次的个人媒体服务。它不是 Plex 那种开箱即用的商业方案&#xff0c;也不是 Jellyfin 那样功能堆叠到需要查文档才能调出字幕设置的全栈平台—…

作者头像 李华
网站建设 2026/10/1 20:42:58

python快手批量采集(二):用 pcursor 接口分页拉取与断点续采实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 20:42:14

MyBatis缓存机制完全解析:一级缓存、二级缓存原理与实战排坑

最近收到不少读者关于MyBatis缓存的私信&#xff0c;问的内容几乎能拼出一套完整的高频面试题&#xff1a;一级缓存和二级缓存到底什么区别&#xff1f;为什么SpringBoot项目里连续调用两次查询&#xff0c;SQL却照样打印两次&#xff1f;为什么开了二级缓存反而读到脏数据&…

作者头像 李华
网站建设 2026/10/1 20:39:14

Spring Boot文件上传cleanup失败原因与解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华