创业公司的团队建设小团队为什么要使用Docker

创业公司小团队为什么要使用Docker - 知乎专栏
{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"","permission":"COLUMN_PUBLIC","memberId":,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"","urlToken":"jikeapp","id":31029,"imagePath":"v2-f0fd84a936ce33a6138a.jpg","slug":"jikeapp","applyReason":"0","name":"即刻技术团队","title":"即刻技术团队","url":"/jikeapp","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":1761,"avatar":{"id":"v2-f0fd84a936ce33a6138a","template":"/{id}_{size}.jpg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/v2-f0fd84a936ce33a6138a_l.jpg","articlesCount":6},"state":"accepted","targetPost":{"titleImage":"/v2-f61a2fc4a62acfab13caa6c93ad55ccb_r.png","lastUpdated":,"imagePath":"v2-f61a2fc4a62acfab13caa6c93ad55ccb.png","permission":"ARTICLE_PUBLIC","topics":[,158144],"summary":"以Docker为代表的容器技术已经持续成为话题好几年了,本以为在没有历史包袱的创业公司中,Docker应该会成为生产环境上部署和管理服务的标准配置,然而最近发现一些友商在得知我们在生产上使用Docker和Kubernetes之后,居然表现出了一些惊讶。我想对于这些团…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T22:44:35+08:00","sourceUrl":"","urlToken":,"id":2616996,"withContent":false,"slug":,"bigTitleImage":false,"title":"创业公司小团队为什么要使用Docker","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":31029,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"/v2-f61a2fc4a62acfab13caa6c93ad55ccb_r.png","author":{"bio":"","isFollowing":false,"hash":"0bfc91be35e12696dfa642fd3a48fcf0","uid":56,"isOrg":false,"slug":"themez","isFollowed":false,"description":"","name":"我我","profileUrl":"/people/themez","avatar":{"id":"9fcb9f5c3c635e6f1ed604da66f61198","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":62514,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":600212}],"title":"创业公司小团队为什么要使用Docker","author":"themez","content":"以Docker为代表的容器技术已经持续成为话题好几年了,本以为在没有历史包袱的创业公司中,Docker应该会成为生产环境上部署和管理服务的标准配置,然而最近发现一些友商在得知我们在生产上使用Docker和Kubernetes之后,居然表现出了一些惊讶。我想对于这些团队没有采纳Docker而是继续使用传统运维方案,还是觉得即使多做一些繁琐的运维工作,也希望对系统有更多的掌控度。Docker所依赖的LXC容器技术,早在十多年前就被Google这类大厂使用了,国内的一些大厂也在很早开始投入研发和使用了,对他们来说,使用容器能够充分利用计算资源,节省硬件开销。然而对于小公司来说,把10台服务器压缩到5台服务器并不能帮助公司活下去。容器技术真正开始广泛产生影响是从Docker的诞生开始,Docker刚出来的时候,官网上的slogan:“Build Ship Run”,准确的阐述了Docker的定位,从打包到部署,传统运维的这一套流程,由于语言,环境,平台的不同在各个公司千差万别,几乎每个公司都会开发一套自己的发布系统。有了Docker之后,这些居然都可以标准化了,并且相对于笨重的虚拟机,Docker几乎就是一个进程简单的包装,只有很少的额外开销,启动速度也几乎相当于直接启动应用进程。对于没有专职运维的即刻团队,很自然的从项目开始就使用Docker来做服务的发布工具了:第一阶段:映射宿主机端口 + HAProxy转发由于一个Docker容器只是一个(或一组)进程的封装,一个容器想要向宿主机之外的网络提供服务,就只能绑定宿主机的端口了,端口的管理也是一件大部分情况下都不希望人去操心的麻烦事,为了避免端口冲突,对于需要暴露端口的容器,Docker会随机绑定一个宿主机端口,这个时候我们就需要服务发现机制来帮助不同机器上的服务来进行通信了。我们使用了一个简单的方案,一套开源的工具:和docker-register,它包含两个组件:1. 每台worker node虚拟机上运行一个docker-register容器,用来扫描本地容器,把他们的服务名(用镜像名作为服务名)和端口(包括容器内端口和宿主机端口)注册到etcd上。2. 一个docker-discover容器用来做中心代理,它内部包含了一个HAProxy,每隔几秒钟扫描Etcd上的注册服务,并生成配置文件,刷新HAProxy,生成backend配置的模版如下{% for service in services %}\nlisten {{ service }}\n
bind *:{{services[service].port}}\n
{% for backend in services[service].backends %}\n
server {{ backend.name }} {{ backend.addr }} check inter 2s rise 3 fall 2{% endfor %}\n{% endfor %}\n这个方案给我们提供了一套系统的几个基本功能:应用发布,服务发现,负载均衡,进程守护,其中应用发布是执行脚本去各个worker node上拉取最新镜像,进程守护则是由Docker daemon的restarts=always来提供。这个方案给我们提供了一套系统的几个基本功能:应用发布,服务发现,负载均衡,进程守护,其中应用发布是执行脚本去各个worker node上拉取最新镜像,进程守护则是由Docker daemon的restarts=always来提供。除了提供一致的运行环境使服务的发布和回滚比较可控,这套简单的系统在发布流程上还是像传统运维一样需要远程执行脚本,功能比较简单,随着我们后端系统成长起来,很快就不够用了。第二阶段:RancherDocker本身只是提供了一个运行环境,除了把服务跑起来之外,要让多个服务容器协同起来工作,我们还需要一个容器编排(Orchestration)系统,一般来说我们期望编排系统能帮我们实现几个目的:基本发布自动化功能:编排过程包含分配机器,拉取镜像,启动/停止/更新容器,存活监控,容器数量扩展和收缩声明式定义服务栈:提供一种机制,可以用配置文件来声明服务的网络端口,镜像及版本,在需要的时候通过配置可再现的创建出一整套服务。服务发现:提供DNS和负载均衡,一个容器启动之后,需要其他服务能够访问到它,一个容器终止运行之后,需要保证流量不会再导向它。状态检查:需要持续监控系统是否符合配置中声明的状态,比如一台宿主机挂了,需要把上面运行的容器在其他健康的节点上启动起来,如果一个容器挂了,需要把它重新启动。从设计思路,社区活跃度等因素来看,Kubernetes无疑是编排工具最好的选择,但由于组件较多,学习成本并不低,还有墙的因素,在国内甚至安装都不是件容易的事。这个时候我们发现正式发布了,虽然没有kubernetes那么热门,但它提供了所有我们需要的功能,还有一个简单容易上手的Web UI。在早期我们的机器和服务数量都比较少,又急需一个编排工具好把有限精力都投入到开发上,所以迅速的把服务都迁移到Rancher上了。准确的说,Rancher是一套容器管理打包方案,支持三种编排引擎:Kubernetes,Swarm,还有Rancher自己开发的Cattle(最近好像换成了Mesos)。从功能的完整性和易用性来看,Rancher甚至可以算得上一个商业软件了,部署极其简单,这也是我们选择它作为入门级容器管理平台的原因。Rancher组件图,中小企业常用的软件功能都能找到:后来围绕Rancher,也使用了一些Catalog里提供的服务栈,我们逐步搭建起来了一套完整的容器运维系统,包含了日志收集,性能监控,配合AWS的后来围绕Rancher,也使用了一些Catalog里提供的服务栈,我们逐步搭建起来了一套完整的容器运维系统,包含了日志收集,性能监控,配合AWS的,应用扩展也是很方便的事情。第三阶段:Kubernetes虽然Rancher非常的易用,但随着我们后端机器和项目数量的增加,它的一些问题也暴露出来了,UI卡顿,发布速度越来越慢,1.3之后甚至经常出现服务的预期状态(容器数量,版本)无法被保证,卡在发布中或者完成中状态,真正让我们下定决心要迁移的是一次重大故障,疑似网络雪崩引起,集群所有机器都在重复断开连接,本来按一般编排系统的设计,worker node上的容器之间通过overlay网络通信,node和Rancher server的连接即使断开也不会影响已经启动的容器运行。但在1.3之后的Rancher中,不知是有意设计还是bug,worker node重新连接上Rancher server之后,节点上所有的容器会被重新schedule,这就导致集群中所有容器都不断的被销毁又重新创建。在Rancher上碰到大大小小的问题,我们发现大部分都很难找到社区提供的解决方案,我们很可能是不幸比较早踩坑的人,虽然Rancher是开源的,但技术上的文档相比使用文档明显欠缺很多,想通过了解他的实现来排查问题比较困难,也很少有Rancher公司之外的contributor,这点是和Kubernetes很明显不同的。和Rancher提供了一整套解决方案不一样,Kubernetes提供的是一个框架,对于Rancher,即使不熟悉其中各个组件,也可以直接用预设的配置安装,拿来当一个发布工具使用,Kubernetes则要求使用者对他的组件有一定程度了解,社区提供了很多帮助部署的installer,但使用前都需要不少配置工作。DevOps同事画的Kubernetes架构图:在Rancher上积累了一些容器编排的经验之后,我们对使用Kubernetes做编排也有了一些信心,于是开始迁移到性能更好,社区更活跃的kubernetes上。我们将线上服务一点点从Rancher上把流量切换到Kubernetes集群,起初发现流量稍微增涨便丢包严重,定位到问题是DNS解析缓慢,观察kernel log发现宿主机conntrack count到达上限,出现丢包。这里解释一下iptable里的conntrack:我们在使用iptable做面向连接的防火墙时,要允许与一个ip地址建立特定连接,除了让该连接下的包能进来,还要让回复的包能出去,由于ip是个无状态协议,这个时候就需要一个session表来记录有状态的连接,conntrack就是用来记录这些session的。在微服务下,服务间调用频繁,会产生大量DNS查询,linux默认的conntrack_max很容易突破限制,所以在部署有DNS service的机器上设置一个较高的conntrack_max值基本上是必须的。容器生态前面介绍了我们使用容器来做服务编排,除了这些,相对于传统架构,使用容器我们还获得了一整套日志和监控的解决方案,比如日志采集,部署在容器中的日志可以直接打印到标准输出中,Docker本身支持多种logging driver,可以将日志直接发到Graylog,AWS CloudWatch等日志平台,也可以让Fluentd等日志采集工具来采集Docker默认输出的json-file,相比之下传统架构可能需要在应用中使用专门配置的logger输出到收集系统,或者配置专门的采集器去采集不同的日志文件。小公司对基础设施的投入不足,一般没有专人去熟悉Kubernetes这种大型开源项目,但即刻算是一个对技术持开放态度,愿意让工程师去踩坑、尝试的公司。相对于采用传统架构,因为容器编排系统都是以提高本身的复杂性来覆盖繁琐的配置和脚本编写工作,本身复杂了就会导致出现问题的时候会比较难排查。但做过运维工作的应该了解,一般自己编写的脚本很难具有通用性,很可能环境稍有改变就不能使用了,运维是比较枯燥的工作,有了编排系统帮助处理这些,我们可以把更多的精力放到更有意义的工作上。题图By:蒙天放参考:","updated":"T14:44:35.000Z","canComment":false,"commentPermission":"anyone","commentCount":14,"collapsedCount":1,"likeCount":106,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-f61a2fc4a62acfab13caa6c93ad55ccb_r.png","links":{"comments":"/api/posts//comments"},"reviewers":["ji-ke-ji-shu-tuan-dui-36"],"topics":[{"url":"/topic/","id":"","name":"容器"},{"url":"/topic/","id":"","name":"Docker"},{"url":"/topic/","id":"","name":"Kubernetes"}],"adminClosedComment":false,"titleImageSize":{"width":1600,"height":900},"href":"/api/posts/","excerptTitle":"","column":{"slug":"jikeapp","name":"即刻技术团队"},"tipjarState":"inactivated","annotationAction":[],"sourceUrl":"","pageCommentsCount":14,"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T22:44:35+08:00","url":"/p/","lastestLikers":[{"bio":"软件工程师","isFollowing":false,"hash":"ea3c4cc039e8","uid":88,"isOrg":false,"slug":"thepiano","isFollowed":false,"description":"","name":"piano","profileUrl":"/people/thepiano","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"9a05ef58c258d01dbc6be4","uid":917400,"isOrg":false,"slug":"eric-76-89-11","isFollowed":false,"description":"","name":"eric","profileUrl":"/people/eric-76-89-11","avatar":{"id":"v2-372bff9c9a7a6affc53d8bd2cebb60ef","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"梦醒时分 往事弃我而去不可留","isFollowing":false,"hash":"2e31ec748fead6f9aadd4","uid":006000,"isOrg":false,"slug":"sms-parry","isFollowed":false,"description":"离开了一所大学一样的高中; \n古文略知一二的理工男; ","name":"SMS Parry","profileUrl":"/people/sms-parry","avatar":{"id":"v2-3fed0193cfbf15e6dc1c7","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"","isFollowing":false,"hash":"72e775fc","uid":153500,"isOrg":false,"slug":"wo-huan-yao-24","isFollowed":false,"description":"","name":"十磅","profileUrl":"/people/wo-huan-yao-24","avatar":{"id":"v2-8ea9deaeea8a1f3c1a56fbcd","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"","isFollowing":false,"hash":"dc7d4bc7f481","uid":84,"isOrg":false,"slug":"lenville","isFollowed":false,"description":"","name":"刘振涛","profileUrl":"/people/lenville","avatar":{"id":"v2-d9c296b64df3f1","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"以Docker为代表的容器技术已经持续成为话题好几年了,本以为在没有历史包袱的创业公司中,Docker应该会成为生产环境上部署和管理服务的标准配置,然而最近发现一些友商在得知我们在生产上使用Docker和Kubernetes之后,居然表现出了一些惊讶。我想对于这些团…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/50/v2-b96b80b37d1d20faf5a36_xl.jpg","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"Android 开发"},{"url":"/topic/","id":"","name":"Android 消息推送"},{"url":"/topic/","id":"","name":"推送 (Push)"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"就想看点好东西","isFollowing":false,"hash":"efdfeb722d6b","uid":358000,"isOrg":true,"slug":"ji-ke-ji-shu-tuan-dui-36","isFollowed":false,"description":"信息过载的今天,获取有效资讯正变得越来越累。不妨换一种轻盈、高效、省心的方式,与你感兴趣的信息相遇。","name":"即刻技术团队","profileUrl":"/org/ji-ke-ji-shu-tuan-dui-36","avatar":{"id":"v2-65f1b5e0e6ab110cb5e7a288c82eae11","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"jikeapp","name":"即刻技术团队"},"content":"今天来讲讲推送这件小事,事虽小,要做好却不容易。推送难,难于上青天。我们在讨论 Android 手机上的推送时,大多数情况是在说集成第三方推送,因为即使是像微信这样的大厂,也需要厂商加到启动白名单里才能保持在线。iOS 手机使用 APNs(Apple Push Notification service)进行推送,而 Android 手机,也是有 GCM(Google Cloud Messaging)作为 Google 官方的推送支持的,但是在国内需要翻墙才能使用,并且需要手机安装了 Google Service ,条件比较苛刻。这样一来,国产手机的推送就成了个问题,也带了机会。微信由于有国际版,将 GCM 作为辅助公共通道,但仅用于激活微信自己的 Push 通道,并没有通过 GCM 来传递数据,这点也是为了复用心跳的优化策略和数据处理逻辑。GCM 最新版本叫 FCM(Firebase Cloud Messaging)推送的实现方式总结一下几种推送实现方式,其中有些我们只要了解即可,因为属于历史解决方案,现在已经被废弃掉了。1. 轮询客户端定期询问服务器有没有新的消息,这种方式最大的缺点就是性能和实时性的矛盾,轮询时间过长和过短都不好。2. 短信这种方式还没出生就不被允许了,首先运营商不会配合,其次拦截手机短信本身就是一个高危权限,大多数用户不会买单。3. 长连目前最通用的方案,客户端与服务端建立 TCP 长连,定时发送心跳包保活,有新消息时服务端通过该长连通道进行推送。这里再简单说明一下长连和心跳包。长连接就是建立连接之后,双方互相发送数据,,发完了也不主动断开连接。但是在某些情况下长连会断开,问题就在断开这件事上,而且这件事必须是由客户端知道,因为客户端是可以重连服务器的,服务器却没法再联系上客户端。这样才确定了心跳包必须由客户端发给服务器。所以心跳包的作用就是告诉服务端客户端还活着,如果服务端挂了,客户端能知道,所以保活说的是保持两边都活着。上面说的某些情况中, NAT 超时算是一个典型的例子。因为 IPv4 的 IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP,手机要连接 Internet ,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断。所以长连接心跳间隔必须要小于 NAT 超时时间(aging-time),如果超过老化时间不做心跳, TCP 长连接链路就会中断,服务器就无法发消息给客户端,只能等到客户端下次心跳失败后,重建连接才能取到消息。有了长连,即使在休眠模式下,有推送消息过来,也能唤醒 Android 系统,这是由系统机制决定的,我们这里只要知道结论就好。想要了解更多的可以去看文末的参考资料。推送的指标在接入推送服务时有几个核心指标需要考量:1. 在线率在线率 = 在线用户数 / 总用户数推送服务后台保持在线的方法Push 进程常驻后台,需要用户手动让应用常驻共享连接通道的方式,比如极光或者个推,通过共享连接,当应用有推送到达时,唤起该应用显然,后者在体验上更加接近 GCM 。2. 到达率到达率 = 实际到达数 / 目标用户数在线数 -& 目标用户数 -& 成功下发数,如果后端的计算或调用出现问题这两个数据就会不准确在线数 -& 实际到达数 -& 展示数,数据收到后,是否展示要看用户有没有打开该应用的允许通知的开关,可以通过如下方法判断notificationManagerCompat.areNotificationsEnabled();\n3. 耗电量耗电量受到很多方面的影响,如果收到推送比较多,打开应用比较频繁,耗电量自然也会上去不少,但这个用户是可以接受的。以下几个耗电量的因素用户是比较反感的:应用间互相唤醒产生的耗电,因为这个耗电是别的应用的,用户本来没有意图要去打开错误重试造成的耗电,重试策略的优化包括重试时间的累加和重置推送选型上面提到,我们这里聊的推送,是第三方推送,那有开发者要问了,为什么不自己做推送?自己做不是不行,但需要考虑几个问题:开发成本问题, ROI 是否可以接受如果不加入白名单,那么一旦应用被彻底杀掉,是没人给你吟唱复活魔法(互相唤起)的第三方推送主要有厂商推送和非厂商推送华为、小米、魅族推送个推、极光、友盟阿里、腾讯、百度其中,选型的几个因素厂商推送通知是否系统通道(所有厂商支持)厂商推送透传是否系统通道(仅魅族)非厂商推送的市场占有率(影响共享连接互相唤起的概率)如何在 Android 手机中查看某个应用使用的是什么推送?adb shell dumpsys activity services | grep igexin\n可以看到,这几个应用都在使用个推大众点评、宝宝树、饿了么、滴滴、简书、领英、 WPS Office 、格瓦拉。推送接入如何解耦由于各个推送 sdk 接口定义不同,为了减少耦合,我们采用多 module 的形式进行接入。appjikepushPushServiceImplpush_厂商1PushPlatformImplpush_厂商2PushPlatformImplpush_非厂商PushPlatformImpljikecorePushServicePushPlatform在 app 层通过注册的方式添加需要的 PushPlatform ,之后通过 PushService 接口来进行调用,具体的启动和切换的实现放在 PushServiceImpl 。其实大部分推送平台的接口标准都差不多,无非是命名上有差异,所以我们用 PushPlatform 这个接口来屏蔽这种差异。对于推送启动和切换的操作,放在 PushService 中。当启动某一个推送服务的时候,就关掉其他的推送服务,后台始终只保持一个推送服务。registeration id通常我们启动一个推送服务后,会收到一个 registeration id(以下简称 reg id),用这个 reg id 和我们自己的服务器进行绑定。这里有个需要注意的地方,绑定的操作我们需要调用2次。after PushService.start() 因为已经接收到 reg id 后有些推送不再触发 receive reg idafter receive reg id 首次启动推送是异步收到 reg id 的收到 reg id 之后,推送 sdk 会自己保存下来以便下次使用,但有时候我们使用 sdk 的方法获取的时候无法获取到,究其原因是 reg id 的保存不是我们自己做的,因此, reg id 这么重要的东西我们自己也要存。推送需要的系统权限推送 sdk 需要获取手机的 imei 号作为合成 reg id 的必要元素,需要以下两个权限Manifest.permission.READ_PHONE_STATE Manifest.permission.WRITE_EXTERNAL_STORAGE启动推送服务的时候需要判断权限。推送的切换策略建议选择手机 rom 而非手机型号作为切换条件,这样可以解决部分用户刷机的问题,比如 Nexus 手机刷了个 MIUI 的情况。获取到 rom 信息后,有3种推送切换策略策略一客户端根据 rom 信息自动选择使用哪个推送优点:无后端工作量,不需要切换缺点:一旦某个推送挂了一天,无法临时切换到其他推送策略二客户端上报 rom 信息,后端选择使用哪个推送优点:灵活切换推送缺点:切换推送重新绑定 reg id 有个时间差策略三使用推送 sdk 自己的集成方案,当 sdk 自己的推送服务离线时,切换到厂商推送优点:最大程度保证推送的稳定性缺点:集成方案本身的不稳定性影响了推送的稳定性推送类型推送类型分为通知和透传通知厂商到达率有优势开发成本较高,需要适配不同厂商的接口标准透传可以自己解析、展示、跳转,灵活性高开发成本较低,适配一次就够(通常通过 json )一些厂商的透传到达率没有厂商优势集成方式一般我们集成推送 sdk 有两种集成方式maven 集成手动集成从维护的角度来说, maven 集成的维护成本要小于手动集成,但是我这里还是推荐手动集成,主要有以下几个原因。maven 集成的方式通常 sdk 会要求使用 manifest placeholder 的方式进行 appid appkey appsecret 的注入,但是这种方式需要在 app 层的 build.gradle 去注入,比较耦合。maven 集成还有一个缺点,准确来说这是 sdk 开发商的问题,总会打包一些我们不需要的资源文件,其实我只需要一个 jar 包而已,并不是整个 aar 啊。手动集成第一次写 manifest 比较麻烦,但是后面只要替换 jar 和 so 文件就好了,因为 manifest 一般不经常变化,除非有重大版本更新。推送的展示接下来就要说说通知 Notification 了,这个东西经过厂商的各种定制,适配起来也有不少麻烦。厂商的推送样式差异部分厂商不支持 NotificationCompat.BigTextStyle (这都能不支持)不支持 NotificationCompat.MediaStyle (这个还可以理解)不支持 Action Button不支持 Ticker自定义样式高度限制原生系统 Android 版本的推送样式差异4.x large icon 需要是方的5.x 6.x 由 setColor 设置底色和 small icon 配合形成 large icon7.x setColor 影响标题颜色通知的跳转透传:采用 Url 方式进行跳转通知:采用 Intent 参数的方式进行跳转推送这件小事就说到这里了。展望未来最近,由泰尔实验室牵头的安卓统一推送研讨会正在进行中,即刻上有一个“”的提醒,我们一起关注安卓推送生态的变化吧。用一句广告语描述厂商和 app 的相爱相杀:你好,我也好。参考资料","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T20:43:13+08:00","url":"/p/","title":"安卓推送这件小事","summary":"今天来讲讲推送这件小事,事虽小,要做好却不容易。推送难,难于上青天。我们在讨论 Android 手机上的推送时,大多数情况是在说集成第三方推送,因为即使是像微信这样的大厂,也需要厂商加到启动白名单里才能保持在线。iOS 手机使用 APNs(Apple Push Notif…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":33,"likesCount":119},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/50/v2-cafb0cfcf537d0c4a489e4_xl.jpg","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"iOS 开发"},{"url":"/topic/","id":"","name":"布局"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"就想看点好东西","isFollowing":false,"hash":"efdfeb722d6b","uid":358000,"isOrg":true,"slug":"ji-ke-ji-shu-tuan-dui-36","isFollowed":false,"description":"信息过载的今天,获取有效资讯正变得越来越累。不妨换一种轻盈、高效、省心的方式,与你感兴趣的信息相遇。","name":"即刻技术团队","profileUrl":"/org/ji-ke-ji-shu-tuan-dui-36","avatar":{"id":"v2-65f1b5e0e6ab110cb5e7a288c82eae11","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"content":"在上一篇介绍中我们曾经讨论过Autolayout的性能问题。然而在iOS中,除了Autolayout,能选择的只有autoresizingMask,或者纯手动布局。在写了无数view.frame = CGRect(…)之后,我们才发现,一个在HTML中非常简单的流式布局,到iOS9才有相应的UIStackView予以支持(如果用UICollectionView则需要将view包装成CollectionViewCell)。尽管你可以使用将系统要求降低到iOS6,对于复杂的布局仍然需要多层View嵌套来实现——其实对于实现stack布局的时候,并不需要一个真正的UIView实例来承载所有的元素,而只是需要它的布局功能而已(就好比UICollectionViewFlowLayout,只负责控制若干个元素的布局规则而不真正负责布局)。做过Web开发的朋友已经习惯了高效的css布局:声明式、易于调试,boxModel结构化清晰等等优点,特别是使用自动化工具之后,只需要保存文件就可以立即在浏览器中看到更新后的效果;而这一切在iOS中就变得遥不可及:命令式赋值、编译后(Objc速度尚可,而swift编译速度较慢)才能看到结果、Autolayout闭源、视图调试复杂、xcode经常crash等等。如果要实现一个高性能的tableView/collectionView,手写布局几乎是唯一选择。Layout的本质在决定使用哪种布局方式之前,先看看设计师是怎么思考布局的:每个元素的大小(size)兄弟元素之间的位置关系:对齐(alignment)和间距(spacing);父子元素之间的容纳关系:子元素的边距(insets)设计师确定的,是一个布局规则。设计稿到了工程师这边以后,我们绝大多数时间都在做以下两件事:确定UIView自身的大小(覆盖sizeThatFits方法),与parent view无关根据规则,将subview放在父parent内的指定位置(对应layoutSubviews),由parent view决定这样自底向上层层递归,最终完成整个屏幕内所有子元素的布局。工程师完成的,是一个基于布局规则的具体实现,关注的对象是单个view或者是两个相邻view之间的关系。也就是说,手动布局或者通过约束(Autolayout)其实是在『实现』布局规则,而不是『声明』布局规则。抽象层级下降了,随之而来的是多到令人头疼的frame、size、origin计算,可读性、可维护性都非常差。那能不能提高抽象层级,用一个数据结构来表示布局规则,从而摆脱繁重又容易出错的数值计算,让布局系统根据规则自动地、异步地计算呢?AsyncDisplayKit的三种布局方式1. 手动布局这是最基础的一种布局方式。在ASDK中的手动布局与UIKit的手动布局类似,只是方法名从sizeThatFits变成了calculatedSizeThatFits,layoutSubviews方法变成了layout方法。略微不同的是ASDK会将布局结果预先在后台线程进行计算,并缓存下来以提升性能,这点对于tableView滚动流畅性有非常大的帮助。然而,跟普通手动布局类似,最大的缺点是可读性和可维护性差。由于自身的大小常常和子元素的布局相关联,这两个方法中的代码容易重复;同时由于布局针对自身subView,很难将其代码与其他View进行复用。2. Unified layout这也是在ASDK中新出现的概念。先介绍一下ASLayout:一个ASLayout对象包含以下元素:它所代表的布局元素元素的尺寸元素的位置它所包含的sublayouts可以看出,当一个node具备了确定的ASLayout对象时,它自身的布局也就随之确定了。为了生成ASLayout,ASDisplayNode为subclass提供了如下覆盖点:- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize\n只要Node能够计算出自己的ASLayout,父元素就可以完成对其的布局。这种方法将sizeThatFits和layoutSubviews结合在一起,一定程度上避免了相似代码的尴尬,但是计算上仍然是手动布局,不够简便。3. Automatic Layout(不是系统的Autolayout)有了ASLayout这一层抽象,如何通过声明规则而不是通过计算得到它,是问题的核心。Facebook将React的概念延伸到native,同时也把声明式的语法带到了iOS,产生了框架。早在ASDK1.0的年代大家纷纷表示希望能将其与ComponentKit相融合,幸运的是,在2.0版本中实现了。我选用了作者Scott Goodson在NSSpain的作为插图,方便大家了解新的布局系统。Automatic Layout是ASDK最推荐也是最为高效的布局方式。它引入了ASLayoutSpec的概念,来描述抽象的布局规则(并不需要创建对应node或者view来装载子元素)。一旦确定布局规则,它就能依照规则对其负责的子元素进行布局计算,得到每个布局元素的ASLayout对象。我们先来看一下它们之间的关系:可以看到ASLayoutSpec和ASDisplayNode都实现了ASLayoutable接口,因此他们都具备生成ASLayout的能力,这样就能唯一确定自身的大小。对于以上提到的Unified布局,当我们实现了calculateLayoutThatFits,ASDK会在布局过程中调用measureWithSizeRange(如果没有缓存过再调用calculateLayoutThatFits)来算出ASLayout。如果ASDisplayNode选择实现layoutSpecThatFits,由更为抽象的ASLayoutSpec来负责指定布局规则,由于ASLayoutSpec也实现了ASLayoutable接口,同样也可以通过调用measureWithSizeRange来获得ASLayout。由于ASLayoutSpec只负责指定布局规则,而不关心其布局的ASLayoutable具体是Node还是其他ASLayoutSpec,我们可以轻松地将ASLayoutSpec生成逻辑独立出来,达到复用的目的。挑大梁的ASStackLayoutSpecASLayoutSpec主要有以下几种:通过它们的命名就可以了解其大致作用。其中用途最广泛的无疑是ASStackLayoutSpec,与UIStackView有异曲同工之妙。ASStackLayoutSpec大量借鉴了CSS的flexbox概念,写过Web代码的同学应该能一眼认出许多熟悉的属性:justifyContent/flexDirection/alignSelf/alignItems/flexBasis等等。不熟悉的朋友也可以通过一个有趣的游戏来学习flexbox:示例(图中的Huy Nguyen也是ASDK布局的主要贡献者之一)图中左边是一个imageNode,右边是由两个textNode组成的一个verticalstack,再和imageNode组合成一个横向的stack,最后加上边缘inset完成整个布局。事实上,与css类似,绝大多数的布局都可以通过stack和inset的组合来完成;而和UIStackView不同的是,layout spec只是一个存在于内存之中的数据结构,并不需要额外创建view容器来承载子元素,大大节约了复杂布局带来的开销;同时因为它的轻量级和独立性,因此能够将布局规则放到后台线程独立计算,并缓存在node之中,对于大量tableView/collectionView的CellNode快速布局有相当大的帮助。Huy Nguyen也为ASLayoutSpec写了一个寓教于乐的,只是将上面提到的的针对css的小游戏版本改成了Objc,方便大家快速学习。在最近的版本中,ASDK也同时支持
(一个跨平台的flexbox引擎,使用C语言实现)来计算基于flexbox的布局,与ASDK本身实现的flexbox概念类似。即刻的实践即刻在多个消息页面使用了ASDK新的布局系统,例如:从我们的实践经验来看有以下优缺点:优点实现一个layout的视角从专注view之间的距离和约束,转变成划分和制定不同view子区域的布局规则,抽象层级变高,可读性、可维护性大大增强。由于ASDisplayNode的显示是异步的,因此无论布局是否依赖于显示结果,都可以在主线程以外进行,并且有缓存,性能有很大提升。借用CSS成熟的flexbox布局模型,有大量现成资料和案例来学习,避免了新造轮子的尴尬。由于ASDK是开源的,调试难度大大降低。布局规则可以脱离实际布局对象进行独立声明,容易复用。缺点由于与iOS原生布局方式完全不同,学习适应需要一定时间(如果将来flexbox渐渐成为前端标准,花一些时间了解也是完全值得的。)对于ASDK依赖非常重。如果你想对已有的view改造成用flexbox进行布局,需要重新使用ASDisplayNode来实现该view。如果不方便重新写,只能选用类似Yoga的独立框架来实现功能。虽然是声明式布局,然而相对css而言仍较为繁琐。去年参加WWDC时我曾经到Pinterest与Scott有过一些当面交流,也尝试说服他们做一些DSL来简化布局声明。他们的回答是,由于ASDK的复杂性和基础性(类似UIKit),他们仍然将大部分时间放在优化异步渲染和布局性能,并没有太多精力在layout的语法上做出进一步突破。可能当布局系统生长成为独立框架以后分开来做比较合适。尽管即刻没有在整个项目都采用新的的布局方式,然而ASDK带来的启发是深远的:声明式、异步布局、flexbox、缓存等等。我们也看到有一些其他布局库做了许多声明式的尝试,如, 等等,从而加快我们平时的开发和维护效率。PS: 即刻正在招聘,如果你是一个追求极致又富有探索精神的iOS工程师,同时也热爱我们的产品,那么欢迎加入我们,一起参加WWDC(公司cover全部费用),持续打造更棒的即刻!联系我们:","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T15:01:42+08:00","url":"/p/","title":"AsyncDisplayKit介绍(二)布局系统","summary":"在上一篇介绍中我们曾经讨论过Autolayout的性能问题。然而在iOS中,除了Autolayout,能选择的只有autoresizingMask,或者纯手动布局。在写…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":14,"likesCount":17}},"annotationDetail":null,"commentsCount":14,"likesCount":106,"FULLINFO":true}},"User":{"themez":{"isFollowed":false,"name":"我我","headline":"","avatarUrl":"/9fcb9f5c3c635e6f1ed604da66f61198_s.jpg","isFollowing":false,"type":"people","slug":"themez","bio":"","hash":"0bfc91be35e12696dfa642fd3a48fcf0","uid":56,"isOrg":false,"description":"","profileUrl":"/people/themez","avatar":{"id":"9fcb9f5c3c635e6f1ed604da66f61198","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}},"ji-ke-ji-shu-tuan-dui-36":{"bio":"就想看点好东西","isFollowing":false,"hash":"efdfeb722d6b","uid":358000,"isOrg":true,"slug":"ji-ke-ji-shu-tuan-dui-36","isFollowed":false,"description":"信息过载的今天,获取有效资讯正变得越来越累。不妨换一种轻盈、高效、省心的方式,与你感兴趣的信息相遇。","name":"即刻技术团队","profileUrl":"/org/ji-ke-ji-shu-tuan-dui-36","avatar":{"id":"v2-65f1b5e0e6ab110cb5e7a288c82eae11","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}},"Comment":{},"favlists":{}},"me":{},"global":{"experimentFeatures":{"ge3":"ge3_9","ge2":"ge2_1","appStoreRateDialog":"close","nwebStickySidebar":"sticky","qrcodeLogin":"qrcode","favAct":"default","default":"None","nwebAnswerRecommendLive":"newVersion","newMore":"new","iOSNewestVersion":"4.2.0","newMobileColumnAppheader":"new_header","sendZaMonitor":"true","homeUi2":"default","answerRelatedReadings":"qa_recommend_by_algo_related_with_article","wechatShareModal":"wechat_share_modal_show","mobileQaPageProxyHeifetz":"m_qa_page_nweb","liveReviewBuyBar":"live_review_buy_bar_2","qaStickySidebar":"sticky_sidebar","androidProfilePanel":"panel_b","liveStore":"ls_a2_b2_c1_f2","zcmLighting":"zcm"}},"columns":{"next":{},"jikeapp":{"following":false,"canManage":false,"href":"/api/columns/jikeapp","name":"即刻技术团队","creator":{"slug":"ji-ke-ji-shu-tuan-dui-36"},"url":"/jikeapp","slug":"jikeapp","avatar":{"id":"v2-f0fd84a936ce33a6138a","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"edition":{},"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{},"message":{"newCount":0},"pushNotification":{"newCount":0}}

我要回帖

更多关于 创业公司团队建设 的文章

 

随机推荐