#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装! 原创 精华

龙眼Litchi
发布于 2022-7-24 15:38
浏览
1收藏

[本文正在参加星光计划3.0-夏日挑战赛]

本文将介绍如何实现,通过华为分享来分享原子化服务,以及如何上架原子化服务从而实现服务的免安装

0. 前言

  • 原子化服务是鸿蒙的一大特性,在服务中心可以看见许多以卡片形式呈现的原子化服务,这些服务体积小,能够快速部署到手机上实现功能,达到免安装的效果。
    #夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
  • 同样的,原子化服务另外一种呈现方式就是基于华为分享的,例如A同学希望分享他在京东上看到的一件商品,他可以通过华为分享将该服务页面快速迁移到B同学的手机上,而B同学的手机上并没有安装京东,也能看到呈现画面。

#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

这里不知道是网络还是应用出现了BUG,总之就没显示出来画面,不过问题不大。接下来我们亲自用案例实现,这个案例首先实现华为分享分享服务,同时要发布测试态原子化服务,这样我们的应用才能够在服务中心以卡片形式呈现,并且实现免安装

1. 原子化服务分享

1.1 华为分享

本案例在最新版本的Deveco上进行编写,我们需要建立携带原子化服务的JAVA工程,注意是JAVA工程,由于该功能目前还未迁移到JS上,我们需要用JAVA进行编写,同时勾选在服务中心显示
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
具体的华为分享原理,其实就是两端在一个组网近场内,一端封装好要分享的数据通过华为分享传输给另一端。快速传输,免安装的体验感一是数据量小,二是华为分享本身过硬的技术两者结合带来的。多余不在赘述,详情参看官网文档,接入华为分享

1.2 接入华为分享

1.2.1 创建IDL接口

我们在JAVA同级目录下,创建两个idl接口:

  • IHwShareCallback
  • IHwShareService
    注意idl接口存放路径一定要是com/huawei/hwshare/third
    #夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
    #夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

1.2.2 实现接口

  • IHwShareCallback
interface com.huawei.hwshare.third.IHwShareCallback {
    [oneway] void notifyState([in] int state);
}
  • IHwShareService
sequenceable ohos.interwork.utils.PacMapEx;
interface com.huawei.hwshare.third.IHwShareCallback;

interface com.huawei.hwshare.third.IHwShareService {
    int startAuth([in] String appId, [in] IHwShareCallback callback);
    int shareFaInfo([in] PacMapEx pacMapEx);
}

1.3 ShareFaManager类

用于管理分享方与华为分享的连接通道和数据交互,建议不要DIY,DIY空间少,容易出错,直接参考官方文档。

import com.huawei.hwshare.third.HwShareCallbackStub;
import com.huawei.hwshare.third.HwShareServiceProxy;
import ohos.aafwk.ability.IAbilityConnection;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.bundle.ElementName;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.interwork.utils.PacMapEx;
import ohos.rpc.IRemoteObject;
import ohos.rpc.RemoteException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

public class ShareFaManager {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, "ShareFa");

    private static final String LOG_FORMAT = "%{public}s: %{public}s";

    public static final String HM_FA_ICON = "ohos_fa_icon";

    public static final String HM_FA_NAME = "ohos_fa_name";

    public static final String HM_ABILITY_NAME = "ohos_ability_name";

    public static final String HM_BUNDLE_NAME = "ohos_bundle_name";

    public static final String SHARING_FA_TYPE = "sharing_fa_type";

    public static final String SHARING_THUMB_DATA = "sharing_fa_thumb_data";

    public static final String SHARING_CONTENT_INFO = "sharing_fa_content_info";

    public static final String SHARING_EXTRA_INFO = "sharing_fa_extra_info";

    private static final String TAG = "ShareHmFaManager";

    private static final String SHARE_PKG_NAME = "com.huawei.android.instantshare";

    private static final String SHARE_ACTION = "com.huawei.instantshare.action.THIRD_SHARE";

    private static final long UNBIND_TIME = 20*1000L;

    private Context mContext;

    private String mAppId;

    private PacMapEx mSharePacMap;

    private static ShareFaManager sSingleInstance;

    private HwShareServiceProxy mShareService;

    private boolean mHasPermission = false;

    private EventHandler mHandler = new EventHandler(EventRunner.getMainEventRunner());

    private final IAbilityConnection mConnection = new IAbilityConnection() {
        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) {
        HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "onAbilityConnectDone success.");
        mHandler.postTask(()->{
            mShareService = new HwShareServiceProxy(iRemoteObject);
                try {
                    mShareService.startAuth(mAppId, mFaCallback);
                } catch (RemoteException e) {
                    HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "startAuth error.");
                }
                });
            }

            @Override
            public void onAbilityDisconnectDone(ElementName elementName, int i) {
                HiLog.info(LABEL_LOG, LOG_FORMAT, TAG, "onAbilityDisconnectDone.");
                mHandler.postTask(()->{
                    mShareService = null;
                    mHasPermission = false;
                    });
            }
    };

    private Runnable mTask = () -> {
        if (mContext != null && mShareService != null) {
            mContext.disconnectAbility(mConnection);
            mHasPermission = false;
            mShareService = null;
        }
    };

    private final HwShareCallbackStub mFaCallback = new HwShareCallbackStub("HwShareCallbackStub") {
        @Override
        public void notifyState(int state) throws RemoteException {
            mHandler.postTask(()->{
                HiLog.info(LABEL_LOG, LOG_FORMAT, TAG, "notifyState: " + state);
                if (state == 0) {
                    mHasPermission = true;
                    if (mSharePacMap != null) {
                        shareFaInfo();
                    }
                }
            });
        }
    };

    /**
     * 单例模式获取ShareFaManager的实例对象
     *
     * @param context 程序Context
     * @return ShareFaManager实例对象
     */
    public static synchronized ShareFaManager getInstance(Context context) {
        if (sSingleInstance == null && context != null) {
            sSingleInstance = new ShareFaManager(context.getApplicationContext());
        }
        return sSingleInstance;
    }

    private ShareFaManager(Context context) {
        mContext = context;
    }

    private void shareFaInfo() {
        if (mShareService == null) {
            return;
        }
        if (mHasPermission) {
            HiLog.info(LABEL_LOG, LOG_FORMAT, TAG, "start shareFaInfo.");
            try {
                mShareService.shareFaInfo(mSharePacMap);
                mSharePacMap = null;
            } catch (RemoteException e) {
                HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "shareFaInfo error.");
            }
        }
        // 不使用时断开
        mHandler.postTask(mTask, UNBIND_TIME);
    }

    /**
     * 用于分享服务
     *
     * @param appId 开发者联盟网站创建原子化服务时生成的appid
     * @param pacMap 服务信息载体
     */
    public void shareFaInfo(String appId, PacMapEx pacMap) {
        if (mContext == null) {
            return;
        }
        mAppId = appId;
        mSharePacMap = pacMap;
        mHandler.removeTask(mTask);
        shareFaInfo();
        bindShareService();
    }

    private void bindShareService() {
        if (mShareService != null) {
            return;
        }
        HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "start bindShareService.");
        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withDeviceId("")
            .withBundleName(SHARE_PKG_NAME)
            .withAction(SHARE_ACTION)
            .withFlags(Intent.FLAG_NOT_OHOS_COMPONENT)
            .build();
        intent.setOperation(operation);
        mContext.connectAbility(intent, mConnection);
    }
}

1.4 简单案例

这里我们编写一个简单案例,MainAbilitySlice实现的是从相册里面挑选一张照片,作为卡片信息分享出去,用于介绍要分享的内容给被分享者。

1.4.1 效果

#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
但是这里必须两端都装了该HAP包才能实现,还不满足免安装的效果,必须得至少将原子化服务发布到测试态,后文将会详细介绍。

1.4.2 实现

具体的样式不在这里给出,请参考附件,附上核心代码。
唯一需要主要的是,里面用到的APPID,是在AGC控制台上创建相应项目应用时得到的APPID,可在AGC控制台,我的项目中找到。

package com.huawei.hwshare.third.slice;

import com.huawei.hwshare.third.MainAbility;
import com.huawei.hwshare.third.ResourceTable;
//import com.huawei.hwshare.third.ShareFaManager;
import com.huawei.hwshare.third.ShareFaManager;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.global.resource.NotExistException;
import ohos.global.resource.RawFileDescriptor;
import ohos.interwork.utils.PacMapEx;
import ohos.media.image.ImagePacker;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.photokit.metadata.AVStorage;
import ohos.utils.net.Uri;
import java.io.*;

public class MainAbilitySlice extends AbilitySlice {
    private static  final int imgRequestCode = 1001;
    //核心: 显示分享的图片
    private Image photo;
    private Uri  uri;
    private byte [] picByte;
    InputStream resource;
    private Image cardimage;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        Button btn = (Button) findComponentById(ResourceTable.Id_btn);
        Button btn1 = (Button) findComponentById(ResourceTable.Id_btn1);
        cardimage = (Image) findComponentById(ResourceTable.Id_cardimg1);

        btn1.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                selectPhoto();
            }
        });
        photo = (Image) findComponentById(ResourceTable.Id_photo);
        btn.setClickedListener(new Component.ClickedListener() {
            @Override
            /*华为分享功能的核心代码*/
            public void onClick(Component component) {
                /*数据包*/
                PacMapEx pacMap = new PacMapEx();
                /*分享的服务类型 默认为0 当前也仅支持0*/
                pacMap.putObjectValue(ShareFaManager.SHARING_FA_TYPE, 0);
                /*分享服务的包名,必选参数*/
                pacMap.putObjectValue(ShareFaManager.HM_BUNDLE_NAME, getBundleName());
                /*额外的信息 非必选*/
                pacMap.putObjectValue(ShareFaManager.SHARING_EXTRA_INFO, "原子化服务分享");
                /*分享的服务的Ability类名,必选参数*/
                pacMap.putObjectValue(ShareFaManager.HM_ABILITY_NAME, MainAbility.class.getName());
                /*卡片展示的服务介绍信息,必须参数*/
                pacMap.putObjectValue(ShareFaManager.SHARING_CONTENT_INFO, "分享成功!");
                /*卡片展示服务介绍图片,最大长度153600,必选参数。*/
                pacMap.putObjectValue(ShareFaManager.SHARING_THUMB_DATA, picByte);
                // byte[] iconImg = getResourceBytes(ResourceTable.Media_icon);
                /*服务图标  非必选参数*/
                //pacMap.putObjectValue(com.huawei.hwshare.third.ShareFaManager.HM_FA_ICON, iconImg);
                // pacMap.putObjectValue(com.huawei.hwshare.third.ShareFaManager.HM_FA_ICON, iconByte);
                /*卡片展示的服务名称*/
                pacMap.putObjectValue(ShareFaManager.HM_FA_NAME, "华为分享服务测试");
                // 第一个参数为appid,在华为AGC创建原子化服务时自动生成。
                ShareFaManager.getInstance(MainAbilitySlice.this).shareFaInfo("942536802034526976", pacMap);
            }
        });

    }
    private byte[] getResourceBytes(int resId) {

        ByteArrayOutputStream outStream = null;

        try {
            resource = getResourceManager().getResource(resId);
            outStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = resource.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            outStream.close();
            resource.close();
            return outStream.toByteArray();
        } catch (IOException | NotExistException e) {
            // HiLog.error(TAG, "get resource occurs io exception!");
        } finally {
            if (resource != null) {
                try {
                    resource.close();
                } catch (IOException e) {
                    //   HiLog.error(TAG, "close input stream occurs io exception!");
                }
            }
            if (outStream != null) {
                try {
                    resource.close();
                } catch (IOException e) {
                    //  HiLog.error(TAG, "close output stream occurs io exception!");
                }
            }
        }
        return null;
    }

    //选择图片
    private void selectPhoto() {
        //调起系统的选择来源数据视图
        Intent intent = new Intent();
        Operation opt=new Intent.OperationBuilder().withAction("android.intent.action.GET_CONTENT").build();
        intent.setOperation(opt);
        intent.addFlags(Intent.FLAG_NOT_OHOS_COMPONENT);
        intent.setType("image/*");
        startAbilityForResult(intent, imgRequestCode);
    }
    /*选择图片回调*/
    @Override
    protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
        if(requestCode==imgRequestCode && resultData!=null)
        {
            //被选择的图片的uri地址
            String img_uri=resultData.getUriString();
            //定义数据能力帮助对象
            DataAbilityHelper helper=DataAbilityHelper.creator(getContext());
            //定义图片来源对象
            ImageSource imageSource = null;
            //选择的Img对应的Id
            String img_id=null;
            /*
             *如果是选择文件则getUriString结果为dataability:///com.android.providers.media.documents/document/image%3A437,其中%3A437是":"的URL编码结果,后面的数字就是image对应的Id
             */
            /*
             *如果选择的是图库则getUriString结果为dataability:///media/external/images/media/262,最后就是image对应的Id
             */
            //判断是选择了文件还是图库
            if(img_uri.lastIndexOf("%3A")!=-1){
                img_id = img_uri.substring(img_uri.lastIndexOf("%3A")+3);
            }
            else {
                img_id = img_uri.substring(img_uri.lastIndexOf('/')+1);
            }
            //获取图片对应的uri,前缀是content,替换成对应的dataability前缀
            uri=Uri.appendEncodedPathToUri(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI,img_id);
            try {
                //读取图片
                FileDescriptor fd = helper.openFile(uri, "r");
                //RawFileDescriptor rfd = helper.openRawFile(uri,"r");
                imageSource = ImageSource.create(fd, null);
                //创建位图
                PixelMap pixelMap = imageSource.createPixelmap(null);
                //组件显示选择的图片
                photo.setPixelMap(pixelMap);
                picByte = new byte[153600];
                //打包图片
                ImagePacker packer = null;
                try{
                    packer= ImagePacker.create();
                    ImagePacker.PackingOptions packingOptions = new ImagePacker.PackingOptions();
                    //图片格式信息
                    packingOptions.format = "image/jpeg";
                    //图片质量
                    packingOptions.quality = 90;
                    //打包
                    packer.initializePacking(picByte, packingOptions);
                    packer.addImage(pixelMap);
                    //完成打包
                    packer.finalizePacking();


                }finally {
                    packer.release();

                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (imageSource != null) {
                    imageSource.release();
                }
            }
        }
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

2. 测试原子化服务

2.1 手动签名

首先我们对工程进行手动签名,得到.cer和.p12文件,这里不再赘述手动签名。
手动签名

2.2 申请原子化服务

  • 进入华为开发者联盟,进入管理中心,点击智慧服务,进入HarmonyOS服务开放平台。
    #夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

2.3 创建服务

#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
箭头所指,填工程包名即可,其他自拟,点击确定
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

至此,服务创建完成,接下来配置服务。

2.4 申请证书

这里要申请两个证书,.csr和.p7b.

2.4.1 申请.csr证书

#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
申请完证书后,记得点击下载,我们会获得一个.cer证书
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

2.4.2 申请.p7b证书

#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
申请完后一定记得点击下载,我们会获得.p7b证书
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

至此,我们手上有.p12,.p7b,.cer,.csr四种证书,这里非常重要!

2.5 工程签名

这里用到刚刚申请好的证书,在release一栏进行签名,不是debug哦!
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
接着编译整个工程,是build APP,不是build hap哦!
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
这样,我们就获得了APP包!
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
到这里,我们就能够理解实现原子化服务为啥要APP包,说白了还是把APP上传服务器,用的时候再下载,只不过体积小,实现了无感安装的过程!

2.6 发布为测试态

我们回到刚刚的服务平台,继续配置服务。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

2.6.1 上传APP包

上传刚刚编译好的APP包,这里由于我已经上传过了,界面稍微不一样,总之就是有个按钮上传就完事了。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

2.6.2 其余信息

其余信息大部分自拟,如果是要最终实现发布的话,要涉及到很多证书或者专利,如果是只是为了简单测试,就可以像我这样(乱填)填一些信息就行了,关键的在后面。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

2.6.2 分发

这里提供了几种找到该服务的方式,这里根据个人情况探索即可。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

2.6.3 测试

这里才是第二重要的地方了,我们需要添加测试设备。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
新增一个组别,信息自拟。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
点击查看,添加测试设备的手机号
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
最后返回到测试界面,点击保存。
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
最最最后,发布为测试态
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区

3.结果

完成前文所有步骤后,稍等5-10分钟,大概就能在测试设备的服务中心看到我们制作的原子化服务了。从屏幕右下角往屏幕中心划,可呼出服务中心,注意看图
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区#夏日挑战赛#【FFH】从零实现原子服务一键分享,免安装!-鸿蒙开发者社区
至此,我们真正实现了免安装功能

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
ShareCard.zip 2.41M 18次下载
已于2022-7-25 20:56:52修改
6
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

通过原子化服务感觉以后的手机只用承担显示和联网的功能,处理等功能都可以交给服务器了。

回复
2022-7-24 18:31:03
回复
    相关推荐