feat(事件管理): 添加跨平台事件管理功能

实现原生代码中的事件监听与触发机制,包括Android和iOS平台的事件管理器
添加ionicReady接口用于标识前端就绪状态
This commit is contained in:
2026-01-21 16:55:20 +08:00
parent a06c7abfc8
commit c14f84bc90
8 changed files with 294 additions and 0 deletions

View File

@@ -13,6 +13,8 @@
</feature>
</config-file>
<source-file src="src/android/ShutoApi.java" target-dir="src/cn/shuto/feishuapi" />
<source-file src="src/android/ShutoEventManager.java" target-dir="src/cn/shuto/feishuapi" />
<source-file src="src/android/ShutoEventListener.java" target-dir="src/cn/shuto/feishuapi" />
</platform>
<platform name="ios">
<config-file parent="/*" target="config.xml">
@@ -21,5 +23,7 @@
</feature>
</config-file>
<source-file src="src/ios/ShutoApi.m" />
<source-file src="src/ios/ShutoEventManager.h" />
<source-file src="src/ios/ShutoEventManager.m" />
</platform>
</plugin>

View File

@@ -11,6 +11,9 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Map;
import java.util.HashMap;
/**
* This class implements a Cordova plugin for Shuto API.
*/
@@ -23,6 +26,9 @@ public class ShutoApi extends CordovaPlugin {
// 保存带回调的事件回调映射
private Map<String, EventCallback> eventCallbacks = new HashMap<>();
// 标识前端是否就绪
private boolean isIonicReady = false;
// 事件回调接口
public interface EventCallback {
void onResult(JSONObject result);
@@ -44,6 +50,9 @@ public class ShutoApi extends CordovaPlugin {
this.eventCallback(args);
callbackContext.success();
return true;
} else if (action.equals("ionicReady")) {
this.ionicReady(callbackContext);
return true;
}
return false;
}
@@ -171,4 +180,19 @@ public class ShutoApi extends CordovaPlugin {
e.printStackTrace();
}
}
// 处理前端就绪状态
private void ionicReady(CallbackContext callbackContext) {
isIonicReady = true;
LOG.d(TAG, "Ionic is ready");
ShutoEventManager.getInstance().fireEvent("ionicReady");
callbackContext.success();
}
// 提供给原生代码查询就绪状态的接口
public boolean isIonicReady() {
return isIonicReady;
}
}

View File

@@ -0,0 +1,10 @@
package cn.shuto.feishuapi;
import java.util.Map;
/**
* 事件监听器接口
*/
public interface ShutoEventListener {
void onEvent(String eventName, Map<String, Object> data);
}

View File

@@ -0,0 +1,89 @@
package cn.shuto.feishuapi;
import org.apache.cordova.LOG;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 独立的事件管理类,用于在原生代码中进行事件的监听注册与触发
*/
public class ShutoEventManager {
private static final String TAG = "ShutoEventManager";
private static ShutoEventManager instance;
private Map<String, Set<ShutoEventListener>> eventListeners;
private ShutoEventManager() {
eventListeners = new ConcurrentHashMap<>();
}
public static synchronized ShutoEventManager getInstance() {
if (instance == null) {
instance = new ShutoEventManager();
}
return instance;
}
public void addListener(ShutoEventListener listener, String eventName) {
if (listener == null || eventName == null) {
return;
}
eventListeners.computeIfAbsent(eventName, k -> new CopyOnWriteArraySet<>()).add(listener);
LOG.d(TAG, "Added listener for event: " + eventName + ", total listeners: " + eventListeners.get(eventName).size());
}
public void removeListener(ShutoEventListener listener, String eventName) {
if (listener == null || eventName == null) {
return;
}
Set<ShutoEventListener> listeners = eventListeners.get(eventName);
if (listeners != null) {
listeners.remove(listener);
LOG.d(TAG, "Removed listener for event: " + eventName + ", remaining listeners: " + listeners.size());
if (listeners.isEmpty()) {
eventListeners.remove(eventName);
}
}
}
public void removeListener(ShutoEventListener listener) {
if (listener == null) {
return;
}
for (Set<ShutoEventListener> listeners : eventListeners.values()) {
listeners.remove(listener);
}
eventListeners.entrySet().removeIf(entry -> entry.getValue().isEmpty());
LOG.d(TAG, "Removed listener from all events");
}
public void fireEvent(String eventName, Map<String, Object> data) {
if (eventName == null) {
return;
}
LOG.d(TAG, "Firing event: " + eventName + ", data: " + data);
Set<ShutoEventListener> listeners = eventListeners.get(eventName);
if (listeners != null && !listeners.isEmpty()) {
for (ShutoEventListener listener : listeners) {
listener.onEvent(eventName, data);
}
} else {
LOG.d(TAG, "No listeners for event: " + eventName);
}
}
public void fireEvent(String eventName) {
fireEvent(eventName, null);
}
}

View File

@@ -3,6 +3,7 @@
#import <Cordova/CDV.h>
#import <WebKit/WebKit.h>
#import "SGGC_Log.h"
#import "ShutoEventManager.h"
@protocol ShutoApiProtocol <NSObject>
- (void)fireEvent:(NSString*)type parameters:(NSDictionary*)parameters;
@@ -16,6 +17,8 @@
NSDictionary* _userInfo;
// ID
NSMutableDictionary* _eventCallbacks;
//
BOOL _isIonicReady;
}
- (void)close:(CDVInvokedUrlCommand*)command;
@@ -26,6 +29,8 @@
- (void)fireEvent:(NSString*)type parameters:(NSDictionary*)parameters;
- (void)fireEventWithCallback:(NSString*)eventName parameters:(NSDictionary*)parameters callback:(void (^)(NSDictionary* result, NSError* error))callback;
- (void)eventCallback:(CDVInvokedUrlCommand*)command;
- (void)ionicReady:(CDVInvokedUrlCommand*)command;
- (BOOL)isIonicReady;
@end
@implementation ShutoApi
@@ -209,4 +214,20 @@
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)ionicReady:(CDVInvokedUrlCommand*)command
{
_isIonicReady = YES;
[SGGC_Log log:@"SGGC_CDVFile" message:@"ShutoApi: Ionic is ready"];
[[ShutoEventManager sharedInstance] fireEvent:@"ionicReady" data:nil];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (BOOL)isIonicReady
{
return _isIonicReady;
}
@end

View File

@@ -0,0 +1,27 @@
//
// ShutoEventManager.h
// ShutoApi
//
// Created by Shuto Team.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ShutoEventListener <NSObject>
- (void)onEvent:(NSString *)eventName data:(NSDictionary *)data;
@end
@interface ShutoEventManager : NSObject
+ (instancetype)sharedInstance;
- (void)addListener:(id<ShutoEventListener>)listener forEvent:(NSString *)eventName;
- (void)removeListener:(id<ShutoEventListener>)listener forEvent:(NSString *)eventName;
- (void)removeListener:(id<ShutoEventListener>)listener;
- (void)fireEvent:(NSString *)eventName data:(NSDictionary *)data;
@end
NS_ASSUME_NONNULL_END

114
src/ios/ShutoEventManager.m Normal file
View File

@@ -0,0 +1,114 @@
//
// ShutoEventManager.m
// ShutoApi
//
// Created by Shuto Team.
//
#import "ShutoEventManager.h"
#import "SGGC_Log.h"
@interface ShutoEventManager ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableSet<id<ShutoEventListener>> *> *eventListeners;
@end
@implementation ShutoEventManager
+ (instancetype)sharedInstance {
static ShutoEventManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[ShutoEventManager alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_eventListeners = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)addListener:(id<ShutoEventListener>)listener forEvent:(NSString *)eventName {
if (!listener || !eventName) {
return;
}
@synchronized(self.eventListeners) {
if (!self.eventListeners[eventName]) {
self.eventListeners[eventName] = [[NSMutableSet alloc] init];
}
[self.eventListeners[eventName] addObject:listener];
[SGGC_Log log:@"ShutoEventManager" message:[NSString stringWithFormat:@"Added listener for event: %@, total listeners: %lu", eventName, (unsigned long)self.eventListeners[eventName].count]];
}
}
- (void)removeListener:(id<ShutoEventListener>)listener forEvent:(NSString *)eventName {
if (!listener || !eventName) {
return;
}
@synchronized(self.eventListeners) {
if (self.eventListeners[eventName]) {
[self.eventListeners[eventName] removeObject:listener];
[SGGC_Log log:@"ShutoEventManager" message:[NSString stringWithFormat:@"Removed listener for event: %@, remaining listeners: %lu", eventName, (unsigned long)self.eventListeners[eventName].count]];
if (self.eventListeners[eventName].count == 0) {
[self.eventListeners removeObjectForKey:eventName];
}
}
}
}
- (void)removeListener:(id<ShutoEventListener>)listener {
if (!listener) {
return;
}
@synchronized(self.eventListeners) {
NSMutableArray *emptyEvents = [[NSMutableArray alloc] init];
for (NSString *eventName in self.eventListeners.allKeys) {
NSMutableSet *listeners = self.eventListeners[eventName];
if ([listeners containsObject:listener]) {
[listeners removeObject:listener];
[SGGC_Log log:@"ShutoEventManager" message:[NSString stringWithFormat:@"Removed listener from event: %@, remaining listeners: %lu", eventName, (unsigned long)listeners.count]];
if (listeners.count == 0) {
[emptyEvents addObject:eventName];
}
}
}
[self.eventListeners removeObjectsForKeys:emptyEvents];
}
}
- (void)fireEvent:(NSString *)eventName data:(NSDictionary *)data {
if (!eventName) {
return;
}
[SGGC_Log log:@"ShutoEventManager" message:[NSString stringWithFormat:@"Firing event: %@, data: %@", eventName, data]];
NSArray *listeners = nil;
@synchronized(self.eventListeners) {
if (self.eventListeners[eventName]) {
listeners = [self.eventListeners[eventName] allObjects];
}
}
if (listeners && listeners.count > 0) {
for (id<ShutoEventListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onEvent:data:)]) {
[listener onEvent:eventName data:data];
}
}
} else {
[SGGC_Log log:@"ShutoEventManager" message:[NSString stringWithFormat:@"No listeners for event: %@", eventName]];
}
}
@end

View File

@@ -12,6 +12,11 @@ var ShutoApi = {
// 执行带回调的事件
executeEvent: function (eventName, params, success, error) {
exec(success, error, 'ShutoApi', 'executeEvent', [eventName, params]);
},
// 标识前端已就绪
ionicReady: function (success, error) {
exec(success, error, 'ShutoApi', 'ionicReady', []);
}
};