From Zero to One: A Complete Guide to Implementing WeChat Sharing in HarmonyOS
Introduction
Hello everyone, I'm Jun Moxiao from the Qinglan Code Organization.
As the HarmonyOS ecosystem continues to flourish, various WeChat functionalities can now be integrated. Today, I'll teach you how to implement all WeChat sharing features, primarily including WeChat mini-program sharing and WeChat H5 sharing. This guide will focus on the key code components, and you'll need to combine them into a utility class on your own.
First, Integrate the WeChat SDK
Simply enter ohpm i @tencent/wechat_open_sdk in the terminal to include the WeChat SDK dependency.
Create a HarmonyOS Application on the WeChat Platform (Skip if Already Created)
Preparation for WeChat Sharing Implementation
Add the following fields to module.json5:
Create a WX Instance (The Earlier the Better)
// WXApi is the openApi interface for communication between third-party apps and WeChat. Its instance is obtained through WXAPIFactory and requires the AppID that the application has applied for
export const WXApi = wxOpenSdk.WXAPIFactory.createWXAPI(APP_ID)
Note:
- The appID is the same as the Android application's appID on the WeChat platform. You can reuse it directly after creating a HarmonyOS application.
Now Let's Implement the WeChat Sharing Utility Class (Building the Foundation, Combine the Complete Utility Class on Your Own)
/**
* WeChat Sharing
*/
export class WxShareViewModel {
// Replace with your actual appID
WXApi = wxOpenSdk.WXAPIFactory.createWXAPI('')
static instance: WxShareViewModel | null = null
isWXApp: boolean = this.WXApi.isWXAppInstalled()
private constructor() {
}
static getInstance() {
if (!WxShareViewModel.instance) {
WxShareViewModel.instance = new WxShareViewModel()
}
return WxShareViewModel.instance
}
}
Note:
- I recommend implementing utility classes as singletons, which is more concise and prevents encapsulation from being contaminated.
Implement Text Sharing
/**
* Share text
* @param text Text content
*/
textShare(text: string) {
this.isWXAPPCallback(() => {
let textObject = new wxOpenSdk.WXTextObject
textObject.text = text
let mediaMessage = new wxOpenSdk.WXMediaMessage()
mediaMessage.mediaObject = textObject
let req = new wxOpenSdk.SendMessageToWXReq()
req.scene = wxOpenSdk.SendMessageToWXReq.WXSceneSession
req.message = mediaMessage
this.WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
})
}
Implement H5 Sharing
1. It's important to note that when sharing H5 pages or mini-programs with images, you need to download the image buffer and compress it to below 64KB for sharing, otherwise it will result in errors with no response.
2. Let's first implement buffer compression to below 64KB
I highly recommend the compression algorithm from the Huawei official website
Note:
- I won't demonstrate other compression methods here, only buffer format compression, since downloaded images are returned as buffers. We can directly compress the buffer to below 64KB and pass it to the SDK without further expansion.
- I've modified Huawei's official example to make it directly usable for compressing buffer formats.
import { image } from '@kit.ImageKit';
/**
* Image compression and saving
* @param sourcePixelMap: Original PixelMap object of the image to be compressed
* @param maxCompressedImageSize: Specified target size for image compression, in KB
* @returns compressedImageInfo: Returns the final compressed image information
*/
export async function compressedImage(sourcePixelMap: image.PixelMap,
maxCompressedImageSize: number): Promise<ArrayBuffer> {
// Create ImagePacker object for image encoding
const imagePackerApi = image.createImagePacker();
const IMAGE_QUALITY = 0;
const packOpts: image.PackingOption = { format: "image/jpeg", quality: IMAGE_QUALITY };
// Encode through PixelMap. compressedImageData is the image file stream obtained from packing.
let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
// Target compressed image byte length
const maxCompressedImageByte = maxCompressedImageSize * 1024;
// Image compression. First determine if the minimum byte size that can be compressed by packing with quality parameter set to 0 meets the specified image compression size. If it does, use packing to binary search for the quality closest to the specified image compression target size. If not, use scale to first resize the image, using a while loop to decrease the scale by 0.4 times each iteration, then use packing (with quality parameter set to 0) to get the compressed image size, ultimately finding the scaling factor closest to the specified image compression target size.
if (maxCompressedImageByte > compressedImageData.byteLength) {
// Use binary packing compression to get the image file stream
compressedImageData =
await packingImage(compressedImageData, sourcePixelMap, IMAGE_QUALITY, maxCompressedImageByte);
} else {
// Use scale to first resize the image, using a while loop to decrease the scale by 0.4 times each iteration, then use packing (with quality parameter set to 0) to get the compressed image size
let imageScale = 1;
const REDUCE_SCALE = 0.4;
// Check if the compressed image size is greater than the specified target size; if so, continue reducing the scale factor.
while (compressedImageData.byteLength > maxCompressedImageByte) {
if (imageScale > 0) {
// Performance note: Since scale directly modifies the image PixelMap data, binary search for scale factor is not suitable. Here we use a loop to decrease by 0.4 times each iteration to determine the most suitable scale factor. If image compression quality requirements are not high, consider increasing the reduceScale value to reduce loops and improve scale compression performance.
imageScale = imageScale - REDUCE_SCALE;
await sourcePixelMap.scale(imageScale, imageScale);
compressedImageData = await packing(sourcePixelMap, IMAGE_QUALITY);
} else {
// When imageScale is less than or equal to 0, it has no meaning, so end compression. We don't consider cases where the image scale factor is less than reduceScale.
break;
}
}
}
return compressedImageData;
}
/**
* Packing compression
* @param sourcePixelMap: Original PixelMap of the image to be compressed
* @param imageQuality: Image quality parameter
* @returns data: Returns the compressed image 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;
}
/**
* Binary packing compression method
* @param compressedImageData: ArrayBuffer of the compressed image
* @param sourcePixelMap: Original PixelMap of the image to be compressed
* @param imageQuality: Image quality parameter
* @param maxCompressedImageByte: Target compressed image byte length
* @returns compressedImageData: Returns the image data compressed by binary packing
*/
async function packingImage(compressedImageData: ArrayBuffer, sourcePixelMap: image.PixelMap, imageQuality: number,
maxCompressedImageByte: number): Promise<ArrayBuffer> {
// Image quality parameter range is 0-100, here we create an array for binary packing image quality parameters with 10 as the minimum binary unit.
const packingArray: number[] = [];
const DICHOTOMY_ACCURACY = 10;
// Performance note: If image compression quality requirements are not high, consider increasing the minimum binary unit dichotomyAccuracy to reduce loops and improve packing compression performance.
for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {
packingArray.push(i);
}
let left = 0;
let right = packingArray.length - 1;
// Binary compression of the image
while (left <= right) {
const mid = Math.floor((left + right) / 2);
imageQuality = packingArray[mid];
// Perform packing compression based on the input image quality parameter, returning the compressed image file stream data.
compressedImageData = await packing(sourcePixelMap, imageQuality);
// Find a compression size as close as possible to but not exceeding the target
if (compressedImageData.byteLength <= maxCompressedImageByte) {
left = mid + 1;
if (mid === packingArray.length - 1) {
break;
}
// Get the image file stream data compressed with the next binary image quality parameter (mid+1)
compressedImageData = await packing(sourcePixelMap, packingArray[mid + 1]);
// Check if the image size compressed with the next image quality parameter (mid+1) is greater than the specified target size. If so, it means the current image quality parameter (mid) produces an image size closest to the target. Use the current mid parameter to get the final target compressed image data.
if (compressedImageData.byteLength > maxCompressedImageByte) {
compressedImageData = await packing(sourcePixelMap, packingArray[mid]);
break;
}
} else {
// The target value is not in the right half of the current range, move the right boundary of the search range to the left to narrow the search range and continue searching the left half in the next iteration.
right = mid - 1;
}
}
return compressedImageData;
}
3. Implement Image Download
export 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) => {
successCallBack(arrayBuffer)
})
})
}
})
}
Note:
- Download images using createHttp, with the url parameter being the actual network address
- The second parameter, successCallBack, is the success callback function that processes the compressed arrayBuffer after a successful network request
- Since Huawei's official example cannot directly compress arrayBuffer and requires pixelMap format, we need to convert arrayBuffer to pixelMap format
4. Implement WeChat Installation Check Callback Function
isWXAPPCallback(callback: Function) {
if (!this.isWXApp) {
AlertDialog.show({ message: JSON.stringify("Please install WeChat first") })
} else {
callback()
}
}
5. Core Preparation Work is Complete, Now Let's Implement H5 Sharing
/**
* Share H5
* @param Url Web page address
* @param title Title
* @param description Description
* @param imgUrl Image network address
*/
h5Share(Url: string, title: string, description: string, imgUrl: string) {
this.isWXAPPCallback(async () => {
const webpageObject = new wxOpenSdk.WXWebpageObject()
webpageObject.webpageUrl = Url
const mediaMessage = new wxOpenSdk.WXMediaMessage()
mediaMessage.mediaObject = webpageObject
mediaMessage.title = title
mediaMessage.description = description
loadImageUrl(imgUrl, async (buffer) => {
mediaMessage.thumbData = new Uint8Array(buffer)
const req = new wxOpenSdk.SendMessageToWXReq()
req.scene = wxOpenSdk.SendMessageToWXReq.WXSceneSession
req.message = mediaMessage
this.WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
})
})
}
Note:
- Wrap with the WeChat installation check callback function before executing the core code
- Finally, use the image buffer download function, pass in the function after successful callback, and pass the compressed buffer to the SDK for sharing
Implement WeChat Mini-Program Sharing
/**
* Share mini-program
* @param userName Original ID of the mini-program (in gh_xxxx format)
* @param path Path of the mini-program
* @param miniprogramType Type of mini-program, default is release version
* @param title Title
* @param description Description
* @param imgUrl Network image address
*/
miniProgramShare(userName: string, path: string, miniprogramType: number, title: string, description: string,
imgUrl: string) {
this.isWXAPPCallback(async () => {
const miniProgramObject = new wxOpenSdk.WXMiniProgramObject()
miniProgramObject.userName = userName
miniProgramObject.path = path
miniProgramObject.miniprogramType = miniprogramType
const mediaMessage = new wxOpenSdk.WXMediaMessage()
mediaMessage.mediaObject = miniProgramObject
mediaMessage.title = title
mediaMessage.description = description
loadImageUrl(imgUrl, async (buffer) => {
mediaMessage.thumbData = new Uint8Array(buffer)
const req = new wxOpenSdk.SendMessageToWXReq()
req.scene = wxOpenSdk.SendMessageToWXReq.WXSceneSession
req.message = mediaMessage
this.WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
})
})
}
Note:
- The image compression function works the same as for H5 sharing
- Most parameters are provided by the API
Top comments (0)