/rexxar-ios

Mobile Hybrid Framework Rexxar iOS Container

Primary LanguageObjective-CMIT LicenseMIT

Rexxar iOS

Test Status IDE iOS Language

README in English

Rexxar 是一个针对移动端的混合开发框架。现在支持 Android 和 iOS 平台。Rexxar-iOS 是 Rexxar 在 iOS 系统上的客户端实现。

通过 Rexxar,你可以使用包括 javascript,css,html 在内的传统前端技术开发移动应用。Rexxar 的客户端实现 Rexxar Container 对于 Web 端使用何种技术并无要求。我们现在的 Rexxar 的前端实现 Rexxar Web,以及 Rexxar Container 在两个平台的实现 Rexxar-iOS 和 Rexxar-Android 项目中所带的 Demo 都使用了 React。但你完全可以选择自己的前端框架在 Rexxar Container 中进行开发。

Rexxar-iOS 现在支持 iOS 7 及以上版本。

Rexxar 简介

关于 Rexxar 的整体介绍,可以看看这篇博客:豆瓣的混合开发框架 -- Rexxar

关于 Rexxar 的设计**,可以看看这篇问答:Infoq 采访:豆瓣混合开发框架 Rexxar

Rexxar 包含三个库:

安装

安装 Cocoapods

CocoaPods 是一个 Objective-c 和 Swift 的依赖管理工具。你可以通过以下命令安装 CocoaPods:

$ gem install cocoapods

Podfile

target 'TargetName' do
  pod 'Rexxar', :git => 'https://github.com/douban/rexxar-ios.git', :commit => '0.2.1'
end

然后,运行以下命令:

$ pod install

使用

你可以查看 Demo 中的例子。了解如何使用 Rexxar。Demo 给出了完善的示例。

Demo 中使用 github 的 raw 文件服务提供一个简单的路由表文件 routes.json,demo.html 以及相关 javascript 资源的访问服务。在你的线上服务中,当然会需要一个真正的生产环境,以应付更大规模的路由表文件,以及 javascript,css,html 资源文件的访问。你可以使用任何服务端框架。Rexxar 对服务端框架并无要求。RXRConfig 提供了对路由表文件地址的配置接口。下一节描述了配置方法。

配置 RXRConfig

设置路由表文件 api:

  RXRConfig.setRoutesMapURL(NSURL(string:"https://raw.githubusercontent.com/douban/rexxar-web/master/example/dist/routes.json")!)

Rexxar 使用 url 来标识页面。提供一个正确的 url 就可以创建对应的 RXRViewController。路由表提供了每个 url 对应的 html 资源的下载地址。Demo 中的路由表如下:

{
    "items": [{
        "remote_file": "https://raw.githubusercontent.com/douban/rexxar-web/master/example/dist/rexxar/demo-252452ae58.html",
        "deploy_time": "Sun, 09 Oct 2016 07:43:47 GMT",
        "uri": "douban://douban.com/rexxar_demo[/]?.*"
    }],
    "partial_items": [{
        "remote_file": "https://raw.githubusercontent.com/douban/rexxar-web/master/example/dist/rexxar/demo-252452ae58.html",
        "deploy_time": "Sun, 09 Oct 2016 07:43:47 GMT",
        "uri": "douban://partial.douban.com/rexxar_demo/_.*"
    }],
    "deploy_time": "Sun, 09 Oct 2016 07:43:47 GMT",
}

设置预置资源文件路径

  RXRConfig.setRoutesResourcePath("rexxar")

使用 Rexxar 一般会预置一份路由表,以及资源文件在应用包中。这样就可以减少用户的下载,加快第一次打开页面的速度。在没有网络的情况下,如果没有数据请求的话,页面也可访问。这都有利于用户体验。

注意,如果设置了预置资源文件路径,即意味着在应用包内预置一份资源文件。这个文件夹需要是 folder references 类型,即在 Xcode 中呈现为蓝色文件夹图标。创建方法是将文件夹拖入 Xcode 项目,选择 Create folder references 选项。

设置缓存路径

  RXRConfig.setRoutesCachePath("com.douban.RexxarDemo.rexxar")

缓存文件夹存在的目的也是减少资源文件的下载次数,加快打开页面的速度。使得用户可以得到近似原生页面的页面加载体验。

缓存资源文件一般会出现在 Rexxar 部署了一次路由表的更新之后。这也是 Rexxar 支持热部署的方法:路由表控制资源文件的更新。一般可以让应用定期访问路由表。比如,在开启应用时,或者关闭应用时更新路由表。更新路由表的方法如下:

  RXRViewController.updateRouteFiles(completion: nil)

如果,新的路由表中出现了 html 文件的更新,或者出现了新的 url。也就是说这些文件并不存在于预置资源文件夹中,Rexxar Container 就会在下载完路由表之后,主动下载新资源,并将新资源储存在缓存文件夹中。

预置资源文件和缓存文件关系

正常程序逻辑下,预置资源文件夹存在的资源,就不会再去服务器下载,也就不会有缓存的资源文件。

在进入一个 RXRViewController 时,会读取资源文件。在读取时,Rexxar Container 先读取缓存文件,如果存在就使用缓存文件。如果缓存文件不存在,就读取预置资源文件。如果,预置资源文件也不存在。RXRViewController 会尝试更新一次路由表,下载路由表中新出现的资源,并再次尝试读取缓存资源文件。如果仍然不存在,就会出现页面错误。

读取顺序如下:

  1. 缓存文件夹中读取 html 文件;
  2. 预置资源文件夹中读取 html 文件;
  3. 重新下载路由表 Routes.json,遍历路由表将新的 html 文件下载到缓存文件夹。再次尝试从缓存文件夹读取 html 文件;

以上三步中,任何一步读取成功就停止,并返回读取的结果。如果,三步都完成了仍没有找到文件,就会出现页面错误。

有了预置资源文件和缓存文件的双重保证,一般用户打开 Rexxar 页面时都不会临时向服务器请求资源文件。这大大提升了用户打开页面的体验。

使用 RXRViewController

你可以直接使用 RXRViewController 作为你的混合开发客户端容器。或者你也可以继承 RXRViewController,在 RXRViewController 基础上实现你自己的客户端容器。在 Demo 中,创建了 FullRXRViewController,它继承于 RXRViewController

为了初始化 RXRViewController,你只需要一个 url。在路由表文件 api 提供的路由表中可以找到这个 url。这个 url 标识了该页面所需使用的资源文件的位置。Rexxar Container 会通过 url 在路由表中寻找对应的 javascript,css,html 资源文件。

  let controller = RXRViewController(URI: uri)
  let titleWidget = RXRNavTitleWidget()
  let alertDialogWidget = RXRAlertDialogWidget()
  controller.activities = [titleWidget, alertDialogWidget]
  navigationController?.pushViewController(controller, animated: true)

定制你自己的 Rexxar Container

首先,可以继承 RXRViewController,在 RXRViewController 基础上以实现你自己客户端容器。

然后,可以使用 Rexxar 提供的三个接口。下面会介绍如何使用这三个接口,更方便地扩展属于自己的特定功能。

定制 RXRWidget

Rexxar Container 提供了一些原生 UI 组件,供 Rexxar Web 使用。RXRWidget 是一个 Objective-C 协议(Protocol)。该协议是对这类原生 UI 组件的抽象。如果,你需要实现某些原生 UI 组件,例如,弹出一个 Toast,或者添加原生效果的下拉刷新,你就可以实现一个符合 RXRWidget 协议的类,并实现以下三个方法:canPerformWithURL:prepareWithURL:performWithController:

在 Demo 中可以找到一个例子:RXRNavTitleWidget ,通过它可以设置导航栏的标题文字。

@interface RXRNavTitleWidget ()

@property (nonatomic, copy) NSString *title;

@end


@implementation RXRNavTitleWidget

- (BOOL)canPerformWithURL:(NSURL *)URL
{
  NSString *path = URL.path;
  if (path && [path isEqualToString:@"/widget/nav_title"]) {
    return true;
  }
  return false;
}

- (void)prepareWithURL:(NSURL *)URL
{
  self.title = [[URL rxr_queryDictionary] rxr_itemForKey:@"title"];
}

- (void)performWithController:(RXRViewController *)controller
{
  if (controller) {
    controller.title = self.title;
  }
}

@end

定制 RXRContainerAPI

我们常常需要在 Rexxar Container 和 Rexxar Web 之间做数据交互。比如 Rexxar Container 可以为 Rexxar Web 提供一些计算结果。如果你需要提供一些由原生代码计算的数据给 Rexxar Web 使用,你就可以选择实现 RXRContainerAPI 协议(Protocol),并实现以下三个方法:shouldInterceptRequest:, responseWithRequest:, responseData

在 Demo 中可以找到一个例子:RXRGeoContainerAPI。这个例子中,RXRGeoContainerAPI 返回了设备所在城市,以及经纬度信息。当然,这个 ContainerAPI 仅仅是一个示例,它提供的是一个假数据,数据永远不会变化。你当然可以遵守 RXRContainerAPI 协议,实现一个类似的但是数据是真实的功能。

定制 RXRDecorator

如果你需要修改运行在 Rexxar Container 中的 Rexxar Web 所发出的请求。例如,在 http 头中添加登录信息,你可以实现 RXRDecorator 协议(Protocol),并实现这两个方法:shouldInterceptRequest:, prepareWithRequest:

在 Demo 的 FullRXRViewController 类中,可以找到一个使用 RXRRequestDecorator 添加登录信息,和 URL 参数的例子。这个例子为 Rexxar Web 发出的请求添加了登录信息,并在 URL 参数中增加了 apikey 信息。

使用 RXRContainerAPI 和 RXRDecorator

RXRContainerAPI 和 RXRDecorator 生效期应该和 RXRViewController 的生命周期一致。

这就是说可以在 RXRViewController 的 viewDidLoad: 方法中注册 RXRContainerAPI 和 RXRDecorator:

override func viewDidLoad() {
  super.viewDidLoad()

  ...

  URLProtocol.registerClass(RXRContainerInterceptor.self)

  ...

  URLProtocol.registerClass(RXRRequestInterceptor.self)
}

可以在 Objective-C 的 dealloc 或者 Swift 的 deinit 方法中取消 RXRContainerAPI 和 RXRDecorator 的注册:

deinit {
  URLProtocol.unregisterClass(RXRContainerInterceptor.self)
  URLProtocol.unregisterClass(RXRRequestInterceptor.self)
}

在 Demo 中的 FullRXRViewController 可以看到如何注册和取消注册 RXRContainerAPI 和 RXRDecorator。

定制 RXRProxy

RXRProxy 是本地请求代理服务,当一个请求允许被本地服务代理时,可以直接返回本地内容(类似 ContainerAPI),当请求不能被本地服务代理时,继续原来的请求(类似 RequestInterceptor)。

Partial RXRViewController

如果,你发现一个页面无法全部使用 Rexxar 实现。你可以在一个原生页面内内嵌一个 RXRViewController,部分功能使用原生实现,另一部分功能使用 Rexxar 实现。

Demo 中的 PartialRexxarViewController 给出了一个示例。

Rexxar 的公开接口

  • Rexxar Container

    • RXRConfig
    • RXRViewController
  • Widget

    • RXRWidget
    • RXRNavTitleWidget
    • RXRAlertDialogWidget
    • RXRPullRefreshWidget
  • ContainerAPI

    • RXRNSURLProtocol
    • RXRContainerInterceptor
    • RXRContainerAPI
  • Decorator

    • RXRRequestInterceptor
    • RXRDecorator
    • RXRRequestDecorator
  • Util

    • NSURL+Rexxar
    • NSDictionary+RXRMultipleItem

Changelog

  • 0.3.0 使用 WKWebView 替换 UIWebView, 由于 WKWebView 对 NSURLProtocol 支持不够友好,你需要特别关心一下 NSURLProtocol 截获 Post 请求时 Body 被清空的问题。
  • master 2018-12-28 配置文件 RXRConfig 删除 + (void)setExternalUserAgent:(NSString *)userAgent;+ (NSString *)externalUserAgent;新增 + (**void**)setRxrUserAgent:(NSString *)userAgent;+ (NSString *)rxrUserAgent;

Unit Test

在项目的 RexxarTests 文件夹下可以找到一系列单元测试。这些单元测试可以通过命令 cmd+u 在 Xcode 中运行。单元测试除了可以验证代码的正确性之外,还提供了如何使用这些代码的示例。可以查看这些单元测试,以了解如何使用 Rexxar。

License

The MIT license.