This commit is contained in:
2026-01-22 09:47:02 +08:00
9 changed files with 460 additions and 0 deletions

166
README.md
View File

@@ -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 () <ShutoEventListener>
@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<String, Object> 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<String, Object> 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

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

@@ -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<String, EventCallback> 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;
}
}

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(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<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)addListenerForEvent:(NSString *)eventName listener:(id<ShutoEventListener>)listener;
- (void)removeListenerForEvent:(NSString *)eventName listener:(id<ShutoEventListener>)listener;
- (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)addListenerForEvent:(NSString *)eventName listener:(id<ShutoEventListener>)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<ShutoEventListener>)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<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', []);
}
};