uniapp 安卓离线打包手把手教程
2026/8/1 8:17:15 网站建设 项目流程

前言

困于uniapp云打包的排队机制,时间就是生命,本着狗命的原则,我总结了一套uniapp的离线打包方案,期间也是踩坑无数,处处碰壁,艰难程度堪比西天取经了,好在最后修成正果。后续我还会出安装离线打自定义基座以及包含UTS插件的离线打包与离线基座的方法,但是都是依据离线打包的配置进行变更。

前期准备

  1. 首先确定我们HBuilderX编辑器的版本,这里采用5.0.4的版本进行距离说明,注意这个版本十分重要,因为后续离线打包使用的SDK版本需要完全对齐。当前最新版本已经来到5.15,如果是新项目推荐使更新到最新版本。
  2. 接着我们创建一个安卓APP项目,由于我们主要是为了体验离线打包,所以随便建立一个项目即可
  3. 接着我们去到官方的 离线SDK下载地址,这里不得不吐槽一下,官方把历史SDK都挂在了百度网盘上,百度网盘这个下载速度,你不开会员你就等吧,一等一个不吱声。推荐大家去咸鱼开个一小时极速下载。

打包资料准备

这里为了清晰给兄弟们演示就不打码了,一些APP证书等信息兄弟们自己打包要保护好。
接着我们需要准备我们打包所需的密钥,证书等,这里我直接使用uniapp的云端证书,一整套他都给你备齐了,按照我的方法收集即可!

  1. 首先进入开发者后台,进入到我们需要离线打包的应用里面

  2. 在Android云端证书里面,先保存appid,注意这是大写版的,然后点击创建证书。证书生成大概需要几秒钟,自己刷几次页面就差不多了

  3. 点击进入到证书详情,收集里面的别名,MD5,SHA1,SHA256,并点击查看密码收集证书密码,,最后直接下载证书,下载的证书文件我们一般重命名为app.keystore

  4. 切换到各平台信息,新增Android App,输入包名(这里的包名也比较主要,不要随便取,我们这里取名com.auto.pack),将前面我们收集的MD5,SHA1,SHA256信息一一填入,最后点击提交就会出现一条我们创建的应用信息

  5. 点击创建离线key,收集离线key

到了这里我们的离线打包材料收集的就差不多了,现在我们的材料应该有

名称内容
证书别名__uni__c7deb4a
AppID__UNI__C7DEB4A
证书密码YX5xZeo1
离线keybe2b072d1cb0ff0e433a9698227e946d
证书文件app.keystore

配置SDK文件进行打包

  1. 首先将我们之前下载的SDK文件解压,进入其中你会看到一个HBuilder-Integrate-AS文件,这个文件就是我们用来打包的主文件,点击进入里面还有一个simpleDemo文件,这个文件就是存放我们APP内容的文件。我们将HBuilder-Integrate-AS直接复制进我们auto-pack项目的根目录。

  2. 接着我们更改指定文件的文件名,一般我们会将HBuilder-Integrate-AS修改为android,里面的simpleDemo修改为app。当前目录结构如下

  3. 接着我们删除android文件下的HBuilder-Integrate-AS.iml以及app文件下的simpleDemo.iml,这些属于 Android Studio的模块配置文件。删除adnroid/app/src/main/assets/apps下的__UNI__A,修改android文件下的settings.gradle文件,将include ':simpleDemo'改为include ':app',接着将adnroid/app/src/main/res/values/strings.xml里面的app_name修改为自己的app名称,注意这里是app名称,也就是用户下载展示的名称,不是我们之前定义的包名com.auto.pack,这里我们取名为auto-pack。

  4. 标记一下,从这里开始可以直接使用自动化打包,见下文我们在编辑器上选择发行,选择Android/iOS-本地打包,点击生成本地打包APP资源,生成完成后会有一个unpackage文件夹,里面的resources文件下面会有一个以AppID为名称的问价夹

  5. 将该文件夹复制到 android/app/src/main/assets/apps下替换掉原有的__UNI__A

  6. 将android/app/src/main/assets/dada/dcloud_control.xml里面的appid修改为我们自己的appid

  7. 将android/app/src/main/AndroidManifest.xml里面的dcloud_appkey内容修改为自己的离线key

  8. 将我们之前保存的app.keystore复制到android/app文件夹下面。 删除他之前的test.jks。

  9. 修改android/app/build.gradle的配置外链

  10. 打开Android Studio,将我们整个android文件夹导入进去

  11. 选择顶部导航的构建,点击Generate Signed App Bundle or APK, 在弹出的弹框里面选择APP点击NUXT; 再次输入你的别名,密码以及选择你的证书文件;最后选择release进行打包

  12. 等待右下角打包进度完成,这个时候你的 android/app 目录下就出现了一个release文件夹,下面的app-release.apk就是你本次打包出来的apk

  13. 最后我们安装app并运行

到这里我们就算真正的完成的一次离线打包的操作,步骤看着繁琐,其实也是真的不简单☺,但是好在很多配置只要你配置一次之后,后续打包都不需要再修改,只要重新生成本地打包资源然后替换掉android/app/src/main/assets/app文件夹下面的内容即可。

创建自动化打包

我们总览上面的操作就可以发现,大部分操作都是对文件的移动与修改,真正需要我们在程序上手动操作的其实只有两个步骤,一个就是在HBuilderX上生成本地打包APP资源,一个就是在Android Studio上点击打包的操作。所幸这两部分操作也有对应的cli命令行工具完成。

pack文件准备
  1. 项目根目录创建一个pack文件夹,专门存放我们的打包文件;
  2. 在pack下创建index.js作为入口文件;
  3. 在pack下创建commom.js文件作为公共方法文件,存储一些公共方法以及我们的证书别名,打包密码等,注意这些敏感信息存在这里风险极大,本文只是为了方便展示所以统一写在这里了;
  4. 创建buildPack.js文件存储打包方法;
  5. 为了方便将我们的证书文件app.keystore也放在pack文件下。
准备common.js文件

这个文件用来准备一些公共变量与方法,在真实项目中,一些敏感信息最好不要直接暴露

import{fileURLToPath}from'url';import{execFileSync,spawn}from'child_process';importpathfrom'path';importfsfrom'fs';// 创建包名,别名等常量exportconst__dirname=path.dirname(fileURLToPath(import.meta.url));exportconstpackageName='com.auto.pack'exportconstdcloudAppKey='be2b072d1cb0ff0e433a9698227e946d'exportconstkeyAlias='__uni__c7deb4a'exportconstAppID='__UNI__C7DEB4A'exportconstkeyPassword='YX5xZeo1'// 相对路径转为绝对路径exportconstresolve=(url)=>{returnpath.resolve(__dirname,url);};/** * 找到路径下appId命名的文件名称 * @param root 根路径 */exportconstgetUniAppIdDirName=(root)=>{if(!fs.existsSync(root)){thrownewError(`未找到资源目录:${root}`);}constentries=fs.readdirSync(root,{withFileTypes:true})constmatchDir=entries.find(entry=>{returnentry.isDirectory()&&entry.name.startsWith('__UNI__')})if(!matchDir){thrownewError(`未找到 __uni__ 开头文件夹,目录:${root}`)}returnmatchDir.name}/** * 复制文件或文件夹到目标路径 * @param sourcePath 需要复制的文件或文件夹路径 * @param targetPath 目标文件或文件夹路径 * @param isClean 是否先删除目标路径下的文件 */exportfunctioncopyDirOrFile(sourcePath,targetPath,isClean=true){try{constsourceIsExist=fs.existsSync(sourcePath)consttargetIsExist=fs.existsSync(targetPath)if(!sourceIsExist){thrownewError(`源路径或源文件不存在:${sourcePath}`)}if(isClean&&targetIsExist){fs.rmSync(targetPath,{recursive:true,force:true});}consttargetDir=path.dirname(targetPath);fs.mkdirSync(targetDir,{recursive:true})fs.cpSync(sourcePath,targetPath,{recursive:true,force:true})console.log('✅ 文件复制成功');}catch(error){console.error('❌ 文件复制失败:',error);throwerror;}}/** * editFileContent 修改文件内容 * @param {string} path - 文件路径 * @param {Array} editConfigList - 编辑配置列表,每个元素为一个对象,包含regex和newContent属性, * regex为正则表达式,用于匹配要替换的内容 * newContent新内容, */exportconsteditFileContent=(path,editConfigList)=>{try{letcontent=fs.readFileSync(path,'utf-8');editConfigList.forEach(config=>{content=content.replace(config.regex,config.newContent);})fs.writeFileSync(path,content);console.log(`✅ 文件内容修改完成:${path}`);}catch(error){console.error('❌ 文件内容修改失败:',error);throwerror;}}// 读取json文件的对象exportconstreadVersionInfo=(path)=>{try{constcontent=fs.readFileSync(path,'utf-8');constmanifestInfo=JSON.parse(content);returnmanifestInfo.version;}catch(error){console.error('❌ 读取json文件失败:',error);throwerror;}}// 进行APP资源打包exportasyncfunctionbuildWgt(){console.log('开始打包wgt');// 这里读取的是我们配置的环境变量,也就是HBuilderX的CLI路径, 我的就是D:\soft\HBuilderX\cli.execonstenvCliPath=process.env.HBUILDERX_CLI_PATH;if(!envCliPath){thrownewError('请配置HBUILDERX_CLI_PATH环境变量');}// 必须要打开HBuilderX程序,才能执行他的命令行工具execFileSync(envCliPath,['open'],{stdio:'inherit'});// 获取HBX版本,增加异常捕获letcliVersion;try{constverBuf=execFileSync(envCliPath,['ver'],{stdio:['ignore','pipe','pipe']});cliVersion=verBuf.toString().replace(/[\r\n]/g,'');console.log('当前HBuilderX版本:',cliVersion);}catch(err){thrownewError(`获取HBuilderX版本失败,请检查cli路径:${envCliPath},${err.message}`);}// 打包命令参数constspawnArgs=['publish','app-android','--type','appResource','--project',resolve('..'),];constchild=spawn(envCliPath,spawnArgs,{shell:true,// Windows下增强兼容性});console.log('创建子进程进行wgt打包');awaitnewPromise((resolve,reject)=>{// 正常输出日志child.stdout.on('data',(data)=>{console.log('[HBX CLI]',data.toString());});// 捕获错误日志(警告、异常信息)child.stderr.on('data',(data)=>{console.error('[HBX CLI ERR]',data.toString());});// 进程启动失败(找不到exe、权限不足)child.on('error',(err)=>{reject(newError(`启动cli进程失败:${err.message}`));});// 进程退出child.on('close',(code)=>{if(code!==0){reject(newError(`WGT打包失败,进程退出码:${code}`));}else{console.log('✅ wgt打包完成');resolve();}});});}
准备buildPack.js文件

该文件是用来修改SDK配置以及Andorid Studio的打包命令

import{execSync}from'child_process'import{buildWgt}from'./common.js'import{copyDirOrFile,resolve,dcloudAppKey,editFileContent,keyAlias,keyPassword,packageName,AppID,readVersionInfo}from'./common.js'constbuildPack=async()=>{try{// 1. 装备APP离线打包资源awaitbuildWgt();// 2. 复制资源到目标文件夹awaitcopyDirOrFile(resolve(`../unpackage/resources/${AppID}`),resolve(`../android/app/src/main/assets/apps/${AppID}`),)// 复制证书密码到指定位置awaitcopyDirOrFile(resolve(`./app.keystore`),resolve(`../android/app/app.keystore`),)// 3.动态修改AndroidManifest.xmleditFileContent(resolve('../android/app/src/main/AndroidManifest.xml'),[{regex:/meta-data\s+android:name="dcloud_appkey"\s+android:value=".*?"/,newContent:`meta-data android:name="dcloud_appkey" android:value="${dcloudAppKey}"`},])// 4.动态修改build.gradle// 读取配置的版本信息constversionInfo=readVersionInfo(resolve(`../unpackage/resources/${AppID}/www/manifest.json`))constversionCode=versionInfo.codeconstversionName=versionInfo.name// 5.动态修改build.gradleeditFileContent(resolve('../android/app/build.gradle'),[{regex:/versionCode \d+/,newContent:`versionCode${versionCode}`,},{regex:/versionName "(.*?)"/,newContent:`versionName "${versionName}"`,},{regex:/applicationId "(.*?)"/,newContent:`applicationId "${packageName}"`,},{regex:/namespace '(.*?)'/,newContent:`namespace '${packageName}'`,},{regex:/keyAlias '(.*?)'/,newContent:`keyAlias '${keyAlias}'`,},{regex:/keyPassword '(.*?)'/,newContent:`keyPassword '${keyPassword}'`,},{regex:/storePassword '(.*?)'/,newContent:`storePassword '${keyPassword}'`,},{regex:/storeFile file$'.*?'$/,newContent:`storeFile file('app.keystore')`,},])// 6.动态修改dcloud_control.xmleditFileContent(resolve('../android/app/src/main/assets/data/dcloud_control.xml'),[{regex:/<hbuilder[\s\S]*?<\/hbuilder>/g,newContent:`<hbuilder> <apps><app appid="${AppID}"/></apps> </hbuilder>`,},])process.chdir(resolve('../android'))execSync(`gradlew :app:assembleRelease`,{stdio:'inherit',})// 移动apk到指定位置constdate=Date.now()awaitcopyDirOrFile(resolve(`../android/app/build/outputs/apk/release/app-release.apk`),resolve(`../unpackage/release/apk/app-release_${date}.apk`),)}catch(error){console.error('打包失败:',error);process.exit(1);}}exportdefaultbuildPack
准备index.js 入口文件
importbuildPackfrom'./buildPack.js'buildPack()
配置打包命令

在项目里面的package.json文件配置打包命令,没有则npm init -y创建

{"name":"auto-pack","version":"1.0.0","main":"main.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","pack":"node ./pack/index.js"// 创建pack命令},"keywords":[],"author":"","license":"ISC","description":""}

最后直接npm run pack即可打包完成!

虽然内容看着比较多,但是关键步骤其实很少,出问题比较多的就是收集证书信息以及修改SDK配置上,所以大家细心点问题应该都不大。如果实在有问题也可以WX交流沟通。 fjl_code_life

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询