首页 文章 精选 留言 我的

精选列表

搜索[图像理解],共10000篇文章
优秀的个人博客,低调大师

到底应该如何理解区块链?

虎嗅注:本文首发于微信公众号“大鱼说区块链”(ID:dysqkl),作者:方刚(快校CEO,前搜狐高级副总裁)。 这个星期,我被一场突如其来的感冒发烧击中了,盖着几床被子都瑟瑟发抖,浑身酸疼,很难受。就在这个星期,也不知怎的,过去一帮搜狐老同事纷纷在微信上联系我,询问区块链的事情,所以顶着头晕脑胀,写几句作为统一回复。 1. TCP/IP是一个协议集合,区块链也是一个协议集合。TCP/IP从来没有to C火过,区块链怎么就像流感一样传开了,真是怪哉。 2. 区块链无疑是重大的,可能是互联网最重要的一次底层迭代。信息上网,价值上链,互联网传递信息,区块链传递价值。人类的进步依赖于用故事形成共识,降低信任成本,提高交易效率,这些故事包括宗教、货币、国家、公司等等。 区块链是一个新故事,它建议人类把共识交给机器和算法,更广更深层面建立无需信任的

优秀的个人博客,低调大师

理解思科IPS的部署架构

在该任务中主要描述关于思科入侵防御系统的接口配置、关于旁路模式和穿越模式的特性与区别、思科IPS系统的virtual sensor、traffic flow notifications、bypass mode、signature、以及 signature检测入侵行为后的动作、signature的引擎等。 关于思科IPS的接口配置 思科的IDS/IPS只有一个command(或者叫做control接口),最多可以有8个monitor接口(或者叫8个sensor接口),多个接口可以同时监控多个网络,如下图5.1所示,可以作穿越模式(在线模式),也可以做旁边路模式(杂合模式),但是必须考虑IPS自身能否承受如此之在的网络流量压力。并且所有的sensor接口使用相同的配置,比如:图5.1所示的环境中,完成了一个signature的配置,那么这个配置即针对图中在线模式(inline)的所有接口生效,也针对图中的所有杂合模式接口生效。默认情况下思科IPS上的所有接口全部是保持关闭状态,并且没有任何接口关联到Virtual sensor,思科IPS任何接口要完成相关的入侵检测行为,必须启动接口,并将接口关联到Virtualsensor,那么什么是Virtual sensor?将在下一博文进行描述。 注意:如果您刚购买了一台全新的思科IPS设备,默认它只有两个接口,一个command接口和一个sensor接口,但是提供了两个模块化的槽位,您可以通过扩展模块来获得更多的sensor接口。 接口处于旁路模式(杂合模式)的特性: 在旁路模式中,分析的数据包并没有真正的穿越sensor接口,它实际上是分析实时通信流中数据包的一个拷贝包,当然这需要接合交换机的端口镜像功能来完成。旁路模式的最大优点在于不影响流量的性能,比如说一些要超低延迟的数据包(IP语音包)就不会受到IPS分析的影响,最降低转发延迟,这种模式的最大缺点是不能阻止初始化攻击,面对攻击时的响应迟缓,也叫做post-event,也就是常说的事后才知。关于旁路模式的部署将在本项目的5.2任务二 旁路模式(杂合模式)下思科IDS/IPS传感器的部署有详细描述。 接口处于穿越模式(在线模式)的特性: 如果是连接同一交换机,两个接口要处于不同的VLAN,一个VLAN就没有意义了,最初穿越模式中的两个接口必须要使用两个物理接口,现在一个物理接口也可以做在线模式,因为它可以跟交换机的trunk联动并提供了VLAN Pairs特性,这就有点类似于VLAN间的单臂路由(将一个物理接口分为多个逻辑子接口并规划到不同的VLAN中)。要配置在线模式,必须遵守这样一个则规则:首先激活两个接口,其次是把两个接口放到interface Pairs,最后把interface Pairs关联到virtual sensor。IPS是没有必要像防火墙那样去区分内外接口的。 使用穿越模式的IPS会对流量性能造成一定的影响,特别是一些要超低延迟的数据包(IP语音包),最大的优点是可以阻止初始化攻击。 本文转自 kingsir827 51CTO博客,原文链接:http://blog.51cto.com/7658423/1285421,如需转载请自行联系原作者

优秀的个人博客,低调大师

理解Docker(7):Docker 存储 - AUFS

(1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 (4)Docker 容器的隔离性 - 使用 cgroups 限制容器使用的资源 (5)Docker 网络 (6)若干企业生产环境中的容器网络方案 (7)Docker 存储- AUFS Docker 存储可以分为分层文件系统和卷,本文将介绍 AUFS 分层文件系统。 1. 基础知识 1.1 Linux 的 rootfs 和 bootfs 一个典型的 Linux 系统要能运行的话,它至少需要两个文件系统: boot file system (bootfs):包含 boot loader 和 kernel。用户不会修改这个文件系统。实际上,在启动(boot)过程完成后,整个内核都会被加载进内存,此时 bootfs 会被卸载掉从而释放出所占用的内存。同时也可以看出,对于同样内核版本的不同的 Linux 发行版的bootfs都是一致的。 root file system (rootfs):包含典型的目录结构,包括/dev, /proc, /bin, /etc, /lib, /usr, and /tmp 等再加上要运行用户应用所需要的所有配置文件,二进制文件和库文件。这个文件系统在不同的Linux 发行版中是不同的。而且用户可以对这个文件进行修改。 Linux 系统在启动时,roofs 首先会被挂载为只读模式,然后在启动完成后被修改为读写模式,随后它们就可以被修改了。 1.2 AUFS AUFS 是一种 Union File System(联合文件系统),又叫 Another UnionFS,后来叫Alternative UnionFS,再后来叫成高大上的 Advance UnionFS。所谓 UnionFS,就是把不同物理位置的目录合并mount到同一个目录中。UnionFS的一个最主要的应用是,把一张CD/DVD和一个硬盘目录给联合 mount在一起,然后,你就可以对这个只读的CD/DVD上的文件进行修改(当然,修改的文件存于硬盘上的目录里)。 举个例子,在 Ubuntu 14.04 系统上现有如下目录结构: $ tree . ├── fruits │ ├── apple │ └── tomato └── vegetables ├── carrots └── tomato 输入以下几个命令: # 创建一个mount目录 $ mkdir mnt # 把水果目录和蔬菜目录union mount到 ./mnt目录中 $ sudo mount -t aufs -o dirs=./fruits:./vegetables none ./mnt # 查看./mnt目录 $ tree ./mnt ./mnt ├── apple ├── carrots └── tomato 我们可以看到在./mnt目录下有三个文件,苹果apple、胡萝卜carrots和蕃茄tomato。水果和蔬菜的目录被union到了./mnt目录下了。 我们来修改一下其中的文件内容: $ echo mnt > ./mnt/apple $ cat ./mnt/apple mnt $ cat ./fruits/apple mnt 上面的示例,我们可以看到./mnt/apple的内容改了,./fruits/apple的内容也改了。 $ echo mnt_carrots > ./mnt/carrots $ cat ./vegetables/carrots $ cat ./fruits/carrots mnt_carrots 关于 AUFS 的几个特点: AUFS 是一种联合文件系统,它把若干目录按照顺序和权限 mount 为一个目录并呈现出来 默认情况下,只有第一层(第一个目录)是可写的,其余层是只读的。 增加文件:默认情况下,新增的文件都会被放在最上面的可写层中。 删除文件:因为底下各层都是只读的,当需要删除这些层中的文件时,AUFS使用 whiteout 机制,它的实现是通过在上层的可写的目录下建立对应的whiteout隐藏文件来实现的。 修改文件:AUFS 利用其 CoW (copy-on-write)特性来修改只读层中的文件。AUFS 工作在文件层面,因此,只要有对只读层中的文件做修改,不管修改数据的量的多少,在第一次修改时,文件都会被拷贝到可写层然后再被修改。 节省空间:AUFS 的 CoW 特性能够允许在多个容器之间共享分层,从而减少物理空间占用。 查找文件:AUFS 的查找性能在层数非常多时会出现下降,层数越多,查找性能越低,因此,在制作 Docker 镜像时要注意层数不要太多。 性能:AUFS 的 CoW 特性在写入大型文件时第一次会出现延迟。 本部分内容主要应用自Docker基础技术:AUFS。 2. Docker 文件系统 2.1 Docker 镜像的 rootfs 前面基础知识部分谈到过,同一个内核版本的所有 Linux 系统的 bootfs 是相同的,而 rootfs 则是不同的。在 Docker 中,基础镜像中的 roofs 会一直保持只读模式,Docker 会利用 union mount 来在这个 rootfs 上增加更多的只读文件系统,最后它们看起来就像一个文件系统即容器的 rootfs。 (图片来源) 可见在一个Linux 系统之中, 所有 Docker 容器都共享主机系统的 bootfs 即 Linux 内核 每个容器有自己的 rootfs,它来自不同的 Linux 发行版的基础镜像,包括 Ubuntu,Debian 和 SUSE 等 所有基于一种基础镜像的容器都共享这种 rootfs 以training/webapp 镜像为例, root@docker1:/var/lib/docker/aufs/diff/b2188d5c09cfe24acd6da5ce67720f81138f0c605a25efc592f1f55b3fd3dffa# docker history training/webapp IMAGE CREATED CREATED BY SIZE COMMENT 6fae60ef3446 16 months ago /bin/sh -c #(nop) CMD ["python" "app.py"] 0 B <missing> 16 months ago /bin/sh -c #(nop) EXPOSE 5000/tcp 0 B <missing> 16 months ago /bin/sh -c #(nop) WORKDIR /opt/webapp 0 B <missing> 16 months ago /bin/sh -c #(nop) ADD dir:9b2a69f6f30d18b02b5 703 B <missing> 16 months ago /bin/sh -c pip install -qr /tmp/requirements. 4.363 MB <missing> 16 months ago /bin/sh -c #(nop) ADD file:c59059439864153904 41 B <missing> 16 months ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt 135.3 MB <missing> 16 months ago /bin/sh -c apt-get update 20.8 MB <missing> 16 months ago /bin/sh -c #(nop) MAINTAINER Docker Education 0 B <missing> 17 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B <missing> 17 months ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/ 1.895 kB <missing> 17 months ago /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic 194.5 kB <missing> 17 months ago /bin/sh -c #(nop) ADD file:f4d7b4b3402b5c53f2 188.1 MB 它是基于 Ubuntu Docker 基础镜像。在基础镜像层中,我们能看到完整的 Ubuntu rootfs: root@docker1:/var/lib/docker/aufs/diff/b2188d5c09cfe24acd6da5ce67720f81138f0c605a25efc592f1f55b3fd3dffa# ls -l total 76 drwxr-xr-x 2 root root 4096 Apr 27 2015 bin drwxr-xr-x 2 root root 4096 Apr 11 2014 boot drwxr-xr-x 3 root root 4096 Apr 27 2015 dev drwxr-xr-x 61 root root 4096 Apr 27 2015 etc drwxr-xr-x 2 root root 4096 Apr 11 2014 home drwxr-xr-x 12 root root 4096 Apr 27 2015 lib drwxr-xr-x 2 root root 4096 Apr 27 2015 lib64 drwxr-xr-x 2 root root 4096 Apr 27 2015 media drwxr-xr-x 2 root root 4096 Apr 11 2014 mnt drwxr-xr-x 2 root root 4096 Apr 27 2015 opt drwxr-xr-x 2 root root 4096 Apr 11 2014 proc drwx------ 2 root root 4096 Apr 27 2015 root drwxr-xr-x 7 root root 4096 Apr 27 2015 run drwxr-xr-x 2 root root 4096 Apr 27 2015 sbin drwxr-xr-x 2 root root 4096 Apr 27 2015 srv drwxr-xr-x 2 root root 4096 Mar 13 2014 sys drwxrwxrwt 2 root root 4096 Apr 27 2015 tmp drwxr-xr-x 10 root root 4096 Apr 27 2015 usr drwxr-xr-x 11 root root 4096 Apr 27 2015 var 我们来看两种典型的文件: (1)bin 目录中的文件会被直接使用 root@docker1:/var/lib/docker/aufs/diff# find -iname mountpoint ./b2188d5c09cfe24acd6da5ce67720f81138f0c605a25efc592f1f55b3fd3dffa/bin/mountpoint (2)在基础镜像层中 proc 目录为空,也就是说容器中看到的 proc 目录中的文件是后来生成的。 2.2 Docker 使用的 AUFS 文件系统 关于 Docker的分层镜像,除了 aufs,docker还支持btrfs, devicemapper和vfs,你可以使用 -s 或 –storage-driver= 选项来指定相关的镜像存储。在Ubuntu 14.04下,Docker 默认 Ubuntu的 AUFS。因为 AUFS 还没有进入Linux 内核主干的原因,RedHat 上使用的是 devicemapper。 我们可以在 docker info 命令的输出中查看所使用的存储驱动: Storage Driver: aufs Root Dir: /var/lib/docker/aufs Backing Filesystem: extfs Dirs: 19 Dirperm1 Supported: false 以一个正在运行着的 Docker 容器为例,其镜像有13层: "RootFS": { "Type": "layers", "Layers": [ "sha256:1154ba695078d29ea6c4e1adb55c463959cd77509adf09710e2315827d66271a", "sha256:528c8710fd95f61d40b8bb8a549fa8dfa737d9b9c7c7b2ae55f745c972dddacd", "sha256:37ee47034d9b78f10f0c5ce3a25e6b6e58997fcadaf5f896c603a10c5f35fb31", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "sha256:b75c0703b86b8ccbdc1f1b28b4982774768861ac250f83bdb940b1e90291f302", "sha256:5c121779bb29172c628a21087ea8ced766959da2f223c8b6bd4ffe943ace43d8", "sha256:3ee91c5cb95b01496b4afdc721ba7fd3c22e0e5e2f3e9e70d3f8579b5082d4f3", "sha256:6bbb1d0f845289217e20b66697fa7d651394d89983b0f5a89b88f037194476fe", "sha256:b44b0832d4c6bf33122ce3aa896b133df88275e6d20663a9bf2d941f764ac1fd", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] } 从 AUFS 的角度,可以看到有一个可写的容器层和14个只读的镜像层: root@docker1:/sys/fs/aufs/si_ab487e40195df24f# cat * /var/lib/docker/aufs/diff/2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab=rw #可写的容器层 /var/lib/docker/aufs/diff/2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab-init=ro+wh #本层及以下是只读的镜像层 /var/lib/docker/aufs/diff/5472f8388f9a61f6bd84498201a5ad71a2ec88cda16c42a3a1da7c30da45f102=ro+wh /var/lib/docker/aufs/diff/61dcf0881e790bf52ec555727b58641791adeefadcc7abc2a77fd228bde1371a=ro+wh /var/lib/docker/aufs/diff/f68672aaf17dd158aabc635b2d8d459d79db1cd5ff38bf3834fe8f9c7a05235e=ro+wh /var/lib/docker/aufs/diff/45818d286499870412357d66eb6af951699f89db785c7c6a242d2e1ac99734f9=ro+wh /var/lib/docker/aufs/diff/b2188d5c09cfe24acd6da5ce67720f81138f0c605a25efc592f1f55b3fd3dffa=ro+wh /var/lib/docker/aufs/diff/85cb840562788e1b458e68265e62fd2da9d0d7e737256500e8a276bcb237183c=ro+wh /var/lib/docker/aufs/diff/c18ba8efcb455e97f6aabe3985b147f6a37b8f5ad090373e88ddd326b4f90896=ro+wh /var/lib/docker/aufs/diff/25de7dcc3a06f0caa3c701d4ed6c62f03e0757f6d477cc822db6e884bb366441=ro+wh /var/lib/docker/aufs/diff/ad9e831217594cdfecd5e824690b0e52f2e16d6e2bb39b7143e66d467150cfe8=ro+wh /var/lib/docker/aufs/diff/56d37c8eecd8be9ba13e07e1486e7a6ac2f0aa01f8e865ee6136137369d8d8a0=ro+wh /var/lib/docker/aufs/diff/31bc6290457af4e560a3103020c85fbb5dfcfb201b0662a33165260529f87c07=ro+wh /var/lib/docker/aufs/diff/e104672666119006648d0b82988c49527e52c64629750c5c9adde88acc790682=ro+wh /var/lib/docker/aufs/diff/7a085e415855435121fb7837c26a5e951f622bc69364d9228d409a4929b627e1=ro+wh 根据上面 AUFS 的定义,容器的文件系统是从 14 个只读镜像层和1个可写容器层通过 AUFS mount 出来的。示意图如下: (图片来源) 这种分层文件系统可以通过官网的图来清晰的展示出来: 做一些实验: (1)在容器中创建一个文件,该文件会被创建在可写的容器层中 root@docker1:/var/lib/docker/aufs/diff# find -iname createdbysammy ./2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab/opt/webapp/createdbysammy root@docker1:/var/lib/docker/aufs/diff# ls -lt total 60 drwxr-xr-x 9 root root 4096 Oct 4 22:37 2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab drwxr-xr-x 6 root root 4096 Oct 1 11:56 2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab-init (2)修改一个镜像层中的文件 修改前,文件/etc/apt/sources.list出现在两个层中: root@docker1:/var/lib/docker/aufs/diff# find -iname sources.list ./f68672aaf17dd158aabc635b2d8d459d79db1cd5ff38bf3834fe8f9c7a05235e/etc/apt/sources.list ./b2188d5c09cfe24acd6da5ce67720f81138f0c605a25efc592f1f55b3fd3dffa/etc/apt/sources.list 在容器中对它进行修改后,它被拷贝到了容器层然后被修改了: root@docker1:/var/lib/docker/aufs/diff# find -iname sources.list ./f68672aaf17dd158aabc635b2d8d459d79db1cd5ff38bf3834fe8f9c7a05235e/etc/apt/sources.list ./2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab/etc/apt/sources.list ./b2188d5c09cfe24acd6da5ce67720f81138f0c605a25efc592f1f55b3fd3dffa/etc/apt/sources.list 而另外两个层中的文件保持了不变。这说明了 AUFS 的 CoW 特性。 (3)删除容器层中的文件 容器中的文件./usr/local/lib/python2.7/dist-packages/itsdangerous.py 位于56d37c8eecd8be9ba13e07e1486e7a6ac2f0aa01f8e865ee6136137369d8d8a0 层中,这是一个只读层。 在容器内删除它: root@fa385836d5b9:/# find -iname itsdangerous.py ./usr/local/lib/python2.7/dist-packages/itsdangerous.py root@fa385836d5b9:/# rm ./usr/local/lib/python2.7/dist-packages/itsdangerous.py root@fa385836d5b9:/# find -iname itsdangerous.py 然后,容器层中出现了一个 .wh 文件,而镜像层中的文件保持不变: root@docker1:/var/lib/docker/aufs/diff# find -iname *itsdangerous.py ./56d37c8eecd8be9ba13e07e1486e7a6ac2f0aa01f8e865ee6136137369d8d8a0/usr/local/lib/python2.7/dist-packages/itsdangerous.py ./2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab/usr/local/lib/python2.7/dist-packages/.wh.itsdangerous.py 在手工将 .wh 文件删除后,文件就会再次回到容器中。 rm ./2ee58d81e4ac6811bbc78beb4b46bf213c79c9e2dc7e441741afc8c4349c6bab/usr/local/lib/python2.7/dist-packages/.wh.itsdangerous.py root@fa385836d5b9:/# find -iname itsdangerous.py ./usr/local/lib/python2.7/dist-packages/itsdangerous.py 参考链接: Docker基础技术:AUFS http://crishantha.com/wp/?p=1549 本文转自SammyLiu博客园博客,原文链接:http://www.cnblogs.com/sammyliu/p/5931383.html ,如需转载请自行联系原作者

优秀的个人博客,低调大师

《深入理解Android》一导读

前 言 为什么要写这本书在PC互联网时代,用户开启电脑后手动打开的第一个应用程序,如果不是QQ,那往往就是浏览器。在移动互联网无比繁荣的今天,移动浏览器虽然没有像PC浏览器那样占据资讯第一入口的地位,但浏览器引擎一个华丽的转身,找到了自己新的、更广阔的发展空间—嵌入到各个超级App中,比如微信、百度搜索框等,无缝展示Web资源,由此可见,浏览器引擎依旧非常重要。浏览器的重要性毋庸讳言,在这便捷的工具中,用户只需键入一个文本的URL或者点击一个链接,瞬间绚丽的新页面就展示在面前。浏览器具备什么样的魔法使这一切悄然发生呢?相信普通用户和众多的前端开发者都会有这个疑问。阅读开源的浏览器引擎代码(比如WebKit),可以帮我们解开这些疑惑,这正是本书的内容。WebKit引擎内容庞大复杂,是一个完整的网页内容解析工具,集成WebKit的具体平

优秀的个人博客,低调大师

阿里云专家理解的DevOps

2017运维/DevOps在线技术峰会上,阿里云平台研发高级专家连铭带来DevOps的相关演讲。本文主要从什么是DevOps开始聊起,接着对比了DevOps与传统模式的区别,并且列举了DevOps的难点和需要解决的问题,包括寻找平衡点、责权划分和制约考核,最后进行了简要总结。一起来了解下吧。 以下是精彩内容整理: 近几个月,运维事件频发。从“炉石数据被删”到“MongoDB遭黑客勒索”,从“Gitlab数据库被误删”到某家公司漏洞被组合攻击。这些事件,无一不在呐喊——做好运维工作的重要性。然而,从传统IT部署到云,人肉运维已经是过去式,云上运维该怎么开展?尤其是云2.0时代,运维已经向全局化、流程化和精细化模式转变。与此同时,人工智能的发展,“威胁论”也随之袭来——运维是不是快要无用武之地了?如何去做更智能的活,当下很多运维人在不断思考和探寻答案。 什么是DevOps? DevOps 是一种工程模式,本质上是一种分工,通过对开发、运维、测试,配管等角色职责的分工,实现工程效率最大化,进而满足业务的需求。 DevOps的核心是角色的分工,而不是组织架构变化,垂直化的组织架构不代表可以实现DevOps所需要的分工模式,横向的组织架构也不代表传统的分工模式。 DevOps的目标是工程效率最大化,它本身也只是一种方法论,是为了实现工程效率最大化的目标而存在的。 DevOps与传统模式的区别 传统分工模式下,PD将需求提出来,开发者根据需求写代码,然后告诉SCM,SCM拿着代码去打包,打包后告诉QA,QA测试完成后通知运维OPS上线,OPS进行上线部署,最后整个需求得到release。 它的优势在于:分工与责任清晰,质量有保障,层层制约,容易把控。 它的劣势也很明显:沟通成本与等待成本高,每一个环节都有成为瓶颈的风险,比如DEV知道怎样写代码,但QA也需要了解需求才能知道怎么做测试,OPS也需要了解需求维持线上稳定性,OPS负责交付,容易演变成擦屁股的角色,包括日常出现的bug。 在DevOps分工模式下,一切都改变了,不再是每个人做完自己的事情然后交给下一个人。这个分工模式下,开发通过工具驱动所有流程运转向前走,比如开发写完代码通过工具驱动自动化打包,自动化测试,自动化部署或升级,还会配备监控;SCM、OPS和QA等在工具的外围,确保在工具中的每一个环节可以正常运转,它们支撑工具的目的是确保DEV可以使用工具完成人肉完成的事情,这是决策的变化,还要保证工具中的几个模块可以支撑最新的业务变化,当业务有了更新的变化时,须保证工具可以支撑开发。 DevOps分工模式的好处很明显:可以减少沟通成本与等待风险,降低正常需求交付所需时间,DEV负责交付,避免交付扯皮。 DevOps分工模式的劣势也很突出:每个环节参与角色较多,风险较高,对于业务形态比较多的企业较明显,工具支撑多种业务形态的成本是非常高的,当工具搞不定时,需要人肉补位保证业务发布,如果补位较多,那么DevOps分工就失败了;专业度会有降低,工具只能支持在精确输入的情况下以非常精确的方式完成一件固定的事情,一旦输入有变化而超出规则,该环节就比较麻烦了,工具的专业提升比人要慢的多;DEV权利过大,容易军阀化。 DevOps的难点和需要解决的问题 寻找平衡点 DevOps是为了追求工程效率最大化而存在的,但是工程效率和稳定性的目标在大部分场景下都是相悖的,如何能够在工程效率提升的前提下,保证稳定性不出问题? 传统分工模式是OPS团队负责,在DevOps分工模式中已经没有OPS团队了,只能开发团队负责,当一个团队同时负责两个相互有冲突的case时,该怎么办呢?如果分成两部分人分别负责业务KPI和稳定性KPI,就回到了传统的分工模式。 责权划分 对于开发者而言,主业是coding,其它包括打包、测试、发布都是辅业,它是工具的使用者,并不能完全将所有事情做得完美,在除coding以外的所有环节中,责任和分工要怎么来分,除了开发以外的事情要占用开发人员多少精力,才能保证DEV使用顺畅,跟上公司业务发展? 其中核心是工具,工具是将二者粘合在一起的,工具起到了赋能和粘合的作用,工具还须可介入,需要人肉补位;另外,工具的进化要运维团队、测试团队和SCM团队来负责,工具自己要足够开放,才能让其它团队可以不断优化某一环节;工具也要保证可持续成长,跟上时代的发展。 制约与考核 打破原先的平衡以后,新的平衡如何建立?重新建立平衡是需要时间的,DEV在工程中话语权加大,权利是一定会被制约的,不是内部,就是外部市场。 每一个问题都要根据公司的实际情况寻找一个平衡点,找到责权划分,怎样去考核和制约,只有将这三个点解完,才可能活下来将分工模式持续跑下去。 DevOps怎么衡量? DevOps可以由四个角度做衡量: 工程效率:从某一个开发的团队接到需求,到需求交付上线的时间有多长。工程效率能够提升多少代表DevOps发挥作用的大小; 稳定性:当稳定性没有保证时,效率越高死的越快; 非研发工作占比:当占比非常大时,离失败就不远了; 业务规模与运维人员比例:谷歌的每一个SRE也要管理2000台机器的业务。 总结 1. 实现自动化运维后,很多运维人员就会面临失业,但这是时代发展的必然结果,我们只需欣然接受; 2. DevOps没有最佳实践,我们该更关注一些案例的环境和业务背景,DevOps本身不是目标,是一个方法,一个理论; 3. DevOps和传统模式没有好坏之分,只有适不适合。

优秀的个人博客,低调大师

深入理解Android Build系统

概述 Android Build 系统是用来编译 Android 系统、Android SDK 以及相关文档的一套框架。在Android系统中,Android 的源码中包含了许许多多的模块。 不同产商的不同设备对于 Android 系统的定制都是不一样的。如何将这些模块统一管理起来,如何能够在不同的操作系统上进行编译,如何在编译时能够支持面向不同的硬件设备,不同的编译类型,且还要提供面向各个产商的定制扩展,Android系统如何解决这些问题呢?这就是我们不得不谈的Android Build 系统。 Android源码目录结构: Linux系统的make命令 在讲解Android编译系统之前,我们首先需要了解Linux系统的make命令。在Linux系统中,我们可以通过make命令来编译代码。Make命令在执行的时候,默认会在当前目录找到一个Makefile文件,然后根据Makefile文件中的指令来对代码进行编译。如gcc,Linux系统中的shell命令cp、rm等等。 看到这里,有的小伙伴可能会说,在Linux系统中,shell和make命令有什么区别呢?make命令事实也是通过shell命令来完成任务的,但是它的神奇之处是可以帮我们处理好文件之间的依赖关系。例如有一个文件T,它依赖于另外一个文件D,要求只有当文件D的内容发生变化,才重新生成文件T。 Make命令是怎么知道两个文件之间存在依赖关系,以及当被依赖文件发生变化时如何处理目标文件的呢?答案就在前面提到的Makefile文件。Makefile文件实际上是一个脚本文件,就像普通的shell脚本文件一样,只不过它遵循的是Makefile语法。Makefile文件最基础的功能就是描述文件之间的依赖关系,以及怎么处理这些依赖关系。 Android Build简介 Android Build 系统是 Android 系统的一部分,主要用来编译 Android 系统,Android SDK 以及相关文档。该系统主要由 Make 文件,Shell 脚本以及 Python 脚本组成。Android build分类: build/core 目录下的文件,这是Android Build的系统框架核心; device目录下的文件,存放的是具体的产品配置文件; 各个模块的编译文件:Android.mk,位于模块的原文件目录下。 Android Build系统核心 Android Build系统核心在目录build/core,这个目录中有mk文件、shell脚本和per脚本,他们构成Android Build系统的基础和架构。 在核心的buil/core里,系统主要干了三件事情: 常用命令: source build/envsetup.sh lunch make envsetup.sh 而在build/envsetup.sh中主要完成了三件事: 执行Android系统的编译,必须先执行envsetup.sh脚本,这个脚本会建立Android的编译环境。其具体执行的是建立shell命令以及调用add_lunch_combo命令,这个命令的将调用该命令的所传递的参数存放到一个全局的数组变量LUNCH_MENU_CHOICES中。 envsetup.sh脚本中定义的常用shell命令: 命令 说明 contact-button 指定当前编译的产品 croot 快速切换到源码的根目录,方便开始编译 m 编译整个源码,但不用将当前的目录切换到源码的根目录 mm 编译当前目录下的所有模块,但是不编译他们的依赖项 mm 编译当前目录下的所有模块,但是不编译他们的依赖项 cgrep 对系统中所有的C/C++文件执行grep命令 sgrep 对系统中所有的源文件执行grep命令 编译 Android 系统 Android 系统的编译环境目前只支持 Ubuntu 以及 Mac OS 两种操作系统。在编译Android系统之前我们需要先获取完整的 Android 源码。打开控制台之后转到 Android 源码的根目录,然后执行如下命名: source build/envsetup.sh lunch full-eng make -j8 关于这几条命令的意思,我们上面提过。第一步命令“source build/envsetup.sh”引入了 build/envsetup.sh脚本,该脚本的作用是初始化编译环境,并引入一些辅助的 Shell 函数; 第二步命令“lunch full-eng”是调用 lunch 函数,并指定参数为“full-eng”。lunch 函数的参数用来指定此次编译的目标设备以及编译类型。 第三部命令“make -j8”才真正开始执行编译。make 的参数“-j”指定了同时编译的 Job 数量,这是个整数,该值通常是编译主机 CPU 支持的并发线程总数的 1 倍或 2 倍(例如:在一个 4 核,每个核支持两个线程的 CPU 上,可以使用 make -j8 或 make -j16)。完整的编译时间依赖于编译主机的配置。 Build 结果 所有的编译产物都将位于 /out 目录下,该目录下主要包含: /out/host/:该目录下包含了针对主机的 Android 开发工具的产物。即 SDK 中的各种工具,例如:emulator,adb,aapt 等。 /out/target/common/:该目录下包含了针对设备的共通的编译产物,主要是 Java 应用代码和 Java 库。 /out/target/product//:包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,是具体目标设备的名称。 /out/dist/:包含了为多种分发而准备的包,通过“make disttarget”将文件拷贝到该目录,默认的编译目标不会产生该目录。 Build 生成的镜像文件 Build 的产物中最重要的是三个镜像文件,它们都位于 /out/target/product// 目录下: system.img:包含了 Android OS 的系统文件,库,可执行文件以及预置的应用程序,将被挂载为根分区。 ramdisk.img:在启动时将被 Linux 内核挂载为只读分区,它包含了 /init文件和一些配置文件。它用来挂载其他系统镜像并启动 init 进程。 userdata.img:将被挂载为 /data,包含了应用程序相关的数据以及和用户相关的数据。 Make 文件 整个 Build 系统的入口文件是源码树根目录下名称为“Makefile”的文件,当在源代码根目录上调用 make 命令时,make 命令首先将读取该文件。Makefile 文件的内容只有一行:“include build/core/main.mk”。该行代码的作用很明显:包含 build/core/main.mk 文件。在 main.mk 文件中又会包含其他的文件,其他文件中又会包含更多的文件,这样就引入了整个 Build 系统。 在整个Build系统中,Make 文件间的关系是相当复杂的。看一张make文件主要的关系图: Make 常用文件: 文件名 说明 main.mk 主要的 Make 文件,该文件中首先将对编译环境进行检查,同时引入其他的 Make 文件。另外,该文件中还定义了几个最主要的 Make 目标,例如 droid,sdk,等(参见后文“Make 目标说明”)。 help.mk 含了名称为 help 的 Make 目标的定义,该目标将列出主要的 Make 目标及其说明。 envsetup.mk 配置 Build 系统需要的环境变量,例如:TARGET_PRODUCT,TARGET_BUILD_VARIANT,HOST_OS,HOST_ARCH 等。 当前编译的主机平台信息(例如操作系统,CPU 类型等信息)就是在这个文件中确定的。 另外,该文件中还指定了各种编译结果的输出路径。 pathmap.mk 将许多头文件的路径通过名值对的方式定义为映射表,并提供 include-path-for 函数来获取。例如,通过 $(call include-path-for, frameworks-native)便可以获取到 framework 本地代码需要的头文件路径。 combo/select.mk 根据当前编译器的平台选择平台相关的 Make 文件。 dumpvar.mk 在 Build 开始之前,显示此次 Build 的配置信息。 config.mk 整个 Build 系统的配置文件,最重要的 Make 文件之一。该文件中主要包含以下内容: 定义了许多的常量来负责不同类型模块的编译。 定义编译器参数以及常见文件后缀,例如 .zip,.jar.apk。 根据 BoardConfig.mk 文件,配置产品相关的参数。 设置一些常用工具的路径,例如 flex,e2fsck,dx。 definitions.mk 最重要的 Make 文件之一,在其中定义了大量的函数。这些函数都是 Build 系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,关于这些函数的说明请参见每个函数的代码注释。 distdir.mk 针对 dist 目标的定义。dist 目标用来拷贝文件到指定路径 dex_preopt.mk 针对启动 jar 包的预先优化。 pdk_config.mk 顾名思义,针对 pdk(Platform Developement Kit)的配置文件。 post_clean.mk 在前一次 Build 的基础上检查当前 Build 的配置,并执行必要清理工作。 legacy_prebuilts.mk 该文件中只定义了 GRANDFATHERED_ALL_PREBUILT 变量。 Makefile 被 main.mk 包含,该文件中的内容是辅助 main.mk 的一些额外内容。 Android 源码中包含了许多的模块,模块的类型有很多种,例如:Java 库,C/C++ 库,APK 应用,以及可执行文件等 。并且,Java 或者 C/C++ 库还可以分为静态的或者动态的,库或可执行文件既可能是针对设备(本文的“设备”指的是 Android 系统将被安装的设备,例如某个型号的手机或平板)的也可能是针对主机(本文的“主机”指的是开发 Android 系统的机器,例如装有 Ubuntu 操作系统的 PC 机或装有 MacOS 的 iMac 或 Macbook)的。不同类型的模块的编译步骤和方法是不一样,为了能够一致且方便的执行各种类型模块的编译,在 config.mk 中定义了许多的常量,这其中的每个常量描述了一种类型模块的编译方式。常见的有:BUILD_HOST_STATIC_LIBRARYBUILD_HOST_SHARED_LIBRARYBUILD_STATIC_LIBRARYBUILD_SHARED_LIBRARYBUILD_EXECUTABLEBUILD_HOST_EXECUTABLEBUILD_PACKAGEBUILD_PREBUILTBUILD_MULTI_PREBUILTBUILD_HOST_PREBUILTBUILD_JAVA_LIBRARYBUILD_STATIC_JAVA_LIBRARYBUILD_HOST_JAVA_LIBRARY 不同类型的模块的编译过程会有一些相同的步骤,例如:编译一个 Java 库和编译一个 APK 文件都需要定义如何编译 Java 文件。为了减少代码冗余,需要将共同的代码复用起来,复用的方式是将共同代码放到专门的文件中,然后在其他文件中包含这些文件的方式来实现的。模块的编译方式定义文件的包含关系: Make 编译镜像 make /make droid 如果在源码树的根目录直接调用“make”命令而不指定任何目标,则会选择默认目标:“droid”(在 main.mk 中定义)。因此,这和执行“make droid”效果是一样的。droid 目标将编译出整个系统的镜像。从源代码到编译出系统镜像,整个编译过程非常复杂。这个过程并不是在 droid 一个目标中定义的,而是 droid 目标会依赖许多其他的目标,这些目标的互相配合导致了整个系统的编译。那么需要编译出系统镜像,需要哪些依赖呢? droid 所依赖的其他 Make目标说明: 名称 说明 apps_only 该目标将编译出当前配置下不包含 user,userdebug,eng 标签(关于标签,请参见后文“添加新的模块”)的应用程序。 droidcore 该目标仅仅是所依赖的几个目标的组合,其本身不做更多的处理。 dist_files 该目标用来拷贝文件到 /out/dist 目录。 files 该目标仅仅是所依赖的几个目标的组合,其本身不做更多的处理 prebuilt 该目标依赖于 $(ALL_PREBUILT),$(ALL_PREBUILT)的作用就是处理所有已编译好的文件。 $(modules_to_install) modules_to_install 变量包含了当前配置下所有会被安装的模块(一个模块是否会被安装依赖于该产品的配置文件,模块的标签等信息),因此该目标将导致所有会被安装的模块的编译。 $(modules_to_check) 该目标用来确保我们定义的构建模块是没有冗余的。 $(INSTALLED_ANDROID_INFO_TXT_TARGET) 该目标会生成一个关于当前 Build 配置的设备信息的文件,该文件的生成路径是:out/target/product//android-info.txt systemimage 生成 system.img。 Build 系统中包含的其他一些 Make 目标: Make目标说明 说明 make clean 执行清理,等同于:rm -rf out/ make sdk 编译出 Android 的 SDK Make目标说明 说明 make clean-sdk 清理 SDK 的编译产物 make update-api 更新 API。在 framework API 改动之后,需要首先执行该命令来更新 API,公开的 API 记录在 frameworks/base/api 目录下。 make dist 执行 Build,并将 MAKECMDGOALS 变量定义的输出文件拷贝到 /out/dist 目录 make all 编译所有内容,不管当前产品的定义中是否会包含 make help 帮助信息 make snod 从已经编译出的包快速重建系统镜像 make libandroid_runtime 编译所有 JNI framework 内容 makeframework 编译所有 Java framework 内容 makeservices 编译系统服务和相关内容 make 编译一个指定的模块,local_target 为模块的名称 make clean- 清理一个指定模块的编译结果 makedump-products 显示所有产品的编译配置信息,例如:产品名,产品支持的地区语言,产品中会包含的模块等信息 makePRODUCT-xxx-yyy 编译某个指定的产品 makebootimage 生成 boot.img 定制 Build 系统中内容 当我们要开发一款新的 Android 产品的时候,我们首先就需要在 Build 系统中添加对于该产品的定义。在 Android Build 系统中对产品定义的文件通常位于 device 目录下,device 目录下可以公司名以及产品名分为二级目录,然后加入到系统中,如以前小米等基于Android深度定制的系统。通常,对于一个产品的定义通常至少会包括四个文件:AndroidProducts.mk,产品版本定义文件,BoardConfig.mk 以及 verndorsetup.sh。 AndroidProducts.mk 该文件只需要定义一个变量,名称为“PRODUCT_MAKEFILES”。 PRODUCT_MAKEFILES := \ $(LOCAL_DIR)/full_stingray.mk \ $(LOCAL_DIR)/stingray_emu.mk \ $(LOCAL_DIR)/generic_stingray.mk 产品版本定义文件 该文件中包含了对于特定产品版本的定义。该文件可能不只一个,因为同一个产品可能会有多种版本。通常情况下,我们并不需要定义所有这些变量。Build 系统的已经预先定义好了一些组合,它们都位于 /build/target/product 下,每个文件定义了一个组合,我们只要继承这些预置的定义,然后再覆盖自己想要的变量定义即可。 # 继承 full_base.mk 文件中的定义 $(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk) # 覆盖其中已经定义的一些变量 PRODUCT_NAME := full_lt26 PRODUCT_DEVICE := lt26 PRODUCT_BRAND := Android PRODUCT_MODEL := Full Android on LT26 BoardConfig.mk 该文件用来配置硬件主板,它其中定义的都是设备底层的硬件特性。例如:该设备的主板相关信息,Wifi 相关信息,还有 bootloader,内核,radioimage 等信息。 vendorsetup.sh 该文件中作用是通过 add_lunch_combo 函数在 lunch 函数中添加一个菜单选项。该函数的参数是产品名称加上编译类型,中间以“-”连接,例如:add_lunch_combo full_lt26-userdebug。/build/envsetup.sh 会扫描所有 device 和 vender 二 级目 录下的名称 为"vendorsetup.sh"文件,并根据其中的内容来确定 lunch 函数的 菜单选项。在配置了以上的文件之后,便可以编译出我们新添加的设备的系统镜像了。 我们可以使用命令: source build/envsetup.sh 来查看Build 系统已经引入了刚刚添加的 vendorsetup.sh 文件。 添加新模块 在源码树中,一个模块的所有文件通常都位于同一个文件夹中。为了将当前模块添加到整个 Build 系统中,每个模块都需要一个专门的 Make 文件,该文件的名称为“Android.mk”。Build 系统会扫描名称为“Android.mk”的文件,并根据该文件中内容编译出相应的产物。 注:在 Android Build 系统中,编译是以模块(而不是文件)作为单位的,每个模块都有一个唯一的名称,一个模块的依赖对象只能是另外一个模块,而不能是其他类型的对象。对于已经编译好的二进制库,如果要用来被当作是依赖对象,那么应当将这些已经编译好的库作为单独的模块。对于这些已经编译好的库使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。例如:当编译某个 Java 库需要依赖一些 Jar 包时,并不能直接指定 Jar 包的路径作为依赖,而必须首先将这些 Jar 包定义为一个模块,然后在编译 Java 库的时候通过模块的名称来依赖这些 Jar 包。 那么怎么编写Android.mk 文件呢?Android.mk 文件通常以以下两行代码作为开头: LOCAL_PATH := $(call my-dir) //设置当前模块的编译路径为当前文件夹路径 include $(CLEAR_VARS)//清理编译环境中用到的变量 为了方便模块的编译,Build 系统设置了很多的编译环境变量。要编译一个模块,只要在编译之前根据需要设置这些变量然后执行编译即可。常见的如: LOCAL_SRC_FILES:当前模块包含的所有源代码文件。 LOCAL_MODULE:当前模块的名称,这个名称应当是唯一的,模块间的依赖关系就是通过这个名称来引用的。 LOCAL_C_INCLUDES:C 或 C++ 语言需要的头文件的路径。 LOCAL_STATIC_LIBRARIES:当前模块在静态链接时需要的库的名称。 LOCAL_SHARED_LIBRARIES:当前模块在运行时依赖的动态库的名称。 LOCAL_CFLAGS:提供给 C/C++ 编译器的额外编译参数。 LOCAL_JAVA_LIBRARIES:当前模块依赖的 Java 共享库。 LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的 Java 静态库。 LOCAL_PACKAGE_NAME:当前 APK 应用的名称。 LOCAL_CERTIFICATE:签署当前应用的证书名称。 LOCAL_MODULE_TAGS:当前模块所包含的标签,一个模块可以包含多个标签。标签的值可能是 debug, eng,user,development 或者 optional。其中,optional是默认标签。标签是提供给编译类型使用的,不同的编译类型会安装包含不同标签的模块。 编译类型说明: 名称 说明 eng 默认类型,该编译类型适用于开发阶段。 当选择这种类型时,编译结果将: 安装包含 eng, debug, user,development 标签的模块 安装所有没有标签的非 APK 模块 安装所有产品定义文件中指定的 APK 模块 user 该编译类型适合用于最终发布阶段。 当选择这种类型时,编译结果将: 安装所有带有 user 标签的模块 安装所有没有标签的非 APK 模块 安装所有产品定义文件中指定的 APK 模块,APK 模块的标签将被忽略 userdebug 该编译类型适合用于 debug 阶段。 该类型和 user 一样,除了: 会安装包含 debug 标签的模块 编译出的系统具有 root 访问权限 根据上表各种类型模块的编译方式,要执行编译,只需要引入表 3 中对应的 Make 文件即可。例如,要编译一个 APK 文件,只需要在 Android.mk 文件中,加入“include $(BUILD_PACKAGE)。除此以外,Build 系统中还定义了一些便捷的函数以便在 Android.mk 中使用,包括: $(call my-dir):获取当前文件夹路径。 $(call all-java-files-under, ):获取指定目录下的所有 Java 文件。 $(call all-c-files-under, ):获取指定目录下的所有 C 语言文件。 $(call all-Iaidl-files-under, ) :获取指定目录下的所有 AIDL 文件。 $(call all-makefiles-under, ):获取指定目录下的所有 Make 文件。 $(call intermediates-dir-for, , , , ):获取 Build 输出的目标文件夹路径。 如:编译一个 APK 文件 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 获取所有子目录中的 Java 文件 LOCAL_SRC_FILES := $(call all-subdir-java-files) # 当前模块依赖的静态 Java 库,如果有多个以空格分隔 LOCAL_STATIC_JAVA_LIBRARIES := static-library # 当前模块的名称 LOCAL_PACKAGE_NAME := LocalPackage # 编译 APK 文件 include $(BUILD_PACKAGE) 编译一个 Java 的静态库: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 获取所有子目录中的 Java 文件 LOCAL_SRC_FILES := $(call all-subdir-java-files) # 当前模块依赖的动态 Java 库名称 LOCAL_JAVA_LIBRARIES := android.test.runner # 当前模块的名称 LOCAL_MODULE := sample # 将当前模块编译成一个静态的 Java 库 include $(BUILD_STATIC_JAVA_LIBRARY) 附:Android编译系统详解

优秀的个人博客,低调大师

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、 Browser各个平台,程序画UI和写逻辑的方式都大致相同。因为JS 可以动态加载,从而理论上可以做到write once, run everywhere, 当然要做额外的适配处理。如图: RN需要一个JS的运行环境, 在IOS上直接使用内置的javascriptcore, 在Android 则使用webkit.org官方开源的jsc.so。 此外还集成了其他开源组件,如fresco图片组件,okhttp网络组件等。 RN 会把应用的JS代码(包括依赖的framework)编译成一个js文件(一般命名为index.android.bundle), , RN的整体框架目标就是为了解释运行这个js 脚本文件,如果是js 扩展的API, 则直接通过bridge调用native方法; 如果是UI界面, 则映射到virtual DOM这个虚拟的JS数据结构中,通过bridge 传递到native , 然后根据数据属性设置各个对应的真实native的View。 bridge是一种JS 和 JAVA代码通信的机制, 用bridge函数传入对方module 和 method即可得到异步回调的结果。 对于JS开发者来说, 画UI只需要画到virtual DOM 中,不需要特别关心具体的平台, 还是原来的单线程开发,还是原来HTML 组装UI(JSX),还是原来的样式模型(部分兼容 )。RN的界面处理除了实现View 增删改查的接口之外,还自定义一套样式表达CSSLayout,这套CSSLayout也是跨平台实现。 RN 拥有画UI的跨平台能力,主要是加入Virtual DOM编程模型,该方法一方面可以照顾到JS开发者在html DOM的部分传承, 让JS 开发者可以用类似DOM编程模型就可以开发原生APP , 另一方面则可以让Virtual DOM适配实现到各个平台,实现跨平台的能力,并且为未来增加更多的想象空间, 比如react-cavas, react-openGL。而实际上react-native也是从react-js演变而来。 对于 Android 开发者来说, RN是一个普通的安卓程序加上一堆事件响应, 事件来源主要是JS的命令。主要有二个线程,UI main thread, JS thread。 UI thread创建一个APP的事件循环后,就挂在looper等待事件 , 事件驱动各自的对象执行命令。 JS thread 运行的脚本相当于底层数据采集器, 不断上传数据,转化成UI 事件, 通过bridge转发到UI thread, 从而改变真实的View。 后面再深一层发现, UI main thread 跟 JS thread更像是CS 模型,JS thread更像服务端, UI main thread是客户端, UI main thread 不断询问JS thread并且请求数据,如果数据有变,则更新UI界面。 二、 代码流程 1、JS入口 对于JS开发者来说, 整个RN APP就只有一个JS文件, 而开发者需要编写的就只有如上部分。主要是四个部分: require 所有依赖到的组件, 相当于java中的import 或者 c++ 中的include。 var AwesomeProject = React.createClass 创建APP, 并且在render函数中返回UI界面结构(采用JSX ), 实际经过编译, 都会变成JS 代码, 比如 变成 React.createElement(View,{style:{flex:1}}, var styles = StyleSheet.create({, 创建CSS 样式,实际上会直接当做参数直接反馈到上面的React.createElement AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject); 以上三个更像是参数,这个才是JS 程序的入口。即把当前APP的对象注册到AppRegistry组件中, AppRegistry组件是js module。 接着就等待Native事件驱动渲染JS端定义的APP组件。 2、Native 入口 对于Android 开发者, 普通安卓程序入口是Activity.onCreate()方法 , 主要有三个对象 ReactRootView, Android 标准的FrameLayout对象,另外一个功能是提供react 世界的入口,函数startReactApplication实际调用attachMeasuredRootView触发react世界的初始化。 MyReactPackage, 配置当前APP 需要加载的模块,RN 的JS框架会在初始化阶段就会把native的模块按照配置加载到JS数据结构中(MessageQueue), 从而才能在JS 层即可直接判断native是否支持某个模块。支持三种类型模块配置, native module(实际就是不需要操作View结构的API), view managers(实际是映射到virtual DOM中的View组件), JS module 。 ReactInstanceManager, 构建React世界的运行环境,发送事件到JS世界, 驱动整个React世界运转。 通过builder可以创建不同的React环境, 比如内置js 路径, 开发环境dev的js名字,是否支持调试等。doInBackground会加载指定的JS文件, onPostExecute会调用runApplication接口运行JS APP。 ReactRootView第一次onMeasured计算完成, 然后会利用ReactInstanceManager创建 ReactContext上下文环境。重要的是初始化bridge以及加载js文件, 利用JSBundleLoader方法加载index.android.bundle. 如图 此刻进入JS 世界, 开发者的js 语句连同react js框架层被执行。该步骤最终语句是执行AppRegistry.registerComponent注册一个APP组件,但还没有到开始渲染。 当运行环境准备完毕, 则调用bridge方法运行上步注册的APP组件,触发一连串JS 和 Native相互通信,配合事件驱动, 从而完成native世界的渲染。如图利用bridge方法运行上面注册的JS APP组件的runApplication方法: 3、事件循环 所有的APP在操作系统中, 最终都会使用一个事件循环来运行。 一般来说,JS 开发者只需要开发各个组件对象,监听组件事件, 然后利用framework接口调用render方法渲染组件。 而实际上,JS 也是单线程事件循环,不管是 API调用, virtural DOM同步, 还是系统事件监听, 都是异步事件,采用Observer(观察者)模式监听JAVA层事件, JAVA层会把JS 关心的事件通过bridge直接使用javascriptCore的接口执行固定的脚本, 比如"requrire (test_module).test_methode(test_args)"。此时,UI main thread相当于work thread, 把系统事件或者用户事件往JS层抛,同时,JS 层也不断调用模块API或者UI组件 , 驱动JAVA层完成实际的View渲染。JS开发者只需要监听JS层framework定义的事件即可。如图即JS thread 的消息队列循环: 分析代码可知,消息线程创建于ReactContext环境初始化时, MessageQueueThread.java当中, 该消息队列主要接收系统事件(如 Vsync、timer、doFrame、backkey)、UI事件(如键盘弹起、滚动等)以及 callback事件(JS 的回调函数)。如图即ReactRootView往JS 传递键盘弹出的事件: 而对于Android 开发者, Android 已经为APP创建一个默认的 Main Looper, 不管是Android System 还是JS 事件都是发送到Main thread通过UI渲染出来。如图即是MessageQueueThread.java直接使用主线程Looper。 跟普通APP不同是,此时JS thread相当于work thread, JS会把对应的事件或者数据通过bridge发送到UI thread。 如图即是native Java层收到的JS事件的处理函数: 三、 通信机制 RN框架最主要的就是实现了一套JAVA和 JS通信的方案,该方案可以做到比较简便的互调对方的接口。一般的JS运行环境是直接扩展JS接口,然后JS通过扩展接口发送信息到主线程。但RN的通信的实现机制是单向调用,Native线程定期向JS线程拉取数据, 然后转成JS的调用预期,最后转交给Native对应的调用模块。这样最终同样也可以达到Java和 JS 定义的Module互相调用的目的。 1、JS调用java JS调用java 使用通过扩展模块require('NativeModules')获取native模块,然后直接调用native公开的方法,比如require('NativeModules').UIManager.manageChildren()。 JS 调用require('NativeModules')实际上是获取MessageQueue里面的一个native模块列表的属性, 如: 使用_genModules 加载所有native module到 RemoteModules数组。RemoteModules每项都是一个映射到native module的JS对象。 调用RemoteModules 的方法, 实际是把moduleID、methodId、args放入三个queue保存。 至此, JS端调用完毕, queue中数据要等待Native层通过bridge来取。 native层会在一定条件下触发事件, 通过bridge调用callFunctionReturnFlushedQueue和 invokeCallbackAndReturnFlushedQueue ,得到的返回值就是这三个queue。 bridge会把这三个queue交给parseMethodCalls解析, 然后通过JNI回调函数转发到Java层 m_callback 函数是在bridge初始化的时候设置到c++层, 如: 然后在回调函数中,陆续调用ReactCallback对象的call方法,weakCallback就是java层初始化bridge时传入的NativeModulesReactCallback对象,也就是ReactCallback的子类。 到此,转入Java层. 从native module配置表中,取到对应module和method,并执行。 2、java调用JS 之前ReactInstanceManager 中运行JS APP组件,JAVA 是调用catalystInstance.getJSModule 方法获取JS 对象,然后直接访问对象方法runApplication。实际上getJSModule 返回的是js对象在java层的映射对象。 java层可以调用的JS模块主要在CoreModulesPackage.createJSModules方法配置,有: 如果调用JSModules对象的方法,则会动态代理跳转到(mBridge).callFunction(moduleId, methodId, arguments); 接着调用ReactBridge中声明的JNI 函数,public native void callFunction(int moduleId, int methodId, NativeArray arguments); 通过JS 的require和 apply函数拼接一段JS 代码, 然后用javascriptCore的脚本运行接口执行,并得到返回值。 这样就在JS引擎中运行了一段JS代码并得到返回值,实现了JAVA层到JS层的调用。每次有JAVA对JS的访问, 则在返回值中从JS层的messageQueue.js中抓取之前累积的一堆JS calls。因为JAVA层要把时间同步、 系统帧绘制等事件传递给JS, 因此queue中的JS calls都会在很短的时间内被抓取。 四、 扩展机制 1、 模块扩展(native module)官方文档操作:https://facebook.github.io/react-native/docs/native-modules-android.html#content 2、 组件扩展(UI component)官方文档操作:https://facebook.github.io/react-native/docs/native-components-android.html#content 因为react模块加载主要在ReactPackage类配置,因此扩展可以通过反射、外部依赖注入等机制,可以做到跟H5容器一样实现动态插拔的插件式扩展。比如API扩展, 通过外部传入扩展模块的类名即可反射构造函数创建新的API: @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList(); modules.addAll(Arrays.<NativeModule>asList( new AsyncStorageModule(reactContext), new FrescoModule(reactContext), new NetworkingModule(reactContext), new WebSocketModule(reactContext), new ToastModule(reactContext))); if (mModuleList != null && mModuleList.size() > 0) { for (int i = 0; i < mModuleList.size(); i++) { try { Log.i("MyReactPackage", "add Module:" + mModuleList.get(i)); Class c = Class.forName(mModuleList.get(i)); Class[] parameterTypes = {ReactApplicationContext.class}; java.lang.reflect.Constructor constructor = c.getConstructor(parameterTypes); Object[] parameters = {reactContext}; NativeModule module = (NativeModule) constructor.newInstance(parameters); modules.add(module); }catch (Exception e) { Log.i("MyReactPackage", "add Module Exeception:" + e); e.printStackTrace(); } } } return modules; } 五、 离线加载 代码离线 离线包支持。 目前RN官方支持内置APK打包以及dev server在线更新。而实际上,一般的容器都会实现一套离线包发布平台。大致的实现方案是自定义一个JSBundleLoader,对接到应用管理发布平台。 分离react 框架代码和应用业务代码。目前官方的生产工具是把框架代码和业务代码弄成一个bundle。 但框架代码很大,需要共用, 因此要分离出框架代码单独前置加载。 应用业务代码变成很小一段JS代码单独发布。如果每次都加载框架代码, 启动业务代码会比较慢,一个helloworld都需要4秒左右。初步实践方案是把ReactInstanceManager设置成全局变量共享,在Native APP 启动初始化或者第一次进入RN APP时初始化ReactInstanceManager。这个可能会导致多个RN APP全局变量冲突。 在线更新离线包更新主要依赖应用管理发布平台,大致可以做到跟H5离线包一致。 资源离线 一般说的是图片资源比较多, RN 使用控件显示图片,如: 通过source属性设置图片资源路径, 映射到native层: 因此不管是离线包内资源还是系统资源,只要能转换成Android 统一资源定位URI对象,即可获取到图片。 在线资源 如果是静态资源,则直接URI统一定位。如果是动态资源, 比如要通过网关获取到base64格式的图片,则需要native扩展特别接口。 六、 总结 1、 可能瓶颈 * 因为bridge, JS和 JAVA是异步互通,如果实现复杂多API的逻辑,可能会导致部分效率损耗在多线程通信。JS 异步的编程方式多多少少带来一些不便。 * 因为bridge, 可能某些场景做不到及时响应。比如帧动画的实时控制。 * Android版本刚推出不完善,并且目前RN版本还在不停的更新中, 可能存在暗坑。 * 加入JS引擎, 内存的控制比较麻烦,会比普通native增加不少。 2、 待研究 动态注入的API插件实现方案,能跟h5容器共用实现。 因为RN已经具备很多的灵活, JS也可以做到很多大型控件,所以native UI扩展需要定义JS 和 native边界, 哪些是JS 实现, 哪些是native实现。 动画的实现方式。 H5容器和RN容器融合方案 write once, 完全跨平台。 JS 层支持 Fragment manager 性能比较数据 Demo还在实现当中,等抓完再补充。 环境搭建

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册