RN学习1——前奏,app插件化和热更新的探索

RN学习1——前奏,app插件化和热更新的探索_第1张图片
react_native_banner-min.png

React Native(以下简称RN)有大量前端开发者的追捧。前端开发是一个活跃的社区,一直尝试着一统前后端,做一个全栈开发,RN就是他们在客户端领域的尝试。

说是从零开始,但其实我还是懂一点点JS代码的,而且算是一个有经验的iOS、Android开发,对很多js和native交互的细节和特性还算了解,在QDaily里面也做过好多hybird的尝试,还经常用JSPatch做hotfix,总的来说,就是对hot update、插件化以及hybird编程非常非常感兴趣。RN也许是已知的开源方案中最好的一个吧。

写在最前

先开始提供个思路,作为一个移动客户端开发(区别于前端开发),我使用RN的目的根本上是为了插件化以及插件的线上热更新,对于前端开发那种全应用RN化的雄心是敬谢不敏的,同事对这种方案开发全app的能力也是存疑的(后文会解释原因)。

至于为什么要学习RN,主要是个人以后目标是做一个更加成熟团队的客户端负责人(or客户端架构师),需要对整个客户端横向技术栈都要有自己的理解和认识,现在已经能够在Android和iOS方面有一些自己的认识,进入RN领域其实也是顺理成章的了。

在未来RN的学习和使用过程中,我也会更加倾向于关于这种方案的内在原理、使用场景、使用边界以及一些其它优缺点方面,实际使用中也会先在一些比较轻量级的场景使用。

动态配置

客户端的新版本都依赖用户进行升级才行,如何能够第一时间让用户使用最新的版本是app开发者的永恒话题。

如果采用后台热更新,无论采用何种方式,我们的流程总是可以归结为以下三部曲:“从 Server 获取配置 –> 解析 –> 执行native代码”。

针对客户端程序的混合开发非常有必要,可以有效的进行app一些突发模块的开发和处理。已知的一般思路包括:

  • 1、简单的js bridge方式,内容呈现采用H5,增加一些和native的交互。现在好奇心日报就是这个方案,虽然简陋,但基本就是这个思路。上个东家微信在这方面也基本就采用了相似的方案,只不过在加密和安全方面做了更多的处理。

  • 2、后台以zip包得形式下发html、css、js和相关png组件,下载之后将所有资源按照原有目录结构放在app本地的一个http服务器中,app中得webview请求localhost的固定地址进行请求,这种请求会有AJAX跨域问题,一般只有native端完全接管网络请求可以使用,例如微信春晚红包就是这个方案。方案2基本是方案1的优化变种,由于兼容性好,入门简单,比较成熟。

  • 3、以zip包得形式下发html、css、js和相关png组件,客户端深订制一套方案,可以让js使用一些原生的UI组件能力,比较典型的就是增加下拉刷新组件。这套方案在淘宝、支付宝广泛使用,需要进行学习和研究,一方面在部署架构,另一方面是具体实现细节。阿里开源了其中的weex组件,基本思路也是这样的。

  • 4、纯js或者lua以patch的形式进行原生开发,通过反射调起原生代码。这个方案在iOS下可行,Android下面还存疑。而且是比较重的客户端耦合,好奇心日报现在用它进行一些紧急bug的处理。

  • 5、采用react-native进行混编,这种方案比较完整,就是原生native客户端集成react-native组件,通过后台订制下发一些使用js和css编写的资源模块,通过react-native框架进行渲染解析,成为原生应用。方案已经在天猫iPad客户端的某些模块上、QZone的某些使用,而且facebook的f8大会的app全部采用其编写,其生产能力是不容质疑的。方案原理和方案3、方案4类似,只不过中间封装了一个更加完善的中间件。

QDaily 的现有尝试

1、基于css的ui配置方案。

基本思路是在css文件中定义ui组件的边距、颜色、字体、大小、背景色等等与UI相关的内容,在代码中通过宏(Android中用import static)进行UI渲染。

在app启动时,将css文件load进入内存,保存成k-v的形式,具体UI代码直接面对这些k-v数据结构即可。

RN学习1——前奏,app插件化和热更新的探索_第2张图片
Qdaily css架构.jpg

这种方法的好处是:

  • 1、在适配夜间模式或者一些固定屏幕版本时,只需增加一套css文件即可处理;
  • 2、而且,由于修改资源文件,app不需要重新打包编译;
  • 3、同时,这种方案可以通过后台配置新的资源文件,在运行时替换掉内存中的约定key下的value,从而实现线上条件下的UI调整。

局限性也是非常明显的:对native代码的依赖太硬,能做的非常少,也就能改改样式,与热更新和热修复都扯不上关系。

2、基于js bridge的bybird方案

基于H5的webview hybird方案算是在性能上做一些妥协后比较成熟的方案了。

在android和iOS部分各封装一个js-bridge用于js和native的交互,相当于一个中间件。该中间件包含一个native部分和一个js部分,两部分沟通采用各自平台的特性方案,iOS采用订制request scheme并拦截request的方案,android采用@javascript的方案。通过中间件,前端开发者仅仅使用1套代码就可以兼容两个平台,两个平台各自暴露native方法给前端。

该方案实现的部分有:

  • 1、两个js-bridge,用于交互。并以此约定标准化调用接口。
  • 2、为webview发起的请求绑定cookie,以能够进行用户识别
  • 3、为webview发起的请求定制化UA,以区分浏览器还是app,以及android还是iOS。
  • 4、针对webview中所有资源(html、js、css、image)都进行本地的持久化,以提高访问速度。

方案好处都能看见,缺陷也很明显:效率太依赖机器性能以及浏览器内核(不过就算内核再好效率也是存疑的),同时针对原生部分的调用依赖于原生提供能接口,几乎是每增加一个功能,native部分也需要对应开发一边接口。

以上两个方案各有特征,但终究没离开采用约定好的配置信息就行混合编程的路子。从本质上来说,就是移动端和服务端约定了一套协议,但是协议内容严重依赖于应用内提供的能力,不利于拓展。尤其是方案1,只是在解析字符串,它完全不具备运行和调试的能力。方案2的效率问题也非常明显。

3、jspatch的热修复方案

iOS7以后,系统中包含了jscontext进行js语言的解析,相当于从读取配置文件到读取逻辑一个质的飞跃。

jspatch将js代码进行解析,并通过反射(invoker)调用objective-c的代码,几乎可以做所有oc可以做的事情(因为OC的runtime实在太强大)。

方案在QDaily中主要用于线上热修复。这个方案也有其不好之处:一个是只支持iOS,针对android还是无能为力;二是编写页面实在难用,难以调试,从整个生态来讲,也都使用比较轻量。

react native在iOS端实现远离和jspatch的远离基本一致,同事结合了方案2中语法的一些特性以及方案1中的配置特性。相信是现在已知的最优解决方案。

混编

决定学习之初直接上混编,因为这才是使用的目的,只有支持这个才具备插件使用的条件。

开一个官方demo——AwesomeProject,然后开始修改(如何安装和配置请自行google,官方教程很详细)。

1、OC调起RN

直接上代码,我们假设native页面本来好好的,点击了一个按钮跳到了一个RN的活动页面

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton* startRNVC = [[UIButton alloc] initWithFrame:CGRectMake(20, 50, 60, 40)];
    [startRNVC setTitle:@"Start RN" forState:UIControlStateNormal];
    [startRNVC addTarget:self action:@selector(gotoRN) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startRNVC];
}

- (void) gotoRN {
  NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                        moduleName:@"AwesomeProject"
                                               initialProperties:nil
                                                   launchOptions:nil];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  [self.navigationController pushViewController:rootViewController animated:YES]; 
}

2、OC中等待RN调起的部分

RN有比较完整的调用代码,只要按步骤做就好了。关键是RCT_EXPORT_MODULE这个宏,会在class的load方法中进行register,这点和js-bridge的方案很像。

@implementation SpringBoard
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(gotoIM:(RCTResponseSenderBlock)callback)
{
  AppDelegate* appdelegate = (AppDelegate*) [UIApplication sharedApplication].delegate;
  UINavigationController *controller = (UINavigationController*)[appdelegate.window rootViewController];
  CDLoginVC *loginVC = [[CDLoginVC alloc] init];
  [controller pushViewController:loginVC animated:YES];
  callback(@[[NSNull null]]);
}

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}
@end

3、RN部分调起Native

RN还不是很理解,就把代码都贴上来了。这里会在页面启动时候直接alert出来,点击会再跳回native部分。

'use strict';

var React = require('React');

var RN = require('react-native');
var {
  Image,
  AppRegistry,
  ListView,
  StyleSheet,
  Text,
  View,
  AlertIOS,
} = RN;


var styles = RN.StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  }
});

function setup(): React.Component {
  AlertIOS.alert(
    'Foo Title',
    'My Alert Msg',
    [
      {text: 'Foo', onPress: function FooClick() {
        var SpringBoard = RN.NativeModules.SpringBoard;
        SpringBoard.gotoIM((events) => { });
      }},
      {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
    ]

  )
  class AwesomeProject extends React.Component {

    render() {
      return 
          This is a simple application.
        ;
    }
  }
  return AwesomeProject;
}

AppRegistry.registerComponent('AwesomeProject', setup);

总结:事实上,效果很好,轻松实现了混编和调用,考虑到RN在调起Native部分需要OC进行代码定制化编写,所以未来考虑增加其与jspatch的协作,增强其能力;android部分还需要继续研究,相信不是问题(QZone已经在进行相关的研究和应用了)。

学习计划和曲线

我个人是一个双平台开发者,同时对hybird编程比较感兴趣,也做过一些研究和尝试,所以RN中关于平台接口部分、原理以及js-native交互部分学习是比较平缓的。但我javascript只是一点三脚猫功夫,更别提ES6、React一个有一个生僻而又让然懵逼的名字,还有node.js等等神一样的存在...这部分估计学习要非常陡峭。

本着先难后易的原则,学习部分会优先进行ES6标准的基本语法和习惯开始,然后通过改造QDaily一个模块进行React和RN的熟悉,在其中不断学习f8的代码和使用方式,并在过程中将RN彻底融入原有app项目中。

这过程可能需要一本基于ES6的javascript的书籍,一套比较权威的RN教程和文档,f8的代码以及针对其的解读,还有若干大牛的博客和社区。

本文结束

mark一下本文的参考文献,以及可能要学习的一些东西:

  • React Native 从入门到原理。文章太好,在宏观上一下子就知道RN是怎么回事了,非常建议app开发这作为RN的入门文章
  • React 入门实例教程。了解下React的编程语法习惯很重要,而且说实话,React真的很潮流,代码风格很时尚、很性感。
  • React Native 中文文档。不用说了,国人翻译版。
  • React/React Native 的ES5 ES6写法对照表。过去知道的js皮毛都是基于ES5的,网上如果搜RN的代码估计也是ES5居多,这个文章可以用于对照学习。
  • 一个不错的RN公众号,也许关注这一个就够了 codedev123。网站:http://www.lcode.org。
  • 一系列f8的源码分析 http://www.jianshu.com/p/f7cb35436f9a。
  • 构建 F8 App / React Native 开发指南。秋百万针对f8官方blog的翻译文章,对f8的学习很有价值。
  • QZone mobile的公众号 qzonemobiledev,里面有好多RN4A的文章。
  • 微软的react-native-code-push,据说很好用,RN最终用于生产环境的时候可以参考下:Github,使用CodePush热更新ReactNative JS代码,React Native项目更新。

你可能感兴趣的:(RN学习1——前奏,app插件化和热更新的探索)