写点什么

如何做到四端统一桥接?微医跨平台桥接标准化方案了解一下

  • 2021-06-17
  • 本文字数:9395 字

    阅读完需:约 31 分钟

如何做到四端统一桥接?微医跨平台桥接标准化方案了解一下

近几年随着 React Native、Flutter、Weex 等跨平台框架的流行,使得程序员可以尽量关注于业务本身,而非平台间的差异。但是不管哪一种方案,从移动端的角度看,都对底层桥接 API 有着共同的诉求。从 H5 到 React Native,再到 Weex 以及后面的 Flutter,原生进行了多轮的 API 重复建设,造成了缺少 API 接口的标准化定义,以及实现的统一管控的现状。所以针对这一情况,我们将统一所有容器 Bridge API,包括接口的定义,以及其底层原生代码。


方案概况

旧方案流程介绍

以前需要桥接一个方法时,我们需要在 Web、RN、Weex、Flutter 各自写一套注册和实现,有时候往往因为需求时间的不同,或者开发人员的不同,导致相同功能的 Bridge 定义和实现也不一样,这不仅仅浪费了开发人员的时间精力,而且当 Web 需要换成 RN 或者 Weex 时,增加了替换的难度及风险。就算规划的好一点,统一了 Bridge 的实现和接口,但那时还是需要开发人员对各端都做注册实现,也是很浪费时间。

新方案流程介绍

我们在基础容器层对各跨平台容器的 Bridge 层做了适配,主要是模块注册、Bridge 解析、调用以及兼容方案,具体实现后文会讲。


WYBridge


首先我们先介绍下底层的 WYBridge 库,它包括了 4 端统一的桥接及实现。就像前面说的,我们以前的 bridge 都是各端写各自的,实现及接口定义都不统一,极不规范。而现在我们只需要在这个库中增加一个 module,上层 4 端会自动注册这个模块,这样各端的定义和实现都是用的 WYBridge 中的,实现了下层的统一。


Android


Android 中的 WYBridge 的核心为 BridgeModule,提供给上层的模块都会继承该接口,里面提供了 4 端都会用到的方法。


public interface BridgeModule {  //模块名称  String getName();}

复制代码


同时我们新建了 BridgeMethod 注解,标记为该模块提供给上层的方法(类似于 RN 中的 @ReactMethod)。提供的方法必须符合下面两种模板中的一种:


@BridgeMethodpublic void xxxx(JSONObject data, BridgeJSCallBack callBack){}
@BridgeMethodpublic void xxxx(BridgeJSCallBack callBack){}

复制代码

下面就是一个简单的 BridgeModle 例子:

public class XXTestModule extends BaseBridgeModule {
@Override public String getName() { return "xxtest"; } @BridgeMethod public void getData(JSONObject data, final BridgeJSCallBack callBack){ //do something }}
复制代码


各端通过解析这些模块,将其注册到自己的平台中。至此我们通过 WYBridge 实现了底层的统一。

iOS


iOS 中的 WYBridge 的核心为宏定义文件,提供给上层的模块通过 XX_EXPORT_MODULE(module_name)宏将该类以 module 的方式暴露给 JS,然后使用 XX_EXPORT_METHOD(js_name)将 Native 方法暴露给 JS。


模块注册


define XX_EXPORT_MODULE(module_name) \    XX_EXTERN void XXRegisterWebModule(Class); \    XX_EXTERN void XXRegisterWeexModule(Class); \    XX_EXTERN void RCTRegisterModule(Class); \    XX_EXTERN void XXRegisterFlutterModule(Class); \    + (void)load {\        XXRegisterWebModule(self);\        XXRegisterWeexModule(self);\        RCTRegisterModule(self);\        XXRegisterFlutterModule(self);\    }\    + (NSString *)moduleName { return @# module_name; }
复制代码

注:load 里面会注册四个平台的RegisterXXModule,若没有对应平台的SDK,可以通过判断头文件注册声明一个空的RegisterXXModule


如上代码所示,XX_EXPORT_MODULE 宏背后是两个静态方法+(NSString *)moduleName 和+(NSString *)load。moduleName 方法简单的返回了 Native 模块的类名,load 方法是大家耳熟能详的的,load 方法调用 RegisterXXModule 函数注册了模块,我这里注册了 4 端,RegisterXXModule 函数的实现是参考 RCTRegisterModule(该函数定义在 RCTBridge.m 中)


void RCTRegisterModule(Class);void RCTRegisterModule(Class moduleClass){  static dispatch_once_t onceToken;  dispatch_once(&onceToken, ^{    RCTModuleClasses = [NSMutableArray new];    RCTModuleClassesSyncQueue = dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);  });...  // Register module  dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{    [RCTModuleClasses addObject:moduleClass];  });}
复制代码


很简单,RCTRegisterModule 函数只做了 3 件事: 1.创建一个全局的可变数组/字典和一个队列(在 Web/Weex/Fluuter 我定义的是一个字典) 2.检查导出给 JS 模块是否遵守了 RCTBridgeModule 协议(由于该检查,需要在 WYBridge 写一个 RCTBridgeModule 的空协议,为了躲过检查,其他端可不作检查) 3.把要导出的类添加到全局的可变数组/字段中进行记录 在 APP 启动后调用 load 方法时,所有需要暴露给 JS 的方法都已经被注册到一个数组/字典中。到此为止,只是把需要导出给 JS 的类记录下来.


方法注册及实现

# define XX_EXPORT_METHOD(method) \XX_EXPORT_METHOD_INTERNAL(@selector(method),xx_export_method_)\RCT_REMAP_METHOD(, method)# define XX_EXPORT_METHOD_INTERNAL(method, token) \   + (NSString *)XX_CONCAT_WRAPPER(token, __LINE__) { \    return NSStringFromSelector(method); \    }# define RCT_REMAP_METHOD(js_name, method) \ _RCT_EXTERN_REMAP_METHOD(js_name, method, NO)# define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \+ (const RCTMethodInfo *)XX_CONCAT_WRAPPER(__rct_export__, XX_CONCAT_WRAPPER(js_name, XX_CONCAT_WRAPPER(__LINE__, __COUNTER__))) { \static RCTMethodInfo config = {# js_name, # method, is_blocking_synchronous_method}; \   return &config; \}typedef struct RCTMethodInfo {      const char *const jsName;      const char *const objcName;      const BOOL isSync; } RCTMethodInfo;
复制代码


通过上面一系列的宏调用不难看出,XX_EXPORT_METHOD 做了 3 件事


  1. 定义一个对象方法,用于真正调用的方法实现

  2. 定义了一个静态方法,该方法名为 xx_export_method___LINE__,返回值是注册方法名,用于 Web 和 Weex,会扫描所有导出的 nativemodule 中以 xx_export_method 的方法

  3. 定义一个静态方法,该方法名的格式是 +(const RCTMethodInfo *)__rct_export__+js_name+___LINE__+__COUNTER__,用于 RN 这个方法包装成了一个 RCTMethodInfo 对象,在运行时 RN 会扫描所有导出的 Native module 中以__rct_export__开头的方法。


以上只是说了 module 和 method 是如何导出的,这些模块和方法的注册将会在各自模块中介绍。


使用

//模块注册XX_EXPORT_MODULE(moduleName)//方法注册XX_EXPORT_METHOD(methodName:success:fail:)//方法实现- (void)methodName:(NSDictionary *)data success:(XXBridgeResolveBlock)successfail:(XXBridgeRejectBlock)fail {     ...     success(resdic); }
复制代码

注:由于RN里会对XX_EXPORT_METHOD()中的参数做解析,然后取的方法名做跳转,而Web/WEEX中直接取的方法名,所以方法注册的时候还没有统一,可以优化。


Web


之前不管是 Android 还是 iOS,都是由原生提供给 H5 一个统一的方法用于调用原生桥接模块,H5 把需要调用的桥接方法名以及参数以 json 字符串的方式传入。这些桥接在 webview 初始化的时候进行注册,注册时只包含方法名,没有模块名,native 根据参数找到对应注册的方法进行调用,整个流程图如下:

前面说到 H5 只有方法名,但是 RN 和 Weex 都是以"模块名.方法名"的方式进行调用,所以为了和其他端统一,H5 调用原生桥接的方法名也需要加上模块名,并且初始化时,需将 WYBridge 中的桥接进行注册。新的流程图如下:


Android

下面具体介绍一下 Android Web 桥接 WYBridge 的实现。

注册

整体仿照 weex 和 RN 的注册。新增新的注册方式,将 BridgeModule 传入:

//新的注册方式boolean registerHandler(Class<? extends BridgeModule> moduleClass);
复制代码

注册的时候,我们会将 BridgeModule 转化成一个管理类,该类提供了 3 个方法:module 实例化、module 方法解析和 module 方法的调用。调用模块实例化方法,创建 BridgeModule 实例,将前面的管理类和 module 实例都以键值对的方式生成 Map 表。

解析

前面管理类中 module 方法解析,实际上就是解析 module 中被 @BridgeMethod 注解的方法, 获取到方法的 Invoker,最后的方法调用就是在这个 Invoker 里面实现的。

for (Method method : mClazz.getMethods()) {  for (Annotation anno : method.getDeclaredAnnotations()) {    if (anno instanceof BridgeMethod) {      String name = method.getName();      methodMap.put(name, new Invoker(method));      ...      break;    }  }}
复制代码

调用

已知 H5 调用都是通过统一的方法,最终会到一个处理 JS 调用 Native 数据类。在这个处理类中,我们将 H5 传入的数据进行解析。调用新的 bridge 方法时,我们和 H5 约定方法名传入为“模块名.方法名”,所以通过解析,我们可以得到对应的模块名和方法名。

这样根据模块名找到存在上面 Map 中该模块的实例和其管理类,又在管理类中根据方法名可以获取前面解析得到的该方法对应的 Invoker。最后我们将传入的参数和回调一同传入 Invoker 中,调用里面 method 的 invoke 方法,即 WYBridge 中方法的调用。

method.invoke(receiver, params);
复制代码

兼容方案

因为原来 H5 都是用过方法名的方式调用原生,现改成“模块名.方法名”,旧的调用模块将不再适用。但是 H5 团队繁多,没有统一的调用基类,由前端一处一处的改动是不现实的,所以需要做到兼容老的版本。 目前 Android 这边的处理方案是,对于旧的桥接方法,新建一个对应的处理类,里面包含了如下 5 个接口:

//旧的方法名String aliasName();//新的模块名String bridgeModuleName();//新的方法名String bridgeMethodName();//入参转换JSONObject mapping(String data);//回调参数转换String backMapping(String data, int code, String msg);
复制代码

在 webView 初始化的时候也将这些处理注册到一个 Map,以旧的方法名为 Key。当 H5 还是调用老的方法时,通过这个类找到对应新的模块名和方法名,从而进行调用,整个流程如下:


iOS

下面具体介绍一下 iOS Web 桥接 WYBridge 的实现。

注册

WYBridge 中,在 APP 启动后调用 load 方法时,所有需要暴露给 JS 的方法以 class 的方式 都已经被注册到字典保存着,循环执行注册方法即可.

以前是维护一份以 methodName 为 key,原生代码实现的 block 为 value 的字典,现是以 moduleName.methodName 为 key, 通过 NSInvocation 封装了方法调用对象、方法选择器、参数、返回值等的 block 为 value 的字典.

[self registerApi:newMethod block:^(XXHandlerModel *handlerModel) { // 1、通过传入handlerModel获取 方法名、参数 // 2、通过 methodName 获取 selector // 3、通过注册的时候保存的对象,生成该方法签名,设置调用对象,设置参数然后调用方法   }];
复制代码

调用

我们和 H5 约定方法名传入为“模块名.方法名”,我们可以得到对应的模块名和方法名,通过获取一个类的所有实例方法,将所有以“xx_export_method_”开头的方法返回保存在字典中,返回的是调用用的方法名.

- (NSDictionary *)clazzMethodFactory {    ...    //1、通过class_copyMethodList获取所有方法    //2、返回以xx_export_method_开头方法字典
}
复制代码

兼容方案

iOS 这边的处理方案同 Android,新建一个对应的处理类 XXBridgeFactoryMapping,处理新老方法名字、入参、回调映射。


React Native


RN 中我们给每一个 Module 都封装了一个 ts 文件,用来统一上层 RN 端调用的接口,业务代码引用的时候直接用该方法就行。举个例子,如 wytest 模块,里面包含了一个 getData 方法:

//WYNativeTest.tsimport {NativeModules} from 'react-Native';
let WYTestModule = NativeModules.wytest;
export var WYNativeTest = {    getData(): Promise<any> {    return WYTestModule.getData().then((value: any) => {        return value;    })    }}
复制代码

而 Native 层,Android 和 iOS 都有自己的一套桥接库,在应用初始化的时候进行注册。以前这些桥接实现都是写在各自库里面,现在需要接入 WYBridge 实现底层统一,各端的方案各有不同,下面具体讲解。

Android

旧的 RN 桥接先是继承 ReactPackage 创建一个原生模块包用来添加原生模块,后通过继承 ReactContextBaseJavaModule 创建原生模块,复写 getName 方法设置模块名,对 public 方法添加 @ReactMethod 注解表示 RN 端可调用该方法。然后在应用初始化时添加该原生模块包,这样在 RN 创建 ReactContext 时,会解析该包,从而解析里面的原生模块,创建 JavaScript Module 注册表,方便 JS 层调用。整个流程如下:


由上图可知整个流程对我们接入 WYBridge 最主要的是 JavaModuleWrapper,里面解析了模块的名称以及 @ReactMethod 注解的方法,所以我们需要修改里面的解析用来支持 WYBridge,但是 JavaModuleWrapper 又是在 NativeModuleRegistry 里面 new 的,而 NativeModuleRegistry 则是在创建 reactContext 中创建的,中间过程无法介入修改。幸好 CatalystInstanceImpl 提供了 extendNativeModules 方法,通过修改 NativeModuleRegistry 注册 Modules。故新的流程图如下:


下面具体介绍一下 Android RN 桥接 WYBridge 的实现。

注册

注册由原来从 RN 初始化时注册改为初始化完成时注册。

reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {    @Override    public void onReactContextInitialized(ReactContext context) {        //注册 modules    }});

复制代码

注册时会解析 RN 的 Package,Package 就是 WYBridge 中所有 module 的集合。将 BridgeModule 以 moduleName 为 key 生成 Map 表。

将生成的 Map 传入到注册类中,这个注册类继承 NativeModuleRegistry。最后调用 CatalystInstanceImpl 中的 extendNativeModules 方法进行注册。

//extendNativeModules 注册 modulepublic void extendNativeModules(NativeModuleRegistry modules) {    ...    Collection<JavaModuleWrapper> javaModules = modules.getJavaModules(this);    ...    this.jniExtendNativeModules(javaModules, cxxModules);}

复制代码

解析

在 extendNativeModules 中可以看到调用了 getJavaModules 方法,在里面将 module 转化成 RN 的模块解析类 JavaModuleWrapper。我们重写里面的方法解析以及 invoke 方法,原来 RN 在 JavaModuleWrapper 里解析 @ReactMethod,现在我们则解析 BridgeModule 中的 @BridgeMethod 注解。最后将注解的方法生成对应的 MethodWrapper 类,该类继承 NativeModule.NativeMethod。

调用

RN 端调用原生,实际上调用的是 JavaModuleWrapper 的 invoke 方法。根据传入的 methodId,找到前面生成的对应 MethodWrapper 类。将参数以及回调传入,调用 method 的 invoke 方法,即 WYBridge 中方法的调用。

method.invoke(receiver, params);
复制代码

兼容方案

因为 RN 项目不分团队,且上层有封装,即使改变底层实现,也可以统一上层的调用。所以并未做兼容方案,直接对原来代码进行修改。

现存问题

因为 RN 之前桥接是在初始化时注册的,现在是在初始化完成时注册,所以当显示 RN 页面时,可能存在该桥接还未注册完成的时候。目前没有特别好的方法避免,只能在显示 RN 页面时做了一些延迟。

iOS

注册

WYBridge 中,在 APP 启动后调用 load 方法时,调用 RCTRegisterModule

解析调用

由于 WYBridge 注册方式回调与其 SDK 规则一致,所以不用适配


Weex


Weex 和 RN 相似,也是在初始化的时候注册桥接。原先 Native 层同样在 Android 和 iOS 都有自己的一套桥接库,实现都是写在各自库里面,现在需要接入 WYBridge 实现底层统一,各端的方案也各有不同,下面具体讲解。

Android

旧的 Weex 注册桥接时,桥接类必须继承 WXModule,方法的解析在 TypeModuleFactory 中实现。注册时调用 registerModule 设置 moduleName 和对应的 Module,对 public 方法添加 @JSMethod 注解表示 Weex 可调用该方法。整个注册和调用流程如下:

上图可知方法的解析是通过 TypeModuleFactory,我们只要修改里面的解析方法,就可以实现 Weex 识别 WYBridge 的方法。而 WXSDKEngine.registerModule 中可以传入 ModuleFactory 进行注册,而 TypeModuleFactory 就是继承该类的,故我们可以重写 ModuleFactory 将其传入,进行注册。下面是整个新的流程:

下面具体介绍一下 Android Weex 桥接 WYBridge 的实现。

注册

注册由原来调用 registerModule(String moduleName, Class<? extends WXModule> moduleClass)改为调用 registerModule(String moduleName, ModuleFactory factory, boolean global),首先创建一个类继承 ModuleFactory,将 Module 进行封装传入注册。后面建立 JS 和 Native 的映射表之类就和原来的 Weex 一致。

解析

在这个新建的类中重写里面的解析过程。原来 Weex 在 TypeModuleFactory 里解析 @JSMethod,现在我们则解析 BridgeModule 中的 @BridgeMethod 注解,然后返回改方法的对应的 Invoker。

前面的 Web 过程就参考这里 weex 的解析过程。

调用

Weex 端调用原生,就是找到对应的 ModuleFactory,在该类中我们根据传入的方法名找到前面生成的 Invoker,在这里处理对应方法参数以及回调,具体参考 Weex 中 NativeInvokeHelper。最后调用 Invoker 的 invoke 方法,即 WYBridge 中方法的调用。

兼容方案

由于 Weex 层没有像 RN 那样团队单一,且上层有 JS 封装统一文件,所以需要对老的调用方式进行兼容。 其原理和 Web 一样,即在旧方法调用时有一个依赖关系,可对应新方法的 moduleName、methodName、参数的映射和回调参数的映射,具体的过程就不再过多的说明了,可参考 Web 的兼容过程。

iOS

注册

WYBridge 中,在 APP 启动后调用 load 方法时,所有需要暴露给 JS 的方法以 Class 的方式 都已经被注册到 WYBridgeGetModuleClassesDic 保存着,循环执行注册方法即可。

解析调用

解析过程同以前 Weex 调用一致

兼容

其实原理和 Web 一样,即在旧方法调用时有一个依赖关系,可对应新方法的 moduleName、methodName、参数的映射和回调参数的映射, Hook Weex 的 WXJSCoreBridge 的 registerCallNativeModule 中调用的模块名和方法名 Hook Weex 的 WXBridgeMethod 的 invocationWithTarget:selector: 的方法 处理参数回调映射。 整体流程图如下:


Flutter


Flutter 就比较简单,我们新建了一个 wrapper_bridge 的插件,所有的调用都通过这个通道来桥接。在 Flutter 层,我们给每个 Module 都新建了一个 dart 文件,并对里面的方法进行包装,方法调用的名称规定为“module 名称.方法名称”,这样业务调用时则会更加的清晰,举个例子,如 wytest 模块,里面包含了一个 getData 方法:


//wytest.dartimport 'dart:async';
import 'package:flutter/services.dart';
class WYTest {  static const MethodChannel _channel =  const MethodChannel('bridge');    static const moduleName = 'wytest.';
  static Future<Map> get getData async {    final Map data = await _channel.invokeMethod(moduleName+'getData');    return data;  }}
复制代码


在业务代码中则可以引入这个文件,调用里面的方法:


import 'package:bridge/wytest.dart';
WYTest.getData.then((value){  print(value.toString());});
复制代码


Flutter 层代码统一,对于 Native 层提供的 module 方法没有注册流程,只是在调用的时候,根据调用方法名称进行区分,所以中间并不需要修改。Flutter 使用平台通道和原生端进行通讯,下面是官网的一张图:



Android

已知 Flutter 调用原生方式是通过 MethodChannel 中 invokeMethod 方法名调用的,最终所有的方法都在原生的 onMethodCall 方法中做处理:

@Overridepublic void onMethodCall(MethodCall call, final MethodChannel.Result result) {}
复制代码

MethodCall 中保存了调用的方法名称以及参数,MethodChannel.Result 则是回调。既然我们已经规定了方法调用名称格式为“module 名.方法名”,所以我们可以通过解析名称从而调用对应的 WYBridge 方法。 在初始化的时候,我们将 WYBridge 里面的 module 遍历生成 Map。

然后在方法调用时,根据传过来的方法名解析出 module 名称和实际调用的方法名称,通过 module 名称找到对应的模块。

知道了方法名以及实际调用的 moudle,我们就可以 invoke 方法。接下来就和前面的那些一样,传入参数和回调,调用 invoke 方法。

invoke 中我们看到,只要是 module 中有的方法都是可以被调用,这样可能会存在安全漏洞,所以我们规定允许被调用的函数必须有 @BridgeMethod 注解,故调用的时候需要进行解析,如下:

//获取调用的方法Method m = moduleClazz.getMethod(methodName, new Class[]{...});for (Annotation anno : me.getDeclaredAnnotations()) {    //判断是@BridgeMethod 注解的才执行    if(anno instanceof BridgeMethod) {        m.invoke(...);    }}
复制代码

这样我们就实现了 Android Flutter 桥接 WYBridge 底层的统一。

iOS

与其他三端一样,在 APP 启动后调用 load 方法时,所有需要暴露给 JS 的方法以 Class 的方式 都已经被注册到 WYBridgeGetModuleClassesDic 保存着. 方法的调用在原生 Flutter 插件中的 handleMethodCall 方法中做处理

获取模块名.方法名通过解析找到对应的方法通过获取一个类的所有实例方法,将所有以“xx_export_method_”开头的方法返回保存在字典中,返回的是调用用的方法名.然后通过注册的时候保存的对象,生成该方法签名,设置调用对象,设置参数然后调用方法


总结及展望


目前 WYBridge 已经在微医和微医生项目中替换了部分 Bridge 投入使用,使用过程中未发现明显 bug,接下来我们将进一步将所有 Bridge 都替换完成,统一所有容器 Bridge API。 这次的方案涉及的都只是桥接方法的调用,对于属性等桥接并未涉及。而且 Android 因为 RN 和 Weex 都涉及到源码,iOS 在 Weex 中也有涉及,所以在升级时这几部分解析也需要跟着升级,并不是特别友好。 通过这次方案研究,进一步衍生开来,对于 RN、Weex、Flutter 一些原生组件桥接也可以得到统一,这是未来我们所要研究的,希望感兴趣的小伙伴和我们一起交流分享。



头图:Unsplash

作者:殷利萍,黄丽丽

原文:https://mp.weixin.qq.com/s/jq_mavgLnt8wPNemTbpoag

原文:如何做到四端统一桥接?微医跨平台桥接标准化方案了解一下

来源:微医大前端技术 - 微信公众号 [ID:wed_fed]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-06-17 08:005069

评论

发布
暂无评论
发现更多内容

阿里数据中台底座的12年建设实践

阿里云大数据AI技术

实践解析丨如何通过 WebAssembly 在 Web 进行实时视频人像分割

声网

大前端 WebRTC webassembly

实战-使用 SSM 工具创建可动态扩容的存储池

学神来啦

Linux 运维 ssm Linux教程

【等保知识】等保测评机构申请条件,所需资料以及流程

行云管家

等保 堡垒机 行云管家 等保测评

启动、内存、卡顿三大分析,用户体验就用它?

App

《人这一辈子,都在为认知闭环买单》读后感---刘润

Changing Lin

我学编程时最后悔的事!

程序员鱼皮

Java c++ Python 大前端 后端

极光开发者周刊【No.0723】

极光GPTBots-极光推送

百度AI寻人获评《新周刊》2021年度公益项目

百度大脑

人工智能 寻人

从零开始学习3D可视化之数据对接(3)

ThingJS数字孪生引擎

大前端 数据 物联网 可视化 数字孪生

上线仅7天,GitHub已标星48.4k!原来是阿里巴巴内部《高并发系统设计》

Java redis 编程 架构 面试

学习下服务器端漏洞,受益匪浅!

网络安全学海

运维 网络安全 信息安全 漏洞扫描 渗透测试·

在线条码生成器

入门小站

工具

6月热点:BML全新升级WebIDE编程环境,度目智能视频分析盒G1上新

百度大脑

人工智能 BML

可以同时管理公有云和私有云资源的软件哪个好?

行云管家

公有云 私有云 云管平台 云资源

C# 三个Timer

喵叔

7月日更

网络攻防学习笔记 Day83

穿过生命散发芬芳

网络攻防 7月日更

Android Flutter 多实例实践

网易云信

flutter 架构

【得物技术】得物开放平台进阶之路

得物技术

安全 后端 平台 订单

结构化流-Structured Streaming(八-下)

Databri_AI

spark 流式计算框架 structuredStreaming

药物研发使用北鲲云高性能计算平台,有效解决研发效率问题

北鲲云

使用数据库乐观锁的方式解决数值累加的问题

陈靓-哲露

线上教育培训机构如何推广自己

石头IT视角

细节分析Linux中五种IO模型和三种实现方式

Linux服务器开发

网络编程 epoll Linux服务器开发 Linux后台开发 IO模型

怎么才能写出100个用户体验的关键时刻?

石云升

读书笔记 用户体验 关键时刻 7月日更

58字节常量池面试题,你如何应对?

卢卡多多

intern 字符串 7月日更

Apache Druid 简介

HoneyMoose

Linux之cal命令

入门小站

Linux

微观管理?

escray

学习 极客时间 朱赟的技术管理课 7月日更

Ipfs国家认可吗?国家对ipfs区块链是什么政策?

区块链 分布式存储 IPFS fil

模块三作业

燕燕 yen yen

架构训练营

如何做到四端统一桥接?微医跨平台桥接标准化方案了解一下_语言 & 开发_微医大前端技术_InfoQ精选文章