安卓转鸿蒙应用开发实践 | 服务卡片开发

2,087 阅读12分钟

一、开发前准备

开发环境

鸿蒙开发使用DevEco Studio开发。具体步骤鸿蒙和Android 开发环境搭建类似。下载HarmonyOS SDK,以及开发工具。

环境准备流程如下:

image.png

开发环境搭建步骤详细参照官网下载Studio以及配置SDK,此处不做赘述

官网链接: developer.harmonyos.com/cn/docs/doc…

开发者账号: 为了确保HarmonyOS应用的完整性,HarmonyOS通过数字证书和Profile文件来对应用进行管控,只有签名过的HAP才允许安装到设备上运行。因此,为了保证应用能够安装到调试设备上,您需要提前申请相应的调试证书与调试Profile。

鸿蒙应用开发,如果使用真机调试,必须有开发者账号,绑定应用包名以及签名才能进行开发调试。

华为的开发者平台网站: developer.huawei.com/consumer/cn…

华为智慧服务开发平台: developer.huawei.com/consumer/cn…

使用自己的账号调试demo的话,需要注册自己的华为开发者账号,创建应用以及签名。

  1. 注册账号,实名认证。
  2. 创建项目
  3. 创建应用
  4. 创建本地签名校验文件
  5. 账号新增签名文件,账号新增设备
  6. 给应用创建profile文件并且绑定真机设备
  7. 下载profile文件,给应用增加签名配置

创建项目:

image.png

创建应用:

创建应用包名必须和本地包名实际应用包名一致,否则签名校验不过,没法真机调试

image.png

创建本地签名证书文件:

DevEco Studio -> Build -> Generate Key and CSR :

image.png

image.png

image.png

创建证书:

华为智慧服务平台->通用设置->证书管理->新增证书->上传CSR文件

image.png

账号下新增设备:

首先获取获取手机udid

adb shell bm get -u

image.png

然后在账号下新增设备,填写获取到的UUID号。 手机UDID必须添加到账号下,才能绑定应用测试签名

image.png

新增应用Profile:

  • 一个证书可以创建100个profile。
  • 此处选择必须已经添加到账号下的设备,以及数字证书才能对此包名的应用进行真机调试

华为智慧服务平台->我的项目->项目设置->HAP Provision Profile->添加

选择对应的证书和设备创建profile文件

image.png

二、工程创建与调试运行

前面开发环境和开发证书均已准备完整,接下来进行创建工程和调试。

开发工具:

基本是和Android Studio 一样

创建应用:

image.png 选择对应应用类型,是鸿蒙应用还是服务卡片: image.png

目录结构:

基本和Android Studio目录结构一样。

entry:

等价于Android Studio 里的 app。

config.json:

等价于AndroidManifest.xml

具体属性参照:

developer.harmonyos.com/cn/docs/doc…

资源文件分类:

developer.harmonyos.com/cn/docs/doc…

resources
|---base  // 默认存在的目录
|   |---element  // 元素资源目录,存放string,color,dimens等
|   |   |---string.json
|   |---layout  // 布局资源目录
|   |   |---ability_main.xml
|   |---graphic  // drawable资源目录,存放xml图片如shape,state-container
|   |   |---bg_white_btn_radius_8.xml
|   |---media  // 图片目录
|   |   |---icon.png
|---en_GB-vertical-car-mdpi // 限定词目录示例,需要开发者自行创建   
|   |---element
|   |   |---string.json
|   |---media
|   |   |---icon.png
|---rawfile  // 默认存在的目录

EntryCard:

新建工程选择了Show In Service Center基本都会自动生成这个目录,这个目录下主要是存放卡片快照。用于在服务中心展示未下载的卡片。一般用默认卡片的视觉图。命名必须是"卡片名称-2x2.png",因为必须有一个2x2的卡片作为默认卡片。

HarmonyDemo
├── EntryCard
│   └── entry  // 这里是存放卡片快照的地方,用于在服务中心展示卡片,一般用默认卡片的视觉图。命名必须是"卡片名称-2x2.png"
├── entry
    ├── agconnect-services.json  //华为开发者网站自动生成的,保存应用配置信息的配置文件
    ├── build.gradle
    ├── proguard-rules.pro
    ├── libs
    └── src
        ├── main
        │   ├── config.json  // 功能等价于 AndroidManifest.xml
        │   ├── java         // java 代码
        │   ├── js           // js 代码
        │   │   ├── widget2x2   // js卡片所在目录,必须和config.json中jsComponentName一致。
        │   │       ├── common
        │   │       ├── i18n
        │   │       └── pages
        │   └── resources                      // 资源目录
        │       ├── base
        │       │   ├── element                // 类似于android studio values目录,存放string.json,float.json
        │       │   ├── graphic                // 存放 Drawable Resource File,xml格式写的一些图
        │       │   ├── layout                // 布局存放目录
        │       │   ├── media                  // 图片资源文件
        │       │   └── profile                
        │       ├── en
        │       │   └── element                // 英文文字资源定义
        │       ├── rawfile
        │       └── zh
        │           └── element                // 中文文字资源定义

js卡片和config.json 对应关系:

image.png

开发与调试:

想要在真机上运行,必须进行签名,使用之前生成的证书和签名文件对应用进行签名配置:

image.png

image.png

添加签名后,本地entry下build.gradle生成的:

image.png

到此,鸿蒙demo应用已经可以在自己的鸿蒙真机设备上运行了。

正式签名配置步骤和debug签名配置步骤一样。只是创建正式发布profile不需要选择设备。

三、鸿蒙相关API和Android对应关系与差异

我们目前还是基于HarmonyOS 2.0开发,从上面的目录结构来看,开发方式总体上来说和Android 开发流程几乎完成一致,仅仅SDK以及各个接口变更了。

完整开发文档: developer.harmonyos.com/cn/docs/doc…

先列一下Harmony SDKAndroid SDK基本组件的对应关系:

  1. ohos.app.Context -> android.content.Context

Context 里面的接口用处基本和Android很相似,请求权限、跳转界面、资源管理,系统接口桥梁都类似。

这里不同点是这里增加了些TaskDispatcher任务分发器,系统默认实现了几种任务分发器。类似线程池。方便执行一些异步任务。

详情可以看 线程开发指导

getGlobalTaskDispatcher:全局并发任务分发器,适用于任务之间没有联系的情况

UITaskDispatcher:绑定到应用主线程的专有任务分发器, 由Ability执行getUITaskDispatcher()创建并返回。由该分发器分发的所有的任务都是在主线程上按顺序执行,它在应用程序结束时被销毁。

image.png

2. 页面: Page Ability ---> Activity setUIContent: 设置界面内容,对应setContentView

3. 服务: Service Ability ---> Service 启动页面或服务: startAbility :对应Android的 startActivity 以及 startService

链接服务: connectAbility:对应Android 的 bindService

Harmony中服务和界面统称Ability, 通过在config.json中修改type定义其类型来标识是服务还是界面。

"abilities": [
  {
    "orientation": "portrait",
    "name": "com.tencent.gallerycard.MainAbility",
    "icon": "$media:ic_launcher_app",
    "description": "$string:mainability_description",
    "formsEnabled": true,
    "visible": true,
    "label": "$string:entry_label",
    "type": "page",  // 表示是页面
  },
  {
    "name": "com.tencent.gallerycard.GalleryServiceAbility",
    "description": "$string:galleryserviceability_description",
    "type": "service"  // 表示是服务
  }
],

Page Ability 生命周期

image.png

Service Ability生命周期:

image.png

4. 子页面:AbilitySlice ---> Fragment 目前看AbilitySlice类似于Fragment可以在一个Ability中切换不同的AbilitySlice来更换显示。但是又不同于Fragment, AbilitySlice 作为 Ability的子页面,是完全占用整个Ability窗口的,Ability的整个页面内容都在AbilitySlice中,Fragment可以仅仅占用一部分。

通过AbilitySlice里提供的present方法在同一Page内进行不同的AbilitySlice间的跳转

// 普通跳转
AbilitySlice#present(new TargetAbility(),intent);

// 需要返回结果
AbilitySlice#presentForResult(new TargetAbility(),intent,requestCode);

5. 意图Intent: 名字和Android 上都一样的,用法有些许不同,可以通过设置 FLAG_NOT_OHOS_COMPONENT 来达到跳转android应用的界面

使用如下:

private void goAndroidApp() {
        Intent androidIntent = new Intent();
        ElementName androidElementName = new ElementName("", "com.tencent.xxx",
                "com.tencent.xxx.xxxActivity");

        String category = "android.intent.category.DEFAULT";
        Set<String> entities = new HashSet<>();
        entities.add(category);
        androidIntent.setElement(androidElementName);
        Operation operation = new OperationBuilder()
                .withDeviceId("")
                .withBundleName("com.tencent.xxx")   // packageName
                .withAbilityName("com.tencent.xxx.xxxActivity") // 类似className
                .withEntities(entities)    // 类似 addCategory
                .withAction("android.intent.action.EDIT")
                .withUri(Uri.parse(imageUri))    // uri数据,类似Android Intent.setData
                .build();
        androidIntent.setOperation(operation);
        androidIntent.addFlags(Intent.FLAG_NOT_OHOS_COMPONENT); // 跳转非鸿蒙应用
        androidIntent.setParam("key", value); // 类似Android Intent putExtras
        startAbility(androidIntent);

6. Data Ability ---> Android 上ContentProvider

DataAbilityHelper: query()方法可以根据uri查询文件的ID,操作系统媒体库基本用这个方法

ValuesBucket: 对应android上的 ContentValues

DatabaseHelper:数据库操作类

7. ohos.agp.components.Component ---> Android 上View

  • 测量: onEstimateSize --> Android 上onMeasure
  • 绘制: onDraw--> Android 上onDraw

8. ohos.agp.components.ComponentContainer ---> Android 上ViewGroup

  • 布局: onArrange---> Android 上 onLayout

这些和Android上不同的点是:鸿蒙中这些接口都不是其本身的方法,而是通过接口的方式,必须addListener进去才可以。

image.png

9. 线程间通信 EventHandler 有点类似Android上的Handler 可以进行线程间通信

10. 列表布局:ListContainer 列表对于ListView

11. 布局文件解析: LayoutScatter 对应Android LayoutInflate

用法如下:

Component component = LayoutScatter.getInstance(context)
        .parse(ResourceTable.Layout_custom_common_dialog, null, false);

12. xml文件存储:Preference 对应Android上 SharedPreference 用法如下:

DatabaseHelper mHelper = new DatabaseHelper(mContext);
Preferences mPreferences = mHelper.getPreferences(sp.xml);

13. 显示设备属性: DisplayAttributes:对应Android 上 DisplayMetrics 获取显示设备的宽高等属性:

DisplayAttributes attributes = DisplayManager.getInstance().getDefaultDisplay(context).get().getAttributes();
attributes.densityPixels;

14. 包管理: IBundleManager 对应Android上PackageManager 获取应用包相关属性 用法如下:

IBundleManager bundleManager = context.getBundleManager();
bundleManager.getApplicationInfo(s,i,j);

15. 位图: PixelMap 对应Android上的Bitmap,用于位图显示操作 ImageSource 用于位图的解析,类似于BitmapFactory.decodeXXX,用法如下:

public class PixelMapUtils {

    public static PixelMap decodePixelMap(String path) {
        try {
            ImageSource imageSource = ImageSource.create(path, null);
            return imageSource.createPixelmap(null);
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.error("PixelMapUtils", e.getMessage());
        }
        return null;
    }

    public static PixelMap decodePixelMap(String path, int width, int height) {
        try {
            ImageSource imageSource = ImageSource.create(path, null);
            ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();
            options.desiredSize = new Size(width, height);
            return imageSource.createPixelmap(options);
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.error("PixelMapUtils", e.getMessage());
        }
        return null;
    }
}

16. 弹窗:CommonDialog 类似Android AlertDialog

四、鸿蒙原子化服务卡片开发

原子化服务是HarmonyOS提供的一种面向未来的服务提供方式,是有独立入口的(用户可通过点击方式直接触发)、免安装的(无需显式安装,由系统程序框架后台安装后即可使用)、可为用户提供一个或多个便捷服务的用户应用程序形态。

简单来说,轻量级鸿蒙应用,免手动安装,无桌面图标,由鸿蒙服务中心管理,大小限制10M以内,api使用和应用一样。用户第一次在服务中心点击的时候会下载应用后台自动安装。

服务中心入口是在鸿蒙手机桌面左下角上滑就能出现。服务中心展示如下。

image.png

分类:

原子化服务卡片有两种类型,java卡片和js卡片。

java卡片:就是用以xml布局,java逻辑控制的卡片。

js卡片: 以JS显示布局界面。

卡片运作机制:

基本概念:

  • 卡片提供方(也就是我们的服务卡片应用)

    提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。

  • 卡片使用方(主要是鸿蒙桌面以及服务中心)

    显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。

  • 卡片管理服务(这个就是鸿蒙的系统服务了)

    用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。

image.png

生命周期时序:

image.png

卡片不是独立存在的,必须依托在Ability之内,其生命周期方法都实现在Ability内

image.png

image.png

同卡片通信以及更新卡片

详情见:developer.harmonyos.com/cn/docs/doc…

js卡片和java卡片通信方式不太一样,此处介绍js卡片,java卡片可以详细看文档。

卡片其实有点类似于android以及ios桌面widget一样。实际上和卡片通信其实也是个跨进程过程。

@Override
    public void updateFormData(long formId, Object... vars) {
        Resource resourceImageSrc = null;
        try {
            resourceImageSrc = context.getResourceManager().getResource(ResourceTable.Media_beauty);
            byte[] bytesImageSrc = imageConvertToByteArray(resourceImageSrc);
            ZSONObject zsonObject = new ZSONObject();
            String fileName = "ic_image_"+System.currentTimeMillis() +".png";
            zsonObject.put("picture", "memory://" + fileName);    // 通过这个zsonObject 的key值要和index.json文件中的data对应
            FormBindingData formBindingData = new FormBindingData(zsonObject);
            formBindingData.addImageData(fileName, bytesImageSrc);
            try {
                ((Ability) context).updateForm(formId, formBindingData);
            } catch (FormException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotExistException e) {
            e.printStackTrace();
        }
    }

通信借助FormBindingData 这个序列化的类,可以通过json 传递普通数据。也可以通过addImageData接口添加图片字节数组。

通过字节数组传递进去的图片,路径为json"picture"对应的value值,此处必须为memory:// 开头表示传递进去的内存数据。

每次更新图片路径必须变更,否则会导致更新不了,所以这里采用当前时间加到图片路径中。

image.png

卡片事件和Action

JS卡片支持为组件设置action,包括router事件和message事件,其中router事件用于应用跳转,message事件用于卡片开发人员自定义点击事件。 关键步骤说明如下:

  1. 在hml中为组件设置onclick属性,其值对应到json文件的actions字段中。
  2. 若设置router事件,则
  • action属性值为"router";
  • abilityName为卡片提供方应用的跳转目标Ability名;
  • params中的值按需填写,其值在使用时通过intent.getStringParam("params")获取即可;
  1. 若设置message事件,则action属性值为"message",params为json格式的值。

image.png

image.png

后台运行

后台任务调度,详情可看developer.harmonyos.com/cn/docs/doc…

应用如果需要常驻后台运行,需要配置后台运行模式,以及配置后台运行权限。鸿蒙这边有规范,特定分类的应用才允许设置相应后台模式,否则不允许上架。

后台模式分类 HarmonyOS提供了十种后台模式,供需要在后台做长驻任务的业务使用。

具体的后台模式类型如下:

长驻任务后台模式英文名描述
数据传输data-transfer通过网络/对端设备进行数据下载、备份、分享、传输等业务
播音audio-playback音频输出业务
录音audio-recording音频输入业务
画中画picture-in-picture画中画、小窗口播放视频业务
音视频通话voip音视频电话,VoIP业务
导航/位置更新location定位、导航业务
蓝牙设备连接及传输bluetooth-interaction蓝牙扫描、连接、传输业务
WLAN设备连接及传输wifi-interactionWLAN扫描、连接、传输业务
屏幕抓取screen-fetch录屏、截屏业务
多设备互联multiDeviceConnection多设备互联,分布式调度和迁移等业务

image.png

折叠屏适配

详细配置步骤参照平行视界开发指导:平行视界开发指导

根据鸿蒙给的建议,以经验值:屏幕像素宽度大于1440认为是折叠屏的展开状态。

首先在针对需要适配的Ability增加如下configChanges属性,避免这些属性变化导致Ability重启。因为折叠屏展开关闭会导致屏幕size变化

然后重写AbilityonConfigurationUpdated方法。图片

public class MainAbility extends Ability {

    private Size currentSize;
    
      @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        setUIContent(getLayoutResId());
        getWindow().setStatusBarColor(RgbPalette.parse("#F5f5f5"));
        getWindow().setBackgroundColor(RgbColor.fromArgbInt(RgbPalette.parse("#F5f5f5")));
        currentSize = ScreenUtils.getScreenSize();
         if (currentSize.width > 1440) {
            // 是折叠屏,界面如果需要不一样的话,就在此处设置
        }
    }
    
    @Override
    public void onConfigurationUpdated(Configuration configuration) {
        super.onConfigurationUpdated(configuration);
        Size size = ScreenUtils.getScreenSize();
        // 屏幕尺寸发生变化
        if (currentSize.width != size.width || currentSize.height != size.height) {
            currentSize = size;
            // 大于1440 就是折叠屏的展开状态
            if (ScreenUtils.getScreenSize().width > ScreenUtils.THRESHOLD_WIDTH) {
                // 展开下,界面布局参数修改
            } else {
                // 折叠态,界面布局参数修改
            }
        }
    }
}

五、鸿蒙第三方库

目前鸿蒙已经实现的常用三方库列表:www.cnblogs.com/HarmonyOS/p…

鸿蒙开源三方库git仓库:gitee.com/openharmony…

鸿蒙技术社区:harmonyos.51cto.com/

六、总结

文章仅是本人第一次从0学习搭建鸿蒙的一份总结,主要旨在让大家对鸿蒙开发有一个整体的认识,一些技术细节,最好对照官方详细文档。有问题大家可以一起学习。