鸿蒙-微信分享功能

因为代码层面和鸿蒙签名为主要需要解决的问题,所以以下回答分为2个方面概述:

1.代码层面

  1.1鸿蒙接入微信:主要看微信提供的官方文档https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/ohos.html  

import { EmitterUtil, ToastUtil } from '@pura/harmony-utils';
import * as WxOpenSDK from '@tencent/wechat_open_sdk';
import { WX_PAY_SUCCESS } from '../constants/emitEvent';

export type OnWXReq = (req: WxOpenSDK.BaseReq) => void

export type OnWXResp = (resp: WxOpenSDK.BaseResp) => void

/* 微信开放平台APPID */
export const WECHAT_APP_ID = "微信开放平台中提供的appid";

const kTag = "WXApiEventHandlerImpl";

class WXApiEventHandlerImpl implements WxOpenSDK.WXApiEventHandler {
  private onReqCallbacks: Map<OnWXReq, OnWXReq> = new Map
  private onRespCallbacks: Map<OnWXResp, OnWXResp> = new Map

  registerOnWXReqCallback(on: OnWXReq) {
    this.onReqCallbacks.set(on, on)
  }

  unregisterOnWXReqCallback(on: OnWXReq) {
    this.onReqCallbacks.delete(on)
  }

  registerOnWXRespCallback(on: OnWXResp) {
    this.onRespCallbacks.set(on, on)
  }

  unregisterOnWXRespCallback(on: OnWXResp) {
    this.onRespCallbacks.delete(on)
  }

  onReq(req: WxOpenSDK.BaseReq): void {
    WxOpenSDK.Log.i(kTag, "onReq:%s", JSON.stringify(req))
    this.onReqCallbacks.forEach((on) => {
      on(req)
    })
  }

  onResp(resp: WxOpenSDK.BaseResp): void {
    WxOpenSDK.Log.i(kTag, "onResp:%s", JSON.stringify(resp))
    this.onRespCallbacks.forEach((on) => {
      on(resp)
    })
    if (resp.errCode == 0) {
      EmitterUtil.post(WX_PAY_SUCCESS, '')
    } else {
     ToastUtil.showToast('微信支付失败')
    }
  }
}

export const WXApi = WxOpenSDK.WXAPIFactory.createWXAPI(WECHAT_APP_ID)

export const WXEventHandler = new WXApiEventHandlerImpl

  1.2编写调用微信工具包的类

import { WXApi } from '../model/WXApiWrap';
import * as wxopensdk from '@tencent/wechat_open_sdk';
import { image } from '@kit.ImageKit';
import { common } from '@kit.AbilityKit';
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { DialogHelper } from '@pura/harmony-dialog';

interface WebpageObject {
  webpageUrl: string,
  title: string,
  description: string,
  image: string
}

/**
 * packing压缩
 * @param sourcePixelMap:原始待压缩图片的PixelMap
 * @param imageQuality:图片质量参数
 * @returns data:返回压缩后的图片数据
 */
async function packing(sourcePixelMap: image.PixelMap, imageQuality: number): Promise<ArrayBuffer> {
  const imagePackerApi = image.createImagePacker();
  const packOpts: image.PackingOption = { format: "image/jpeg", quality: imageQuality };
  const data: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
  return data;
}

/**
 * packing二分方式循环压缩
 * @param compressedImageData:图片压缩的ArrayBuffer
 * @param sourcePixelMap:原始待压缩图片的PixelMap
 * @param imageQuality:图片质量参数
 * @param maxCompressedImageByte:压缩目标图像字节长度
 * @returns compressedImageData:返回二分packing压缩后的图片数据
 */
async function packingImage(compressedImageData: ArrayBuffer, sourcePixelMap: image.PixelMap, imageQuality: number,
  maxCompressedImageByte: number): Promise<ArrayBuffer> {
  // 图片质量参数范围为0-100,这里以10为最小二分单位创建用于packing二分图片质量参数的数组。
  const packingArray: number[] = [];
  const DICHOTOMY_ACCURACY = 10;
  // 性能知识点: 如果对图片压缩质量要求不高,建议调高最小二分单位dichotomyAccuracy,减少循环,提升packing压缩性能。
  for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {
    packingArray.push(i);
  }
  let left = 0;
  let right = packingArray.length - 1;
  // 二分压缩图片
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    imageQuality = packingArray[mid];
    // 根据传入的图片质量参数进行packing压缩,返回压缩后的图片文件流数据。
    compressedImageData = await packing(sourcePixelMap, imageQuality);
    // 判断查找一个尽可能接近但不超过压缩目标的压缩大小
    if (compressedImageData.byteLength <= maxCompressedImageByte) {
      left = mid + 1;
      if (mid === packingArray.length - 1) {
        break;
      }
      // 获取下一次二分的图片质量参数(mid+1)压缩的图片文件流数据
      compressedImageData = await packing(sourcePixelMap, packingArray[mid + 1]);
      // 判断用下一次图片质量参数(mid+1)压缩的图片大小是否大于指定图片的压缩目标大小。如果大于,说明当前图片质量参数(mid)压缩出来的图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid,得到最终目标图片压缩数据。
      if (compressedImageData.byteLength > maxCompressedImageByte) {
        compressedImageData = await packing(sourcePixelMap, packingArray[mid]);
        break;
      }
    } else {
      // 目标值不在当前范围的右半部分,将搜索范围的右边界向左移动,以缩小搜索范围并继续在下一次迭代中查找左半部分。
      right = mid - 1;
    }
  }
  return compressedImageData;
}

/**
 * 图片压缩,保存
 * @param sourcePixelMap:原始待压缩图片的PixelMap对象
 * @param maxCompressedImageSize:指定图片的压缩目标大小,单位kb
 * @returns compressedImageInfo:返回最终压缩后的图片信息
 */
async function compressedImage(sourcePixelMap: image.PixelMap,
  maxCompressedImageSize: number): Promise<ArrayBuffer> {
  // 创建图像编码ImagePacker对象
  const imagePackerApi = image.createImagePacker();
  const IMAGE_QUALITY = 0;
  const packOpts: image.PackingOption = { format: "image/jpeg", quality: IMAGE_QUALITY };
  // 通过PixelMap进行编码。compressedImageData为打包获取到的图片文件流。
  let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
  // 压缩目标图像字节长度
  const maxCompressedImageByte = maxCompressedImageSize * 1024;
  // 图片压缩。先判断设置图片质量参数quality为0时,packing能压缩到的图片最小字节大小是否满足指定的图片压缩大小。如果满足,则使用packing方式二分查找最接近指定图片压缩目标大小的quality来压缩图片。如果不满足,则使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。
  if (maxCompressedImageByte > compressedImageData.byteLength) {
    // 使用packing二分压缩获取图片文件流
    compressedImageData =
      await packingImage(compressedImageData, sourcePixelMap, IMAGE_QUALITY, maxCompressedImageByte);
  } else {
    // 使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据
    let imageScale = 1;
    const REDUCE_SCALE = 0.4;
    // 判断压缩后的图片大小是否大于指定图片的压缩目标大小,如果大于,继续降低缩放倍数压缩。
    while (compressedImageData.byteLength > maxCompressedImageByte) {
      if (imageScale > 0) {
        // 性能知识点: 由于scale会直接修改图片PixelMap数据,所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片,来查找确定最适合的缩放倍数。如果对图片压缩质量要求不高,建议调高每次递减的缩放倍数reduceScale,减少循环,提升scale压缩性能。
        imageScale = imageScale - REDUCE_SCALE;
        await sourcePixelMap.scale(imageScale, imageScale);
        compressedImageData = await packing(sourcePixelMap, IMAGE_QUALITY);
      } else {
        // imageScale缩放小于等于0时,没有意义,结束压缩。这里不考虑图片缩放倍数小于reduceScale的情况。
        break;
      }
    }
  }
  return compressedImageData;
}

function loadImageUrl(url: string, successCallBack: (ImgArrayBuffer: ArrayBuffer) => void) {
  http.createHttp()
    .request(url,
      {
        method: http.RequestMethod.GET,
      },
      async (error: BusinessError, data: http.HttpResponse) => {
        if (http.ResponseCode.OK === data.responseCode) {
          let imageBuffer: ArrayBuffer = data.result as ArrayBuffer;
          const imageSource = image.createImageSource(imageBuffer)
          imageSource.createPixelMap()
            .then((pixelMap: image.PixelMap) => {
              compressedImage(pixelMap, 64)
                .then((arrayBuffer: ArrayBuffer) => {
                  successCallBack(arrayBuffer)
                })
            })
        }
      })
}


class WxSharp {
  isWXApp: boolean = WXApi.isWXAppInstalled()

  isWXAPPCallback(callback: Function) {
    if (!this.isWXApp) {
      DialogHelper.showToast("请先安装微信");
    } else {
      callback()
    }
  }

  wxSharpWebPage(options:WebpageObject) {
    this.isWXAPPCallback(async () => {
      const webpageObject = new wxopensdk.WXWebpageObject()
      webpageObject.webpageUrl = options.webpageUrl

      const mediaMessage = new wxopensdk.WXMediaMessage()
      mediaMessage.mediaObject = webpageObject
      mediaMessage.title = options.title
      mediaMessage.description = options.description

      loadImageUrl(options.image, async (buffer) => {
        mediaMessage.thumbData = new Uint8Array(buffer)
        const req = new wxopensdk.SendMessageToWXReq()
        req.scene = wxopensdk.SendMessageToWXReq.WXSceneSession
        req.message = mediaMessage
        WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
      })
    })
  }
}

export const WxSharpHandler = new WxSharp

  1.3调用微信分享工具类

        WxSharpHandler.wxSharpWebPage({
          webpageUrl:'分享链接',
          title: '分享标题。',
          description: ' ',
          image: '分享图片'
        })
       })

2.此时你把代码都写好了,跃跃欲试,使用真机去测试,发现跳转到微信会提示“第三方应用信息校验失败”,发现有2个方向:

一个官方提供的答案

首先你要安装hdc工具,然后执行该命令,结果失败

Set parameter persist.sys.abilityms.support.start_other_app true fail! errNum is:1001!

chatgpt提示hdc无法修改权限所以报错,需要root才能修改,直接失败!

尝试第二条路:手动签名

官方提供步骤:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-signing-V13

这里需要一步一步按步骤下来,稍微错一点,全是坑(走官方步骤需要注册agc,并且注册好应用等一系列东西)

坑点1: 真的需要一步一步按步骤来!

每一个生成的文件需要一一对应!

你申请的证书要对应生成的scr文件!

你申请的profile文件,要去生成对应的证书!

一点偏差都不可以!

坑点2:你生成的p12文件和scr文件需要自己加后缀!否则无法识别!会提示一个莫名其妙的错误让你一顿好找!

坑点3:生成的证书和profile文件不可以改名字!否则也识别不出来!

坑点4:需要把之前的应用卸载,否则也打包失败!

 

  

posted on 2025-06-05 17:24  ChoZ  阅读(243)  评论(0)    收藏  举报

导航