diff --git a/README.md b/README.md index 1a8e774..8cef8f3 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,26 @@ cordova.plugins.ShutoApi.getUserInfo( ); ``` + +#### ionicReady() +标识前端应用已就绪。调用此方法后,原生代码中的事件管理器会触发 `ionicReady` 事件。 + +```javascript +cordova.plugins.ShutoApi.ionicReady(); +``` + + +建议在应用初始化完成后调用: + +```javascript +document.addEventListener('deviceready', function() { + // 应用初始化逻辑... + + // 通知原生代码前端已就绪 + cordova.plugins.ShutoApi.ionicReady(); +}, false); +``` + ### 事件 #### navigate @@ -169,8 +189,86 @@ cordova.plugins.ShutoApi.removeEventListener('navigate', handleNavigate); ## 原生代码使用 +### 独立事件管理器(ShutoEventManager) + +插件提供了独立的事件管理器 `ShutoEventManager`,用于在原生代码中进行事件的监听和触发。这与Cordova插件分离,可以在任何原生代码中使用。 + +#### 事件列表 + +- `ionicReady`: 当前端调用 `ionicReady()` 方法时触发 + +--- + ### iOS +#### 1. 导入头文件 + +```objective-c +#import "ShutoEventManager.h" +``` + + +#### 2. 注册事件监听 + +```objective-c +// 让类遵循 ShutoEventListener 协议 +@interface YourViewController () +@end + +@implementation YourViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + // 注册 ionicReady 事件监听 + [[ShutoEventManager sharedInstance] addListenerForEvent:@"ionicReady" listener:self]; +} + +// 实现协议方法 +- (void)onEvent:(NSString *)eventName data:(NSDictionary *)data { + if ([eventName isEqualToString:@"ionicReady"]) { + NSLog(@"前端已就绪,可以执行后续操作"); + // 在这里处理前端就绪后的逻辑 + } +} + +- (void)dealloc { + // 移除所有事件监听 + [[ShutoEventManager sharedInstance] removeListener:self]; +} + +@end +``` + + +#### 3. 移除事件监听 + +```objective-c +// 移除特定事件的监听 +[[ShutoEventManager sharedInstance] removeListenerForEvent:@"ionicReady" listener:self]; + +// 移除所有事件的监听 +[[ShutoEventManager sharedInstance] removeListener:self]; +``` + + +#### 4. 触发自定义事件 + +```objective-c +// 触发事件(可带数据) +NSDictionary *eventData = @{ + @"key": @"value", + @"timestamp": @([NSDate timeIntervalSinceReferenceDate]) +}; +[[ShutoEventManager sharedInstance] fireEvent:@"customEvent" data:eventData]; + +// 触发事件(不带数据) +[[ShutoEventManager sharedInstance] fireEvent:@"customEvent" data:nil]; +``` + + +#### 5. 通过插件实例触发事件(Cordova相关) + 在iOS原生代码中,可以通过以下方式触发事件: ```objective-c @@ -233,6 +331,74 @@ NSDictionary* fetchData = @{ ### Android +#### 1. 导入类 + +```java +import cn.shuto.feishuapi.ShutoEventManager; +import cn.shuto.feishuapi.ShutoEventListener; +``` + + +#### 2. 注册事件监听 + +```java +public class YourActivity extends AppCompatActivity implements ShutoEventListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // 注册 ionicReady 事件监听 + ShutoEventManager.getInstance().addListener("ionicReady", this); + } + + // 实现接口方法 + @Override + public void onEvent(String eventName, Map data) { + if ("ionicReady".equals(eventName)) { + Log.d("YourActivity", "前端已就绪,可以执行后续操作"); + // 在这里处理前端就绪后的逻辑 + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // 移除所有事件监听 + ShutoEventManager.getInstance().removeListener(this); + } +} +``` + + +#### 3. 移除事件监听 + +```java +// 移除特定事件的监听 +ShutoEventManager.getInstance().removeListener("ionicReady", this); + +// 移除所有事件的监听 +ShutoEventManager.getInstance().removeListener(this); +``` + + +#### 4. 触发自定义事件 + +```java +// 触发事件(可带数据) +Map eventData = new HashMap<>(); +eventData.put("key", "value"); +eventData.put("timestamp", System.currentTimeMillis()); +ShutoEventManager.getInstance().fireEvent("customEvent", eventData); + +// 触发事件(不带数据) +ShutoEventManager.getInstance().fireEvent("customEvent"); +``` + + +#### 5. 通过插件实例触发事件(Cordova相关) + 在Android原生代码中,可以通过以下方式触发事件: ```java diff --git a/plugin.xml b/plugin.xml index bd36afa..f463aaf 100644 --- a/plugin.xml +++ b/plugin.xml @@ -13,6 +13,8 @@ + + @@ -21,5 +23,7 @@ + + \ No newline at end of file diff --git a/src/android/ShutoApi.java b/src/android/ShutoApi.java index fab020b..8d15639 100644 --- a/src/android/ShutoApi.java +++ b/src/android/ShutoApi.java @@ -14,6 +14,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. */ @@ -27,6 +30,9 @@ public class ShutoApi extends CordovaPlugin { // 保存带回调的事件回调映射 private Map eventCallbacks = new HashMap<>(); + // 标识前端是否就绪 + private boolean isIonicReady = false; + // 事件回调接口 public interface EventCallback { void onResult(JSONObject result); @@ -48,6 +54,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; } @@ -178,4 +187,19 @@ public class ShutoApi extends CordovaPlugin { LOG.e(TAG, "error", e); } } + + // 处理前端就绪状态 + private void ionicReady(CallbackContext callbackContext) { + isIonicReady = true; + LOG.d(TAG, "Ionic is ready"); + + ShutoEventManager.getInstance().fireEvent("ionicReady"); + + callbackContext.success(); + } + + // 提供给原生代码查询就绪状态的接口 + public boolean isIonicReady() { + return isIonicReady; + } } diff --git a/src/android/ShutoEventListener.java b/src/android/ShutoEventListener.java new file mode 100644 index 0000000..c4e5dbd --- /dev/null +++ b/src/android/ShutoEventListener.java @@ -0,0 +1,10 @@ +package cn.shuto.feishuapi; + +import java.util.Map; + +/** + * 事件监听器接口 + */ +public interface ShutoEventListener { + void onEvent(String eventName, Map data); +} diff --git a/src/android/ShutoEventManager.java b/src/android/ShutoEventManager.java new file mode 100644 index 0000000..238332e --- /dev/null +++ b/src/android/ShutoEventManager.java @@ -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> eventListeners; + + private ShutoEventManager() { + eventListeners = new ConcurrentHashMap<>(); + } + + public static synchronized ShutoEventManager getInstance() { + if (instance == null) { + instance = new ShutoEventManager(); + } + return instance; + } + + public void addListener(String eventName, ShutoEventListener listener) { + if (eventName == null || listener == 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(String eventName, ShutoEventListener listener) { + if (eventName == null || listener == null) { + return; + } + + Set 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 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 data) { + if (eventName == null) { + return; + } + + LOG.d(TAG, "Firing event: " + eventName + ", data: " + data); + + Set 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); + } +} diff --git a/src/ios/ShutoApi.m b/src/ios/ShutoApi.m index ab5572f..926b8ac 100644 --- a/src/ios/ShutoApi.m +++ b/src/ios/ShutoApi.m @@ -3,6 +3,7 @@ #import #import #import "SGGC_Log.h" +#import "ShutoEventManager.h" @protocol ShutoApiProtocol - (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 diff --git a/src/ios/ShutoEventManager.h b/src/ios/ShutoEventManager.h new file mode 100644 index 0000000..d819d17 --- /dev/null +++ b/src/ios/ShutoEventManager.h @@ -0,0 +1,27 @@ +// +// ShutoEventManager.h +// ShutoApi +// +// Created by Shuto Team. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol ShutoEventListener +- (void)onEvent:(NSString *)eventName data:(NSDictionary *)data; +@end + +@interface ShutoEventManager : NSObject + ++ (instancetype)sharedInstance; + +- (void)addListenerForEvent:(NSString *)eventName listener:(id)listener; +- (void)removeListenerForEvent:(NSString *)eventName listener:(id)listener; +- (void)removeListener:(id)listener; +- (void)fireEvent:(NSString *)eventName data:(NSDictionary *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/ios/ShutoEventManager.m b/src/ios/ShutoEventManager.m new file mode 100644 index 0000000..dcd2dcc --- /dev/null +++ b/src/ios/ShutoEventManager.m @@ -0,0 +1,114 @@ +// +// ShutoEventManager.m +// ShutoApi +// +// Created by Shuto Team. +// + +#import "ShutoEventManager.h" +#import "SGGC_Log.h" + +@interface ShutoEventManager () +@property (nonatomic, strong) NSMutableDictionary> *> *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)addListenerForEvent:(NSString *)eventName listener:(id)listener { + if (!eventName || !listener) { + 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)removeListenerForEvent:(NSString *)eventName listener:(id)listener { + if (!eventName || !listener) { + 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)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 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 diff --git a/www/ShutoApi.js b/www/ShutoApi.js index f685cc8..9afc24f 100644 --- a/www/ShutoApi.js +++ b/www/ShutoApi.js @@ -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', []); } };