首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Lua进程内存优化方案总结

Lua进程内存优化方案总结

作者头像
腾讯技术工程官方号
发布于 2024-08-20 12:21:52
发布于 2024-08-20 12:21:52
44800
代码可运行
举报
运行总次数:0
代码可运行

作者:benderzhao

方案

常见的内存优化方法有很多,针对不同的场景有不同的解决方案,下面按照由简到繁的顺序一一道来。

字段裁剪

显而易见,把没用的字段干掉,就可以省内存。根据前文的内存计算公式,哪怕只存了一个bool值,占用也是16字节。因此,首先考虑是去掉一些完全没用的字段,其次是去掉一些默认值的字段。

比如游戏里常见的物品,有id、数量、各种属性等。如果出于方便或者可读性,亦或者C++良好的编码习惯,为每个字段都设置一个初始值,那么物品结构就大概长这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local item = {
    id = 123123,
    count = 1,
    property1 = 0,
    property2 = 0,
    property3 = "",
    property4 = false,
}

这种写法property1property4的默认值占用了大量的内存,单个item从2个Key-Value变为了6个,内存也膨胀了3倍。

比较节省内存的做法是无默认值,在使用时or下即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local property1 = item.property1 or 0

当然,如果使用的地方特别多,比如有上万处地方直接使用了item.property1,这种改动还是太大。这个后面会讲到还有别的方法。

结构调整

如果不熟悉Lua的实现,雀食会设计出一些常见于C++,但不友好于Lua内存的结构。

还是用物品举例,假设一个玩家有1000个物品,每个物品有一些属性。比较符合直觉的数据结构是这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [10001] = {count = 1, property1 = 1},
    [10002] = {count = 2, property2 = 4},
    [10003] = {count = 4, property4 = 10},
    ...
    [11000] = {count = 3, property1 = 2},
}

使用一个Table来存储所有的物品,每个Key是物品id,Value是具体的属性。

这种结构浅显易懂,但是有个问题,总共有1000+个Table,而Table不同于C++的struct,它是有很大的内存开销的,这种数据结构占用的内存会出乎意料的大,在这里光Table的占用就会有几十KB。

考虑换成另一种结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    count = {
        [10001] = 1,
        ...
        [11000] = 3,
    },
    property1 = {
        [10001] = 1
        ...
        [11000] = 2,
    }
 ...
}

这里把相同的字段放在一起,比如所有的count是一个Table,Key是物品id,Value是数量。这种结构与前面的对比,Key-Value的数量是没差别的,但是只有个位数的Table,对比前面的1000+,有几个数量级的差距。

当然,改动还是比较大的,但是如果对于这个结构的访问都收敛到物品模块内,对外只提供接口,那就还可以接受。

对于其他结构也是一样,主旨就是减少Table的使用。

提取公共表

前面字段裁剪提到,如果有一些默认字段不好剔除,比如有上万次使用的地方,挨个去加判断肯定不现实,因此可以考虑提取元表来优化。

还是用物品举例,假设有1000个物品,每个物品有3个属性,绝大部分情况下都是默认值0。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [10001] = {count = 1, property1 = 0, property2 = 0, property3 = 0},
    [10002] = {count = 2, property1 = 0, property2 = 0, property3 = 0},
    [10003] = {count = 4, property1 = 0, property2 = 0, property3 = 0},
    ...
    [11000] = {count = 3, property1 = 1, property2 = 0, property3 = 0},
}

因为每个物品结构的字段都是一样,且大部分都是相同的值为0,因此我们提取一个元表base:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local base = {
    property1 = 0, property2 = 0, property3 = 0
}

然后将原始数据里与base内容一样的字段剔除掉,变为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [10001] = {count = 1},
    [10002] = {count = 2},
    [10003] = {count = 4},
    ...
    [11000] = {count = 3, property1 = 1,
}

再为每个物品设置元表,get不到的字段就去base里找。set则只在自己的Table里设置。所有物品共用一张元表。

显而易见,通过共用base的默认值,很多重复的Key-Value被优化掉了,也就节省了内存。

这种方法适合于结构一致,且有大量相同值的情况。

内存压缩

假如结构不一致,或者字段的值都各不相同,又该如何优化呢?例如我们已经把没用的字段剔除了,现在物品结构长这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [10001] = {count = 1},
    [10002] = {count = 2},
    [10003] = {count = 4},
    ...
    [11000] = {count = 3, property1 = 1,
}

考虑前面的指导思想,就是减少Table的使用,因此我们可以考虑把Table序列化为字符串,例如变成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [10001] = "count=1",
    [10002] = "count=2",
    [10003] = "count=4",
    ...
    [11000] = "count=3,property1=1",
}

这样就少了一大堆的二级的Table。当然,这种序列化格式还是比较占内存,这里只是举个例子方便理解。实际可以序列化为紧凑的二进制形式。

改为字符串后,要是想访问里面的count,怎么办?还是设置元表,在使用的时候还原回Table即可。

而既然都序列化为二进制字符串了,那干脆再调用下lz4压缩下,牺牲一点点CPU换来更高的优化效果。比如变为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [10001] = "\01\FF",
    [10002] = "\01\FE",
    [10003] = "\01\1F",
    ...
    [11000] = "\01\3F\24",
}

到此为止了吗?不,还可以进一步优化,考虑每个结构其实都是长差不多的,比如都有count字段存放整数,那与其挨个结构压缩,不如聚合N个结构一起压缩,那样的压缩比更好。

假设N取10,将原来的id除10聚合,先变为临时结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [1000] = {
        [10001] = {count = 1},
        [10002] = {count = 2},
        ...
        [10009] = {count = 4},
 },
    [1001] = {...}
    ...
 [1100] = {...}
}

这样10个一组,再分别对每一组序列化后压缩,变为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local items = {
    [1000] = "\01\FF\12\22",
    [1001] = "\01\FE\12\22",
    ...
    [1100] = "\01\3F\24\12\22",
}

这样可节省的内存将会更多。访问的方式也是一样,当访问到某id时,除10找到对应的组,解压缩反序列化即可。

这种优化方式对于一些冷数据的,尤为有效,因为大部分情况下都不会访问它们。

下沉C++

在前面的优化方法都尝试之后,还想继续优化内存,怎么办?考虑一个问题,似乎C++很少因为定义几个字段几个Struct就内存炸了的情况,究其原因,有以下几点:

  1. C++的Struct或者Class无额外消耗,一个空Class几乎不消耗内存。而Lua的Table本质是一个很复杂的HashTable与Vector结构,即使是个空Table也消耗了一大坨内存。
  2. C++的字段是紧密排列,一个int就是固定的4字节,无额外消耗。而Lua因为是弱类型的解释语言,除了本身的数据存储,还需要类型描述以及GC等信息,单个字段的消耗是16字节+,相比C++膨胀了数倍,虽然实际上Lua已经实现的很精巧了。

那么,我们也仿照C++实现一套内存布局,是不是就可以了?

比如某个Table结构有abc三个字段,都为int范围的整数,那我们在C++中开辟一块12字节的内存来存放就行了,干掉Lua中的Table,把对abc的读写操作都映射到C++里的这块内存上。

如何映射呢?当然也是用元表了。也许你会说元表不也会占用空间?是会占用,所以我们要把所有类型相同的结构共用一份元表,比如有1000个Item,只有一份元表。

理论上来说,这种方式就可以达到接近C++的内存使用,从而优化Lua的内存占用,顺便还减轻了GC的压力,因为Lua中已经没有复杂的Table结构了。

将大象装进冰箱的思路有了,下面就讲下具体的几步。

结构定义

首先第一步,是要先描述结构的定义,可以采用自定义的格式,比如还是物品的例子,干脆直接用Lua描述它的格式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local Item = {
    [id] = {size = 4},
    [count] = {size = 4},
    [property1] = {size = 4},
    ...
}

Item有3个字段,每个字段4字节。或者也可以使用json、protobuf等格式,比如protobuf:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message Item {
    int32 id = 1;
    int32 count = 2;
    int32 property1 = 3;
    ...
}

考虑到生态,使用protobuf来描述最好。各种工具齐全,且大部分人都了解这个语法,无需再重新学习。

内存布局

有了protobuf的定义后,接下来就是在内存里把这些字段排列开来,也许你突然想到,既然都用了protobuf,那直接用它的反射库不就好了?

protobuf反射库

protobuf的反射库做的事情与我们要做的差不多,解析proto文件,生成一套描述信息Reflection,然后就可以根据Reflection初始化一块内存Message,并动态的读写其中的字段。

比如典型的接口:

最终的实现,也是将内存地址强转成类型指针,直接copy进内存,与我们的思路可以说一模一样。

那看起来底层实现直接用protobuf就好了?然而,protobuf的反射库除了太重,还有个最大的问题,是没法支持热更新。

反射需求

Lua天生就支持热更新,因此,在将Lua内存下沉到C++时,也必须考虑这个问题。考虑之前的物品的Lua结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local Item = {
    id = 123123,
    count = 1,
    property1 = 0
}

对应的protobuf定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message Item {
    int32 id = 1;
    int32 count = 2;
    int32 property1 = 3;
}

最开始id写在了分配的C++的内存的偏移0,count写在了偏移4,以此类推。

当我们业务需要,假设需要增加一个字段time,出于可读性考虑,我们加在了中间位置。Lua结构变为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local Item = {
    id = 123123,
    count = 1,
    time = 1212123123,
    property1 = 0
}

对应的新的protobuf定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message Item {
    int32 id = 1;
    int32 count = 2;
    int32 time = 3;
    int32 property1 = 4;
}

这时候,再使用新的protobuf的偏移,去读写我们之前分配好的内存,会明显错位了,比如现在property1的偏移是12,但是在旧的内存布局里,偏移是8。

怎么办?也许你已经想到了,首先protobuf的定义不能乱来,应该兼容旧版本,比如新增字段后,新的定义应该为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message Item {
    int32 id = 1;
    int32 count = 2;
    int32 time = 4; // 插在中间可以,tag必须兼容
    int32 property1 = 3;
}

其次,在内存中永久维护一份message对应的layout,当加载新的protobuf定义后,根据tag修补layout的结构。

在这个例子中,旧的layout是(id, 0), (count, 4), (property1, 8),后面的数字是字段开始的偏移。

加载新定义之后,新的tag只会往后补充,layout变为(id, 0), (count, 4), (property1, 8), (time, 12)

这样,使用新的layout访问旧内存布局,是兼容没问题的。

也许你会问,要是删除字段怎么办?岂不是会留有一个空洞?比如某天property1废弃了,那layout变为了(id, 0), (count, 4), (废弃, 8), (time, 12),有几种办法:新增的同类型字段可复用这个tag;等待重启;当看不见。

另外又因为热更并不频繁,所以这部分兼容的代码,可以做在Lua里,实现更简单,也不会造成性能的困扰。

小结

所以考虑热更新需求和代码复杂度,我们并不直接使用protobuf的反射库,改为自己实现一套类似的内存布局管理。

同时protobuf的内存生命周期管理也不是我们期望的,这个下面会讲到。

内存管理

熟悉Lua的人都知道,Lua把所有的短字符串都放到一个HashMap存起来,这样内存里只会存一份字符串的拷贝。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local a = "test"
local b = "test"

ab都指向了同样的字符串地址,节省了内存,而且这样判断ab是否相等时,可以直接使用指针比较,而无需调用strcmp,也提高了性能。

而什么时候从hashmap删掉呢?自然是没有使用了,Lua通过GC来删掉。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local a = "test"
a = nil

其他需要GC的类型比如Table、UserData也是同理。

那既然我们把Lua内存下沉到C++,Lua复杂的结构如何保证既不会内存泄露,又不会野指针呢?要知道,Lua的Table是可以随便相互各种引用的。

是不是也要复刻这套GC呢?其实大可不必,我们使用最简单的引用计数即可。

引用计数

引用计数众所周知,引用+1,取消引用-1,为0表示没人引用了,就释放掉。比如std::shared_ptr就是干这个的。

但是引用计数有个问题是循环引用,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local a = {}
local b = {}
a.b = b
b.a = a

这样ab相互引用,就没机会减到0了。Lua为了避免这个问题,采用三色标记法来一波一波的回收。

那为什么Lua内存下沉到C++中,反倒可以使用引用计数,没有循环引用的问题呢?

原因很简单,我们使用了protobuf来描述结构。直接不让这么定义就行了,比如这种是不允许的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message A {
  B b = 1;
}

message B {
  A a = 1;
}

实际上也几乎不会有必须这样写的业务需求。大部分是分层的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message A {
  Base b = 1;
}

message B {
  Base a = 1;
}

message Base {
  ...
}

当去掉了循环的边,所有的结构都变成了一颗树,只要使用引用计数管理即可,简单又高效。

又因为Lua都是单线程的,所以使用一个单线程版本的shared_ptr即可,性能更佳。

复杂结构

当然,我们的结构不可能总全是int,必须要支持嵌套、repeated、map的定义,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message Player {
  string name = 1;
  int32 score = 2;
  bool is_vip = 3;
  float experience = 4;
  repeated Item items = 5;
  Pet pet = 6;
  map<string, Friend> friends = 7;
}

在前面我们知道各个字段是按照偏移来存放在内存里的,那这里的nameitemspetfriends成员应该如何存?毕竟几乎都是编译期未知的大小。

那么很简单,只存放指针即可,固定8字节。指针索引到具体的实例上去,对应的就是StringArrayMap

字符串池子

如前所述,我们也仿照Lua,把所有C++里的字符串用一个hashmap管理起来。

虽然实际上不需要在C++中用到字符串的比对,因为访问a.b时,Lua层已经把b映射到某个偏移了,C++也就无需在用b再做字符串比较查找字段。

这种设计的主要目的还是减少内存的占用,毕竟程序中还是存在大量的相同字符串的。

另外String虽然也使用了引用计数管理,但是在释放时,需要从池子中删掉,并且池子是不能增加计数的,不然永远到不了0,这里要用到weakptr了。

Array实现

Array对应的就是Lua中的数组,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local array = {"a", "b", "c"}

也是protobuf里的repeated,对应的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
repeated string array = 1;

虽然repeated看起来和普通的message区别很大,但是在C++里实现是差不多的。

message是在访问a.b时,把b映射到某个偏移读写。

repeated则是在访问a[1]时,把1也映射到某个偏移,逻辑更简单了,乘以单个的元素大小即可。

不过这里需要注意的是,在设置元素时,要确保是符合protobuf的定义的,毕竟Lua是可以随便写,如果上面的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
array[1] = 2

把整数设置到了字符串数组中,C++层要能够检测并抛出异常出来。

Map实现

Map在Lua里虽然也是Table,但是是用来存放未知的KV的,典型的比如一个好友的集合:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
local friends = {
    ["123123"] = {name = "张三"},
    ["123124"] = {name = "李四"},
}

对应到protobuf的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
map<string, Friend> friends = 1;

这个Map就不能靠偏移来实现了,是放KV的也就只能用HashMap结构。

而既然要节省内存,那自然要用最精确的字段来存储了,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
map<int32, int32> map1 = 1;
map<int64, int32> map2 = 2;
map<int32, int64> map1 = 3;
map<int64, int64> map1 = 4;

这有4种尺寸的KV排列组合,如果我们想简单实现,定义个union,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
union Data {
    int32_t i32;
    int32_t i64;
    ...
};

然后用一个C++的std::unordered_map<Data, Data>来存放所有类型的KV。这种方式固然简单,但是内存明显并没有做到最优。

怎么办呢?似乎也没什么好方法,罗列下所有的可能性,然后定一个指针union,像这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
union Map {
    std::unordered_map<int32_t, int32_t> * i32_32;
    std::unordered_map<int32_t, int64_t> * i32_64;
 ...
};

然后根据protobuf的定义,初始化对应的类型,并按照那个类型来读写。

看起来很蠢,但是却是最简单有效的做法。也许你会说,有字节对齐,i32_32i32_64占用的实际内存会不会其实是一样的?

是有这个可能,但是我们没法假定std::unordered_map内部实现的结构定义,而且哪怕是一样的,除了代码多一点,CPU和内存均无损失。

而代码方面,可以借助template以及C++17的if constexpr,最大程度的减少冗余。

测试结果

废了好大劲,终于正确无误的把Lua内存下沉到C++中,现在是检验优化成果的时候了。

分成了普通结构、ArrayMap三种场景。

普通结构

就是最普通简单的结构体,定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message SimpleStruct {
  int32 a = 1;
  int32 b = 2;
  int32 c = 3;
  ...
  int32 y = 25;
  int32 z = 26;
}

初始化了5000份数据,实测下沉C++后,内存为Lua的1/3左右。

Array

也采用了最简单的数组类型,定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
repeated int32 labels = 6;

插入1000w条数据后,C++内存为Lua的1/4左右。

Map

最后是Map类型,定义为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
map<int32, int32> params = 9;

插入1000w条数据后,C++的内存居然和Lua差不多,甚至还稍大些。这就尴尬了。

Map优化

分析代码,Map下沉之后,内存不减反增。而我们只是封装了一层std::unordered_map,所以问题必然出现在它与Lua的实现的不同上面。

根据资料,std::unordered_map采用的是拉链法,除了KV本身的存储外,还有桶、链表等消耗,那是会多耗费些内存。

再观察Lua的实现,它其实用的是Coalesced hashing,这种实现虽然Set稍显麻烦,但是Get却很简单,同时因为是一整块内存,内存利用率更高。

既然std::unordered_map比不过它,那么我们自己实现一个类似的coalesced_map即可,同样的算法,不过做了些变种,比如支持Delete。

coalesced_mapstd::unordered_map比较下性能,Set会稍慢点,Get是一致的,符合预期。实际上程序中也是读多写少,Lua这种策略没错。

优化后测试

最后,重新跑一遍测试,C++内存为Lua的1/2左右。不得不说,Lua实现真的很精巧。

总体来说,下沉方案效果还可以,但是还有继续扣内存的空间。

总结

Lua确实是嵌入式脚本语言一哥,开发效率高,运行效率也不差。

不过如果一不注意,就容易陷入CPU与内存的陷阱中,大量使用时还是要多加注意。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯技术工程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
React Native 系列(二) -- React入门知识
前言 本系列是基于React Native版本号0.44.3写的,最初学习React Native的时候,完全没有接触过React和JS,本文的目的是为了给那些JS和React小白提供一个快速入门,让你们能够在看React Native语法的时候不那么费劲,有过前端开发经验的可以直接忽略。 什么是React React是一个JavaScript框架,用来开发web应用。Web应用开发中,比较流行的有三个框架: react angular vue 从名字上,就能看到react native是基于React(都
Scott_Mr
2018/05/16
1.9K0
React Native开发之React基础
为了帮助大家快速上手React Native开发,在这本节中将向大家介绍开发React Native所需要的一些React必备基础知识。
CrazyCodeBoy
2019/12/10
2.2K0
React Native开发之React基础
使用umi开发react-native应用
自此,开发者可以迅速投入到业务代码的开发,而不用去搭建脚手架,写一堆配置和胶水代码去整合各种框架等等。
conanma
2021/11/02
7.4K0
React Native之React速学教程(上)
React Native之React速学教程(上) 本文出自《React Native学习笔记》系列文章。 React Native是基于React的,在开发React Native过程中少不了的需要用到React方面的知识。虽然官方也有相应的Document,但篇幅比较多,学起来比较枯燥。 通过《React Native之React速学教程》你可以对React有更系统和更深入的认识。为了方便大家学习,我将《React Native之React速学教程》分为上、中、下三篇,大家可以根据需要进行阅读学习。 概
CrazyCodeBoy
2018/05/07
2.7K0
React Native之React速学教程(上)
react-native-web
如果使用了 ART,需要安装 react-art(比如,使用了 react-native-svg 来做RN端icon方案,这就是基于 react-art)
conanma
2021/11/02
3.2K0
A Cold Dive into React Native (Tutorial for Beginners)
原文:A Cold Dive into React Native (Tutorial for Beginners)
WindCoder
2018/09/19
1K0
A Cold Dive into React Native (Tutorial for Beginners)
react-native之ART绘图详解
背景 在移动应用的开发过程中,绘制基本的二维图形或动画是必不可少的。然而,考虑到Android和iOS均有一套各自的API方案,因此采用一种更普遍接受的技术方案,更有利于代码的双平台兼容。 art是一个旨在多浏览器兼容的Node style CommonJS模块。在它的基础上,Facebook又开发了react-art ,封装art,使之可以被react.js所使用,即实现了前端的svg库。然而,考虑到react.js的JSX语法,已经支持将 等等svg标签直接插入到dom中(当然此时使用的就不是react
xiangzhihong
2018/01/26
4.4K0
小白看React Native
本文介绍了React Native技术栈,从React Native简介、React Native核心组件、React Native生命周期、React Native的props和state、React Native的调试、React Native的性能优化、React Native的第三方库、React Native的跨平台、React Native的社区等方面进行了介绍。同时,还通过实例介绍了React Native的代码结构、React Native的代码风格、React Native的代码规范、React Native的代码评审。最后,总结了React Native的常见问题以及解决方案,并介绍了学习React Native的资源推荐。
MelonTeam
2018/01/04
2.3K0
小白看React Native
ReactJs和React Native的那些事
介绍  1,React Js的目的 是为了使前端的V层更具组件化,能更好的复用,它能够使用简单的html标签创建更多的自定义组件标签,内部绑定事件,同时可以让你从操作dom中解脱出来,只需要操作数据就会改变相应的dom。  2,React Native的目的 是希望我们能够使用前端的技术栈就可以创建出能够在不同平台运行的一个框架。可以创建出在移动端运行的app,但是性能可能比原声app差一点。  3,ReactJs和React Native的原理是相同的,都是由js实现的虚拟dom来驱动界面view层渲染。
xiangzhihong
2018/02/05
2.4K0
ReactJs和React Native的那些事
iOS React Native 混合开发集成React Native
   有时候我们并不是需要全部使用React Native,我们想和原生混合开发,那我们应该怎么办呢。
星宇大前端
2019/01/15
2.6K0
在 web 环境运行 react-native 页面
QQ音乐技术团队
2017/10/23
4.8K0
React Native之react-native-scrollable-tab-view详解
在React Native开发中,官方为我们提供的Tab控制器有两种:TabBarIOS和ViewPagerAndroid。TabBarIOS,仅适用于IOS平台 ViewPagerAndroid,仅适用于Android平台(严格来讲并不算,因为我们还需要自己实现Tab)。在项目开发中,我们优先选择一些开源兼容性比较好的第三方库,例如,react-navigation,以及本文即将说到的react-native-scrollable-tab-view(官方地址)。react-native-scrolla
xiangzhihong
2018/02/06
7.1K0
React Native之react-native-scrollable-tab-view详解
React-Native入门指南(一)
最近手头的工作繁多,有研究性的项目和系统研发,正好遇到同事离职,接手了框架的UI组件,不仅需要维护和填坑,还需要开发新的功能组件。因为身在H5-Hybird的框架部门,最近团队开始尝试使用React-Native来做些东西。之前也有过开发iOS App的冲动,学了点Object-c,这次正好借此机会进入App开发,以弥补自己在Native-App上的经验不足。
疯狂的技术宅
2019/03/28
2.6K0
React-Native入门指南(一)
React-Native入门指南 终章
在facebook React-native的官网可以看到目前支持的组件如下: https://facebook.github.io/react-native/docs/getting-started.html#content
疯狂的技术宅
2019/03/28
1.7K0
React-Native入门指南 终章
React Native(一):基础
React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的UI框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用。(来自百度百科)
Helloted
2022/06/07
4860
React Native(一):基础
详解React Native渲染原理
在《一篇文章详解React Native初始化和通信机制》中我们详细的介绍了React Native的初始化和通信机制。如果对通信机制不了的的读者可以先去阅读通信机制。
VV木公子
2020/04/14
11.4K0
详解React Native渲染原理
React Native 第一篇-Hello World!
前几天配置好了环境,今天打算写个hello world看看rn的神奇之处 首先运行成功的界面是 Simulator Screen Shot 2016年4月22日 上午12.38.41.png 同时还
傅_hc
2018/06/28
5460
React Native运行原理解析
Facebook 于2015年9月15日推出react native for Android 版本, 加上2014年底已经开源的IOS版本,至此RN (react-native)真正成为跨平台的客户端框架。本篇主要是从分析代码入手,探讨一下RN在安卓平台上是如何构建一套JS的运行框架。 一、 整体架构 RN 这套框架让 JS开发者可以大部分使用JS代码就可以构建一个跨平台APP。 Facebook官方说法是learn once, run everywhere, 即在Android 、 IOS、 Browse
xiangzhihong
2018/02/05
6.5K0
React Native运行原理解析
1.1、介绍
React 是 Facebook 开发的一款 JavaScript 库,而 React 被建造是因为 Facebook 认为市场上所有的 JavaScript MVC 框架都不能满足他们的扩展需求, 由于他们非常巨大的代码库和庞大的组织,使得 MVC 很快变得非常复复杂,每当需要添加一项新的功能或特性时,系统的复杂度就成级数增长,致使代码变得脆弱和不可预测,结果导致他们的 MVC 正在土崩瓦解。2011 年 Facebook 软件工程师 Jordan Walke 创建了 React ,并且首次使用 Facebook 的 Newsfeed 功能。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。
张果
2023/03/01
3.6K0
1.1、介绍
React-Native简介
本文介绍如何使用React Native开发Android原生应用,通过实例演示了如何利用React Native开发简单的Android原生应用,包括如何使用React Native的组件和布局、如何调用原生模块等。同时,文章还介绍了如何为React Native项目添加自定义UI组件,以及如何调用原生模块。
IMWeb前端团队
2017/12/29
2.1K0
React-Native简介
相关推荐
React Native 系列(二) -- React入门知识
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档