跳到主要内容

元服务-帝心世界

通过使用Text、Image等常用组件、Swiper、Grid、List、Scroll等可滑动组件,构造出第一个HarmonyOS页面

首页个人中心入口卡片
帝心世界首页帝心世界首页入口卡片拉起指定页面卡片数据同步

1 数据准备及常规配置

应用或者元服务均可,创建项目过程较为简单,不在笔记中体现。

数据资料见源码,此处记录过程。

  1. src/main/resources/base/media资源目录中存放项目配置所需图片资源。
  2. src/main/resources/rawfile资源目录中存放项目所需图片及数据资源。
  3. 修改src/main/resources/base/element/string.jsonEntryAbility_label等字段描述,供src/main/module.json5文件配置使用。同时在color.json中新增页面中所需的颜色配置,例如theme_color
  4. 修改src/main/module.json5常见配置及网络权限
  5. 准备数据页面所需数据模型文件:src/main/ets/model/DxinWorldModel.ets
export interface ArticleClass {
id: string;
imageSrc: string;
webUrl: string;
title?: string;
brief?: string;
}

2 Index页面

首页根容器布局

  1. 案例中涉及页面跳转,所以首页根目录设计为Navigation组件,配合toolbarConfiguration属性即可实现底部tab效果。注意关联NavPathStack对象。
  2. 在后续自定义组件中需要使用NavPathStack对象的pushPath方法跳转页面,父子组件中可以通过参数传递,也可以设计为全军存储。此处选用后者方案。
import { AppStorageV2 } from '@kit.ArkUI';

@Entry
@Component
struct Index {
// 定义全局的pageStack
pageStack: NavPathStack = AppStorageV2.connect(NavPathStack, 'NavPathStack', () => new NavPathStack())!
build() {
Navigation(this.pageStack) {
// 轮播图
// Banner()
// 技术文章
// TechnicalArticles()
// 公众号
// WechatArticles()
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.theme_color'))
.title('帝心世界')
.mode(NavigationMode.Auto) // 默认自适应
// .mode(NavigationMode.Stack) // 全屏
// .mode(NavigationMode.Split) // 分屏
.titleMode(NavigationTitleMode.Free) // 大标题 无按钮 有角标
// .titleMode(NavigationTitleMode.Mini) //小标题+返回按钮 + 角标
// .titleMode(NavigationTitleMode.Full) // 突出标题 无按钮 有角标
.menus([
{ 'value': '待开发', 'icon': $r('app.media.DxinWorld') },
{ 'value': '待开发', 'icon': $r('app.media.DxinWorld') },
{ 'value': '待开发', 'icon': $r('app.media.DxinWorld') },
])
// 底部工具栏
.toolbarConfiguration([
{
'value': '首页',
'icon': $r('app.media.world')
},
{
'value': '个人中心',
'icon': $r('app.media.Personal'),
action: () => {
//跳转页面
this.pageStack.pushPath({ name: "Personal" })

}
}
], {
//工具栏背景颜色,不设置时为系统默认颜色。
backgroundColor: $r('app.color.theme_color'),
// 工具栏背景模糊样式,不设置时关闭背景模糊效果。
backgroundBlurStyle: BlurStyle.COMPONENT_ULTRA_THICK,
// 设置工具栏布局方式。默认值:BarStyle.STANDARD
barStyle: BarStyle.SAFE_AREA_PADDING,
})
}
}

轮播图组件

  1. 导入rawfile/DataSource/BannerData.json文件中的数组数据,导入即可直接使用。
  2. 遍历数组数据,设计UI效果即可。
  3. 使用AppStorageV2全局存储的路由对象跳转指定页面。
// 导入即对象
import bannerDataArr from '../../resources/rawfile/DataSource/BannerData.json';
import { AppStorageV2, router } from '@kit.ArkUI';
import { ArticleClass } from '../model/DxinWorldModel';

@ComponentV2
export default struct Banner {
bannerList: Array<ArticleClass> = bannerDataArr as []
pageStack: NavPathStack = AppStorageV2.connect(NavPathStack, 'NavPathStack', () => new NavPathStack())!

build() {
Column() {
Swiper() {
ForEach(this.bannerList, (item: ArticleClass) => {
Image($rawfile(item.imageSrc))
.objectFit(ImageFit.Contain)
.width('100%')
.height('100%')
.borderRadius(16)
.onClick(() => {
this.pageStack.pushPath({ name: "WebDetails", param: item.webUrl })
})
})
}
.width('100%')
.height(100)
.padding({ left: 16, right: 16 })
.autoPlay(true)
.loop(true)
.indicator(
new DotIndicator()
.color('#1a000000')
.selectedColor('#0A59F7')
)
}
}
}

技术文章组件

  1. 导入rawfile/DataSource/TechnicalArticlesData.json文件中的数组数据,导入即可直接使用。
  2. 遍历数组数据,设计UI效果即可。
  3. 使用AppStorageV2全局存储的路由对象跳转指定页面。
import TechnicalArticlesData from "../../resources/rawfile/DataSource/TechnicalArticlesData.json";
import { AppStorageV2 } from "@kit.ArkUI";
import { ArticleClass } from "../model/DxinWorldModel";

@Component
export default struct TechnicalArticles {
enablementList: Array<ArticleClass> = TechnicalArticlesData as []
pageStack: NavPathStack = AppStorageV2.connect(NavPathStack, 'NavPathStack', () => new NavPathStack())!

build() {
Column() {
Text('技术文章')
.fontColor('#182431')
.fontSize(16)
.fontWeight(500)
.fontFamily('HarmonyHeiTi-medium')
.padding({ left: 16 })

Grid() {
ForEach(this.enablementList, (item: ArticleClass) => {
GridItem() {
TechnicalArticlesItem({ enablementItem: item })
.onClick(() => {
this.pageStack.pushPath({ name: "WebDetails", param: item.webUrl })
})
}
})
}
.rowsTemplate('1fr')
.columnsGap(8)
.scrollBar(BarState.Off)
.height(100)
.padding({ top: 2, left: 16, right: 16 })
}
.margin({ top: 18 })
.alignItems(HorizontalAlign.Start)
}
}

@Component
struct TechnicalArticlesItem {
enablementItem: ArticleClass = { id: '',imageSrc: '',webUrl: ''}

build() {
Column() {
Image($rawfile(this.enablementItem.imageSrc))
.width('60%')
.aspectRatio(1)
.objectFit(ImageFit.Contain)
.borderRadius(16)
Text(this.enablementItem.title)
.width('100%')
.fontSize(14)
.textAlign(TextAlign.Center)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
.fontWeight(400)
.padding({ left: 12, right: 12 })
Text(this.enablementItem.brief)
.width('100%')
.fontSize(12)
.textAlign(TextAlign.Start)
.textOverflow({ overflow: TextOverflow.MARQUEE })
.maxLines(1)
.fontWeight(400)
.fontColor('rgba(0, 0, 0, 0.6)')
}
.width(100)
.aspectRatio(1)
.borderRadius(16)
.backgroundColor('#fff')
.justifyContent(FlexAlign.SpaceEvenly)
}
}

微信公众号文章组件

  1. 导入rawfile/DataSource/TechnicalArticlesData.json文件中的数组数据,导入即可直接使用。其他同上。
import TechnicalArticlesData from "../../resources/rawfile/DataSource/WechatArticles.json";
import { AppStorageV2 } from "@kit.ArkUI";
import { ArticleClass } from "../model/DxinWorldModel";

@Component
export default struct TechnicalArticles {
enablementList: Array<ArticleClass> = TechnicalArticlesData as []
pageStack: NavPathStack = AppStorageV2.connect(NavPathStack, 'NavPathStack', () => new NavPathStack())!

build() {
Column() {
Text('技术文章')
.fontColor('#182431')
.fontSize(16)
.fontWeight(500)
.fontFamily('HarmonyHeiTi-medium')
.padding({ left: 16 })

Grid() {
ForEach(this.enablementList, (item: ArticleClass) => {
GridItem() {
TechnicalArticlesItem({ enablementItem: item })
.onClick(() => {
this.pageStack.pushPath({ name: "WebDetails", param: item.webUrl })
})
}
})
}
.rowsTemplate('1fr')
.columnsGap(8)
.scrollBar(BarState.Off)
.height(100)
.padding({ top: 2, left: 16, right: 16 })
}
.margin({ top: 18 })
.alignItems(HorizontalAlign.Start)
}
}

@Component
struct TechnicalArticlesItem {
enablementItem: ArticleClass = { id: '',imageSrc: '',webUrl: ''}

build() {
Column() {
Image($rawfile(this.enablementItem.imageSrc))
.width('60%')
.aspectRatio(1)
.objectFit(ImageFit.Contain)
.borderRadius(16)
Text(this.enablementItem.title)
.width('100%')
.fontSize(14)
.textAlign(TextAlign.Center)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
.fontWeight(400)
.padding({ left: 12, right: 12 })
Text(this.enablementItem.brief)
.width('100%')
.fontSize(12)
.textAlign(TextAlign.Start)
.textOverflow({ overflow: TextOverflow.MARQUEE })
.maxLines(1)
.fontWeight(400)
.fontColor('rgba(0, 0, 0, 0.6)')
}
.width(100)
.aspectRatio(1)
.borderRadius(16)
.backgroundColor('#fff')
.justifyContent(FlexAlign.SpaceEvenly)
}
}

3 Web详情页

  1. 新建WebDetails页面作为跳转目标页面。
  2. NavDestination作为根容器组件,在onReady事件中通过context.pathInfo.param获取参数作为Web页面的Src属性。
  3. 使用Web组件加载网页,需申请网络权限,在前文常规配置中已申请过。因涉及页面加载Dom能力,需设置domStorageAccess属性。
  4. 作为跳转目标页面,需要入口函数配合src/main/resources/base/profile/route_map.json文件使用
import webview from '@ohos.web.webview'

@Builder
export function WebDetailsBuilder() {
WebDetails()
}

@Entry
@Component
struct WebDetails {
webController: webview.WebviewController = new webview.WebviewController()
webUrl: string = 'https://hm.codefe.cn/' // 随便给一个默认值,例如鸿蒙学苑

// 获取传递过来的页面url
build() {
NavDestination() {
Web({
src: this.webUrl,
controller: this.webController
})
.domStorageAccess(true)

}
.onReady((context) => {
this.webUrl = context.pathInfo.param as string
})

}
}

4 路由跳转配置

  1. src/main/module.json5中新增"routerMap": "$profile:route_map",

  2. 创建src/main/resources/base/profile/route_map.json文件并添加配置项。如下代码不仅配置了WebDetails页面的配置,且为后续的个人中心Personal页面也添加了跳转配置项。

{
"routerMap": [
{
"name": "WebDetails",
"pageSourceFile": "src/main/ets/pages/WebDetails.ets",
"buildFunction": "WebDetailsBuilder"
},
{
"name": "Personal",
"pageSourceFile": "src/main/ets/pages/Personal.ets",
"buildFunction": "PersonalBuilder"
}
]
}

5 个人中心页面

  1. 个人中心页面按照帝心习惯,添加了个人小标记DxinLabel组件。先创建src/main/ets/view/DxinLabel.ets自定义组件
@Component
export default struct DxinLabel {
build() {
Row() {
Text('开发者:帝心')
.fontSize(14)
.fontColor('#ffe7850e')

Text('HarmonyOS NEXT独享')
.fontSize(14)
.fontColor('#ff62a6e9')
}
.width('100%')
.height(30)
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor($r('app.color.theme_color'))
}
}
  1. 新建Personal页面,该页面无需做复杂设计,仅仅为后续卡片拉起不同页面的铺垫。
import DxinLabel from "../view/DxinLabel"

@Builder
export function PersonalBuilder() {
Personal()
}

@Entry
@Component
struct Personal {
build() {
NavDestination() {
Column({ space: 30 }) {
Text($r("app.string.EntryAbility_label"))
.fontSize(30)
Image($r('app.media.DxinWorld'))
.width('50%')
.aspectRatio(1)
.borderRadius('50%')
DxinLabel()
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.theme_color'))
.justifyContent(FlexAlign.SpaceBetween)
.expandSafeArea()
}
.hideBackButton(true)
}
}

6 添加入口卡片

  1. entry目录下任意子目录身上右键均可创建服务卡片,静态和动态卡片区别不在此处赘述,暂时选择动态卡片即可。入口卡片命名为Into即可,创建成功后,卡片文件名程自动追加为IntoCard。仅作为入口使用,尺寸选择最小1X2即可。

    创建入口卡片

  2. 卡片创建成功后会自动创建三个文件

新增文件

  1. 微调卡片页面文件,使代码简约即可。action字段设置点击卡片时的事件类型为router,简单理解为跳转页面。abilityName字段设置为待跳转的目标ability。会进入src/main/ets/entryability/EntryAbility.ets文件执行对应生命周期文件拉起指定页面。
@Entry
@Component
struct IntoCard {
build() {
Column() {
Text('Dxin World')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.onClick(() => {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility'
});
})
}
}
  1. 运行项目添加卡片到桌面,点击卡片进入元服务。

7 卡片拉起指定页面

  1. 新建卡片PullTargetPage设置两个按钮,通过router事件传递指定参数拉起对应页面

创建拉起指定页面卡片

// 不同按钮拉起指定页面的服务卡片
@Entry
@Component
struct PullTargetPageCard {
// 封装成有参数的函数,进入EntryAbility,拉起指定页面
pullTargetPage(pageName: string) {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility',
params: {
targetPage: pageName
}
});
}

build() {
Column() {
Button('首页')
.onClick(() => {
this.pullTargetPage('Index')
})
Button('个人中心')
.onClick(() => {
this.pullTargetPage('Personal')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}
  1. 卡片的router事件会进入EntryAbility中运行生命周期函数。第一次进入会执行onCreate,后续进入则执行onNewWant,所以在onNewWant中获取参数并设置给onWindowStageCreate中的loadContent方法拉起目标页面
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
currentPage: string = 'Index'
windowStage: window.WindowStage | null = null

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
}
// 后续再次进入应用的时候。自动拉起
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want?.parameters?.params) {
let params: Record<string, string> = JSON.parse(want.parameters.params as string)
// 动态获取拉起哪个页面的参数值
this.currentPage = params?.targetPage
}
//手动调用onWindowStageCreate 能进入onNewWant时就能断定this.windowStage不为空了
this.onWindowStageCreate(this.windowStage!)
}

onWindowStageCreate(windowStage: window.WindowStage): void {
// this.windowStage为空的时候赋值为windowStage,否则就不赋值。 避免每次都赋值一次
this.windowStage ??= windowStage
windowStage.loadContent('pages/' + this.currentPage);
}

}

8 页面数据同步卡片

如何在页面中修改卡片内容。即:页面数据如何同步到卡片上。

重点在于:卡片 → 传 formIdEntryAbilityLocalStorage 存储)→ 页面获取 → 页面更新卡片

难点在于:

  1. 在页面上更新卡片内容需要使用formProvider.updateForm(this.formId, formData);。而页面上怎么会有卡片id呢。
  2. 添加卡片后,通过卡片拉起服务时,可以在onNewWant()生命周期中动态获取卡片`ID``
  3. onNewWant()生命周期中动态获取卡片ID又该如何给页面共享呢:使用LocalStorage页面级UI状态存储。
    1. 在生命周期中初始化LocalStorage时绑定数据。不能创建完LocalStorage对象再set数据
    2. 页面需要开启页面共享的LocalStorage实例的能力:@Entry({ useSharedStorage: true })

8.1 创建用于展示数据同步的卡片,以计数器为案例效果。取名Counter

创建数据同步卡片

本小节为页面数据同步到卡片中,后续小节为卡片数据同步回页面中,所以在卡片布局上不仅呈现数据,还需要操作按钮,为避免后续修改代码,此处提前准备布局效果。

数据同步卡片布局

@Entry
@Component
struct CounterCard {
@LocalStorageProp('count') count: number = 1

build() {
Column() {
Row() {
Button('-')
.onClick(() => {
this.count--
})
Text(this.count.toString())
.fontSize(26)
Button('+')
.onClick(() => {
this.count++
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)

Button('Card2Page')
.onClick(() => {
postCardAction(this, {
action: 'message',
abilityName: 'EntryAbility',
params:{
count:this.count
}
});
})

Button('come in')
.onClick(() => {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility'
});
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}

8.2 点击CounterCard卡片进入应用时,无指定拉起页面参数,所以EntryAbility默认展示首页,为方便查看页面与卡片之间的切换效率。调整默认拉起页面为Personal

//src/main/ets/entryability/EntryAbility.ets 
currentPage: string = 'Personal'
windowStage: window.WindowStage | null = null

8.3 准备初始化的LocalStorage对象

//LocalStorage 需要初始化对象并且绑定数据。才可以和UI同步
initFormId: Record<string, string> = {
'formId': ''
}
localStorage = new LocalStorage(this.initFormId)

8.4 通过点击CounterCard卡片进入应用时,已经不是首次进入,在onNewWant中获取卡片ID并存储到localStorage

//页面数据同步卡片:这里面要干一件事:获取到卡片id。
const formId = want.parameters![formInfo.FormParam.IDENTITY_KEY].toString()
this.localStorage.set('formId',formId)

8.5 在onWindowStageCreate中拉起页面时,需要传递localStorage

// 记得传递localStorage
windowStage.loadContent('pages/' + this.currentPage,this.localStorage);

8.6 在Personal页面中开启共享localStorage能力

//开启LocalStorage数据共享
@Entry({ useSharedStorage: true })

8.7 在Personal页面中获取localStorage中存储过的卡片ID、定义计数器数据、提前准备context用来关闭窗口。

// 用来同步数据的count
@State count: number = 1
// 获取localStorage中存储过的卡片ID
@LocalStorageProp('formId') formId: string = ''
// 如果同步数据失败或者用户并未通过卡片进入(那我怎能知道卡片ID)时,关闭窗口
context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;

8.8 设计UI效果和同步数据功能。formBindingDatacreateFormBindingData方法绑定的数据会默认存储进LocalStorage,所以在8.1步骤的卡片页面中可以通过LocalStorageProp取出数据。

// 计数器
Column() {
Text('formId:' + this.formId).fontSize(30)
Row() {
Button('-')
.onClick(() => {
this.count--
})
Text(this.count.toString())
.fontSize(26)
Button('+')
.onClick(() => {
this.count++
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)

Button('Page2Card')
.onClick(async () => {
if (!this.formId) {
// 没有id的时候
promptAction.openToast({ message: '请先添加卡片,从卡片进入' })
// 等待 3000 毫秒后执行代码
setTimeout(() => {
this.context.terminateSelf()
}, 3000);
return
}
try {
await formProvider.updateForm(
this.formId,
formBindingData.createFormBindingData({
'count': this.count
}))
promptAction.openToast({ message: '同步数据成功' })
// 等待 3000 毫秒后执行代码
setTimeout(() => {
this.context.terminateSelf()
}, 3000);
} catch (e) {
promptAction.openToast({ message: '同步数据给卡片失败了' })
}
})
}
.width('90%')
.height(200)
.backgroundColor('#ffd8f4d6')
.justifyContent(FlexAlign.SpaceEvenly)

8.9 上一步将数据从页面同步给卡片,为了避免在让用户在页面和卡片之间频繁切换。设计了三秒定时器执行this.context.terminateSelf()关闭窗口。那么就会导致用户通过卡片进入页面时为第一次进入,此时走onCreate方法,而非onNewWant方法,所以在onCreate方法中也应该设置卡片ID(如果有的话,初次运行则没有)

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want.parameters![formInfo.FormParam.IDENTITY_KEY]) {
//页面数据同步卡片:这里面要干一件事:获取到卡片id。
const formId = want.parameters![formInfo.FormParam.IDENTITY_KEY].toString()
this.localStorage.set('formId', formId)
}
}

9 卡片数据同步页面

9.1 在前文8.1步骤中,已经提前设计好了CounterCard卡片的布局效果,其中Card2Page按钮用来将卡片数据同步回页面。指定卡片的action字段为message事件,会触发卡片声明周期EntryFormAbility中的onFormEvent函数

Button('Card2Page')
.onClick(() => {
postCardAction(this, {
action: 'message',
abilityName: 'EntryAbility',
params:{
count:this.count
}
});
})

9.2 在onFormEvent函数中接收卡片的最新数据

onFormEvent(formId: string, message: string) {
let currentCardCount = JSON.parse(message).count as number
//怎么同步到页面 首选项。因为不在一个线程了,无法使用localStorage
const prefs = preferences.getPreferencesSync(this.context,{name:"dxinWorld"})
prefs.putSync('count',currentCardCount)
prefs.flushSync() // 立即同步
}

9.3 在Personal页面中获取preferences首选项dxinWorld中存储在count字段里的数据。

aboutToAppear() {
this.count = preferences.getPreferencesSync(this.context, { name: "dxinWorld" }).getSync("count", 0) as number
}

10 配置服务器域名

在访问服务端获取数据时,如果服务器的域名没有被AGC平台管控,则在真机运行无法成功。模拟器不受影响。

例如设计在Personal页面中设计一个状态变量,将数据呈现在UI中,点击按钮访问服务端获取相应数据更换诗词内容。真机运行会报错。

// 诗词
@State poemContent: string = '日拱一卒无有尽,功不唐捐终入海'
// 配置服务器域名知识点
Column() {
Text(this.poemContent)
Button('获取诗词')
.onClick(async () => {
try {
let resp = await http.createHttp().request('https://v1.jinrishici.com/all.txt')
this.poemContent = resp.result.toString()
} catch (e) {
console.log(`dxin => 真机运行情况下:网络请求异常${e.message},--${e.code}`)
// 真机运行情况下:网络请求异常It is not allowed to access this domain,--2300998
}
})
}
.width('90%')
.height(100)
.backgroundColor('#67fbc6a8')
.justifyContent(FlexAlign.SpaceEvenly)

解决方案:在元服务域名管理中进行域名配置。

image-20260519185910225


11 元服务上架

11.1 生成密钥和证书请求文件

  1. 菜单选择“Build > Generate Key and CSR”。

  2. 在 Generate Key and CSR弹窗中,按需填写基本信息

生成密钥和证书请求文件

  1. 填写P12文件保存位置及密码。

P12文件

  1. 填写完成后,点击 OK, 回到 Generate Key and CSR弹窗,此时会把刚才新建的 p12 文件的路径给带出来,以及 Password 也会带出来并回显到弹窗中,此时,还需要填写 Alias,即别名,下边的 Advance setting额外配置视情况设置,或者默认即可

Generate Key and CSR

  1. 生成密钥和证书请求文件的整体流程总结,如下图所示:

生成密钥和证书请求文件

11.2 申请发布证书

登录AppGallery,进入控制台,申请发布证书。

AppGallery

选择 「证书、APP ID和Profile」入口,——选择「证书」。点击『新增证书』。

certificate

证书名称:自己填写一个证书名字(不超过100个字符。)。

证书类型:选择「发布证书」。

选取证书请求文件(CSR):上传「密钥和证书请求文件」时获取的.csr文件。

certificateName

提交成功后下载证书、该证书在后续发布元服务过程中被使用。

image-20260520085230811

11.3 申请发布Profile

Profile的格式是 .p7b,包含了项目的一些包名、证书之类的信息等等,是发布元服务(应用)必须的内容。选择 Profile Tab项,点击右上角『添加』。

Profile

应用名称:下拉列表选择创建的应用名称。

Profile名称:自定义,建议与元服务名称保持一致。

类型:选择 发布。

选择证书:选择前边创建好的证书。完成信息填写后点击右上角「添加」。

ProfilenName

回到 Profile 的列表页,会出现刚才添加的Profile 项。点击右边的 「下载」,将 profile 文件进行下载到电脑上备用。

downProfile

11.4 配置签名信息

DevEco Studio,菜单选择“File > Project Structure”,进入“Project Structure”界面选择 “Signing Configs” Tab项,取消“自动签名”。

![Signing ](https://hm-1252173264.cos.ap-shanghai.myqcloud.com/docs/Signing .548224a5.png)

Store File:密钥库文件,选择生成密钥和证书请求文件时生成的.p12文件。

Store Password:密钥库密码,需要与生成密钥和证书请求文件时设置的密钥库密码保持一致。

Key alias:密钥的别名信息,需要与生成密钥和证书请求文件时设置的别名保持一致。

Key password:密钥的密码,需要与生成密钥和证书请求文件时设置的密码保持一致。

Profile file:选择申请发布Profile时下载的.p7b文件。

Certpath file:选择申请发布证书时下载的.cer文件。

11.5 编译打包元服务

打开DevEco Studio,菜单选择“Build > Build Hap(s)/APP(s) > Build APP(s)”。

等待编译构建签名的元服务,编译完成后,将在工程目录build > outputs > default目录下,获取可用于发布的元服务包。可能根据版本不同或者签名不同出现在build/default/outputs/default

11.6 发布元服务

登录AppGallery Connect,旧版界面选择“我的元服务”,新版界面选择“APP”。下文以新版界面演示。

release

点击新建发布,并选择指定App ID生成的应用包名。

createRelease

进入列表,选中「HarmonyOS」Tab,点击待发布的元服务的操作项: “编辑”。

填写元服务的相关信息,带*的必填项,按需填写。应用信息——可本地化基础信息——应用图标。

生成元服务图标

  • 因元服务具备免安装特性,可在元服务市场中搜索使用,被搜索时展示的元服务图标尤为重要。所以开发元服务需要先生成元服务图标,以及后期上架元服务时,也需要提供该图标。

    DevEco Studio支持Image Asset功能,帮助开发者生成统一的元服务图标样式。

    DevEco Studio 5.0.3.800及以上版本支持使用元服务图标生成能力。

  • 在工程中选中模块或文件,右键单击New > Image Asset,进入图标配置页面。

  • 在Path中选择本地已经按照要求准备好的图片路径。图片尺寸及要求如下:

    • 图标格式:.png、.jpeg、.jpg格式的静态图片资源。
    • 图标尺寸:1024 x 1024 px (正方形)
    • 图标背景:不透明。
    • 质量要求:图标内容需清晰可辨,避免存在模糊、锯齿、拉伸等问题。
  • 在预览界面可以配置图标颜色、名称、保存路径等。

    • Color:推荐使用的图标颜色。选择不同颜色,右边图标预览区域可查看相应的效果。
    • Name:生成的图标名称。
    • Res Directory:生成的512px512px尺寸图标在工程中的保存位置。
    • Save to:生成的216px216px尺寸图标需要指定本地文件夹的保存位置。后续在AppGallery Connect上架元服务时,需使用该图标。
  • 点击OK,保存配置并在相应模块目录src > main > resources > base > media路径下生成元服务图标。可在模块级module.json5中的icon字段中配置元服务图标。

appIcon

填写元服务的相关信息,带*的必填项,按需填写。应用信息——应用分类。

上传软件包。

  • 版本信息

    • 准备提交
      • 版本选取。
      • 可本地化基础信息——应用介绍和简介。
      • 内容分级——设置。
      • 隐私声明——填写协议服务。
    • 1.0.0准备提交——备案信息:根据该元服务是否是涉及服务器交互,即网络请求,来选择APP类型信息。如果无网络请求,则选择单机APP。
  • 提交审核。等待审核通过或者拒绝。


功不唐捐终入海!

加载中...