鸿蒙HarmonyOS应用开发初体验

简介: 近期鸿蒙HarmonyOS正举行开发者日活动,趁机参加并了解了一下鸿蒙OS的现状。对DevEco Studio以及如何创建Harmony工程做一个简单的介绍

image.png

1. 开发环境搭建


下载安装IDE(当前版本 2.1 Beta3)

华为为Harmony应用开发提供了配套的IDE:DevEco Studio(内心比较排斥这种带Eco字眼儿的命名,PPT怎么吹无所谓,开发工具咱能不能务实一点儿?)

下载IDE需要登录Huawei账号,我安装的是Mac版,下载后的安装过程还是比较顺畅的

image.png

启动界面显示DevEco Studio仍然是基于IntelliJ的定制IDE

下载SDK

跟Android一样,IDE启动第一件事情是下载Harmony SDK

image.png

每个版本的SDK中都提供了三套API用来开发Java、Js、C++代码,版本上需要保持一致。
不同的华为设备对SDK版本有不同要求,比如在测试中发现,我的API4的代码无法运行在P40上,改为API5就OK了

关于SDK源码

需要注意,目前无法通过SDKManager打包下载源码,源码需要通过gitee单独下载

https://gitee.com/openharmony

这为代码调试带来障碍,不知道后期是否可以像Andoird那样与SDK一起打包下载源码

创建项目

Harmony主打多端协同,所以很重视设备多样性,可面向不同设备创建模板项目

Screen Shot 2021-04-18 at 10.28.09 AM.png

相比AndroidStudio,Harmony提供了更加丰富的项目模板,模板中除了UI以外还提供了部分数据层代码,基本上是一个可以二次开发的APP。

Screen Shot 2021-04-18 at 5.48.55 PM.png

<br/>

2. 鸿蒙项目结构


IDE界面

试着创建了一个News Feature Ability(新闻流)的模板项目,成功在IDE中打开:

image.png

IDE窗口与AndroidStudio类似,值得一提的Harmony右边提供的Preview窗口,可以对xml或者Ablitiy文件进行预览,有点Compose的Preview的感觉,但是只能静态预览,无法交互

工程文件

image.png

工程文件和Android类似,甚至可以找到一一对应的关系

Harmony Android 说明
entry app 默认启动模块(主模块),相当于app_module
MyApplication XXXApplication 鸿蒙的MyApplication是AbilityPackage的子类
MainAbility MainActivity 入口页。鸿蒙中将四大组件的概念统一成Ability
MainAbilityListSlice XXXFragment Slice类似Fragment,UI的基本组成单元
Component View Component类相当于View,后文介绍
config.json AndroidManifest.xml 鸿蒙使用json替代xml进行Manifest配置,配置项目差不多
resources/base/... res/... 包括Layout文件在内的各种资源文件依旧使用xml
resources/rawfile/ assets/ rawfile存储任意格式原始资源,相当于assets
build.gradle build.gradle 编译脚本,两者一样
build/outpus/.../*.hap build/outputs/.../*.apk 鸿蒙的产物是hap(harmony application package) <br/> 解压后里面有一个同名的.apk文件, <br/> 这后续是因为鸿蒙需要同时支持apk安装的兼容方案

Ability

Ability是应用所具备能力的抽象,Harmony支持应用以Ability为单位进行部署。一个应用由一个或多个FA(Feature Ability)或PA(Particle Ability)组成。FA有UI界面,提供与用户交互的能力;而PA无UI界面,提供后台运行任务的能力以及统一的数据访问抽象

  • FA支持Page Ability:

    • Page Ability用于提供与用户交互的能力。一个Page可以由一个或多个AbilitySlice构成,AbilitySlice之间可以进行页面导航

image.png

  • PA支持Service Ability和Data Ability:

    • Service Ability:用于提供后台运行任务的能力。
    • Data Ability:用于对外部提供统一的数据访问抽象。

可以感觉到,各种Ability可以对照Android的四大组件来理解

Harmony Android
Page Ability (FA) Activity
Service Ability (PA) Service
Data Ability(PA) ContentProvider
AbilitySlice Fragment

代码一览

MainAbility

以预置的News Feature Ability为例子,这是一个拥有两个Slice的Page Ability,通过Router注册两个Slice

public class MainAbility extends Ability {

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setMainRoute(MainAbilityListSlice.class.getName()); //添加路由:ListSlice
        addActionRoute("action.detail", MainAbilityDetailSlice.class.getName());//DetailSlice

        ...
    }
}
AI 代码解读

以下是在模拟器中运行两个Slice的页面效果

MainAbilityListSlice MainAbilityDetailSlice
image.png image.png

MainAbilityListSlice

主要看一下列表的显示逻辑

public class MainAbilityListSlice extends AbilitySlice {

    ...
    
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_news_list_layout);
        initView();
        initData(); //加载数据
        initListener();
        newsListContainer.setItemProvider(newsListAdapter); //Adatper设置到View
        newsListAdapter.notifyDataChanged(); //刷新数据
    }


    private void initListener() {
        newsListContainer.setItemClickedListener((listContainer, component, i, l) -> {
            //路由跳转"action.detail"
            LogUtil.info(TAG, "onItemClicked is called");
            Intent intent = new Intent();
            Operation operation = new Intent.OperationBuilder()
                    .withBundleName(getBundleName())
                    .withAbilityName("com.example.myapplication.MainAbility")
                    .withAction("action.detail")
                    .build();
            intent.setOperation(operation);
            startAbility(intent);
        });
    }
    
    private void initData() {
        ...
        totalNewsDatas = new ArrayList<>();
        newsDatas = new ArrayList<>();
        initNewsData();//填充newsDatas
        newsListAdapter = new NewsListAdapter(newsDatas, this);//设置到Adapter
    }

    ...
}
AI 代码解读

类似ListView的用法,通过Adatper加载数据; setItemClickedListener中通过路由跳转MainAbilityDetailSlice。

Layout_news_list_layout布局文件定义如下,ListContainer即ListView,是Comopnent的一个子类,Component就是HarmonyOS中的View

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">

    <ListContainer
        ohos:id="$+id:selector_list"
        ohos:height="40vp"
        ohos:width="match_parent"
        ohos:orientation="horizontal"
        />

    <Component
        ohos:height="0.5vp"
        ohos:width="match_parent"
        ohos:background_element="#EAEAEC"
        />

    <ListContainer
        ohos:id="$+id:news_container"
        ohos:height="match_parent"
        ohos:width="match_parent"/>


</DirectionalLayout>
AI 代码解读

看一下Adapter的实现, 继承自BaseItemProvider

/**
 * News list adapter
 */
public class NewsListAdapter extends BaseItemProvider {
    private List<NewsInfo> newsInfoList;
    private Context context;

    public NewsListAdapter(List<NewsInfo> listBasicInfo, Context context) {
        this.newsInfoList = listBasicInfo;
        this.context = context;
    }

    @Override
    public int getCount() {
        return newsInfoList == null ? 0 : newsInfoList.size();
    }

    @Override
    public Object getItem(int position) {
        return Optional.of(this.newsInfoList.get(position));
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Component getComponent(int position, Component componentP, ComponentContainer componentContainer) {
        ViewHolder viewHolder = null;
        Component component = componentP;
        if (component == null) {
            component = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_item_news_layout, null, false);
            viewHolder = new ViewHolder();
            Component componentTitle = component.findComponentById(ResourceTable.Id_item_news_title);
            Component componentImage = component.findComponentById(ResourceTable.Id_item_news_image);
            if (componentTitle instanceof Text) {
                viewHolder.title = (Text) componentTitle;
            }
            if (componentImage instanceof Image) {
                viewHolder.image = (Image) componentImage;
            }
            component.setTag(viewHolder);
        } else {
            if (component.getTag() instanceof ViewHolder) {
                viewHolder = (ViewHolder) component.getTag();
            }
        }
        if (null != viewHolder) {
            viewHolder.title.setText(newsInfoList.get(position).getTitle());
            viewHolder.image.setScaleMode(Image.ScaleMode.STRETCH);
        }
        return component;
    }

    /**
     * ViewHolder which has title and image
     */
    private static class ViewHolder {
        Text title;
        Image image;
    }
}
AI 代码解读

基本上就是标准的ListAdatper,把View替换成Component而已。

关于模拟器

代码完成后可以再模拟器中运行。关于模拟器有几点想说的:

  1. Harmony的模拟器启动非常快,无需下载镜像,因为这个模拟器并非本地运行,而只是一个远端设备的VNC,因此必须在线使用,而且不够流畅时有丢帧现象。虽然真机调试效果更好,但不是人人都买得起P40的
  2. 模拟器嵌入到IDE窗口显示(像Preview窗口一样),非独立窗口,这会带来一个问题,当同时打开多个IDE时,模拟器可能会显示在另一个IDE中(就像Logcat跑偏一样)。
  3. 想使用模拟器必须进过开发者认证,官方推荐使用银行卡认证。模拟器远端链接的是一台真实设备,难道是为未来租用设备要计费??image.png记得以前看过一篇文章,如果是来自国外地区的注册账号可以免认证使用模拟器,但是懒得折腾了

<br/>

3. 开发JS应用


除了Java,鸿蒙还支持基于JS开发应用,借助前端技术完善其跨平台能力。

鸿蒙为JS工程提供了多种常用UI组件,但是没有采用当下主流的react、vue那样JS组件,仍然是基于CSS3/HTML5/JS这种传统方式进行开发。JS工程结构如下

image.png

目录 说明
common 可选,用于存放公共资源文件,如媒体资源、自定义组件和JS文档等
i18n 可选,用于存放多语言的json文件
pages/index/index.hml hml文件定义了页面的布局结构,使用到的组件,以及这些组件的层级关系
pages/index/index.css css文件定义了页面的样式与布局,包含样式选择器和各种样式属性等
pages/index/index.js js文件描述了页面的行为逻辑,此文件里定义了页面里所用到的所有的逻辑关系,比如数据、事件等
resources 可选,用于存放资源配置文件,比如:全局样式、多分辨率加载等配置文件
app.js 全局的JavaScript逻辑文件和应用的生命周期管理。

<br/>

4. 跨设备迁移


通过前面的介绍,可能感觉和Android大同小异,但是HarmonyOS最牛逼之处是多端协作能力,例如可以将Page在同一用户的不同设备间迁移,实现无缝切换。

以Page从设备A迁移到设备B为例,迁移动作主要步骤如下:

  • 设备A上的Page请求迁移。
  • HarmonyOS回调设备A上Page的保存数据方法,用于保存迁移必须的数据。
  • HarmonyOS在设备B上启动同一个Page,并回调其恢复数据方法。

通过调用continueAbility()请求迁移。如下,获取设备列表,配对成功后请求迁移

doConnectImg.setClickedListener( 
    clickedView -> { 
        // 通过FLAG_GET_ONLINE_DEVICE标记获得在线设备列表 
        List deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); 
        if (deviceInfoList.size() < 1) { 
            WidgetHelper.showTips(this, "无在网设备"); 
        } else { 
            DeviceSelectDialog dialog = new DeviceSelectDialog(this); 
            // 点击后迁移到指定设备 
            dialog.setListener( 
                deviceInfo -> { 
                    LogUtil.debug(TAG, deviceInfo.getDeviceName()); 
                    LogUtil.info(TAG, "continue button click"); 
                    try { 
                        // 开始任务迁移 
                        continueAbility(); 
                        LogUtil.info(TAG, "continue button click end"); 
                    } catch (IllegalStateException | UnsupportedOperationException e) { 
                        WidgetHelper.showTips(this, ResourceTable.String_tips_mail_continue_failed); 
                    } 
                    dialog.hide(); 
                }); 
            dialog.show(); 
        } 
    });
        
AI 代码解读

Page迁移涉及到数据传递,此时需要借助IAbilityContinuation进行通信。

跨设备通信 IAbilityContinuation

跨设备迁移的Page需要实现IAbilityContinuation接口。

Note: 一个应用可能包含多个Page,仅需要在支持迁移的Page中通过以下方法实现IAbilityContinuation接口。同时,此Page所包含的所有AbilitySlice也需要实现此接口。
public class MainAbility extends Ability implements IAbilityContinuation { 
    ... 
    @Override 
    public void onCompleteContinuation(int code) {} 
 
    @Override 
    public boolean onRestoreData(IntentParams params) { 
        return true; 
    } 
 
    @Override 
    public boolean onSaveData(IntentParams params) { 
        return true; 
    } 
 
    @Override 
    public boolean onStartContinuation() { 
        return true; 
    } 
}
public class MailEditSlice extends AbilitySlice implements IAbilityContinuation { 
    ... 
    @Override 
    public boolean onStartContinuation() { 
        LogUtil.info(TAG, "is start continue"); 
        return true; 
    } 
 
    @Override 
    public boolean onSaveData(IntentParams params) { 
        ... 
        LogUtil.info(TAG, "begin onSaveData:" + mailData); 
        ... 
        LogUtil.info(TAG, "end onSaveData"); 
        return true; 
    } 
 
    @Override 
    public boolean onRestoreData(IntentParams params) { 
        LogUtil.info(TAG, "begin onRestoreData"); 
        ... 
        LogUtil.info(TAG, "end onRestoreData, mail data: " + cachedMailData); 
        return true; 
    } 
 
    @Override 
    public void onCompleteContinuation(int i) { 
        LogUtil.info(TAG, "onCompleteContinuation"); 
        terminateAbility(); 
    } 
}
AI 代码解读
  • onStartContinuation(): Page请求迁移后,系统首先回调此方法,开发者可以在此回调中决策当前是否可以执行迁移,比如,弹框让用户确认是否开始迁移。
  • onSaveData(): 如果onStartContinuation()返回true,则系统回调此方法,开发者在此回调中保存必须传递到另外设备上以便恢复Page状态的数据。
  • onRestoreData(): 源侧设备上Page完成保存数据后,系统在目标侧设备上回调此方法,开发者在此回调中接受用于恢复Page状态的数据。注意,在目标侧设备上的Page会重新启动其生命周期,无论其启动模式如何配置。且系统回调此方法的时机在onStart()之前。
  • onCompleteContinuation(): 目标侧设备上恢复数据一旦完成,系统就会在源侧设备上回调Page的此方法,以便通知应用迁移流程已结束。开发者可以在此检查迁移结果是否成功,并在此处理迁移结束的动作,例如,应用可以在迁移完成后终止自身生命周期。

以Page从设备A迁移到设备B为例,详细的流程如下:

  1. 设备A上的Page请求迁移。
  2. 系统回调设备A上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onStartContinuation()方法,以确认当前是否可以立即迁移。
  3. 如果可以立即迁移,则系统回调设备A上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onSaveData()方法,以便保存迁移后恢复状态必须的数据。
  4. 如果保存数据成功,则系统在设备B上启动同一个Page,并恢复AbilitySlice栈,然后回调IAbilityContinuation.onRestoreData()方法,传递此前保存的数据;此后设备B上此Page从onStart()开始其生命周期回调。
  5. 系统回调设备A上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onCompleteContinuation()方法,通知数据恢复成功与否。

<br/>

5. 总结和感想


  1. 从SDK到IDE与Android都高度相似,任何Android开发者基本上就是一个准鸿蒙程序员
  2. AndroidStudio的功能迭代很快,DevEco Studio在功能上还存在较大差距
  3. 需要实名认证开发者之后才能使用IDE的各种完整功能,内心抗拒
  4. 源码需要另外下载,对调试不友好
  5. 当前还不支持Kotlin。大势所趋,所以未来一定会支持,而且Kotlin是开源的问题不大
  6. JS UI框架的技术架构同样有些过时
  7. 杀手锏是对多端协作的支持,但这可能需要更多的厂商加入才能真正发挥威力

目前人们对于鸿蒙的态度呈现两极化,有的人追捧有的人贬低,我觉得都大可不必,多给鸿蒙一些空间和耐心,静观其变、乐见其成。当然首选需要华为做到自己不主动炒作,真正静下心来打磨鸿蒙,只要华为有决心有耐心,作为开发者的我们为什么不支持呢?

image.png

Harmony线上挑战赛

伴随开发者活动日,鸿蒙还举办了多轮线上挑战赛活动(目前还在进行中),难度不高参与即能完成,且中奖率很高(亲测),有兴趣可以参与一下,希望我的好运传递给你

活动详情:https://mp.weixin.qq.com/s/3IrZGZkm1GGNNWzJvytegw

image.png

相关链接

目录
打赏
0
0
0
0
10
分享
相关文章
原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力
原生鸿蒙版小艺APP成功接入DeepSeek-R1,为HarmonyOS用户带来更智能高效的交互体验。通过此次升级,用户在编程、学习和工作中的问题可迅速获得专业解答。同时,此举为HarmonyOS应用开发者提供了新的技术参考,激发了更多创新应用场景的开发,助力打造差异化竞争优势,推动HarmonyOS生态繁荣发展。
157 68
原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
29 5
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
59 3
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
《鸿蒙 HarmonyOS 应用开发从入门到精通(第 2 版)》学习笔记 ——HarmonyOS 环境搭建之注册华为开发者联盟帐号
要进行HarmonyOS应用开发,首先需要注册华为开发者联盟帐号并完成实名认证。注册时可选择成为个人或企业开发者,两者享有不同权益。个人开发者需准备手机号/邮箱、身份证扫描件及银行卡号等资料,通过审核后即可享受应用市场、主题、商品管理等多项服务。具体步骤包括访问华为开发者官网(https://developer.huawei.com/consumer/cn/),选择注册方式并按指引操作。实名认证需填写个人信息并签署相关协议,等待1-3个工作日的审核结果。
93 16
|
7天前
鸿蒙开发:了解@Builder装饰器
@Builder装饰是鸿蒙UI开发中,非常重要的一个装饰器,在实际的开发中,合理且正确的使用,能够让我们的代码更加的简洁,有两点需要注意,一是,是用私有还是全局,取决于当前的组件的复用机制,如果多个页面都使用了,建议以全局为主;二是传参的动态更新,有更新就使用引用参数传递,没有更新按值传递即可。
56 28
Harmony OS开发-ArkTS语言速成二
本文介绍了ArkTS基础语法,包括三种基本数据类型(string、number、boolean)和变量的使用。重点讲解了let、const和var的区别,涵盖作用域、变量提升、重新赋值及初始化等方面。期待与你共同进步!
115 47
Harmony OS开发-ArkTS语言速成二
|
2月前
|
鸿蒙开发:实现一个超简单的网格拖拽
实现拖拽,最重要的三个方法就是,打开编辑状态editMode,实现onItemDragStart和onItemDrop,设置拖拽移动动画和交换数据,如果想到开启补位动画,还需要实现supportAnimation方法。
91 13
鸿蒙开发:实现一个超简单的网格拖拽
|
2月前
|
鸿蒙开发:自定义一个股票代码选择键盘
金融类的软件,特别是股票基金类的应用,在查找股票的时候,都会有一个区别于正常键盘的键盘,也就是股票代码键盘,和普通键盘的区别就是,除了常见的数字之外,也有一些常见的股票代码前缀按钮,方便在查找股票的时候,更加方便的进行检索。
鸿蒙开发:自定义一个股票代码选择键盘
|
2月前
|
API
鸿蒙开发:自定义一个英文键盘
实现方式呢,有很多种,目前采用了比较简单的一种,如果大家采用网格Grid组件实现方式,也是可以的,但是需要考虑每行的边距以及数据,还有最后两行的格子占位问题。
鸿蒙开发:自定义一个英文键盘
鸿蒙元服务项目实战:备忘录内容编辑开发
富文本内容编辑我们直接使用RichEditor组件即可,最重要的就是参数,value: RichEditorOptions,通过它,我们可以用来设置样式,和获取最后的富文本内容,这一点是很重要的。
鸿蒙元服务项目实战:备忘录内容编辑开发

热门文章

最新文章

  • 1
    【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
    29
  • 2
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    59
  • 3
    EMAS 性能分析全面适配HarmonyOS NEXT,开启原生应用性能优化新纪元
    62
  • 4
    鸿蒙原生应用再添新丁!北京广电、充电管家、有我帮 入局鸿蒙
    27
  • 5
    鸿蒙开发:了解@Builder装饰器
    56
  • 6
    原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力
    157
  • 7
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    63
  • 8
    华为鸿蒙自己家的“AI”编辑器插件用起来到底怎么样?
    66
  • 9
    《鸿蒙 HarmonyOS 应用开发从入门到精通(第 2 版)》学习笔记 ——HarmonyOS 环境搭建之注册华为开发者联盟帐号
    93
  • 10
    微信纯血鸿蒙版正式发布,295天走完微信14年技术之路!
    91
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等