Harmonyos Next Multi-Environment + چند کانال + خروجی مسیر سفارشی + نام سفارشی برنامه یک کلیک و بسته HAP

مقدمه
هنگام انجام توسعه موبایل ، به ناچار با سناریوهای زیر روبرو خواهید شد
-
تست: یک بسته آزمایشی به من بدهید ، من می خواهم یک عملکرد را آزمایش کنم ، اگر بیشتر مضطرب هستید ، لطفاً این کار را سریع انجام دهید
-
محصول: من یک سوال رسمی را به من داد.
-
رهبر تیم: آیا این بسته آزمایشی یا بسته رسمی در این دستگاه تست نصب شده است؟
-
محصول: آخرین بسته بندی محیط XX را برای من نصب کنید ، می خواهم از آن برای تظاهرات استفاده کنم ، فوری تر است
-
عملیات: شماره نسخه این بسته ای که برای من ارسال کرده اید چیست؟ کی آن را زدی؟ آیا این آخرین است؟ همه چیز فوری است ، لطفاً تأیید کنید
-
تست: چرا این اشکال در این نسخه برطرف نشده است؟
(در نتیجه ، شما مدت طولانی جستجو کردید و دریافتید که نسخه موجود در تستر حاوی کد تعمیر نشده است)
من نمی دانم چه زمانی بسته در آزمایش قرار داده شده است ، و شما نمی دانید چه زمانی برنامه در بسته قرار داده شده است. -
محصول: دلایل تقاضای کسب و کار ، شما باید کیسه های جلیقه XX را روی قفسه ها قرار دهید و نیازی به تغییر تجارت اصلی نیست.
فقط صفحه UI ، نام برنامه ، نماد برنامه را تغییر دهید -
شما: برای مقابله با سناریوی فوق ، باید بعد از اتمام ساخت بسته ، نام را به صورت دستی تغییر داده و یک شماره و نشانگر را اضافه کنید و آن را در یک فهرست کپی کنید
سناریوهای فوق در شرکتهای کوچک و متوسط بسیار رایج است.
اول ، ارائه ها
- قوانین نامگذاری پرونده بسته: شماره نسخه + نام سفارشی + محصول + روز ماه ، ساعت ، دقیقه و دوم.
- قوانین نامگذاری پرونده بسته بندی HAP: شماره نسخه + نام سفارشی + محصول + هدف + روز ماه + ساعت ، دقیقه و دوم.
کد منبع در پایان مقاله
در مرحله اولیه پروژه ، ثابت ها به طور کلی در یک کلاس خاص در پروژه تعریف می شوند تا بین محیط های رسمی یا محیط های آزمایش تمایز قائل شوند.
قبل از هر بسته ، مقدار ثابت را به صورت دستی اصلاح کرده و در آخر بسته محیط مربوطه را کامپایل کنید.
در شرایط عادی ، این عمل یک مشکل بزرگ نیست ، صرفه جویی در وقت ساده و آسان است
اما وضعیت واقعی این است که وقتی شما یک بسته رسمی را تهیه می کنید ، اتفاقات زیادی برای انجام آن دارید ، همیشه با موقعیت هایی روبرو خواهید شد که فراموش می کنید محیط را به عقب تغییر دهید ، که منجر به تبدیل آن به یک بسته آزمایش می شود.
یا شاید سایر همکاران به اشکال زدایی در محیط آزمایش تغییر کنند و به کد تجاری توجه نکردند و آن را ارسال کردند.
محیط محلی شما یک محیط رسمی است و پس از تکمیل بسته ، کد از راه دور را دریافت می کنید.
اگر با بسته ها مشکلی دارید ، می توانید در بخش نظرات پیام بگذارید تا ببینید که تجربه آنها جادویی تر است
من ابتدا این کار را می کنم ، من به طور تصادفی یک بسته آزمایشی ارسال کردم و آن را بصورت آنلاین ارسال کردم
در آن زمان ، کارهای زیادی برای انجام دادن وجود داشت.
در طول دوره تدوین ، من مشغول تنظیمات دیگر بودم و چندین تنظیم در محیط های مختلف وجود داشت.
بسته رسمی گردآوری شده شامل برخی از تنظیمات برای محیط آزمایش است
ابتدا اهداف و مشخصات بسته بندی را روشن کنید
- هر بار که بسته ای از یک محیط متفاوت را تهیه می کنید ، نیازی به تغییر دستی پیکربندی نیست.
- پس از اتمام تدوین ، بسته برنامه (یا بسته HAP) به صورت خودکار تغییر نام داده می شود ، با شماره نسخه ، محیط مربوطه را می توان با نام و نقطه زمان بسته بندی متمایز کرد
- پس از اتمام تدوین ، پرونده بسته به طور خودکار در یک فهرست کپی می شود.
- پارامترهای پیکربندی پروژه به طور یکنواخت در یک پرونده در فهرست root project تنظیم می شوند (برای مثال: local.properties)
اصلاح پویا پارامترها از طریق Hvigor
اولین قدم پیکربندی محصول است
محصولات مختلفی را برای مطابقت با محیط های مختلف تنظیم کنید
ساخت-پروفیل. json5 از فهرست اصلی پروژه را اصلاح کنید
محصول را در زیر ماژول ها اضافه کنید-> اهداف-> گره ApplyToproducts
محصول | محیط مربوطه | پیکربندی امضای مربوطه |
---|---|---|
AGC | بسته های رسمی برای لیست بازار برنامه | پیش فرض |
agcdebug_official | محیط رسمی بسته اشکال زدایی | agc_debug |
agcdebug_test | محیط تست بسته اشکال زدایی | agc_debug |
شركت_فيكي | محیط توزیع سازمانی | شرکت |
شركت_يايي شركت | محیط آزمایش توزیع شرکت | شرکت_xxx |
xxx | محیط xxx | پیکربندی امضای XXX |
کانال XXX کیف XXX | محیط xxx | پیکربندی امضای XXX |
محصولات مختلف گاهی اوقات باید با تنظیمات مختلف امضا مطابقت داشته باشند
build-profile.json5
مرحله دوم پیکربندی hvigorfile.ts در فهرست اصلی پروژه است.
یک پرونده جدید HvigorfileConfig.ts ایجاد کنید
این پرونده عمدتا دو عملکرد زیر را پیاده سازی می کند ، و سپس آنها را در hvigorfile.ts وارد کرده و از آنها استفاده می کند
- زمان فعلی را دریافت کنید و آن را قالب بندی کنید تا برگردد
- محتویات پرونده local.properties را در فهرست اصلی پروژه دریافت کنید
پیکربندی hvigorfile.ts عمدتا توابع زیر را پیاده سازی می کند
- محتوای مربوط به خروجی پیکربندی پرونده های محلی.
- آدرس رابط محیط مربوطه را با توجه به محصول پیکربندی کنید
- نام پرونده بسته برنامه را سفارشی کنید
- زمان بسته بندی را پیکربندی کنید
- پس از اتمام تدوین ، اطلاعات بسته بندی فعلی خروجی است ، مانند: زمان بسته بندی ، نام بسته ، شماره نسخه ، محصول
- پس از اتمام تدوین ، به طور خودکار پرونده برنامه را در فهرست سفارشی کپی کنید.
در زیر پیکربندی کامل hvigorfile.ts است
/*导入OhosAppContext和OhosPluginId,用于动态修改app.json5配置中的版本号等信息*/
import { appTasks, OhosAppContext, OhosPluginId } from '@ohos/hvigor-ohos-plugin';
/*导入FileUtil,用于复制打包后的hap包和app包文件和判断文件路径是否存在*/
import { hvigor, FileUtil } from '@ohos/hvigor'
/*导入hvigorfileConfig.ts中的getLocalFileContent方法*/
import { getLocalFileContent } from './hvigorfileConfig.ts';
/*获取配置参数的全局对象*/
let localData = getLocalFileContent()
console.info("===========================config===========================")
/*输出local.properties配置参数*/
console.info(JSON.stringify(localData,null,1))
/*记录打包编译时,当前的product*/
let _productName = "def"
/*记录打包编译时,当前的包名*/
let _bundleName = "def"
/*记录当前打包编译时的版本号*/
let _versionCode = "def"
/*记录当前打包编译时的版本名称*/
let _versionName = "def"
/*记录当前打包编译时的app名称*/
let appName = ""
/*获取根项目的节点*/
let rootNode = hvigor.getRootNode()
/*为根节点添加一个afterNodeEvaluate hook 在hook中修改app.json5的内容并生效*/
hvigor.getRootNode().afterNodeEvaluate(rootNode => {
/*获取app插件的上下文对象*/
const appContext = rootNode.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext;
/*获取当前product*/
const productName = appContext.getCurrentProduct().getProductName() ?? ""
/*通过上下文对象获取从app.json5文件中读出来的obj对象*/
const appJsonOpt = appContext.getAppJsonOpt();
/*通过上下文对象获取从根目录build-profile.json5文件中读出来的obj对象,可用于修改app中的signingConfigs*/
const buildProfileOpt = appContext.getBuildProfileOpt();
/*修改AppScope/app.json5中的版本号-数据来源于local.properties中的参数配置*/
if (productName.toLowerCase().indexOf("agc") >= 0) {
/*上架应用市场的版本号*/
appJsonOpt['app']['versionCode'] = localData["versionCode"]
appJsonOpt['app']['versionName'] = localData["versionName"] + ""
} else {
/*企业分发的版本号或者各个马甲包的版本号*/
appJsonOpt['app']['versionCode'] = localData["companyVersionCode"]
appJsonOpt['app']['versionName'] = localData["companyVersionName"] + ""
}
/*将appJsonOpt对象设置回上下文对象以使能到构建的过程与结果中*/
appContext.setAppJsonOpt(appJsonOpt);
/*保存打包的相关信息,用于自定义app包名称*/
_productName = productName
_versionCode = appJsonOpt['app']['versionCode']
_versionName = appJsonOpt['app']['versionName']
/*获取当前编译时生成的时间*/
/*getLocalFileContent()方法被调用时生成的时间*/
let timeStr = localData["buildTime"]
/*遍历工程目录下的build-profile.json5文件中app-->products节点的数据*/
const products = buildProfileOpt['app']['products']
for (let i = 0; i < products.length; i++) {
const item = products[i]
/*设置打包时间*/
/*buildOption,arkOptions,buildProfileFields,buildTime需要在build-profile.json5提前定义出来*/
item?.["buildOption"]?.["arkOptions"]?.["buildProfileFields"]?.["buildTime"] = timeStr
let output=item["output"]
if (output && output["artifactName"]) {
let tempProductName = item["name"]
if (tempProductName) {
/*设置正式环境或者测试环境地址*/
if(tempProductName.toLowerCase().indexOf("test")){
/*如果product含有test,则配置为测试环境地址*/
item?.["buildOption"]?.["arkOptions"]?.["buildProfileFields"]?.["url"] = localData["devUrl"]
}else{
item?.["buildOption"]?.["arkOptions"]?.["buildProfileFields"]?.["url"] = localData["OfficialUrl"]
}
/*设置打包输出文件的自定义名称*/
/*格式为:版本号+自定义名字+product+时间.app*/
const resultName = output["artifactName"] = _versionCode + "Harmony_" + tempProductName + "_" + timeStr
if (item["name"] == productName) {
/*如果是当前product,记录打包时的app文件名和bundleName*/
appName = resultName
_bundleName = item["bundleName"]
if(!_bundleName){
/*如果没有在build-profile.json5设置bundleName,直接获取app.json5中的bundleName*/
_bundleName=appJsonOpt['app']['bundleName']
}
}
}
}
}
/*将buildProfileOpt对象设置回上下文对象以使能到构建的过程与结果中*/
appContext.setBuildProfileOpt(buildProfileOpt);
})
/*添加一个构建结束的回调函数(打包完成后,复制app文件到新目录+输出相关信息)*/
hvigor.buildFinished(buildResult => {
const path = require('path');
const appContext = rootNode.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext;
/*获取.app文件所在目录*/
let dirPath = appContext.getBuildProductOutputPath()
/*获取.app文件完整路径*/
let appPath = path.join(dirPath, appName + ".app")
// console.info("======appPath=====" + appPath)
/*如果.app文件存在,就复制到新目录下*/
if (FileUtil.exist(appPath)) {
const parentPath = path.join(path.dirname(__filename), "_app")
/*如果工程的根目录没有_app目录,则创建*/
FileUtil.ensureDirSync(parentPath)
/*目标文件路径*/
const destPath = path.join(parentPath, appName + ".app")
console.info("======destPath=====" + destPath)
/*将打包完成的.app文件复制到项目根目录下的_app目录中*/
FileUtil.copyFileSync(appPath, destPath)
}
/*复制完成后,打印当前编译出的包信息*/
console.info("================================================")
console.info("buildTime :" + localData["buildTime"])
console.info("productName:" + _productName)
console.info("bundleName :" + _bundleName)
console.info("versionCode:" + _versionCode)
console.info("versionName:" + _versionName)
})
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
}
مرحله 3 برای پیکربندی ماژول ورود
اگر بسته های برنامه تولید شده توسط محصولات مختلف را انتخاب کنیم ، می خواهیم نمادهای مختلف برنامه ، مجموعه کد منبع و سایر سبک های منابع را پیاده سازی کنیم ، بنابراین باید به ترتیب با محصولات مختلف Android (شبیه به پیکربندی چند کانال Android) پیکربندی کنیم.
ابتدا ، دو دایرکتوری Main_Company و Main_def را تعریف کنید و به ترتیب در این دو دایرکتوری ، نسخه منبع تنظیمات ETS و منابع فهرست منابع را پیکربندی کنید.
hvigorfile.ts ماژول ورود را پیکربندی کنید
پیکربندی hvigorfile.ts در ماژول ورودی عمدتا توابع زیر را پیاده سازی می کند
- نام پرونده بسته پیکربندی HAP را سفارشی کنید
- زمان بسته بندی HAP را پیکربندی کنید
- پس از اتمام تدوین ، به طور خودکار پرونده HAP را در فهرست سفارشی کپی کنید.
در زیر پیکربندی کامل hvigorfile.ts است
import { hapTasks, OhosHapContext, OhosPluginId } from '@ohos/hvigor-ohos-plugin';
import { hvigor, FileUtil, getNode } from '@ohos/hvigor'
import { readFileSync } from 'fs';
import { appTasks, OhosAppContext } from '@ohos/hvigor-ohos-plugin';
/*导入变量为config的对象,获取打包配置参数*/
import { config } from '../hvigorfileConfig'
/*通过当前目录的hvigorfile.ts获取节点*/
const rootNode = getNode(__filename);
/*通过key-value形式记录hap包名称,key=Module Target,value=hap的文件名*/
let hapNameMap = {}
/*记录product,打包输出时用于自定义hap包名称*/
let _productName = "def"
/*为节点添加一个afterNodeEvaluate hook 在hook中修改该目录下的build-profile.json5的内容并使能*/
rootNode.afterNodeEvaluate(node => {
const path = require('path');
const parentDir = path.dirname(path.dirname(__filename));
/*通过项目根目录的hvigorfile.ts脚本文件路径获取对应的节点对象*/
const appNode = getNode(path.join(parentDir, "hvigorfile.ts"))
const appContext = appNode.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext;
const appJsonOpt = appContext.getAppJsonOpt();
/*获取当前product,用于自定义hap文件名*/
_productName = appContext.getCurrentProduct().getProductName() ?? ""
/*获取此节点使用插件的上下文对象 此时为hap插件 获取hap插件上下文对象*/
const hapContext = node.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
/*通过上下文对象从entry目录build-profile.json5文件中读出来的obj对象*/
const buildProfileOpt = hapContext.getBuildProfileOpt();
const targets = buildProfileOpt['targets']
/*获取编译时生成的时间,[在hvigorfileConfig.ts文件中的getLocalFileContent()方法中生成]*/
/*因为工程根目录下hvigor先于module目录下hvigor执行,所以这里的buildTime直接通过变量config获取*/
/*记得导入import { config } from '../hvigorfileConfig'*/
let timeStr = config["buildTime"]
/*因为工程根目录下hvigor先于module目录下hvigor执行,同理可得:此时获取的versionCode是被动态修改之后的值*/
let versionCode = appJsonOpt["app"]["versionCode"]
/*遍历build-profile.json5中targets节点下的内容*/
for (let i = 0; i < targets.length; i++) {
let output = targets[i]["output"]
if (output && output["artifactName"]) {
/*获取target name,自定义hap名称需要*/
let tempName = targets[i]["name"]
if (tempName) {
/*设置自定义hap名称,版本号+自定义名+product+target+打包时间.hap*/
const resultName =output["artifactName"] = versionCode + "Harmony" + "_"+ _productName + "_" + tempName + "_" + timeStr
/*key-value形式保存hap文件名,key=target name*/
hapNameMap[tempName] = resultName + ".hap"
// console.info("===========" + resultName)
}
}
}
//console.info("===hapNameMap========" + JSON.stringify(hapNameMap))
/*将buildProfileOpt对象设置回上下文对象以使能到构建的过程与结果中*/
hapContext.setBuildProfileOpt(buildProfileOpt);
})
/*添加一个构建结束的回调函数(打包完成后,复制hap文件到新目录)*/
hvigor.buildFinished(buildResult => {
const hapContext = rootNode.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
hapContext?.targets((target: Target) => {
/*通过target name获取对应的hap文件*/
let hapName = hapNameMap[target.getTargetName()]
//console.info(target.getTargetName()+"======target=====" +hapName)
if (!hapName) {
return
}
const path = require('path');
/*获取编译完成后的hap包所在路径*/
const dirPath = target.getBuildTargetOutputPath();
/*得到hap包完整路径*/
let hapPath = path.join(dirPath, hapName)
//console.info("======hapPath=====" +hapPath)
if (FileUtil.exist(hapPath)) {
/*复制到目标目录*/
const parentPath = path.join(path.dirname(path.dirname(__filename)), "_hap")
/*如果目标目录不存在就创建*/
FileUtil.ensureDirSync(parentPath)
/*定义目标文件路径*/
const destPath = path.join(parentPath, hapName)
/*复制hap文件到目标路径*/
FileUtil.copyFileSync(hapPath, destPath)
}
})
})
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
}
در بالا پیکربندی کامل است.
کاملاً پیچیده به نظر می رسد ، اما تا زمانی که اسناد رسمی را با دقت بخوانید و چند بار دیگر آنها را تمرین کنید ، بسیار ساده خواهد شد.
اثر عملکرد واقعی
مشکلات احتمالی
برخی از توسعه دهندگان ممکن است با پیکربندی یک محصول برای مطابقت با چندین هدف ماژول ، چندین کانال یا محیط های مختلف را پیاده سازی کنند (DeviceType یا DistributionFilter/Distrofilter یکسان هستند).
اگر مستقیماً در حال اجرا باشد ، مشکلی وجود ندارد ، اما پس از وارد کردن بسته برنامه ، خطایی را گزارش خواهید کرد
همانطور که در شکل زیر نشان داده شده است:
توضیح رسمی:
بنابراین ، توصیه می شود که آن را از طریق چندین محصول پیکربندی کنید ، یا devicetypes های مختلف را در همان ماژول پیکربندی کنید