这篇文章主要介绍一下自己在实际项目开发中,抽离的一套具有打开微信分享,打开微信登陆,打开微信小程序等功能的Flutter Plugin组件,iOS与Android均已实现对应的原生功能
创作不易,希望各位大佬给个start再走,🙏🙏🙏🙏
github地址
公共pub仓库地址
正片开始
本文主要分为两个部分讲解,flutter部分的实现,iOS部分的实现(由于本人主要做的事iOS开发,安卓部分的实现是我的朋友实现的,所以就不对安卓部分展开讲解)
Flutter部分
这个部分其实不难,主要用到
MethodChannel
这个方法来进行和原生的通讯`
static const MethodChannel _channel = const MethodChannel('flutter_wechate_share_plugin');
- 注册微信开发平台
/// register 是和原生端约定好关于微信注册的一个通讯名称, {'appid': appid,'universalLink' : universalLink}, 则是flutter端传递给原生端的参数
/// 通过 微信 appid 注册应用,universalLink 为 ios 平台的必传参数
static Future<dynamic> register(String appid, {String universalLink = ''}) async {
if(appid.length == 0 || appid == null) return;
var result = await _channel.invokeMethod(
'register',
{
'appid': appid,
'universalLink' : universalLink
}
);
return result;
}
- 分享功能
/*
* arguments 主要格式:
* {
* "kind": "text",
* "to": "session", timeline - 分享到朋友圈,favorite - 收藏,session - 分享到好友(默认)
* "title": "the title of message",
* "description": "short description of message.",
* "coverUrl": "https://example.com/path/to/cover/image.jpg",
* "resourceUrl": "https://example.com/path/to/resource.mp4"
* }
* kind: 分享内容格式,默认为 text -> 文本, image -> 图片, music -> 音乐, video -> 视频, webpage -> 网页
* to: 分享方式,默认为 session -> 会话列表, timeline -> 朋友圈, favorite -> 收藏
* title: 分享的标题
* description: 分享内容描述
* coverUrl: 分享的资源缩略图
* resourceUrl: 分享的资源图
*/
static Future<dynamic> share(Map<String, dynamic> arguments) async {
arguments['kind'] = arguments['kind'] ?? 'text';
arguments['to'] = arguments['to'] ?? 'session';
var result = await _channel.invokeMethod(
'share',
arguments
);
return result;
}
- 打开微信小程序
/*
* originalId: 组织id,必传参数
* path 小程序页面的路径 不填默认拉起小程序首页
* type 分享小程序的版本 (0-正式,1-开发,2-体验)
* */
static Future<dynamic> startMiniProgram(String originalId, {String path, String type = "0"}) async {
if(originalId.length == 0 || originalId == null) {
return;
}
var result = await _channel.invokeMethod(
'startMiniProgram',
{
'originalId': originalId,
'path': path,
'type': type
}
);
return result;
}
- 其他功能实现
/// 检测本地是否安装了微信
static Future<dynamic> isWechatInstalled() async {
var result = await _channel.invokeMethod(
'isWechatInstalled'
);
return result == 'true' ? true : false;
}
/// 获取微信的版本.
static Future<dynamic> getApiVersion() async {
var result = await _channel.invokeMethod(
'getApiVersion'
);
return result;
}
/// 打开微信app.
static Future<dynamic> openWechat() async {
var result = await _channel.invokeMethod(
'openWechat'
);
return result;
}
/// 登陆
/// {
/// "scope": "snsapi_userinfo",
/// "state": "customestate"
/// }
static Future<dynamic> login(Map<String, String> arguments) async {
arguments['scope'] = arguments['scope'] ?? 'snsapi_userinfo';
var result = await _channel.invokeMethod(
'login',
arguments
);
return result;
}
iOS部分
- 先来看一个比较核心的问题,iOS端注册关于微信的回调
我们知道要想成功唤起微信客户端,原生端除了注册微信appid外,还需要在AppDelegate的几个系统代理中调用WXApi的一些方法,比如:
#pragma mark - AppDelegate
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *_Nonnull))restorationHandler {
return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
但是我们的flugin项目是没有Appdelegate的,怎么办呢?在主工程注册?这显然不现实,第一代码侵入性太高,第二回调结果不好处理,在这里,Flutter这个框架给我们提供了一个 FlutterApplicationLifeCycleDelegate 这个代理,实现这个代理后,我们同样可以在plugin项目中拿到Appdelegate中的代理方法的监听实现
-
实现方案一:拦截Appdelegate的代理方法
1.注册成为代理
2.实现代理的方法,注册WXApi的对应实现方法
大家想想这种方式实现后有没有问题呢?我们知道代理模式是一对一的,我们要是在plugin里面实现了对Appdelegate响应处理的话,要是以后你把这个组件运用到自己的项目中,但是在主工程也想响应这里代理方法的话,那么就会产出冲突,要么主工程响应代理方法,plugin不会响应,要么plugin响应代理方法,主工程又不会响应,怎么着都不行,所以我们这里介绍第二种实现方案!
-
实现方案二: Runtime + 消息转发的方式进行的监听
UIApplication+Hook类,实现对系统方法的交换
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oldMethod = class_getInstanceMethod([self class], @selector(setDelegate:));
Method newMethod = class_getInstanceMethod([self class], @selector(flutter_setDelegate:));
methodExchange([self class], oldMethod, newMethod);
});
}
static inline void
/// 方法交换
/// @param class 需要交换方法的类
/// @param oldMethod 类的方法
/// @param newMethod 交换后的实现方法
methodExchange(Class class,Method oldMethod,Method newMethod)
{
BOOL isAddedMethod = class_addMethod(class, method_getName(oldMethod), method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
if (isAddedMethod) {
class_replaceMethod(class, method_getName(newMethod), method_getImplementation(oldMethod), method_getTypeEncoding(oldMethod));
} else {
method_exchangeImplementations(oldMethod, newMethod);
}
}
static inline void
/// 获取要交换的两个方法
/// @param class 被交换的类
/// @param selector 实现交换的类
classSelectorHook(Class class,SEL selector)
{
Method oldMethod = class_getInstanceMethod(class, selector);
NSString *oldSelName = NSStringFromSelector(method_getName(oldMethod));
// 在FlutterWechateSharePlugin实现
Method newMethod = class_getInstanceMethod([FlutterWechateSharePlugin class], NSSelectorFromString([NSString stringWithFormat:@"flutter_%@",oldSelName]));
methodExchange(class, oldMethod, newMethod);
}
- (void)flutter_setDelegate:(id<UIApplicationDelegate>) delegate
{
static dispatch_once_t delegateOnceToken;
dispatch_once(&delegateOnceToken, ^{
// 交换application的一些方法
classSelectorHook([delegate class], @selector(application:handleOpenURL:));
classSelectorHook([delegate class], @selector(application:openURL:options:));
classSelectorHook([delegate class], @selector(application:openURL:sourceApplication:annotation:));
classSelectorHook([delegate class], @selector(application:continueUserActivity:restorationHandler:));
});
// 此时的flutter_setDelegate为原delegate,保证不影响原始功能
[self flutter_setDelegate:delegate];
}
在FlutterWechateSharePlugin类实现,对hook方法的处理
#pragma mark - AppDelegate - Hook 实现
- (BOOL)flutter_application:(UIApplication *)application handleOpenURL:(NSURL *)url {
[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application,url, nil];
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)flutter_application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application,url,sourceApplication,annotation, nil];
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)flutter_application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application,url,options, nil];
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)flutter_application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *_Nonnull))restorationHandler {
[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application,userActivity,restorationHandler, nil];
return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
其中 + (BOOL)performAppDelegateTarget:(Class)cls action:(SEL)sel params:(id)arg, ... 这个方法主要实现了对FlutterWechateSharePlugin类中的消息转发
- iOS与flutter通信的具体功能实现代码
/// 这个方法是处理oc和flutter通讯的核心方法
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
// 注册微信
if ([METHOD_REGISTERAPP isEqualToString:call.method]) {
NSString *appId = call.arguments[@"appid"];
NSString *universalLink = call.arguments[@"universalLink"];
[WXApi registerApp:appId universalLink:universalLink];
result(nil);
}
// 判断本地是否安装微信
else if ([METHOD_ISINSTALLED isEqualToString:call.method]) {
result([NSNumber numberWithBool:[WXApi isWXAppInstalled]]);
}
// 打开微信
else if ([METHOD_OPENWECHAT isEqualToString:call.method]) {
result([NSNumber numberWithBool:[WXApi openWXApp]]);
}
// 微信登陆
else if ([METHOD_LOGINWECHAT isEqualToString:call.method]) {
NSString* scope= call.arguments[@"scope"];
NSString* state= call.arguments[@"state"];
SendAuthReq *request = [[SendAuthReq alloc] init];
request.scope = scope;
request.state = state;
[WXApi sendReq:request completion:^(BOOL success) {
}];
}
// 分享
else if ([@"share" isEqualToString:call.method]) {
NSDictionary *arguments = [call arguments];
// kind 为分享的类型,text -> 文本, image -> 图片, music -> 音乐, video -> 视频, webpage -> 网页
NSString *kind = arguments[@"kind"];
// 分享文字内容
if ([kind isEqualToString:METHOD_SHARETEXT]) {
[self handleShareTextCall:call result:result];
}
// 分享图片内容
else if ([kind isEqualToString:METHOD_SHAREIMAGE]) {
[self handleShareImageCall:call result:result];
}
// 分享音乐
else if ([kind isEqualToString:METHOD_SHAREMUSIC]) {
[self handleShareMusicCall:call result:result];
}
// 分享网页
else if ([kind isEqualToString:METHOD_SHAREWEBPAGE]) {
[self handleShareWebPageCall:call result:result];
}
}
// 打开微信小程序
else if ([METHOD_STARTMINIPROGRAM isEqualToString:call.method]) {
[self handleStartMiniProgramCall:call result:result];
}
else {
result(FlutterMethodNotImplemented);
}
}
/// 分享文字
/// @param call 分享的内容
/// @param result 分享结果
- (void)handleShareTextCall:(FlutterMethodCall *)call result:(FlutterResult)result {
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
// to 是分享的渠道, session -> 会话列表, timeline -> 朋友圈, favorite -> 收藏, 默认是 session -> 会话列表
NSString *to = call.arguments[@"to"];
// 分享到朋友圈
if ([@"timeline" isEqualToString:to]) {
req.scene = WXSceneTimeline;
}
// 添加到收藏列表
else if ([@"favorite" isEqualToString:to]) {
req.scene = WXSceneFavorite;
}
// 分享到好友
else {
req.scene = WXSceneSession;
}
req.bText = YES;
req.text = call.arguments[@"text"];
// 验证是否分享成功回调
[WXApi sendReq:req completion:^(BOOL success){
}];
result(nil);
}
/// 分享图片
/// @param call 分享的内容
/// @param result 分享结果
- (void)handleShareImageCall:(FlutterMethodCall *)call result:(FlutterResult)result {
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
// to 是分享的渠道, session -> 会话列表, timeline -> 朋友圈, favorite -> 收藏, 默认是 session -> 会话列表
NSString *to = call.arguments[@"to"];
// 分享到朋友圈
if ([@"timeline" isEqualToString:to]) {
req.scene = WXSceneTimeline;
}
// 添加到收藏列表
else if ([@"favorite" isEqualToString:to]) {
req.scene = WXSceneFavorite;
}
// 分享到好友
else {
req.scene = WXSceneSession;
}
req.bText = NO;
WXMediaMessage *message = [WXMediaMessage message];
message.title = call.arguments[@"title"];
message.description = call.arguments[@"description"];
WXImageObject *mediaObject = [WXImageObject object];
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:call.arguments[@"resourceUrl"]]];
if (imageData != nil) {
mediaObject.imageData = imageData;
} else {
NSString *imageUri = call.arguments[@"url"];
NSURL *imageUrl = [NSURL URLWithString:imageUri];
mediaObject.imageData = [NSData dataWithContentsOfFile:imageUrl.path];
}
message.mediaObject = mediaObject;
req.message = message;
[WXApi sendReq:req completion:^(BOOL success){
}];
result(nil);
}
/// 分享音乐
/// @param call 分享的内容
/// @param result 分享结果
- (void)handleShareMusicCall:(FlutterMethodCall *)call result:(FlutterResult)result {
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
// to 是分享的渠道, session -> 会话列表, timeline -> 朋友圈, favorite -> 收藏, 默认是 session -> 会话列表
NSString *to = call.arguments[@"to"];
// 分享到朋友圈
if ([@"timeline" isEqualToString:to]) {
req.scene = WXSceneTimeline;
}
// 添加到收藏列表
else if ([@"favorite" isEqualToString:to]) {
req.scene = WXSceneFavorite;
}
// 分享到好友
else {
req.scene = WXSceneSession;
}
req.bText = NO;
// 分享的消息会话模型
WXMediaMessage *message = [WXMediaMessage message];
message.title = call.arguments[@"title"];
message.description = call.arguments[@"description"];
// 音乐的资源路径
NSString *musicDataUrl = call.arguments[@"resourceUrl"];
// 音乐的网页链接地址
NSString *musicUrl = call.arguments[@"url"];
// 音乐的封面链接地址
NSString *coverUrl = call.arguments[@"coverUrl"];
// 音乐的数据模型
WXMusicObject *mediaObject = [WXMusicObject object];
mediaObject.musicUrl = musicUrl;
mediaObject.musicDataUrl = musicDataUrl;
NSData *coverImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:coverUrl]];
[message setThumbImage:[UIImage imageWithData:coverImageData]];
message.mediaObject = mediaObject;
req.message = message;
[WXApi sendReq:req completion:^(BOOL success){
}];
result(nil);
}
/// 分享网页
/// @param call 分享的内容
/// @param result 分享结果
- (void)handleShareWebPageCall:(FlutterMethodCall *)call result:(FlutterResult)result {
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
// to 是分享的渠道, session -> 会话列表, timeline -> 朋友圈, favorite -> 收藏, 默认是 session -> 会话列表
NSString *to = call.arguments[@"to"];
// 分享到朋友圈
if ([@"timeline" isEqualToString:to]) {
req.scene = WXSceneTimeline;
}
// 添加到收藏列表
else if ([@"favorite" isEqualToString:to]) {
req.scene = WXSceneFavorite;
}
// 分享到好友
else {
req.scene = WXSceneSession;
}
req.bText = NO;
// 分享的消息会话模型
WXMediaMessage *message = [WXMediaMessage message];
message.title = call.arguments[@"title"];
message.description = call.arguments[@"description"];
WXWebpageObject *mediaObject = [WXWebpageObject object];
mediaObject.webpageUrl = call.arguments[@"url"];
[message setThumbImage:[UIImage imageWithData:call.arguments[@"coverUrl"]]];
message.mediaObject = mediaObject;
req.message = message;
[WXApi sendReq:req completion:^(BOOL success){
}];
result(nil);
}
/// 打开微信小程序
/// @param call 参数内容
/// @param result 返回结果结果
- (void)handleStartMiniProgramCall:(FlutterMethodCall *)call result:(FlutterResult)result {
WXLaunchMiniProgramReq *req = [[WXLaunchMiniProgramReq alloc] init];
req.userName = call.arguments[@"originalId"];
req.path = call.arguments[@"path"];
// 默认打开正式版本的小程序
req.miniProgramType = WXMiniProgramTypeRelease;
NSString *miniProgramType = call.arguments[@"type"];
if (miniProgramType.length != 0 && miniProgramType != nil) {
NSInteger type = [call.arguments[@"type"] integerValue];
switch (type) {
case 0:
{
req.miniProgramType = WXMiniProgramTypeRelease;
}
break;
case 1:
{
req.miniProgramType = WXMiniProgramTypeTest;
}
break;
case 2:
{
req.miniProgramType = WXMiniProgramTypePreview;
}
break;
default:
break;
}
}
[WXApi sendReq:req completion:^(BOOL success){
}];
result(nil);
}
#pragma mark - WXApiDelegate
- (void)onResp:(BaseResp*)resp {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:[NSNumber numberWithInt:resp.errCode] forKey:ARGUMENT_KEY_RESULT_ERRORCODE];
if (resp.errStr != nil) {
[dictionary setValue:resp.errStr forKey:ARGUMENT_KEY_RESULT_ERRORMSG];
}
if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
// 分享
[_channel invokeMethod:METHOD_ONSHAREMSGRESP arguments:dictionary];
} else if ([resp isKindOfClass:[WXLaunchMiniProgramResp class]]) {
// 打开小程序
if (resp.errCode == WXSuccess) {
WXLaunchMiniProgramResp *launchMiniProgramResp =
(WXLaunchMiniProgramResp *)resp;
[dictionary setValue:launchMiniProgramResp.extMsg
forKey:ARGUMENT_KEY_RESULT_EXTMSG];
}
[_channel invokeMethod:METHOD_ONLAUNCHMINIPROGRAMRESP arguments:dictionary];
}
}