Flutter插件reshub_flutter的介绍与使用

Flutter插件reshub_flutter的介绍与使用

Shiply平台介绍及reshub_flutter接入

一、Shiply 远程资源产品简介

Shiply 是一款企业级远程资源管理解决方案,通过端云协同技术实现资源动态化管理闭环。依托差量更新算法与全链路监控体系,助力移动应用实现安装包体积缩减30%+热更新生效成功率99.9%CDN流量成本降低最高80% 的三大核心价值突破。

1.1 远程资源的关键应用场景

Shiply 远程资源适用于解决以下场景的问题:

  1. App 包体积优化:通过远程按需加载资源(如H5离线包、模型、运营素材),显著减少软件包大小,提升下载转化率。

  2. 运营活动分钟级发布:如双11、奥运会等大型活动期间,需要快速上线运营页面,传统的更新方式难以满足分钟级发布的需要。而通过远程配置、远程资源,实现动态加载页面素材和活动规则,可以实现分钟级的更新和优化。

  3. 端智能模型管理:在端智能模型时代,功能优化往往通过更新模型实现,为了更新模型提审一个版本未免大费周章,通过远程资源部署模型可实现发布即更新。

  4. 远程资源按需更新:将部分资源(如图片、配置文件、素材等)存放在远程服务器上,App 在运行时按需下载。这种方式可以有效减小 App 的初始包体积,同时实现资源的动态更新。

  5. 功能插件化:将 App 的功能模块化,以插件的形式存在。根据用户的需求和使用场景,动态加载和更新插件,从而避免了整体 App 的频繁更新。

  6. 构建用户个性化体验:对于只有VIP用户需要的动画、素材资源,可以实现定向发布更新,按需下载,还能避免占用普通用户的存储空间。

  7. 主题、字体、皮肤管理:如何灵活地管理 App 的字体、皮肤和主题,以满足不同用户的个性化需求。

  8. 智能硬件固件更新:对于智能硬件的固件更新,需要确保更新过程的安全性和可靠性。

  9. H5 离线包:将 HTML、JavaScript、CSS 等页面内静态资源打包的压缩包内,预先下载离线包到本地,从本地加载离线包,摆脱网络环境对页面加载的影响,实现“秒开”。

1.2 远程资源的功能优势

  • 动态发布+热更新

    • 免发版免审核:资源下发即生效,无需应用发版,无需商店审核。
    • 发布效率革命:将发版周期从数周提升至小时级,甚至分钟级。
  • 资源管控闭环

    • 提供资源加密、压缩、下载/预下载/仅WiFi下载、差量更新、校验(MD5/CRC32)、版本控制、强制下线、自动降级等核心功能。
    • 差量更新:基于BSDiff等差量算法生成差量包,针对线上覆盖率最高的历史版本智能匹配(最高支持20个历史版本的差量),最高节省 80% 流量。
  • 全链路监控

    • 实时统计看板:生效数/成功率/耗时/CDN流量分析/错误量/版本覆盖率。
    • 可视化追溯:资源版本历史记录、生效时间管理,结合时间轴追溯变更影响。
  • 发布安全

    • 权限控制:支持开发、测试、发布负责人权限划分。
    • 操作审计:关键变更操作记录存档,问题可追溯。
    • 防篡改:文件完整性校验、本地防篡改、端到端校验保障资源下载-加载成功率99%+、满足企业级安全发布要求。
  • 分发灵活:分发规则灵活丰富、且支持无限扩展。

    • 默认规则丰富:支持版本、地区、网络、用户群(号码包)、百分比、时间等等。
    • 支持无限扩展:支持任意自定义字段作为分发条件。
  • 下发文件格式:没有文件的限制!适用于任何资源的下发。

    • 平台支持将任意格式资源打包 zip 和 7z 格式下发,SDK 自动解压还原成原始文件。也支持 APK、SO 的下发。

二、reshub_flutter SDK接入

Flutter 版本的 Shiply 资源 SDK。

2.1 配置依赖

在 Flutter 工程中的 pubspec.yaml 中添加 reshub_flutter 的依赖:

dependencies:
    reshub_flutter: 0.0.13

在代码中导入插件接口:

import 'package:reshub_flutter/reshub_flutter.dart';

iOS 需要在 Target 的 Build Settings 中设置 Allow Non-modular Incudes In Framework Modules = YES


2.2 初始化 reshub

具体例子可以参考 example/lib/main.dart

2.2.1 在 Dart 层初始化 ReshubCenter

String deviceId = "device123";
String appVersion = "1.2.3";
// 是否是 debug 包
bool isDebugPackage = false;
Map<String, String> params = Map();
params["testKey"] = "testValue";
ReshubFlutter.initReshubCenter(deviceId, appVersion, isDebugPackage, params);

if (Platform.isIOS) {
    ReshubFlutter.initReshub("14e1a90fcd", "d96d3d4a-fe58-4b8d-99c4-61d894a3d3bb", "online");
} else if (Platform.isAndroid) {
    ReshubFlutter.initReshub("9d256c9ecc", "bfe91841-af1a-4572-9868-8b3c70a8d19c", "online");
}

2.2.2 在 Dart 层初始化 reshub 实例

ReshubFlutter reshubInstance = ReshubFlutter();

2.2.3 Native 层设置

Android 避免重复初始化

组件的初始化调用,需要结合具体业务场景来考虑: 组件本身设计为单例模式,多次调用,只会生效一次。 在混合工程中,如果 Native 侧已经对 Reshub 组件进行过初始化,对于 Android 平台,需要在 Native 初始化 Reshub 组件的代码处额外调用:

ReshubHostApiImpl.markHasInitReshubCenter()
iOS 避免重复初始化
[[ReshubHostApiImpl sharedInstance] markHasInitReshubCenter];
Android 对接日志实现

如果是混合工程,并且 Native 侧已经设置过 Reshub 组件日志实现,可以不用再额外设置;如果 Native 侧没有设置过 Reshub 组件日志实现,需要在 dart 层初始化 ReshubCenter 之前,先设置好日志实现类:

ReshubHostApiImpl.setLogDelegate(log: IRLog)
iOS 对接日志实现
  1. 接出日志,需要实现 RaftInterface 标准里的 RAFTLogProtocol 协议:
// 复制 RDelivery 的 RDLoggerImpl 文件,将 NSLog 一行换成App中打印日志的函数即可。
https://git.woa.com/RDelivery/Core/rdelivery-ios/blob/master/RDelivery/DefaultInjectImpl/Log/RDLoggerImpl.m
  1. 替换成自己日志实现,完成日志接出:
@interface CustomDependImpl : ResHubDependImpl
@end

@implementation CustomDependImpl
+ (instancetype)defaultDepends {
    CustomDependImpl *depend = [super defaultDepends];
    // ResHubDependImpl 下的所有组件,都可以替换成你的实现,以日志组件为例
    depend.logImpl = [CustomLogImpl sharedInstance];
    return depend;
}
@end
  1. 依赖注入到 ResHub:
- (void)injectReshubDependImpl {
    CustomDependImpl *depend = [CustomDependImpl defaultDepends];
    [[ReshubHostApiImpl sharedInstance] injectDependImpl:depend];
}

2.3 使用 reshub 实例加载资源

// 业务方在 shiply 前端页面创建的资源 key 名字
String resId = "test_res_key";

// 获取资源 key 的锁定版本,同一个进程内多次调用都返回同一个版本
reshubInstance.get(resId).then(
    (value) => printText("get result => path: ${value.localPath} "));

// 获取资源 key 的最新版本,同一个进程内多次调用可能返回不同版本,比如使用过程中从 server 拉取到了更高的版本,则会返回高版本
reshubInstance.getLatest(resId).then(
    (value) => printText("getLatest result => path: ${value.localPath} "));

// 加载资源 key 的本地版本,同一个进程内多次调用都返回同一个版本,这个方法会异步尝试下载更高版本的资源
reshubInstance.load(resId).then(
    (value) => printText("load result => isSuccess: ${value.isSuccess} "
        "path: ${value.resModel?.localPath},originLocalPath: ${value.resModel?.originLocalPath}"));

// 加载资源 key 的最新版本,这个方法会同步尝试下载更高版本的资源
reshubInstance.loadLatest(resId).then(
    (value) => printText("loadLatest result => isSuccess: ${value.isSuccess} path: ${value.resModel?.localPath}"));

示例代码

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:reshub_flutter/reshub_flutter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ReshubFlutter reshubInstance;
  StringBuffer textLog = StringBuffer();
  String _platformVersion = 'Unknown';
  String resId = "test_hippy_search_android";

  [@override](/user/override)
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    reshubInstance = ReshubFlutter();
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      platformVersion = await ReshubFlutter.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('reshub flutter plugin example app'),
        ),
        body: Column(children: [
          ElevatedButton(onPressed: _onPressInit, child: const Text('init ')),
          ElevatedButton(onPressed: _onPressGet, child: const Text('call get ')),
          ElevatedButton(onPressed: _onPressGetLatest, child: const Text('call getLatest ')),
          ElevatedButton(onPressed: _onPressLoad, child: const Text('call load ')),
          ElevatedButton(onPressed: _onPressLoadLatest, child: const Text('call loadLatest ')),

          Expanded(
              child: SingleChildScrollView(
                scrollDirection: Axis.vertical,
                child: Text(textLog.toString()),
              ))
        ]),
      ),
    );
  }

  void _onPressInit() {
    Map<String, String> params = Map();
    params["testKey"] = "testValue";
    ReshubFlutter.initReshubCenter("device123", "1.2.3", true, params);
    // https://shiply.tds.woa.com/project/setting/normal?projectId=195 shiply体验项目 RDeliveryDemo-Android 产品
    if (Platform.isIOS) {
      resId = "testpatch";
      ReshubFlutter.initReshub("14e1a90fcd", "d96d3d4a-fe58-4b8d-99c4-61d894a3d3bb", "online");
      printText('init called, platform = iOS');
    } else if (Platform.isAndroid) {
      ReshubFlutter.initReshub("9d256c9ecc", "bfe91841-af1a-4572-9868-8b3c70a8d19c", "online");
      printText('init called, platform = Android');
    }
  }

  void _onPressGet() {
    reshubInstance.get(resId).then(
            (value) => printText("get result => path: ${value.localPath} "));
  }

  void _onPressGetLatest() {
    reshubInstance.getLatest(resId).then(
            (value) => printText("getLatest result => path: ${value.localPath} "));
  }

  void _onPressLoad() {
    reshubInstance.load(resId).then(
            (value) => printText("load result => isSuccess: ${value.isSuccess} "
                "path: ${value.resModel?.localPath},originLocalPath: ${value.resModel?.originLocalPath}"));
  }

  void _onPressLoadLatest() {
    reshubInstance.loadLatest(resId).then(
            (value) => printText("loadLatest result => isSuccess: ${value.isSuccess} path: ${value.resModel?.localPath}"));
  }

  void printText(String log) {
    textLog.write('$log\n');
    setState(() {});
  }
}

更多关于Flutter插件reshub_flutter的介绍与使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter插件reshub_flutter的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


reshub_flutter 是一个相对较少被提及的 Flutter 插件,因此关于它的具体功能和用法可能并不广为人知。如果你在探索使用这个插件,以下是一些通用的步骤和建议,可以帮助你更好地理解和使用它。

1. 查找插件的官方文档或仓库

  • pub.dev 上搜索 reshub_flutter,查看插件的描述、版本、依赖关系、使用示例等信息。
  • 如果插件有 GitHub 仓库,访问该仓库,查看 README 文件、代码示例、问题(Issues)和拉取请求(Pull Requests)等。

2. 添加插件到你的项目

  • pubspec.yaml 文件中添加插件的依赖:
    dependencies:
      reshub_flutter: ^<version>
    
  • 运行 flutter pub get 来安装插件。

3. 导入插件并初始化

  • 在你的 Dart 文件中导入插件:
    import 'package:reshub_flutter/reshub_flutter.dart';
    
  • 根据插件的文档或示例代码,初始化插件。通常,这会在 main() 函数或 initState() 方法中完成。

4. 探索插件的功能

  • 查看插件提供的类、方法和属性。尝试使用这些 API 来实现你想要的功能。
  • 如果插件有示例项目,下载并运行它,看看它是如何工作的。

5. 调试和测试

  • 在你的项目中使用插件时,逐步调试以理解每个部分的工作原理。
  • 如果遇到问题,查看控制台输出和日志,或者使用插件的调试工具(如果有的话)。

6. 查阅社区资源

7. 贡献和改进

  • 如果你在使用过程中发现插件的问题或有改进的建议,可以在插件的 GitHub 仓库中提交 Issue 或 Pull Request。

8. 备份和回滚

  • 在尝试新插件时,确保你的项目有备份,或者使用版本控制工具(如 Git)来管理代码。如果插件导致问题,你可以轻松回滚到之前的状态。

9. 联系插件作者

  • 如果你在使用过程中遇到困难,并且无法通过文档或社区找到解决方案,可以尝试联系插件的作者,询问具体的使用方法或问题。

10. 记录和分享

  • 如果你成功使用了 reshub_flutter,考虑将你的经验和代码分享给社区,帮助其他人更好地理解和使用这个插件。

示例代码(假设 reshub_flutter 是一个状态管理插件)

import 'package:flutter/material.dart';
import 'package:reshub_flutter/reshub_flutter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('ResHub Flutter Example')),
        body: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ResHubController _controller = ResHubController();

  [@override](/user/override)
  void initState() {
    super.initState();
    _controller.init();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('ResHub Flutter Example'),
          ElevatedButton(
            onPressed: () {
              _controller.doSomething();
            },
            child: Text('Click Me'),
          ),
        ],
      ),
    );
  }

  [@override](/user/override)
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
回到顶部