阅读

微信分享前端开发全程详解含iOS、安卓、H5、ReactNative以及微信开放标签的适配和使用

编者按: 红宝书第5版2024年12月1日出炉了,涵盖了JS最新版本ES15的特性,感兴趣的可以去看看,https://u.jd.com/saQw1vP 2024年9月,本人在做微信分享前端部分的iOS、安卓和H5的页面和功能时踩了不少坑,于是写了这篇文章,内容包括微信分享在上面三个端的技术点和坑点、解决办法,微信开放标签的相关适配,以及ReactNative的特别处理部分。

重要通知:红宝书第5版2024年12月1日出炉了,涵盖了JS最新版本ES15的特性,感兴趣的可以去看看,https://u.jd.com/saQw1vP

红宝书第五版中文版

红宝书第五版英文原版pdf下载(访问密码: 9696)

一、前期准备:微信开放平台、微信公众平台注册并审核通过

1、微信开放平台入口
2、微信公众平台入口
3、官网域名,分享用网页的域名(可以是二级域名)部署完成,ICP备案完成。
Tip1:微信开放平台认证一次性的三百。公众平台首次认证三百,认证到期后每年需要再年审三百。平台网页:https://developers.weixin.qq.com/community/develop/doc/000c040991c220d8dcea6324e56000
Tip2:官网域名用于iOS的Universal Links,分享用网页的域名用于放到js接口安全域名里面,凡是在js接口安全域名里的网页在微信里打开都可以使用开放标签和对应的微信js接口。后面会说怎么放。
Tip3:注册和申请这两个平台的同时,移动应用也添加和关联进去,后面要用到APPID,另外移动应用需要开启微信分享功能,详见文档。

二、客户端SDK集成:

1、友盟分享集成 开发文档
2、微信官网自行集成 开发文档
本人用的是友盟分享集成,也可以微信官网自行集成,如果友盟分享已经集成了那至少微信文档里SDK的集成这一步已经完成了。
不要遗漏文档里任何一处,以免后面遇到不该遇到的坑。
注意iOS需要配置Universal Links:参考文档

有个需要服务端或运维小伙伴配合的步骤,就是创建个文件名为apple-app-site-association的文件,没有后缀,里面的内容如下,

根据自己的参数来修改里面appID和paths的内容
{
"applinks": {
"apps": [],
"details": [
{
"appID":"苹果teamID.com.aaa.bbb"
"paths": ["/app/*"]
}
]
}
}

然后让后端或者运维小伙伴,把这个文件放在官网的服务器根目录下。

比如服务器地址是: https://www.baidu.com/ ,把文件放在这根目录下后,访问 https://www.baidu.com/apple-app-site-association ,这文件就会被下载下来。
然后根目录新建一个.well-known文件夹,里面也放一份这个文件,这样会先去根目录找,如果没找到还会去.well-known文件夹找

三、微信分享H5页面开发(本人用的是Vue2)

H5开发需要适配两个端:手机浏览器端和微信浏览器端

1、手机浏览器的适配比较简单,跳转APP采用Schema逻辑(iOS和安卓均可)。

H5
// 打开app 目前采用的方法 用于适配非微信端打开的网页跳转app并传参
openApp() {
const sendInfo = {
key: value
}
const schemaInfo = JSON.stringify(sendInfo)
const appScheme = 'yourSchema://yourshare域名/pagePath?schemaInfo=' + schemaInfo // 替换为你的 app 的 URL scheme
const fallbackUrl = '应用宝链接' // 替换为你的 app 在应用商店的链接


const u = navigator.userAgent
const isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
if (isiOS) {
window.location.href = appScheme // ios对应的app协议
setTimeout(function () {
let hidden = window.document.hidden || window.document.mozHidden || window.document.msHidden || window.document.webkitHidden
if (typeof hidden === 'undefined' || hidden == false) {
// App store下载地址
window.location.href = fallbackUrl
}
}, 500)
} else {
window.location.href = appScheme // 安卓对应的app协议
setTimeout(function () {
let hidden = window.document.hidden || window.document.mozHidden || window.document.msHidden || window.document.webkitHidden
if (typeof hidden === 'undefined' || hidden == false) {
// 应用宝下载地址
window.location.href = fallbackUrl
}
}, 1500)
}
},

2、微信浏览器端的适配,需要使用微信的开放标签来处理H5和APP的跳转逻辑

微信开放标签:

1、绑定域名加入JS接口安全域名
登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
2、使用前需将「 JS 接口安全域名绑定的服务号」绑定在「移动应用的微信开放平台账号」下,并确保服务号与此开放平台账号同主体且均已认证。请前往 微信开放平台-管理中心-公众号详情-接口信息 设置域名与所需跳转的移动应用。
具体流程参考这篇说明文档:

https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_H5_Launch_APP.html

3、根据开发文档开发适配微信端网页开放标签,具体流程参考这篇文档:

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html

注意:
1、安全角度考虑,下面这段所需的参数从服务器接口获取,不要放前端
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [], // 必填,需要使用的JS接口列表
openTagList: [] // 可选,需要使用的开放标签列表,例如['wx-open-launch-app']
});
2、网页的域名和审核通过的js安全域名Host完全一致 + 第一点里面的config初始化成功 + 要在微信里面打开此网页 = 能显示微信开放标签里的内容且点击能跳转到APP,缺一不可
3、开放标签里放不了本地的图,本地的图可以上传到oss上生成链接的形式展示
<wx-open-launch-app
v-if="iswx"
id="launch-btn"
style="width:100%; position: relative; display: flex; flex-direction: row; align-items: center; justify-content: center;height: 44px; background-color: #F7F8FA"
:extinfo="getSchemaInfo"
appid="yourWxAppId"
@error="handleError"
@launch="handleLaunch"
>
<component is="script" type="text/wxtag-template">
<div style="position: relative; display: flex; flex-direction: row; align-items: center; justify-content: center; height: 44px"
@click="onButtonClick">
<div style="font-size: 14px; font-weight: 400; color: #388BFF; line-height: 20px; text-align: center">固定在底部的条条</div>
<img style="width: 14px; height: 14px; margin-left: 4px;" src="https://yourResources.oss-cn-hangzhou.aliyuncs.com/yourPic.png" alt="">
</div>
</component>
</wx-open-launch-app>
4、开放标签里不能使用postion为fixed和absolute的样式,无效,如果需要可以在开放标签里设置style,加fixed或absolute

参考5里面的代码

5、一个取巧的办法是开放标签可以是透明的盖住普通浏览器样式的页面组件,实现微信端点击跳转APP且无需适配开放标签里面的样式
<wx-open-launch-app
v-if="iswx"
id="launch-btn"
style="max-width: 100%; height: 68px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #F1F2F4; position: fixed; top: 0; left: 0; right: 0; width: 100%; margin: auto; z-index: 99;"
@error="handleError"
@launch="handleLaunch"
:extinfo="getSchemaInfo"
appid="yourWxAppId"
>
<script type="text/wxtag-template">
<div style="opacity: 0; width: 100%; height: 68px;"></div>
</script>
</wx-open-launch-app>
6 也可以根据网页是否在微信里打开的来进一步适配浏览器和微信的页面

如上的代码里有个iswx

let ua = window.navigator.userAgent.toLowerCase()
// 通过正则表达式匹配ua中是否含有MicroMessenger字符串
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
this.iswx = true
} else {
this.iswx = false
}

四、客户端处理跳转逻辑

参考开发文档

1、安卓部分 - 浏览器通过schema跳转

安卓 MainActivity.java文件里
@Override
public void onNewIntent(Intent intent){
super.onNewIntent(intent);
setIntent(intent);
}

@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();

if (!intentHex.equals("") && intentHex.equals(Integer.toHexString(System.identityHashCode(intent)))) {
// 获取 scheme 名称 重复的intent 不予处理
} else {
intentHex = Integer.toHexString(System.identityHashCode(intent));
String scheme = intent.getScheme();
Uri uri = intent.getData();
if(scheme!= null && uri != null) {
if (uri.getScheme().equals("yourSchema")) {
String schemaInfo = uri.getQueryParameter("schemaInfo");
try {
// 原生的可以处理跳转逻辑,ReactNative的这里可以发送到RN端
MainApplication application = (MainApplication) this.getApplicationContext();

WritableMap map = Arguments.createMap();
map.putString("schemaInfo", schemaInfo);

ReactInstanceManager reactInstanceManager = application.getReactNativeHost().getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if(reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("Demo_EmitterKey", map);
} else {
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {

Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
/**
*要执行的操作
*/

context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("Demo_EmitterKey", map);
}
}, 4000);//4秒后执行Runnable中的run方法

reactInstanceManager.removeReactInstanceEventListener(this);

}
});
}

} catch (SecurityException e) {

// TODO: Display a yellow box about this
}

}
}
}


}

安卓部分 - 微信页面通过开放标签跳转

WXEntryActivity文件里

/**
* 从微信启动App
*
* @param req
*/
@Override
public void onReq(BaseReq req) {
super.onReq(req);

//获取开放标签传递的extinfo数据逻辑
try {
if (req.getType() == ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX && req instanceof ShowMessageFromWX.Req) {
ShowMessageFromWX.Req showReq = (ShowMessageFromWX.Req) req;
WXMediaMessage mediaMsg = showReq.message;
String extInfo = mediaMsg.messageExt;
// 原生这里已经可以处理跳转逻辑了
}
} catch (Exception e) {
e.printStackTrace();
}

}

2、iOS部分 - 浏览器通过schema跳转

参考这篇帖子

iOS AppDelete.mm文件里
// 支持所有iOS系统
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url sourceApplication:sourceApplication annotation:annotation];
if (!result) {
// 其他如支付等SDK的回调
}


//开启SDK Log
[WXApi startLogByLevel:WXLogLevelDetail logBlock:^(NSString *log) {
NSLog(@"WeChatSDK: %@", log);
}];

// 在调用WXApi的handle方法前,须先调用registerApp注册。ret为注册结果,若注册失败,请根据sdk的log排查原因
BOOL ret = [WXApi registerApp:@"微信的AppID" universalLink:@"https://your域名/app/"];

if ([WXApi handleOpenURL:url delegate:self]) {
/// handled by OpenSDK
}

// 上面做了微信开放标签的适配



if ([[url scheme] isEqualToString:@"yourSchema"]) {
NSString *inputString = [url query];
// 找到等号的位置
NSUInteger equalSignIndex = [inputString rangeOfString:@"="].location;
if (equalSignIndex != NSNotFound) {
// 提取等号后面的字符串
NSString *schemaInfo = [inputString substringFromIndex:equalSignIndex + 1];

NSLog(@"schemaInfo: %@", schemaInfo);

if (![schemaInfo isEqualToString:@""]) {
NSDictionary *schemaInfoDic = @{@"schemaInfo":schemaInfo};


//这里原生的就可以跳转了,ReactNative的则把消息全部转给JS
NSMutableDictionary *mutableDic = [NSMutableDictionary dictionaryWithDictionary:schemaInfoDic];

//把消息全部转给JS
[[NSNotificationCenter defaultCenter] postNotificationName:@"SendSchemaInfoToRN" object:mutableDic?mutableDic:@{}];
} else {
NSLog(@"empty schemaInfo");
}
} else {
NSLog(@"No '=' found.");
}
}

return result;
}

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url options:options];
if (!result) {
// 其他如支付等SDK的回调
}

//开启SDK Log
[WXApi startLogByLevel:WXLogLevelDetail logBlock:^(NSString *log) {
NSLog(@"WeChatSDK: %@", log);
}];

// 在调用WXApi的handle方法前,须先调用registerApp注册。ret为注册结果,若注册失败,请根据sdk的log排查原因
BOOL ret = [WXApi registerApp:@"微信的Appid" universalLink:@"https://your域名/app/"];

if ([WXApi handleOpenURL:url delegate:self]) {
/// handled by OpenSDK
}


// 上面做了微信开放标签的适配


if ([[url scheme] isEqualToString:@"yourSchema"]) {
NSString *inputString = [url query];
// 找到等号的位置
NSUInteger equalSignIndex = [inputString rangeOfString:@"="].location;
if (equalSignIndex != NSNotFound) {
// 提取等号后面的字符串
NSString *schemaInfo = [inputString substringFromIndex:equalSignIndex + 1];

NSLog(@"schemaInfo: %@", schemaInfo);


if (![schemaInfo isEqualToString:@""]) {
NSDictionary *schemaInfoDic = @{@"schemaInfo":schemaInfo};

//这里原生的就可以跳转了,ReactNative的则把消息全部转给JS
NSMutableDictionary *mutableDic = [NSMutableDictionary dictionaryWithDictionary:schemaInfoDic];

//把消息全部转给JS
[[NSNotificationCenter defaultCenter] postNotificationName:@"SendSchemaInfoToRN" object:mutableDic?mutableDic:@{}];
} else {
NSLog(@"empty schemaInfo.");
}
} else {
NSLog(@"No '=' found.");
}
}


return result;
}

iOS部分 - 微信页面通过开放标签跳转

在通过Schema跳转的代码基础上,加上下面这段

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler
{
if (![[UMSocialManager defaultManager] handleUniversalLink:userActivity options:nil]) {
// 其他SDK的回调
NSLog ( @"其他SDK的回调11");
}
NSLog ( @"其他SDK的回调22");

//开启SDK Log
[WXApi startLogByLevel:WXLogLevelDetail logBlock:^(NSString *log) {
NSLog(@"WeChatSDK: %@", log);
}];

// 在调用WXApi的handle方法前,须先调用registerApp注册。ret为注册结果,若注册失败,请根据sdk的log排查原因
BOOL ret = [WXApi registerApp:@"微信的Appid" universalLink:@"https://your域名/app/"];
if ([WXApi handleOpenUniversalLink:userActivity delegate:self]) {
/// handled by OpenSDK
}


// 触发回调方法
[RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
NSURL *url = userActivity.webpageURL;
if (url && [TencentOAuth CanHandleUniversalLink:url]) {
return [TencentOAuth HandleUniversalLink:url];
}


return YES;
}



-(void) onReq:(BaseReq*)req
{
if([req isKindOfClass:[GetMessageFromWXReq class]])
{
// 微信请求App提供内容, 需要app提供内容后使用sendRsp返回
NSString *strTitle = [NSString stringWithFormat:@"微信请求App提供内容"];
NSString *strMsg = @"微信请求App提供内容,App要调用sendResp:GetMessageFromWXResp返回给微信";

}
else if([req isKindOfClass:[ShowMessageFromWXReq class]])
{
ShowMessageFromWXReq* temp = (ShowMessageFromWXReq*)req;
WXMediaMessage *msg = temp.message;

//显示微信传过来的内容
WXAppExtendObject *obj = msg.mediaObject;

NSString *strTitle = [NSString stringWithFormat:@"微信请求App显示内容"];
NSString *strMsg = [NSString stringWithFormat:@"标题:%@ \n内容:%@ \n附带信息:%@ \n缩略图:%u bytes\n\n", msg.title, msg.description, obj.extInfo, msg.thumbData.length];

}
else if([req isKindOfClass:[LaunchFromWXReq class]])
{
//从微信启动App
LaunchFromWXReq* temp = (LaunchFromWXReq*)req;
WXMediaMessage *msg = temp.message;


NSLog ( @"req:%@", msg.messageExt);


NSDictionary *schemaInfoDic = @{@"schemaInfo":msg.messageExt};


//把消息全部转给JS
NSMutableDictionary *mutableDic = [NSMutableDictionary dictionaryWithDictionary:schemaInfoDic];

NSLog(@"Schema 收到微信回调参数 #####%@",mutableDic);


//把消息全部转给JS
[[NSNotificationCenter defaultCenter] postNotificationName:@"SendSchemaInfoToRN" object:mutableDic?mutableDic:@{}];
}
}

3、ReactNative的特殊处理

ReactNative在获取到浏览器或者微信传来的ScehmaInfo后,发送消息到JS的操作在前面的代码里都写进去了,可以看对应的注释。
安卓由于ReactNative就一个MainActivity,拿到微信传过来的数据后还需要特殊处理。

WXEntryActivity文件里

/**
* 从微信启动App
*
* @param req
*/
@Override
public void onReq(BaseReq req) {
super.onReq(req);

//获取开放标签传递的extinfo数据逻辑
try {
if (req.getType() == ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX && req instanceof ShowMessageFromWX.Req) {
ShowMessageFromWX.Req showReq = (ShowMessageFromWX.Req) req;
WXMediaMessage mediaMsg = showReq.message;
String extInfo = mediaMsg.messageExt;

// 原生这里已经可以处理跳转逻辑了,ReactNative需要特殊处理下,判断MainActivity是否存在,也就是说APP是否处于杀死状态

if (ActivityUtils.isActivityExistsInStack(MainActivity.class)) {
// 发送到RN端
MainApplication application = (MainApplication) this.getApplicationContext();

WritableMap map = Arguments.createMap();
map.putString("schemaInfo", extInfo);

ReactInstanceManager reactInstanceManager = application.getReactNativeHost().getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();

if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("Demo_EmitterKey", map);
} else {
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {

Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
/**
*要执行的操作
*/
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("Demo_EmitterKey", map);
}
}, 4000);//4秒后执行Runnable中的run方法
reactInstanceManager.removeReactInstanceEventListener(this);

}
});
}
} else {
// 如果APP处于启动状态,则到MainActivity里处理逻辑
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("keySendToMainActivity", extInfo);
startActivity(intent);
}
}
} catch (Exception e) {
e.printStackTrace();
}

}

MainActivity文件里

@Override
protected void onCreate(Bundle savedInstanceState) {
MainApplication.getInstance().setMainActivity(this);
super.onCreate(null);

...

// 这里加上这些内容
if (getIntent().getStringExtra("keySendToMainActivity") != null && !"".equals(getIntent().getStringExtra("keySendToMainActivity"))) {
// 发送到RN端
MainApplication application = (MainApplication) this.getApplicationContext();

WritableMap map = Arguments.createMap();
map.putString("schemaInfo", getIntent().getStringExtra("keySendToMainActivity"));

ReactInstanceManager reactInstanceManager = application.getReactNativeHost().getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();

if(reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("Demo_EmitterKey", map);
} else {
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {

Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
/**
*要执行的操作
*/

context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("Demo_EmitterKey", map);
}
}, 4000);//4秒后执行Runnable中的run方法
reactInstanceManager.removeReactInstanceEventListener(this);
}
});
}
}
}


鼓励一下

如果觉得我的文章对您有用,欢迎打赏(右边栏二维码),您的支持将鼓励我继续创作!”


咨询联系方式

  • 请发邮件: admin@kovli.com