近期(4.12 ~ 4.25)鸿蒙OS正在举行开发者日活动,趁机参加并了解一下鸿蒙OS的现状和应用开发体验。 developer.huawei.com/consumer/cn…css
华为为Harmony应用开发提供了配套的IDE:DevEco Studio(心里比较排斥这种带Eco字眼儿的命名,PPT怎么吹无所谓,开发工具咱能不能务实一点儿?)前端
下载IDE须要登陆Huawei帐号,我安装的是Mac版,下载后的安装过程仍是比较顺畅的vue
启动界面显示DevEco Studio仍然是基于IntelliJ的定制IDEjava
跟Android同样,IDE启动第一件事情是下载Harmony SDKreact
每一个版本的SDK中都提供了三套API用来开发Java、Js、C++代码,版本上须要保持一致。 不一样的华为设备对SDK版本有不一样要求,好比在测试中发现,个人API4的代码没法运行在P40上,改成API5就OK了git
须要注意,目前没法经过SDKManager打包下载源码,源码须要经过gitee单独下载程序员
这为代码调试带来障碍,不知道后期是否能够像Andoird那样与SDK一块儿打包下载源码json
Harmony主打多端协同,因此很重视设备多样性,可面向不一样设备建立模板项目markdown
相比AndroidStudio,Harmony提供了更加丰富的项目模板,模板中除了UI之外还提供了部分数据层代码,基本上是一个能够二次开发的APP。
试着建立了一个News Feature Ability(新闻流)的模板项目,成功在IDE中打开:
IDE窗口与AndroidStudio相似,值得一提的Harmony右边提供的Preview窗口,能够对xml或者Ablitiy文件进行预览,有点Compose的Preview的感受,可是只能静态预览,没法交互
工程文件和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是应用所具有能力的抽象,Harmony支持应用以Ability为单位进行部署。一个应用由一个或多个FA(Feature Ability)或PA(Particle Ability)组成。FA有UI界面,提供与用户交互的能力;而PA无UI界面,提供后台运行任务的能力以及统一的数据访问抽象
能够感受到,各类Ability能够对照Android的四大组件来理解
Harmony | Android |
---|---|
Page Ability (FA) | Activity |
Service Ability (PA) | Service |
Data Ability(PA) | ContentProvider |
AbilitySlice | Fragment |
以预置的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 |
---|---|
![]() |
![]() |
主要看一下列表的显示逻辑
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而已。
代码完成后能够再模拟器中运行。关于模拟器有几点想说的:
Harmony的模拟器启动很是快,无需下载镜像,由于这个模拟器并不是本地运行,而只是一个远端设备的VNC,所以必须在线使用,并且不够流畅时有丢帧现象。虽然真机调试效果更好,但不是人人都买得起P40的
模拟器嵌入到IDE窗口显示(像Preview窗口同样),非独立窗口,这会带来一个问题,当同时打开多个IDE时,模拟器可能会显示在另外一个IDE中(就像Logcat跑偏同样)。
想使用模拟器必须进过开发者认证,官方推荐使用银行卡认证。模拟器远端连接的是一台真实设备,难道是为将来租用设备要计费??记得之前看过一篇文章,若是是来自国外地区的注册帐号能够免认证使用模拟器,可是懒得折腾了
除了Java,鸿蒙还支持基于JS开发应用,借助前端技术完善其跨平台能力。
鸿蒙为JS工程提供了多种经常使用UI组件,可是没有采用当下主流的react、vue那样JS组件,仍然是基于CSS3/HTML5/JS这种传统方式进行开发。JS工程结构以下
目录 | 说明 |
---|---|
common | 可选,用于存放公共资源文件,如媒体资源、自定义组件和JS文档等 |
i18n | 可选,用于存放多语言的json文件 |
pages/index/index.hml | hml文件定义了页面的布局结构,使用到的组件,以及这些组件的层级关系 |
pages/index/index.css | css文件定义了页面的样式与布局,包含样式选择器和各类样式属性等 |
pages/index/index.js | js文件描述了页面的行为逻辑,此文件里定义了页面里所用到的全部的逻辑关系,好比数据、事件等 |
resources | 可选,用于存放资源配置文件,好比:全局样式、多分辨率加载等配置文件 |
app.js | 全局的JavaScript逻辑文件和应用的生命周期管理。 |
经过前面的介绍,可能感受和Android大同小异,可是HarmonyOS最牛逼之处是多端协做能力,例如能够将Page在同一用户的不一样设备间迁移,实现无缝切换。
以Page从设备A迁移到设备B为例,迁移动做主要步骤以下:
经过调用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
进行通讯。
跨设备迁移的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为例,详细的流程以下:
IAbilityContinuation.onStartContinuation()
方法,以确认当前是否能够当即迁移。IAbilityContinuation.onSaveData()
方法,以便保存迁移后恢复状态必须的数据。IAbilityContinuation.onRestoreData()
方法,传递此前保存的数据;此后设备B上此Page从onStart()
开始其生命周期回调。IAbilityContinuation.onCompleteContinuation()
方法,通知数据恢复成功与否。目前人们对于鸿蒙的态度呈现两极化,有的人追捧有的人贬低,我以为都大可没必要,多给鸿蒙一些空间和耐心,静观其变、乐见其成。固然首选须要华为作到本身不主动炒做,真正静下心来打磨鸿蒙,只要华为有决心有耐心,做为开发者的咱们为何不支持呢?
伴随开发者活动日,鸿蒙还举办了多轮线上挑战赛活动(目前还在进行中),难度不高参与即能完成,且中奖率很高(亲测),有兴趣能够参与一下,但愿个人好运传递给你
活动详情:mp.weixin.qq.com/s/3IrZGZkm1…