1 热发布
网页发布 VS APP发布
网页发布:服务端上线新的网页代码,用户端通过链接直接访问。
APP发布:�发布新的包装包(应用市场:上传新的包装包;应用内升级:服务端发布新的包装包),用户端下载,安装。
网页发布属于热发布,完全�受控于服务端
APP发布的限制:
应用市场审核
用户端下载,安装
APP热发布的目的是实现类似于网页的发布方式,排除�更新对于应用市场和用户的依赖,完全由服务端控制。
2 APP热发布的方式
2.1 H5热发布
功能完全由H5实现,APP使用WebView来加载。
优势:完全热发布
劣势:
- 性能-H5的性能相比Native差得比较多,交互体验较差。
- 缓存-H5的缓存受制于系统WebView,在无网或缓存过期情况下无法呈现界面。
2.2 数据热发布
数据由服务端控制,动态发布,APP根据数据来展现UI,实现UI有限的动态性(UI动态支持预先在APP中定义好)。
这里的数据也包括脚本数据的情况,APP通过脚本引擎执行脚本,根据结果来执行对应逻辑。
优势:数据层面热发布
劣势:不能实现UI和逻辑的完全热发布
2.3 插件化热发布
APP的独立功能作为插件,动态下载,动态运行,插件发布由服务端动态控制。
优势:功能�性热发布
劣势:使用非系统公开API,有失效风险
2.4 React Native
还有一种思路,APP作为基础容器,应用功能作为脚本文件,动态下发,�通过解析引擎动态执行,展现为Native UI,同时控制数据和业务逻辑。
脚本文件发布由服务端动态控制,能够实现整个APP或者部分功能完全热发布。
React Native是facebook开源的构建跨平台应用的框架,它的原理是,通过JavascriptCore引擎,解析JSX(类似XML的Javascript扩展语言)脚本代码,生成Virtual DOM,再转换为Native视图,同时通过桥接JS和Native,实现事件交互。
React Native通过Virtual DOM,以diff的方式交给Native渲染,�其diff算法相当高效,能够保证良好的Native性能。这是它优于H5的地方。
优势:
- 完全热发布
- 良好的Native性能
- 跨平台复用:Andriod, iOS, H5。
- 大厂出品:持续维护性(两周一个release版本);开放性,三方开源库支持。
劣势:
- 成熟度不够。
- 目前支持Android4.1和iOS7.0以上系统版本。
对动态性要求较高的APP,React Native提供了一种解决方案,实现类似网页的发布效果,同时保证良好的Native性能
使用React Native的APP:
Facebook,QQ,QQ音乐,QQ空间(发现Tab)
此外,Weex是阿里开源的移动端跨平台开发框架,原理与React Native类似,只不过它的脚本语言换成了Vue(也是JS框架)。
3 React Native原理
3.1 React Native集成
React Native(以下简称RN)的集成参考官网:
https://facebook.github.io/react-native/docs/getting-started.html
3.2 RN目录结构
- *.android.js:Android 的JS文件
- *.ios.js:iOS的JS文件
- android:Android的Native工程
- ios:iOS的Native工程
- node_modules:RN基于NodeJS的基础库。包括基本组件,以及打包脚本等等。
3.3 RN原理
�
Java层
Java层为应用的入口,启动C++层的JS解析器,执行JS,并通过C++传递的渲染指令,从而构建Native UI等。核心逻辑在ReactCore中完成。在UI层面,ReactCore完成了对Native视图组件的封装,它与JS层定义的组件是一一映射的。
Java层集成众多优秀开源库,图片处理使用的是Fresco,网络通信使用的是okhttp。JS层的组件会通过这些基础库来实现功能,如Image组件使用Fresco来加载远程图片,fetch组件使用okhttp来进行网络请求。
C++层
C++层主要封装了JavaScriptCore(Android和iOS通用的JS引擎),用来实现JS的解析和执行。基于JavaScriptCore,意味着JS可以使用ES6的新特性,如class、箭头操作符等。
Bridge�实现Java和 JS之间的通信。
JSLoader用来加载JS文件,包括assets目录和本地文件。
JS层
主要实现UI布局和事件分发,主要有以下几个部分:
Component:JS层通JS/JSX编写的Component来构建Virtual DOM,Virtual DOM是DOM在内存中的一种轻量级表达方式,可以通过不同的渲染引擎生成不同平台下的UI。Component的存在让计算 DOM diff 更高效,从而使得映射到Native的视图�渲染有很好的性能。
Layout:React使用css-layout,css-layout使用JS实现了flexbox ,能编译成Native代码,最终达到跨平台的展示目的。
Lifecycle&Data:React 组件通过内部的 state 变量控制生命周期及事件回调。如getInitialState方法用于定义组件初始状态,后续组件可通过 state 属性读取该状态。当�调用setState 方法就修改状态值时,RN会自动调用 render 方法,重新渲染组件。
4 Android的RN集成
Android上,应用是通过Activity来承载的。
复杂应用通常由多个模块组成,每个模块对应一个Activity。简单来说,我们可以以Activity为模块单元。
4.1 Activity与bundle
RN通过bundle来承载应用。bundle包括JS脚本代码及其引用的资源(图片)。
在Android上,我们为每个Activity绑定一个bundle,来实现模块的独立管理。
在Native中,我们通过BaseReactActivity�来连接RN,它通过JsBundle和Component与Bundle关联起来。
Bundle两种形式,一个是Assets,打包在apk中一起发布;一个File,它是动态更新下载的。在有File时使用File,没有File时使用Assets。
Activity的注册我们只在Manifest中定义几个Activity模板。例如,
- SingleTaskActivity:launch mode为SingleTask
- StandardActivity:launch mode为Standard
- WebViewActivity:process为单独的web进程
Activity跳转时,指定对应的模板Activity,传递bundle信息即可。
4.2 JS
JS文件结构(avcdemo.android.js)
import React, {Component} from 'React'; //引入React组件
import {
AppRegistry,
View,
Text,
Image
} from 'react-native'; //引入RN组件
class AVCDemo extends Component { //定义Component
render() { //render定义视图结构
return(
<View style={styles.full}>
<Text style={styles.text}>Hello RN</Text>
<Image source={require('./res/demo/kaola.png')} style={styles.img}></Image>
</View>
)
}
}
var styles = { //定义样式
full: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
text: {
fontSize: 80
},
img: {
width: 200,
height: 200
}
}
AppRegistry.registerComponent('demo', () => AVCDemo); //注册Component
4.3 Native Activity
DemoActivity
public class DemoActivity extends AppReactActivity {
@Override
protected String getMainComponentName() {
return "demo";
}
@Nullable
@Override
protected String getBundleAssetName() {
return "avcdemo.android.bundle";
}
}
可以看到Activity通过bundle最终绑定到Component。
4.4 Component
Component有几个基本概念:props,state,render,生命周期。
props:静态属性,提供定制参数给调用方。如Image的source。
state:动态属性,内部使用。当通过setState方法改变时,会调用render方法进行重新渲染。比如,我们获取网络数据回来时需要改变界面,这时就需要将网络数据作为一个动态属性,同时render方法中使用�该属性来进行渲染。
render:渲染方法,定义Component的视图结构。
与Activity类似,Component也有生命周期。
4.5 RN组件
下面来看看RN对应用实现的支持。首先是组件。
实现应用的组件大致分为4个层面:
- UI
- 网络
- 存储
- 线程
4.5.1 UI
UI层面又可以细分为:UI组件,动画,交互事件。
UI组件
RN提供了较为丰富的基础组件,以及部分复杂组件。
UI组件本质上会做一个到Native组件的映射。
RN UI组件 | Android UI组件 | |
---|---|---|
View | -> | ViewGroup |
Image | -> | ImageView |
Text | -> | TextView |
TextInput | -> | EditText |
ViewPagerAndroid | -> | ViewPager |
WebView | -> | WebView |
ScrollView | -> | ScrollView |
ListView | -> | ScrollView |
RN的ListView并没有采用Native的ListView,而是基于RN的ScrollView,在js层来处理ListView的逻辑。
动画
RN提供了两种动画方式:Animated和LayoutAnimation。
Animated与Android的属性动画类似,可以实现透明度,缩放和位移等效果。
LayoutAnimation与Android LayoutAnimation类似,作用于�加入和移除视图树。
交互事件
交互事件主要有两种:点击和Touch,按键。
RN提供Touchablexxx(TouchableOpacity,TouchableHighlight等)系列组件来实现点击、长按、滑动交互。
onPressIn:点击开始;
onPressOut:点击结束或者离开;
onPress:单击事件回调;
onLongPress:长按事件回调。
事件拦截(类似于Android的onInterceptTouchEvent)
onStartShouldSetResponder
onMoveShouldSetResponder
按键处理,RN提供了Andorid的back键处理BackAndroid
4.5.2 网络
RN提供fetch API用于网络请求,它本质是基于OkHttp实现。
它支持GET,POST等基本方法,包括header的传递,以及响应json格式化。
另外,它也提供XMLHttpRequest和WebSocket的支持。
4.5.3 存储
RN提供了简单的持久存储组件AsyncStorage。
它支持key-value存储,效果类似于Android的SharedPreference,实际上它是存储在本地的sqlite数据库中。
如果需要传统的sqlite存储,需要自己实现。会有一些开源组件供实现参考:https://github.com/jbrodriguez/react-native-android-sqlite。
4.5.4 线程
RN的应用逻辑是运行JS线程上的,独立于Android的UI线程。JS线程上的更新批量传递给Native作UI更新。
RN提供Timers来实现异步逻辑。 可以通过JS本身的异步接口来实现(async/await)。
4.6 RN扩展
RN官方提供的组件能够满足基本业务需求,如果要实现复杂应用,需要进行扩展。
目前很多RN开源组件:https://github.com/jondot/awesome-react-native。
另外,也可以自己定制。定制分为几个方面:
- JS调用Native
- 定制JS层组件
- 使用Native UI组件
4.6.1 JS调用Native
当JS层无法满足需求时,通过需要Native层支持提供接口,来供JS层调用。RN提供Native Modules的方式来支持。
简单来说,就是Native按照规则定义好接口,同时注册到RN中,JS层直接调用即可。
显然,这种方式定义的Native Module不能动态发布,它需要固化在APP中。
4.6.2 定制�JS层组件
Component组件
以ImageText为例(ImageText.js)
'use strict';
import React, {Component} from 'React';
import {
View,
Image,
Text,
} from 'react-native';
class ImageText extends Component {
props: {
imgUrl?: string,
text?: string,
}
render() {
return(
<View style={styles.container}>
<Image style={styles.img} source={{uri: this.props.imgUrl}}></Image>
<Text style={styles.text}>{this.props.text}</Text>
</View>
)
}
}
var styles = {
container: {
flexDirection: 'row',
marginRight: 8,
},
img: {
width: 14,
height: 14,
},
text: {
},
}
module.exports = ImageText;
�然后将Component输出:
module.exports = ImageText;
方法组件
以网络请求为例,网络请求通常会加上一些能用参数。可以定义一个网络请求组件(Requestor.js)。
'use strict';
import HOST from './Host';
function get(url, headers) {
if (headers == null) {
headers = {};
}
headers[HOST.API_VERSION_KEY] = HOST.API_VERSION_VALUE;
console.log("headers: " + JSON.stringify(headers));
return fetch(url, {
headers: headers
})
}
module.exports = {get};
需要将方法组件输出:
module.exports = {get};
常量组件
'use strict';
var apiVersion = 200;
module.exports = {
HOST: "http://api.kkmoving.com",
API_VERSION_KEY: "apiVersion",
API_VERSION_VALUE: apiVersion
};
4.6.3 使用Native UI组件
Native UI组件定义参考Native UI Components
简单来说,就是定义好Native UI组件,然后通过ViewManager注册到RN中,同时在JS层定义Component,关联到Native UI组件。然后,这个Component就可以使用。
�需要注意的是,如果Native UI组件要求自定义大小,需要额外实现LayoutShadowNode并关联到ViewManager,LayoutShadowNode提供MeasureFunction回调来实现组件的丈量。具体实现可以参考RN的Text组件实现(Native 代码:ReactTextViewManager,ReactTextView,ReactTextShadowNode)。
4.7 RN工程打包
RN打包分为两个部分,一个是bundle打包,一个是APP打包。
bundle打包,是指bundle的JS文件和图片资源打包为独立的文件,�作为更新包供远程下载。这是动态bundle。
bundle打包命令:
react-native bundle --platform android --dev false \
--entry-file xxx.android.js \
--bundle-output xxx.android.bundle \
--assets-dest xxx
APP打包,首先执行bundle打包,将生成的静态bundle跟随app包一起发布。保证无网时能够展现应用基本功能。
在Android上,跟随打包会将JS文件打包到assets目录中,同时将图片资源打包到drawable目录中。
RN打包Android的方式是通过gradle,在正常apk打包过程之前,会编译RN的bundle。但默认只能打包一个bundle,如果需要支持打包多个bundle,需要修改react.gradle文件(node_modules/react-native/react.gradle)。
RN在开发调试比较方便,通过react-native run-android
运行应用,JS文件编辑后,可以直接在应用上reload来实现更新,不需要重新安装APP,类似网页直接刷新的效果。
4.8 热发布实现
RN热发布的策略相对简单。
在APP端,初始发布APP时,自带静态bundle,并每个bundle维护一个版本号。
应用在适当的时机,检查bundle是否需要更新。如果有更新,则下载动态bundle。
�应用优先加载动态bundle,如无动态bundle刚加载静态bundle。
在服务端,�提供动态bundle的发布,版本维护,更新检测和下载。
这是最简单的策略,当然要做得精细,还需要考虑差量更新,bundle拆包(后面会讲)。
5 �RN实践之商品详情页
界面实现(Demo)
- ScrollView
- ViewPager
- 带样式的Text
- 远程加载的Image
- 浮层
- 动画
- 地址选择
- RN WebView
在线更新(Demo)
- 检查更新
- 下载/解压
- 应用更新
6 踩过的坑
6.1 混淆设置
在RN中如果要使用Proguard,需要做两个设置:
- 在build.gradle中设置:
def enableProguardInReleaseBuilds = true
- 在proguard.pro中,基于RN官方Proguard模板,另外设置:
-keep class com.facebook.** { *; }
这一点比较坑,官方文档和各种模板中都没有提到。如果不加一句,release运行时会直接crash。
6.2 Android Studio设置
- gradle.properties不能设置
org.gradle.configureondemand=true
具体原因未知
7 待解决的问题
- RN基础库较大(6M)。
- Bundle文件过大(基础Bundle 500k左右)。Bundle拆包,将RN库的JS拆离到单独的基础Bundle。
- 复杂组件的JS实现。
- 性能分析和优化。