First experience of HarmonyOS application development

Recently (4.12 ~ 4.25) Hongmeng OS is holding a developer day event, take the opportunity to participate and learn about the current situation and application development experience of Hongmeng OS. developer.huawei.com/consumer/cn…

image.png

1. Development environment setup


Download and install the IDE (current version 2.1 Beta3)

Huawei provides a supporting IDE for Harmony application development: DevEco Studio (Inwardly, I reject this kind of naming with the word Eco. It doesn’t matter how PPT is played, can we be more pragmatic about the development tools?)

To download the IDE, you need to log in to your Huawei account. I installed the Mac version. The installation process after downloading is relatively smooth.

image.png

The launch screen shows that DevEco Studio is still a custom IDE based on IntelliJ

Download SDK

Like Android, the first thing the IDE starts up is to download the Harmony SDK

image.png

Each version of the SDK provides three sets of APIs for developing Java, Js, and C++ code, and the versions need to be consistent. Different Huawei devices have different requirements for the SDK version. For example, in the test, it was found that my API4 code could not run on the P40, and it was OK to change to API5.

About SDK source code

It should be noted that the source code cannot be packaged and downloaded through SDKManager at present, and the source code needs to be downloaded separately through gitee

gitee.com/openharmony

This brings obstacles to code debugging. I don't know if it can be packaged and downloaded with the SDK in the later stage like Andoird.

Create project

Harmony focuses on multi-terminal collaboration, so it attaches great importance to the diversity of devices, and can create template projects for different devices

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

Compared with AndroidStudio, Harmony provides a richer project template. In addition to the UI, the template also provides some data layer code, which is basically an APP that can be developed twice.

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


2. Hongmeng Project Structure


IDE interface

Try to create a News Feature Ability (news flow) template project and successfully open it in the 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)
解压后里面有一个同名的.apk文件,
这后续是因为鸿蒙需要同时支持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

        ...
    }
}
复制代码

以下是在模拟器中运行两个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
    }

    ...
}
复制代码

类似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>
复制代码

看一下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;
    }
}
复制代码

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

关于模拟器

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

  1. Harmony的模拟器启动非常快,无需下载镜像,因为这个模拟器并非本地运行,而只是一个远端设备的VNC,因此必须在线使用,而且不够流畅时有丢帧现象。虽然真机调试效果更好,但不是人人都买得起P40的

  2. 模拟器嵌入到IDE窗口显示(像Preview窗口一样),非独立窗口,这会带来一个问题,当同时打开多个IDE时,模拟器可能会显示在另一个IDE中(就像Logcat跑偏一样)。

  3. 想使用模拟器必须进过开发者认证,官方推荐使用银行卡认证。模拟器远端链接的是一台真实设备,难道是为未来租用设备要计费??image.png记得以前看过一篇文章,如果是来自国外地区的注册账号可以免认证使用模拟器,但是懒得折腾了


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逻辑文件和应用的生命周期管理。

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(); 
		} 
	});
        
复制代码

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(); 
    } 
}
复制代码
  • 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()方法,通知数据恢复成功与否。

5. 总结和感想


  1. 从SDK到IDE与Android都高度相似,任何Android开发者基本上就是一个准鸿蒙程序员
  2. AndroidStudio的功能迭代很快,DevEco Studio在功能上还存在较大差距
  3. 需要实名认证开发者之后才能使用IDE的各种完整功能,内心抗拒
  4. The source code needs to be downloaded separately, which is not friendly to debugging
  5. Kotlin is not currently supported. The general trend, so it will be supported in the future, and it is not a big problem that Kotlin is open source
  6. The technical architecture of the JS UI framework is also somewhat outdated
  7. The killer feature is support for multi-terminal collaboration, but this may require more vendors to join in to really play the role

At present, people's attitudes towards Hongmeng are polarized. Some people admire it and some people despise it. I don't think it's necessary. Give Hongmeng more space and patience. Of course, the first choice requires Huawei to not take the initiative to hype it, and really calm down to polish Hongmeng. As long as Huawei has the determination and patience, why don't we as developers support it?

Harmony Online Challenge

Along with the developer activity day, Hongmeng also held several rounds of online challenge events (currently in progress), which can be completed without high difficulty, and the winning rate is very high (pro-test). If you are interested, you can participate. I hope my good luck to you

Event details: mp.weixin.qq.com/s/3IrZGZkm1…

image.png

Related Links

Guess you like

Origin juejin.im/post/6952682996767916039