一、简介

Hybrid Development混合开发是目前移动端开发异常火热的新兴技术,它能够实现跨平台开发,极大地节约了人力和资源成本。跨平台开发催生了很多新的开源框架,就目前而言,在混合开发中比较流行的有FaceBook开源React Native,有Goggle开源的Flutter。React Native实现的是通过下发JS脚本的方式达到JS与Native交互。Flutter实现的则是通过采用现代响应式框架来构建UI,Flutter与ReactiveCocoa框架配合使用最佳。当然开发者也可以在Native中内嵌WebView的方式(WebKit)实现混合开发。虽然方式不同,但目的相同,都是跨平台,殊途同归吧。对跨平台有了粗略的了解后,再来看看iOS系统中对JS与Native是如何交互的,其实,系统是给开发者提供了一个极其强大的框架来实现这个功能的,即JavaScriptCore框架。这个框架通过定义JSValue值对象和声明JSExport协议作为桥梁完成Native与JS的通信。JS虽然是单线程语言,但是iOS是支持多线程执行任务的,开发者可以在异步情况下执行任意一个环境的JavaScript代码。大概结构图如下:

二、分析

参考这上图,可以看出JavaScriptCore框架结构还是很清晰的,JavaScriptCore中有那么几个核心的类在开发者是很常用的,需要弄懂它们代表的意思。

三、API

知道了这几个核心类的概念已经对这个框架有了个基本的认识,具体的API如何使用,我们可以选择性点击去深入研究一下。只有对它们的属性和方法都了如指掌,开发起来才能得心应手,手到擒来。哎呀妈,不废话了。。。例如JSContext和JSValue开发中必用的类,额外的可能还会用JSManagerValue,如下:

JSContetxt类:

//初始化,可以选择对应的虚拟机
- (instancetype)init;
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine; //执行js代码,返回js值对象
- (JSValue *)evaluateScript:(NSString *)script;
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL; //获取当前的js上下文
+ (JSContext *)currentContext; //获取当前的js执行函数,返回js值对象
+ (JSValue *)currentCallee; //获取当前的js函数中this指向的对象,返回js值对象
+ (JSValue *)currentThis; //获取当前的js函数中的所有参数
+ (NSArray *)currentArguments; //js的全局对象
@property (readonly, strong) JSValue *globalObject; //js执行的异常数据
@property (strong) JSValue *exception;
@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception); //js运行的虚拟机
@property (readonly, strong) JSVirtualMachine *virtualMachine; //js上下文名称
@property (copy) NSString *name; //分类
@interface JSContext (SubscriptSupport)
//获取和设置属性为js全局对象
- (JSValue *)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key;
@end //分类(C函数风格)
@interface JSContext (JSContextRefSupport)
//获取和设置全局上下文
+ (JSContext *)contextWithJSGlobalContextRef:(JSGlobalContextRef)jsGlobalContextRef;
@property (readonly) JSGlobalContextRef JSGlobalContextRef;
@end

JSValue类:

//js上下文
@property (readonly, strong) JSContext *context; //使用OC数据初始化js值对象,创建有值的JSValue
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context;
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
+ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context;
+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context;
+ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context;
+ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context;
+ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context; //使用OC数据初始化js值对象,创建空的JSValue
+ (JSValue *)valueWithNewObjectInContext:(JSContext *)context;
+ (JSValue *)valueWithNewArrayInContext:(JSContext *)context;
+ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context;
+ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context;
+ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *resolve, JSValue *reject))callback;
+ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context;
+ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context;
+ (JSValue *)valueWithNewSymbolFromDescription:(NSString *)description inContext:(JSContext *)context;
+ (JSValue *)valueWithNullInContext:(JSContext *)context;
+ (JSValue *)valueWithUndefinedInContext:(JSContext *)context; //js数据转OC数据
- (id)toObject;
- (id)toObjectOfClass:(Class)expectedClass;
- (BOOL)toBool;
- (double)toDouble;
- (int32_t)toInt32;
- (uint32_t)toUInt32;
- (NSNumber *)toNumber;
- (NSString *)toString;
- (NSDate *)toDate;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;
- (CGPoint)toPoint;
- (NSRange)toRange;
- (CGRect)toRect;
- (CGSize)toSize; //js值对象判断
@property (readonly) BOOL isUndefined;
@property (readonly) BOOL isNull;
@property (readonly) BOOL isBoolean;
@property (readonly) BOOL isNumber;
@property (readonly) BOOL isString;
@property (readonly) BOOL isObject;
@property (readonly) BOOL isArray;
@property (readonly) BOOL isDate;
@property (readonly) BOOL isSymbol;
- (BOOL)isEqualToObject:(id)value;
- (BOOL)isEqualWithTypeCoercionToObject:(id)value;
- (BOOL)isInstanceOf:(id)value; //js调用函数
- (JSValue *)callWithArguments:(NSArray *)arguments;
- (JSValue *)constructWithArguments:(NSArray *)arguments;
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments; //js属性设置
- (JSValue *)valueForProperty:(JSValueProperty)property;
- (void)setValue:(id)value forProperty:(JSValueProperty)property;
- (BOOL)deleteProperty:(JSValueProperty)property;
- (BOOL)hasProperty:(JSValueProperty)property;
- (void)defineProperty:(JSValueProperty)property descriptor:(id)descriptor;
- (JSValue *)valueAtIndex:(NSUInteger)index;
- (void)setValue:(id)value atIndex:(NSUInteger)index;
- (JSValue *)objectForKeyedSubscript:(id)key;
- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index;
- (void)setObject:(id)object forKeyedSubscript:(id)key;
- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index;
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context; //OC与JS类型对应关系
Objective-C type | JavaScript type
---------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
---------------------+---------------------

JSManagerValue类:

//对JSValue进行一层包装,对内存进行有效的管理,防止提前或者过度释放
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value;
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:
- (instancetype)initWithValue:(JSValue *)value;
@property (readonly, strong) JSValue *value;

四、案例

[1] 首先打开Safari浏览器的web检查器,会用来查看js运行的效果 ,控制台打印

[2] 导入JavaScriptCore框架

[3] 导入头文件开始测试,Native调用JS

[3-1] 调用无参数的JS函数

native.js

-(void)nativeCallJs {

    //方式一
//从js文件获取js代码
NSString *path = [[NSBundle mainBundle] pathForResource:@"native" ofType:@"js"];
NSData *jsData = [NSData dataWithContentsOfFile:path];
NSString *script = [[NSString alloc] initWithData:jsData encoding:NSUTF8StringEncoding]; //执行js代码
[self.jsContext evaluateScript:script];
}
-(void)nativeCallJs {

    //方式二
//js代码写在端上
NSString *script = @"\
(function(){ \
console.log(\"native call js ------- Wellcome Native\");\
})();"; //执行js代码
[self.jsContext evaluateScript:script];
}
- (void)viewDidLoad {
[super viewDidLoad]; //js上下文
self.jsContext = [[JSContext alloc] init]; //native调用js
[self nativeCallJs];
}

[3-2] 调用有参数的JS函数

native.js

-(void)nativeCallJsWithArguments:(NSString *)argument {

    //方式一
//从js文件获取js代码
NSString *path = [[NSBundle mainBundle] pathForResource:@"native" ofType:@"js"];
NSData *jsData = [NSData dataWithContentsOfFile:path];
NSString *jsString = [[NSString alloc] initWithData:jsData encoding:NSUTF8StringEncoding]; //拼接js参数
NSString *script = [NSString stringWithFormat:jsString,argument]; //执行js代码
[self.jsContext evaluateScript:script];
}
-(void)nativeCallJsWithArguments:(NSString *)argument {

    //方式二
//js代码写在端上
NSString *jsString = @"\
function receive(argument) { \
console.log(\"native call js ------- Wellcome \"+argument);\
};\
receive('%@')"; //拼接js参数
NSString *script = [NSString stringWithFormat:jsString,argument]; //执行js代码
[self.jsContext evaluateScript:script];
}
- (void)viewDidLoad {
[super viewDidLoad]; //js上下文
self.jsContext = [[JSContext alloc] init]; //native调用js
[self nativeCallJsWithArguments:@"我的老哥"];
}

[4] 导入头文件开始测试,JS调用Native

注意:调用包括无参数和有参数的OC方法,这里使用代码块Block为例

-(void)jsCallNative {

    //定义无参数block
void (^Block1)(void) = ^(){
NSLog(@"js call native ------- hello JavaScript");
}; //定义有参数block
void (^Block2)(NSString *) = ^(NSString *argument){
NSLog(@"js call native ------- hello JavaScript----Wellcome %@",argument);
}; //设置block为JSContext全局对象的属性,然后可以在safari控制台执行函数oc_block()输出打印;
[self.jsContext setObject:Block1 forKeyedSubscript:@"oc_block1"];
[self.jsContext setObject:Block2 forKeyedSubscript:@"oc_block2"];
}
- (void)viewDidLoad {
[super viewDidLoad]; //js上下文
self.jsContext = [[JSContext alloc] init]; //js调用native
[self jsCallNative];
}

[5]导入头文件开始测试, OC和JS对象的映射

//OC与JS数据传递的数据就是JSValue值对象,存储在JS的全局对象中
//存和取的过程
[self.jsContext setObject:(id) forKeyedSubscript:(NSObject<NSCopying> *)];
[self.jsContext objectForKeyedSubscript:(id)]

[5-1] 系统提供的OC数据类型,不用特殊存储,可以直接存取

//系统提供的OC数据类型,不用特殊存储,可以直接存取
[self.jsContext setObject:@"mac" forKeyedSubscript:@"os"];
JSValue *osValue = [self.jsContext objectForKeyedSubscript:@"os"];
NSString *osName = [osValue toString];
NSLog(@"-----osName = %@-----",osName);
-- ::17.471840+ 混合开发[:] -----osName = mac-----

[5-2] 特殊的OC类型,如自定义对象,则必须遵守JSExport协议,JS才能拿到自定义对象的所有属性和方法

#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h> //遵守JSExport协议,使得JS在上下文中可以获取到OC中定义的属性和方法
@protocol PersonProtocol <JSExport>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int grade;
@property (nonatomic, assign) float score;
-(void)description;
@end @interface Person : NSObject<PersonProtocol>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int grade;
@property (nonatomic, assign) float score;
-(void)description;
@end #import "Person.h"
@implementation Person
-(void)description {
NSLog(@"姓名:name = %@",self.name);
NSLog(@"年龄:age = %d",self.age);
NSLog(@"年级:grade = %d",self.grade);
NSLog(@"分数:score = %.1f",self.score);
}
@end
//特殊的OC类型,自定义对象,则必须遵守JSExport协议,JS才能拿到自定义对象的所有属性和方法
Person *person = [[Person alloc] init];
person.name = @"张三";
person.age = ;
person.grade = ;
person.score = ;
[self.jsContext setObject:person forKeyedSubscript:@"personEntity"];
JSValue *personValue = [self.jsContext objectForKeyedSubscript:@"personEntity"]; //personEntity为OC在JS的对象形式
Person *xyq_person = (Person *)[personValue toObject];
[xyq_person description];
-- ::17.472563+ 混合开发[:] 姓名:name = 张三
-- ::17.472709+ 混合开发[:] 年龄:age =
-- ::17.472810+ 混合开发[:] 年级:grade =
-- ::17.472889+ 混合开发[:] 分数:score = 98.0

五、实践

到现在为止,相信我们对JS和Native的交互原理有了自己的理解。在案例中使用了js文件下发和解析的方式实现了Native执行JS代码,这个正是Facebook开源的React Native的设计思路。React Native支持跨平台,通过一套js文件就可以在Andriod和iOS上完成Native的界面渲染。现在我们通过一个小测试来模拟Hybrid App的构建原理,通过按钮点击切换控制器视图的背景色。

(1) 创建JavaScript脚本,在脚本中创建Native需要的任意UI控件存到数组,作为函数的返回值

UIKit.js

//定义一个自调用函数,JavaScript脚本加载完成立即执行
(function(){
return renderUI();
})(); /* JavaScript脚本 定义一个Label类
* rect:尺寸 text:文本 color:颜色
*/
function Label(rect,text,fontSize,textColor,textAlignment,bgColor){
this.rect = rect;
this.text = text;
this.fontSize = fontSize;
this.textColor = textColor;
this.textAlignment = textAlignment; //NSTextAlignmentCenter = 1
this.bgColor = bgColor;
this.type = "Label";
} /* JavaScript脚本 定义一个Button类
* rect:尺寸 text:文本 color:颜色 callFunction:函数
*/
function Button(rect,title,fontSize,titleColor,bgColor,callFunction){
this.rect = rect;
this.title = title;
this.fontSize = fontSize;
this.titleColor = titleColor;
this.bgColor = bgColor;
this.callFunction = callFunction;
this.type = "Button";
} /* JavaScript脚本 Rect类
* x:坐标x y:坐标y w:宽度 h:高度
*/
function Rect(x,y,w,h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
} /* JavaScript脚本 Color类
* r:red g:green b:black a:alpa
*/
function Color(r,g,b,a){
this.r = r;
this.g = g;
this.b = b;
this.a = a;
} //渲染方法,实例化上面类的对象
function renderUI() { //创建js标签对象
var screenWidth = 375.0;
var labeWidth = 200;
var labelRect = new Rect((screenWidth-labeWidth)*0.5, 100, labeWidth, 44);
var labeFontSize = 20;
var labelTextColor = new Color(1,0,0,1);
var labelBgColor = new Color(1,1,0,1);
var label = new Label(labelRect, "I From JS", labeFontSize, labelTextColor, 1, labelBgColor); //创建js按钮对象
var buttonWidth = 200;
var buttonRect = new Rect((screenWidth-buttonWidth)*0.5, 200, buttonWidth, buttonWidth);
var buttonFontSize = 40;
var buttonTitleColor = new Color(1,0,1,1);
var buttonbgColor = new Color(1,1,1,1);
var button = new Button(buttonRect,"Button",buttonFontSize,buttonTitleColor,buttonbgColor,function(r,g,b){
var randColor = new Color(r,g,b,1);
configEntity.chageViewColor(randColor);
}); //返回js对象
return [label, button]; }

(2) 创建自定义对象,遵守JSExport协议,添加为JS的全局对象的属性,作为与Native交互的桥接器

//自定义Config类
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h> NS_ASSUME_NONNULL_BEGIN @protocol ConfigProtocol <JSExport>
-(void)chageViewColor:(JSValue *)colorValue;
@end @interface Config : NSObject<ConfigProtocol>
@property (nonatomic, strong) UIViewController *currentVc;
-(void)chageViewColor:(JSValue *)colorValue; //改变当前控制器视图背景色
@end NS_ASSUME_NONNULL_END
//Created by 夏远全 on 2019/11/12.

#import "Config.h"

@implementation Config

-(void)chageViewColor:(JSValue *)colorValue {

    CGFloat red = colorValue[@"r"].toDouble;
CGFloat green = colorValue[@"g"].toDouble;
CGFloat blue = colorValue[@"b"].toDouble;
CGFloat alpha = colorValue[@"a"].toDouble; self.currentVc.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
@end

(3) 在VC中解析JavaScript脚本,获取UI控件元素,进行界面的渲染

#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import "Person.h"
#import "Config.h" @interface ViewController ()
@property (nonatomic, strong) JSContext *jsContext;
@property (nonatomic, strong) NSMutableArray *actions; //所有的回调函数
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor]; //js上下文
self.jsContext = [[JSContext alloc] init]; //从JS文件获取UI进行渲染
[self renderUIFromJs];
} -(void)renderUIFromJs { //创建Config对象
Config *config = [[Config alloc] init];
config.currentVc = self;
[self.jsContext setObject:config forKeyedSubscript:@"configEntity"]; //从js文件获取js代码
NSString *path = [[NSBundle mainBundle] pathForResource:@"UIKit" ofType:@"js"];
NSData *jsData = [NSData dataWithContentsOfFile:path];
NSString *script = [[NSString alloc] initWithData:jsData encoding:NSUTF8StringEncoding]; //执行js代码
JSValue *jsValue = [self.jsContext evaluateScript:script];
for (int i=; i<jsValue.toArray.count; i++) { //取出每一个控件对象值
JSValue *subValue = [jsValue objectAtIndexedSubscript:i]; //创建控件
NSString *type = [subValue objectForKeyedSubscript:@"type"].toString;
if ([type isEqualToString:@"Label"]) { //this.rect = rect;
//this.text = text;
//this.fontSize = fontSize;
//this.textColor = textColor;
//this.textAlignment = textAlignment; //NSTextAlignmentCenter = 1
//this.bgColor = bgColor;
//this.type = "Label"; CGFloat X = subValue[@"rect"][@"x"].toDouble;
CGFloat Y = subValue[@"rect"][@"y"].toDouble;
CGFloat W = subValue[@"rect"][@"w"].toDouble;
CGFloat H = subValue[@"rect"][@"h"].toDouble;
NSString *text = subValue[@"text"].toString;
NSInteger fontSize = subValue[@"fontSize"].toInt32;
UIColor *textColor = [UIColor colorWithRed:subValue[@"textColor"][@"r"].toDouble green:subValue[@"textColor"][@"g"].toDouble blue:subValue[@"textColor"][@"b"].toDouble alpha:subValue[@"textColor"][@"a"].toDouble];
UIColor *bgColor = [UIColor colorWithRed:subValue[@"bgColor"][@"r"].toDouble green:subValue[@"bgColor"][@"g"].toDouble blue:subValue[@"bgColor"][@"b"].toDouble alpha:subValue[@"bgColor"][@"a"].toDouble];
NSTextAlignment alignment = subValue[@"textAlignment"].toInt32; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(X, Y, W, H)];
label.text = text;
label.font = [UIFont systemFontOfSize:fontSize];
label.textColor = textColor;
label.textAlignment = alignment;
label.backgroundColor = bgColor;
[self.view addSubview:label]; }
if ([type isEqualToString:@"Button"]) { //this.rect = rect;
//this.title = title;
//this.fontSize = fontSize;
//this.titleColor = titleColor;
//this.bgColor = bgColor;
//this.type = "Button";
//this.callFunction = callFunction; CGFloat X = subValue[@"rect"][@"x"].toDouble;
CGFloat Y = subValue[@"rect"][@"y"].toDouble;
CGFloat W = subValue[@"rect"][@"w"].toDouble;
CGFloat H = subValue[@"rect"][@"h"].toDouble;
NSInteger fontSize = subValue[@"fontSize"].toInt32;
NSString *title = subValue[@"title"].toString;
UIColor *titleColor = [UIColor colorWithRed:subValue[@"titleColor"][@"r"].toDouble green:subValue[@"titleColor"][@"g"].toDouble blue:subValue[@"titleColor"][@"b"].toDouble alpha:subValue[@"titleColor"][@"a"].toDouble];
UIColor *bgColor = [UIColor colorWithRed:subValue[@"bgColor"][@"r"].toDouble green:subValue[@"bgColor"][@"g"].toDouble blue:subValue[@"bgColor"][@"b"].toDouble alpha:subValue[@"bgColor"][@"a"].toDouble]; JSValue *actionValue = subValue[@"callFunction"];
[self.actions addObject:actionValue]; UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(X, Y, W, H)];
[button setTitleColor:titleColor forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:title forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize:fontSize];
button.backgroundColor = bgColor;
button.tag = self.actions.count-;
[self.view addSubview:button];
}
}
} -(void)buttonClick:(UIButton *)button { JSValue *actionValue = [self.actions objectAtIndex:button.tag];
[actionValue callWithArguments:@[@(arc4random_uniform()),@(arc4random_uniform()),@(arc4random_uniform())]]; } - (NSMutableArray *)actions {
if (!_actions) {
_actions = [NSMutableArray array];
}
return _actions;
}

(4) 演示gif

六、思考

这个js文件目前是写死的放在了Bundle目录下,试想一下,如果在本地每次更改js文件后再重新渲染界面,是不是都得端上重新发版,例如样式相同改一个颜色啥的,发个版就太繁琐了。最好的做法是将js文件部署到服务器上,每次只需要更新js内容后提交到服务器,端上进入当前页面时,从服务器拉取新的js文件渲染界面即可,效率高,成本低,可以实现快速更新迭代。好开心,吃个竹子吧,

Hybrid App: 了解JavaScript如何与Native实现混合开发的更多相关文章

  1. Hybrid小程序混合开发之路 - 数据交互

    HTML+CSS是历史悠久.超高自由度.控制精准.表现能力极强.编码简单.学习门槛超低.真跨平台的一种UI界面开发方式. 本文介绍的是微信小程序和H5混合开发的一种数据交互方式. 很多应用在原生界面中 ...

  2. 利用C#开发移动跨平台Hybrid App(一):从Native端聊Hybrid的实现

    0x00 前言 前一段时间分别读了两篇博客,分别是叶小钗兄的<浅谈Hybrid技术的设计与实现>以及徐磊哥的<从技术经理的角度算一算,如何可以多快好省的做个app>.受到了很多 ...

  3. Native App vs Web App 以及 Hybrid App的实现原理

    移动应用基本的三种类型 1)  Native 应用程序 2)  Web 应用程序 3)  混合应用程序(Hybrid: Native应用和web应用结合) Native 应用 直接运行在电脑上或者智能 ...

  4. 聊聊Web App、Hybrid App与Native App的设计差异

    目前主流应用程序大体分为三类:Web App.Hybrid App. Native App. 一.Web App.Hybrid App.Native App 纵向对比 首先,我们来看看什么是 Web ...

  5. 超赞!聊聊WEB APP、HYBRID APP与NATIVE APP的设计差异

    编者按:这3类主流应用你都了解吗?设计师除了要有视觉功夫,对不同形式的APP也应当了然于胸,今天百度的同学写了一篇非常全面的总结,帮你迅速搞定3类主流APP的设计方法,附带一大波避雷针,带你巧妙跳过A ...

  6. Web App、Hybrid App与Native App的设计差异

    目前主流应用程序大体分为三类:Web App.Hybrid App. Native App. 一.Web App.Hybrid App.Native App 纵向对比 首先,我们来看看什么是 Web ...

  7. 聊聊Web App、Hybrid App与Native App的设计差异(转)

    目前主流应用程序大体分为三类:Web App.Hybrid App. Native App. 一.Web App.Hybrid App.Native App 纵向对比 首先,我们来看看什么是 Web ...

  8. [转帖]聊聊Web App、Hybrid App与Native App的设计差异

    聊聊Web App.Hybrid App与Native App的设计差异 https://www.cnblogs.com/zhuiluoyu/p/6056672.html 编者按:这3类主流应用你都了 ...

  9. Hybrid App移动应用开发初探

    一.移动App类型及其优缺点 1.1 Native App Native App(原生App)是用原生语言(Object-C/Java/C#/....)开发,用户需要下载安装的手机应用. 优点是 可以 ...

随机推荐

  1. Save your cats Aizu - 2224

    Nicholas Y. Alford was a cat lover. He had a garden in a village and kept many cats in his garden. T ...

  2. cocos2d-x 3.2锚点,Point,addchild,getcontensize

    一,锚点 打个比方.在墙挂一幅画时,要钉一个钉子,那个钉子就是锚点. 然后挂图时,钉子(锚点)放在要订的位置(position),订下去.完成(贴图结束). 贴图的基本点,锚点默认为(0.5,0.5) ...

  3. indexeddb:浏览器中的数据库

    随着浏览器功能的不断加强,越来越多的网站开始考虑将大量的数据存储在客户端.这样的考虑是为了直接从本地获取数据,减少从服务器获取数据耗费的网络资源. 原有的浏览器数据存储方案都不适合存储大量数据.Coo ...

  4. unittest执行用例方法

    #coding=utf-8 from selenium import webdriver from time import sleep import unittest#导入unittest库 impo ...

  5. CSS中的各种单位

    单位 描述                                                                                               ...

  6. phpstorm 新加入项目的文件--全局搜索不到 ctrl + shift + R

    通过文件名查找文件 ,能搜到其他的现有文件,只是新加入的文件,无法出现在搜索到的结果中 . 总不可能在搜索的关键词一直拼写错误吧 , 那能想到的只有缓存出问题了. 新加入的文件,新加入的文件.... ...

  7. PHP array_unshift

    1.函数的作用:在数组的开头插入一个或者多个元素 2.函数的参数: @params  array  &$array @params  mixed $value1 @params  mixed ...

  8. [CF494B] Obsessive String

    Hamed has recently found a string t and suddenly became quite fond of it. He spent several days tryi ...

  9. [JOYOI1510] 专家复仇 - Floyd

    题目限制 时间限制 内存限制 评测方式 题目来源 1000ms 131072KiB 标准比较器 Local 题目背景 外星人完成对S国的考察后,准备返回,可他们的飞碟已经没燃料了……S国的专家暗自窃喜 ...

  10. 利用git工具将自己的代码文件上传到Github

    GitHub 是一个面向开源及私有软件项目的托管平台,作为开源代码库以及版本控制系统,Github拥有超过900万开发者用户.随着越来越多的应用程序转移到了云上,Github已经成为了管理软件开发以及 ...