![832ea19c86ec4e9d2974f53fe20ae951.png](https://i-blog.csdnimg.cn/blog_migrate/72561e22328777eab9495c0e0ec1c4b5.png)
什么是 Hippy
Hippy 是一款高性能的跨端开发框架,它能帮助开发者快速构建移动双端应用。
与传统 web 相比,Hippy 应用兼顾性能和扩展性,支持动态更新,且无任何协议风险。适合需快速迭代且对性能有要求的移动场景。
为什么选择 Hippy
如果你对用户体验有要求,同时又追求高效开发迭代,相信 Hippy 是你最好的选择。Hippy 具有以下优势:
- 开发效率高。根据使用 Hippy 业务经验看,使用 HIppy 开发相对于原有 Android 和 iOS 双端开发,可节省 70% 人力
![3fb2ffd55849ab8a5671c9aab654fd31.png](https://i-blog.csdnimg.cn/blog_migrate/bcd70934eb98b1fb7ce32e1fe0b063a6.png)
- 稳定性好。目前 Hippy 引擎日启动次数超15亿,引擎加载成功率 99.9992%,业务加载成功率 99.985%
![d1611b241754ab3984beeca2b25c0d05.png](https://i-blog.csdnimg.cn/blog_migrate/d4c20ff5e3eb63bf92f4bd9364dbdcce.png)
- 迭代快。业务发版周期减少 75% ~ 100%
- 性能高。平均帧率高于同期竞品,自研 Layout 引擎超越 Yoga。
![fb39277cd0d8ea9c6da2102c70aa1cd9.png](https://i-blog.csdnimg.cn/blog_migrate/9fb75dc8f7594e9705081777797b86a7.jpeg)
- 扩展性好。Hippy 采用分层设计思想,通过上下层抽离和解耦,将整体划分为框架层、引擎层、渲染层。每层均可自由替换。框架层既可以使用 React 也可以使用 Vue,或者未来的新兴框架。引擎层可以使用 JS,将来也支持 Dart,Lua 等语言。渲染层目前使用原生 Native 绘制,明年也将支持自绘,业务可以根据需求自由选择。
![04a3c312be5aadc419930acf94e0b2bb.png](https://i-blog.csdnimg.cn/blog_migrate/b3b742782c3762e33826edeb8dc67e65.jpeg)
- 无协议风险。Hippy 完全自研,使用自由宽松的 Apache 协议,商业项目可放心使用。
Hippy 示例
1. demo 上手
git clone https://github.com/Tencent/Hippy.git
2. 组件简介
git 工程自带的 example 中已包含基本组件的用法和示例,mac 上可按以下步骤启动 demo
- 运行 npm i 安装依赖
- 运行 npm run build 生成 sdk 包
- 运行 npm run buildexample -- hippy-react-demo
- 打开 examples / ios-demo / HippyDemo.xcodeproj
![a86a0c47488d5aee252e077c236de736.png](https://i-blog.csdnimg.cn/blog_migrate/b1f6dccb3916be1d78c7389b119f4902.jpeg)
3. 扩展简介
Android
- 封装终端 UI 组件
App 开发中有可能使用到大量的 UI 组件,Hippy SDK 已包括其中常用的部分,如 View
、Text
、Image
等,但这有可能无法满足你的需求,这就需要你对 UI 组件进行扩展封装。
- 组件扩展
我们将以 MyView 为例,从头介绍如果扩展组件。
扩展组件包括:
- 扩展 HippyViewController。
- 实现 createViewImpl 方法、
- 实现 Props 设置方法。
- 手势事件处理。
- 注册 HippyViewController。
扩展 HippyViewController
HippyViewController
是一个视图管理的基类(如果是 ViewGroup 的组件,基类为 HippyGroupController
)。
在这个例子中我们需要创建一个 MyViewController
类,它继承 HippyViewController<MyView>
。MyView
是被管理的 UI 组件类型,他应该是一个 Android View 或者 ViewGroup。 @HippyController
注解用来定义导出给JS使用时的组件信息。
@HippyController(name = "MyView")
public class MyViewController extends HippyViewController<MyView>
{
...
}
实现 createViewImpl 方法
当需要创建对应试图时,引擎会调用 createViewImpl
方法。
@Override
protected View createViewImpl(Context context)
{
// context实际类型为HippyInstanceContext
return new MyView(context);
}
实现属性 Props 方法
需要接收 JS 设置的属性,需要实现带有 @HippyControllerProps
注解的方法。@HippyControllerProps
可用参数包括:
name
(必须):导出给JS的属性名称。defaultType
(必须):默认的数据类型。取值包括HippyControllerProps.BOOLEAN
、HippyControllerProps.NUMBER
、HippyControllerProps.STRING
、HippyControllerProps.DEFAULT
、HippyControllerProps.ARRAY
、HippyControllerProps.MAP
。defaultBoolean
:当 defaultType 为 HippyControllerProps.BOOLEAN 时,设置后有效。defaultNumber
:当 defaultType 为 HippyControllerProps.NUMBER 时,设置后有效。defaultString
:当 defaultType 为 HippyControllerProps.STRING 时,设置后有效。
@HippyControllerProps(name = "text", defaultType = HippyControllerProps.STRING, defaultString = "")
public void setText(MyView textView, String text)
{
textView.setText(text);
}
手势事件处理
Hippy 手势处理,复用了 Android 系统手势处理机制。扩展组件时,需要在控件的 onTouchEvent
添加部分代码,JS 才能正常收到 onTouchDown
、onTouchMove
、onTouchEnd
事件。事件的详细介绍,参考 Hippy 事件机制。
@Override
public NativeGestureDispatcher getGestureDispatcher()
{
return mGestureDispatcher;
}
@Override
public void setGestureDispatcher(NativeGestureDispatcher dispatcher)
{
mGestureDispatcher = dispatcher;
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
boolean result = super.onTouchEvent(event);
if (mGestureDispatcher != null)
{
result |= mGestureDispatcher.handleTouchEvent(event);
}
return result;
}
注册 HippyViewController
需要在 HippyPackage
的 getControllers
方法中添加这个 Controller,这样它才能在 JS 中被访问到。
@Override
public List<Class<? extends HippyViewController>> getControllers()
{
List<Class<? extends HippyViewController>> components = new ArrayList<>();
components.add(MyViewController.class);
return components;
}
- 更多特性
处理组件方法调用
在有些场景,JS 需要调用组件的一些方法,比如 MyView
的 changeColor
。这个时候需要在 HippyViewController
重载dispatchFunction
方法来处理 JS 的方法调用。
public void dispatchFunction(MyView view, String functionName, HippyArray var)
{
switch (functionName)
{
case "changeColor":
String color = var.getString(0);
view.setColor(Color.parseColor(color));
break;
}
super.dispatchFunction(view, functionName, var);
}
事件回调
Hippy SDK 提供了一个基类 HippyViewEvent
,其中封装了 UI 事件发送的逻辑,只需调用 send
方法即可发送事件到 JS 对应的组件上。比如我要在 MyView 的 onAttachedToWindow 的时候发送事件到前端的控件上面。
示例如下:
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// this is show how to send message to js ui
HippyMap hippyMap = new HippyMap();
hippyMap.pushString("test", "code");
new HippyViewEvent(" onAttachedToWindow").send(this, hippyMap);
}
HippyViewController 的回调函数
onAfterUpdateProps
:属性更新完成后回调。onBatchComplete
:一次上屏操作完成后回调(适用于 ListView 类似的组件,驱动 Adapter 刷新等场景)。onViewDestroy
:试图被删除前回调(适用于类似回收试图注册的全局监听等场景)。onManageChildComplete
:在HippyGroupController
添加、删除子试图完成后回调。
混淆说明
扩展组件的 Controller
类名和属性设置方法方法名不能混淆,可以增加混淆例外。
-keep class * extends com.tencent.mtt.hippy.uimanager.HippyGroupController{ public *;}
-keep class * extends com.tencent.mtt.hippy.uimanager.HippyViewController{ public *;}
前端实现
主要分为3步:
- 引入 UIManagerModule
- 编写控件方法
- 导出控件
Hippy-React 范例如下:
import React from "react";
import { UIManagerModule } from "@tencent/hippy-react"
export class MyView extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.changeColor = this.changeColor.bind(this);
}
//自定义的调用终端控件上的方法不过一般不这么做一般定义一个属性
changeColor(color) {
UIManagerModule.callUIFunction(this, "changeColor", [color]);
}
render() {
return (
<div
nativeName="MyView" // **必须:**将前端组件与终端组件进行绑定
{...this.props}
></div>
)
}
}
实际例子
import React from "react";
import { MyView } from "@tencent/hippy-widgets/components/MyView";
//使用自定义的View
export default class MyViewDemo extends React.Component {
constructor(props) {
super(props);
}
//接受终端发送的事件
onAttachedToWindow(e) {
console.log(e);
}
componentDidMount() {
//调用控件扩展的方法,不过一般不这么做都是扩展在属性里面
this.refs["myview"].changeColor("#ff0000");
}
render() {
return (
<MyView
style={{ width: 200, height: 100 }}
ref="myview"
onAttachedToWindow={this.onAttachedToWindow.bind(this)}
text={"自定义的view"}
></MyView>
);
}
}
iOS
- 封装终端 UI 组件
App 开发中会使用大量 UI 组件,Hippy SDK 已经包含了一些基本 UI,比如 View, Text, Image 等。
而用户若想进行自定义组件也十分简单。
注意:自定义 UI 组件的名称和事件中不要带上
Hippy
几个字(不区分大小写),否则在 iOS 上可能会碰到找不到组件或事件名称的问题。
- 组件扩展
我们以创建 MyView 为例,从头介绍如何扩展一个组件。
本文仅介绍 ios 端工作,前端工作请查看对应的文档。
扩展一个 UI 组件需要包括以下工作:
- 创建对应的 ViewManager
- 注册类并绑定前端组件
- 绑定 View 属性及方法
- 创建对应的 shadowView 和 View
创建对应的 ViewManager
ViewManager 是对应的视图管理组件,负责前端视图和终端视图直接进行属性、方法的调用。
SDK 中最基础的 ViewManager 是 RCTViewManager,封装了基本的方法,负责管理 RCTView。
用户自定的 ViewManager 必须继承自 RCTViewManager
RCTMyViewManager.h
@interface RCTMyViewManager:RCTViewManager
@end
RCTMyViewManager.m
@implementation RCTMyViewManager
RCT_EXPORT_MODULE(MyView)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(opacity, alpha, CGFloat)
RCT_CUSTOM_VIEW_PROPERTY(overflow, CSSOverflow, RCTView)
{
if (json) {
view.clipsToBounds = [RCTConvert CSSOverflow:json] != CSSOverflowVisible;
} else {
view.clipsToBounds = defaultView.clipsToBounds;
}
}
- (RCTView *)view {
return [RCTMyView new];
}
- (RCTShadowView *)shadowView {
return [RCTShadowView new];
}
RCT_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag) {
// do sth
}
RCT_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback) {
// do sth
NSArray *result = xxx;
callback(result);
}
RCT_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
// do sth
if (success) {
resolve(result);
}
else {
reject(@"errorcode", @"errormessage", error);
}
}
@end
类型导出
RCT_EXPORT_MODULE()
将 RCTMyViewManager 类注册,前端在对 MyView 进行操作时会通过 RCTMyViewManager 进行实例对象指派。RCT_EXPORT_MODULE()
中的参数可选。代表的是 ViewManager 对应的 View 名称。
若用户不填写,则默认使用类名称。
但是 SDK 中有个特殊处理逻辑,若参数中的字符串以 'Manager' 结尾,那 SDK 在根据字符串查找对应的 View 时会将删除结尾 'Manager' 后的字符串作为 View 名称
属性导出
RCT_EXPORT_VIEW_PROPERTY
将终端 View 的属性和前端属性绑定。当前端设定属性值时,会自动调用 setter 方法设置到终端对应的属性。RCT_REMAP_VIEW_PROPERTY()
负责将前端对应的属性名和终端对应的属性名对应起来。以上述代码为例,前端的 opacity 属性对应终端的 alpha 属性。此宏一共包含三个参数,第一个为前端属性名,第二个为对应的终端属性名称,第三个为属性类型。另外,此宏在设置终端属性时使用的是 keyPath 方法,即终端可以使用 keyPath 属性。RCT_CUSTOM_VIEW_PROPERTY()
允许终端自行解析前端属性。SDK 将前端传递过来的原始 json 类型数据传递给函数体(用户可以使用 RCTConvert 类中的方法解析对应的数据),用户获取后自行解析。
这个方法带有两个隐藏参数 - view, defaultView。view 是指当前前端要求渲染的 view。default 指当前端渲染参数为nil 时创建的一个临时 view,使用其默认属性赋值。
方法导出
RCT_EXPORT_METHOD
能够使前端随时调用终端对应的方法。前端通过三种模式调用,非别是 callNative, callNativeWithCallbackId, callNativeWithPromise。终端对应三种方式时,函数体写法可以参照上面的示例。
- callNative 此方法不需要终端返回任何值。
- callNativeWithCallbackId 此方法需要终端在函数体中以单个block形式返回数据。block 类型为
RCTResponseSenderBlock
,参数为一个 NSArray 变量。 - callNativeWithPromise 此方法对应的前端使用的是 promise 写法,对应到终端需要业务根据自身情况返回 resolve block或者 reject block。其中 resolve block 数据类型为
RCTPromiseResolveBlock
, 参数为一个可以被 json 化的对象。如果参数为 nil,则 JS 端会将其转化为 undefined。reject block 数据类型为RCTPromiseRejectBlock
,参数包括一个错误码,错误信息,以及错误实例对象 (NSError) 。
一个 ViewManager 可以管理一种类型的多个实例,为了在 ViewManager 中区分当前操作的是哪个 View,每一个导出方法对应的第一个参数都是 View 对应的 tag 值,用户可根据这个 tag 值找到对应操作的 view。
由于导出方法并不会在主线程中调用,因此如果用户需要进行 UI 操作,则必须将其分配至主线程。推荐在导出方法中使用 [self.bridge, uiManager addUIBlock:] 方法。其中的 block 类型为RCTViewManagerUIBlock
。
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry)。第二个参数为字典,其中的 key 就是对应的 view tag值,value 就是对应的 view。
创建 shadowView 和 View
在 OC 层,RCTUIManager 负责将 JS 层的解析结果,映射到 OC 层的视图层级,RCTShadowView 它不是真正展现的视图,只是一个映射结果而已,每一个 RCTShadowView 对应一个真正的视图,但它已经完成了基本的布局。
RCTView 会根据 RCTShadowView 的映射结果构建真正的 View 视图。因此对于大多数情况下的自定义 view manager 来说,直接创建一个 RCTShadowView 即可。
RCTUIManager 将调用 [RCTMyViewManager view] 方法去创建一个真正的 view,用户需要实现这个方法并返回自己所需要的 RCTMyView。
到此,一个简单的 RCTMyViewManager 与 RCTMyView 创建完成。
前端实现
主要分为3步:
- 引入UIManagerModule
- 编写控件方法
- 导出控件
Hippy-React 范例如下:
import React from "react";
import { UIManagerModule } from "@tencent/hippy-react"
export class MyView extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.changeColor = this.changeColor.bind(this);
}
//自定义的调用终端控件上的方法不过一般不这么做一般定义一个属性
changeColor(color) {
UIManagerModule.callUIFunction(this, "changeColor", [color]);
}
render() {
return (
<div
nativeName="MyView" // **必须:**将前端组件与终端组件进行绑定
{...this.props}
></div>
);
}
}
实际例子
import React from "react";
import { MyView } from "@tencent/hippy-widgets/components/MyView";
//使用自定义的View
export default class MyViewDemo extends React.Component {
constructor(props) {
super(props);
}
//接受终端发送的事件
onAttachedToWindow(e) {
console.log(e);
}
componentDidMount() {
//调用控件扩展的方法,不过一般不这么做都是扩展在属性里面
this.refs["myview"].changeColor("#ff0000");
}
render() {
return (
<MyView
style={{ width: 200, height: 100 }}
ref="myview"
onAttachedToWindow={this.onAttachedToWindow.bind(this)}
text={"自定义的view"}
></MyView>
);
}
}
c++ 扩展
Hippy 开发的时候,前端 JS 经常需要访问一些双端(Android 和 iOS)通用能力,比如 setTimeout、console.log 等。这类情况推荐使用 internalBinding 来实现底层能力扩展
- 扩展示例
我们将以 TestModule 为例,从头扩展一个 Module,这个 Module 将展示前端如何调用终端能力,并且把结果返回给前端。
创建 ModuleBase 的 子类
在 core/module/ 下创建 test-module.h
#ifndef CORE_MODULES_TEST_MODULE_H_
#define CORE_MODULES_TEST_MODULE_H_
#include "core/modules/module-base.h"
#include "core/napi/callback-info.h"
class TestModule : public ModuleBase {
public:
explicit TestModule(hippy::napi::napi_context context){};
void RetStr(const hippy::napi::CallbackInfo& info);
void Print(const hippy::napi::CallbackInfo& info);
};
#endif // CORE_MODULES_TEST_MODULE_H_
在 core/module/ 下创建 http://test-module.cc
#include "core/modules/module-register.h"
#include "core/modules/test-module.h"
#include "core/napi/js-native-api.h"
REGISTER_MODULE(TestModule, RetStr)
REGISTER_MODULE(TestModule, Print)
void TestModule::RetStr(const hippy::napi::CallbackInfo& info) {
hippy::napi::napi_context context = info.GetContext();
HIPPY_CHECK(context);
info.GetReturnValue()->Set(hippy::napi::napi_create_string(context, "hello world"));
}
void TestModule::Print(const hippy::napi::CallbackInfo& info) {
hippy::napi::napi_context context = info.GetContext();
HIPPY_CHECK(context);
HIPPY_LOG(hippy::Debug, "hello world");
info.GetReturnValue()->SetUndefined();
}
hippy-base 项目中增加对应 JS 桥接代码
双平台通用模块一般放在 lib/global 下,我们在 global 下 增加 TestModule.js
const TestModule = internalBinding('TestModule'); global.TestModule = TestModule;
lib/entry/ 下双平台 hippy.js 里增加
require('../../global/TestModule.js');
在 generateCppTool 目录下运行 node build.js
。对应 build 目录会生成对应双平台cc文件
native-source-code-android.cc
native-source-code-ios.cc
替换 core/napi/v8 下的 http://native-source-code-android.cc 和 core/napi/jsc 下的 http://native-source-code-ios.cc
重新编译 HippyCore
效果
global.TestModule.Print();
global.TestModule.RetStr();
2019-11-08 17:32:57.630 7004-7066/? D/HippyCore: hello world
![b72875d6b7accbf73d2783a2b500e7db.png](https://i-blog.csdnimg.cn/blog_migrate/10943ec577144b70fad69c73cd8b4462.jpeg)
Hippy 和 RN、Flutter 区别
- Hippy 和 RN 上层业务都使用 JS 作为开发语言,相比 Flutter 的 Dart,Hippy 对前端更友好。
- Hippy 不仅支持 React,也同时支持 Vue。React 和 Vue 相关的前端库,大部分都可以直接应用于 Hippy 项目。
- Hippy 不仅支持原生 Native 渲染,明年也将支持自绘渲染,业务可以根据需要(性能、包大小)自由选择合适的渲染引擎。
- Hippy 部分接口进行了特别优化,不再通过 Jni 调用 Android SDK,而直接通过 c++ binding 调用 NDK,极大的提高了性能。
![efad938b10acff1914eae57c4e8e80e9.png](https://i-blog.csdnimg.cn/blog_migrate/acafc81cb67b0d85221b86e8d51accce.png)
同时,Hippy 在以下方面皆有良好表现:
![9227ce6b90ca310be9edb7ae89829f94.png](https://i-blog.csdnimg.cn/blog_migrate/b9eb83574a0e119a060236deabcf2ab7.png)
注: github 地址
https://github.com/Tencent/Hippygithub.com欢迎大家使用~~~
有什么问题可以随时提 issues,觉得好用请不要吝惜您的 star!~~~