天天看點

#夏日挑戰賽#【FFH】從零實作原子服務一鍵分享,免安裝!

[本文正在參加星光計劃3.0-夏日挑戰賽]

本文将介紹如何實作,通過華為分享來分享原子化服務,以及如何上架原子化服務進而實作服務的免安裝

0. 前言

  • 原子化服務是鴻蒙的一大特性,在服務中心可以看見許多以卡片形式呈現的原子化服務,這些服務體積小,能夠快速部署到手機上實作功能,達到免安裝的效果。
  • 同樣的,原子化服務另外一種呈現方式就是基于華為分享的,例如A同學希望分享他在京東上看到的一件商品,他可以通過華為分享将該服務頁面快速遷移到B同學的手機上,而B同學的手機上并沒有安裝京東,也能看到呈現畫面。
這裡不知道是網絡還是應用出現了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】從零實作原子服務一鍵分享,免安裝!

至此,我們真正實作了免安裝功能

附件連結:https://ost.51cto.com/resource/2215

繼續閱讀