Gartner数据劲爆:阿里全球第三,华为中国第二!

看了一份数据,非常振奋人心,给大家分享一下。

国外著名信息分析公司 Gartner,4月21号发布了一份数据,瞬间引发了朋友圈是刷屏。

这份数据是讲什么的呢?云计算!

可能由于疫情,很多公司上云的热情变得很迫切,2020年,全球云计算市场保持高速增长,市场规模达642.86亿美元,同比增长40.7%。

同时市场进一步向头部厂商集中,全球云计算3A格局稳固,亚马逊、微软和阿里云排名全球前三。

%title插图%num

中国云计算的市场增长了62%,成为世界领头羊,在这样的背景下,中国云计算公司发展迅猛。

*为突出的就是阿里巴巴,从2016年占全球3.7%的份额开始,到现在已经占据全球云计算9.5%的份额。

在IaaS之上,Gartner预计,到2024年全球PaaS市场规模将增长至当前的一倍以上。评估显示,阿里云位于全球数据库领导者象限。

%title插图%num

世界排名前六的云计算大厂,有三家是美国科技公司、三家是中国科技公司

依次排名为:亚马逊(40.8%)、微软(19.7%)、阿里巴巴(9.5%)、谷歌(6.1%)、 华为(4.2%)、腾讯(未知)。

(这份数据真的蛮难找的,国内都只说前三,查了外网才知道后面3家是谁)

如果仅仅只看中国市场的话,基本上和全球的格局大体一致,四大巨头阿里、华为、腾讯和百度占据了80%以上的份额

%title插图%num

这里面比较意外的是华为,2018年的时候华为云还在 Others 里面,具体名次还排不上号,到了今年已经是中国第二,全球第五了。

并且和其它家不一样的是,华为云几乎是全包型,服务器芯片自己造,服务器也自己造了,云服务也是华为提供,基本上都干了!

总感觉华为干啥都有一股拼劲。

只要投入资源去整很快就可以做到头部,就像我上次写的文章《华为,搞了一个大事》,不声不响的就把自动驾驶搞定了。

2019年有幸被华为云邀请,去深圳和东莞的研发园区参观,在酒店居住或者园区参观的时候,看了很多30-40岁的中年人。

他们脸上没有看到程序员35+的焦虑感。

走路的时候带着急冲冲的步伐,有的还一边走路一边讨论科研问题,早晨居住的酒店门口,还会给没来得及吃早餐的工程师送一份早餐带走。

看着大量高才生来来往往的,带我们参观的人开玩笑说,这块区域是深圳人才密度*高的地方,随便一挥手就是985、211。

那一刻我就在想,如果华为科技在中国不成功,试问一下哪家公司还能担当此重任?

不过在云计算这片土地上,我感觉阿里还是更强一些。

华为云很难超过阿里云,华为更多是一种偏B端的打法,在面对消费者C端这方面感觉还有很多课要补。

%title插图%num

阿里巴巴在中国的云计算之父王坚带领下,投入更早布局更完整,有了一个很好的开端,再加上阿里这些年在云计算生态的深入布局。

在这个领域内,还是更看好阿里一些。

搞云计算是一个非常烧钱的生意,据说企业如果没有100亿美金就不要玩了,所以现在的头部都是大厂。

而再过5年之后,主流市场认为只会有三个巨头:阿里云、腾讯云与华为云!

希望这3家不断变强,攻占世界吧!

从 0 到 1,高德 Serverless 平台建设及实践

导读:高德从 FY21 财年开始启动 Serverless 建设,至今一年了,高德 Serverless 业务的峰值超过十万 qps 量级,平台从 0 到 1,qps 从零到十万,成为阿里集团内 Serverless 应用落地规模*大的 BU,这中间的过程是怎么样的?遇到过哪些问题?高德为什么要搞 Serverless/Faas?是如何做 Serverless/Faas 的?技术方案是什么样的?目前进展怎么样?后续又有哪些计划?本文将和大家做一个简单的分享。

%title插图%num

Why-高德为什么要搞 Serverless

高德为什么要搞 Serverless?背景原因是高德 FY21 财年启动了一个客户端上云项目。客户端上云项目的主要目的是为了提升客户端的开发迭代效率。

以前客户端业务逻辑都在端上,产品需求的变更需要走客户端发版才能发布,而客户端发版需要走各种测试流程、灰度流程,解决客户端崩溃等问题,目前的节奏是一个月一个版本。

客户端上云之后,某些易变的业务逻辑放到云上来。新的产品需求在云端来开发,不用走月度的版本发布,加快了需求的开发迭代效率,离产研同频的理想目标又近了一步(为什么要说“又”,是因为高德之前也做了一些优化往产研同频的方向努力,但是我们希望云端一体化开发可以是其中*有效的一个技术助力)。

%title插图%num

1. 目标:客户端开发模式–端云一体

虽然开发模式从以前的端开发转变为现在的云+端开发,开发同学应该还是原来负责相应业务的同学,但是大家知道,服务端开发和客户端开发显然是有差异的,客户端开发是面向单机模式的开发,服务端开发通常是集群模式,需要考虑分布式系统的协调、负载均衡、故障转移降级等各种复杂问题。如果使用传统的服务端模式来开发,这个过渡风险就会比较大。

Faas 很好地解决了这一问题。我们结合高德客户端现有的 xbus 框架(一套客户端上的本地服务注册、调用的框架),扩展了 xbus-cloud 组件,使得云上的开发就像端上开发一样,目标是一套代码、两地运行,一套业务代码既能在客户端上运行,也能在服务端上运行。

高德客户端主要有三个端:IOS、android、车机(类 Linux 操作系统)。主要有两种语言:C++ 和 Node.js。传统地图功能:如地图显示、导航路径显示、导航播报等等,由于需要跨三个端,采用 C++ 语言来开发。地图导航基础之上的一些地图应用功能,如行前/行后卡片、推荐目的地等,主要用 Node.js 来开发。

FY20 财年淘系前端团队开发了 Node.js Faas runtime。高德客户端上云项目,Node.js 的部分就采用了现有的淘系的 Node.js runtime,来接入集团的 Faas 平台,完成 Node.js 这部分的一些业务上云。2020 年十一期间很好地支撑了高德的十一出行节业务。

C++ Faas 没有现有的解决方案,因此我们决定在集团的基础设施之上做加法,新建 C++ Faas 基础平台,来助力高德客户端上云。

  • 端云一体的*佳实践关键:客户端和 Faas 之间的接口抽象

原本客户端的逻辑移到 Faas 服务端上来,或者新的需求一部分在 Faas 服务端上开发,这里的成败关键点在于:客户端和 Faas 的接口协议定义,也就是 Faas 的 API 定义,好的 API 定义除了对系统的可维护性有好处以外,对后续支撑业务的迭代开发也很重要,好的 API 定义请参考谷朴大神的文档:《API 设计*佳实践的思考》。

理想情况下:客户端做成一个解析 Faas 返回结果数据的一个浏览器。浏览器协议一旦定义好,就不会经常变换,你看 IE、Chrome 就很少更新。

当然我们的浏览器会复杂一些,它是地图浏览器。如何检验客户端和 Faas 之间的接口定义好不好,可以看后续的产品需求迭代,如果有些产品需求迭代只需要在 Faas 上完成,不需要客户端的任何修改,那么这个接口抽象就是成功的。

2. BFF 层开发提效

提到高德,大家首先想到的应该是其工具属性:高德是一个导航工具(这个说法现在已经不太准确了,因为高德这几年在做工具化往平台化的转型,我们要做万能的高德,高德的交易类业务正在兴起,高德打车、门票、酒店等业务发展很迅猛)。

针对高德导航来说,相比集团其他业务(如电商)来说,有大量的只读场景是高德业务的一大技术特点。这些只读场景里,大量的需求是 BFF(Backend For Frontend)类型的只读场景。为什么这么说?因为导航的*核心功能,例如 routing、traffic、eta 等都是相对稳定的,这部分的主要工作在持续不断地优化算法,使得高德的交通更准,算出的路径更优。这些核心功能在接口和功能上都是相对比较稳定的,而前端需求是多变的,例如增加个路径上的限宽墩提示等。

%title插图%num

Faas 特别适合做 BFF 层开发,在 Faas 上调用后端相对稳定的各个 Baas 服务,Faas 服务来做数据和调用逻辑封装、快速开发、发布。在业界,Faas 用的*多的场景也正是 BFF 场景(另外一个叫法是 SFF 场景,service for frontend)。

3. Serverless 是云时代的高级语言

FY21,高德是集团内*个全面上云的 BU,虽然高德已经全面上云了,但是这还不是云时代的终局,目前主要是全面 pouch 化并上云,容器方面做了标准化,在规模化、资源利用率方面可以全面享受云的红利,但是业务开发模式上基本上还和以前一样,仍是一个大型的分布式系统的写法。对于研发模式来说还并没有享受云的红利,可以类比为我们现在是在用汇编语言的方式来写跑在云上的服务。而 Serverless、云原生可以理解为云时代的高级语言,真正做到了 Cloud as a computer,只需要关注于业务开发,不需要考虑大型分布式系统的各种复杂性。

4. Go-Faas 补充 Go 语言生态

前面讲到了因为客户端上云项目,我们在阿里云 FC(函数计算)团队之上做加法,开发了 C++ Faas Runtime。不仅如此,我们还开发了 Go-Faas,我们为什么会做 Go-Faas 呢?这里也简单介绍一下背景,高德服务端 Go 部分的 qps 峰值已超百万。高德已补齐了阿里各中间件的 Go 客户端,和集团中间件部门共建。可观测性、自动化测试体系也基本完善,目前 Go 生态已基本完善。

补齐了 Go-Faas 之后,我们就既能用 Go 写 Baas 服务,又能用 Go 写 Faas 服务了,在不同的业务场景采用不同的服务实现方式,Go-Faas 主要应用于上文提到的 BFF 场景。

%title插图%num

How-技术方案介绍

1. 整体技术架构

上文讲了我们为什么要做这个事情,接下来讲我们具体是怎么做这个事情的,是如何实现的,具体的技术方案是什么样的。

本着在集团现有的基础设施、现有的中间件基础之上做加法的思想,我们和 CSE、阿里云 FC 函数计算团队合作共建,开发了 C++ Faas Runtime 和 Go Faas Runtime。整体和集团拉通的技术架构如下图所示,主要分为研发态、运行态、运维态三个部分。

%title插图%num

  • 运行态

先说运行态,业务流量从我们网关进来,调用到 FC API Server,转发到 C++/Go Faas Runtime,runtime 来完成用户函数里的功能。runtime 的架构本文下一章节会具体介绍。

和 runtime container 一起部署的有监控、日志、Dapr 各种 side car,side car 来完成各种日志采集上报功能,dapr side car 来完成调用集团中间件的功能。

另外目前 dapr 还在试点的阶段,调用中间件主要是通过 Broker 和各个中间件 proxy 来完成,中间件调用的有HSF、Tair、metaq、diamond 等中间件 proxy。

*后 Autoscaling 模块来管理函数实例的扩缩容,达到函数自动伸缩的目的。这里的调度就有各种策略了,有根据请求并发量的调度、函数实例的 CPU 使用率的调度。也能提前设置预留实例数,避免缩容到 0 之后的冷启动问题。

底层调用的是集团 ASI 的能力,ASI 可以简单理解为集团的 K8S+ sigma(集团的调度系统),*终的部署是 FC 调用 ASI 来完成函数实例部署,弹性伸缩的,部署的*小单位是上图中的 pod,一个 pod 里包含 runtime container 和 sidecar set container。

  • 研发态

再来看研发态,运行态决定函数是如何运行的,研发态关注函数的开发体验,如何方便地让开发者开发、调试、部署、测试一个函数。

C++ Faas 有个跨平台的难点问题,C++ Faas runtime 里有一些依赖库,这些依赖库没有 Java 依赖库管理那么方便。这样依赖库的安装比较麻烦,Faas 脚手架就是为了解决这个问题,调用脚手架,一键生成 C++ Faas 示例工程,安装好各种依赖包。为了本地能方便地 debug,开发了一个 C++ Faas Runtime Boot 模块,函数 runtime 启动入口在 boot 模块里,boot 模块里集成 runtime 和用户 Faas 函数,可以对 runtime 来做 debug 单步调试。

我们和集团 Aone 团队合作,函数的发布集成到 Aone 环境上了,可以很方便地在 Aone 上来发布 Go 或者 C++ Faas,Aone 上也集成了一键生成 example 代码库的功能。

C++ 和 Go Faas 的编译都依赖相应的编译环境,Aone 提供了自定义编译镜像的功能,我们上传了编译镜像到集团的公共镜像库,函数编译时,在函数的代码库里指定相应的编译镜像,编译镜像里安装了 Faas 的依赖库、SDK等。

  • 运维态

*后来看函数的运维监控,runtime 内部集成了鹰眼、sunfire 采集日志的功能,runtime 里面会写这些日志,通过 sidecar 里的 agent 采集到鹰眼、或者 sunfire 监控平台上去(FC 是通过 SLS 来采集的)之后,就能使用集团现有的监控平台来做 Faas 的监控了,也能接入集团的 GOC 报警平台。

2. C++/Go Faas Runtime 架构

上面讲的是和 Aone、FC/CSE、ASI 集成的一个整体架构,Runtime 是这个整体架构的一部分,下面具体讲讲 Runtime 的架构是怎样的,Runtime 是如何设计和实现的。

%title插图%num

*上面部分的用户 Faas 代码只需要依赖 Faas SDK 就可以了,用户只需要实现 Faas SDK 里的 Function 接口就能写自己的 Faas 了。然后如果需要调用外部系统,可以通过 SDK 里的 Http Client 来调用,如果要调用外部中间件,通过 SDK 里的 Diamond/Tair/HSF/metaq Client 来调用中间件就可以。SDK 里的这些接口屏蔽了底层实现的复杂性,用户不需要关心这些调用*后是如何实现,不需要关心 runtime 的具体实现。

SDK 层就是上面提到的 Function 定义和各种中间件调用的接口定义。SDK 代码是开发给 Faas 用户的。SDK 做的比较轻薄,主要是接口定义,不包含具体的实现。调用中间件的具体实现在 Runtime 里有两种实现方式。

往下是 Runtime 的一个整体架构。Starter 是 runtime 的启动模块,启动之后,runtime 自身是一个 Server,启动的时候根据 Function Config 模块的配置来启动 runtime,runtime 启动之后开启请求和管理监听模式。

再往下是 Service 层,实现 SDK 里定义的中间件调用的接口,包含 RSocket 和 dapr 两种实现方式,RSocket 是通过 RSocket broker 的模式来调用中间件的,runtime 里集成了 dapr(distributed application runtime),调用中间件也可以通过 dapr 来调用,在前期 dapr 试点阶段,如果通过 dapr 调用中间件失败了,会降级到 rsocket 的方式来调用中间件。

再往下就是 rsocket 的协议层,封装了调用 rsocket 的各种 metadata 协议。dapr 调用是通过 grpc 方式来调用的。

*下面一层就是集成了 rsocket 和 dapr 了。

rsocket 调用还涉及到 broker 选择的问题,upstream 模块来管理 broker cluster、broker 的注册反注册、keepalive 检查等等,LoadBalance 模块来实现 broker 的负载均衡选择以及事件管理、连接管理、重连等等。

*后 runtime 里的 metrics 模块负责鹰眼 trace 的接入,通过 filter 模式来拦截 Faas 链路的耗时,并输出鹰眼日志。打印 sunfire 日志,供 sidecar 去采集。下图是一个实际业务的 sunfire 监控界面:

%title插图%num

  • Dapr

dapr 架构如下图所示,具体可以参考官方文档:https://dapr.io/

%title插图%num

runtime 里以前调用中间件是通过 rsocket 方式来调用的,这里 rsocket broker 会有一个中心化问题,为了解决 outgoing 流量去中心化问题,和集团中间件团队合作引入了 dapr 架构。只是 runtime 层面集成了 dapr,对于用户 Faas 来说无感知,不需要关心具体调用中间件是通过 rsocket 调用的还是通过 dapr 调用的。后面 runtime 调用中间件切换到 dapr 之后,用户 Faas 也是不需要做任何修改的。

%title插图%num

How-业务如何接入 Serverless

如前文所述,接入统一在 Aone 上接入。提供了 C++ Faas/Go Faas 的接入文档。提供了函数的 example 代码库,代码库有各种场景的示例,包括调用集团各种中间件的代码示例。C++ Faas/Go Faas 的接入对整个集团开发,目前已经有一些高德以外的 BU,在自己的业务中落地了 C++ /Go Faas。Node.js Faas 使用淘宝提供的 runtime 和模板来接入,Java Faas 使用阿里云 FC 提供的 runtime 和模板来接入就可以了。

1. 接入规范-稳定性三板斧:可监控、可灰度、可回滚

针对落地新技术大家可能担心的稳定性问题,我们的应对法宝是阿里集团的稳定性三板斧:可监控、可灰度、可回滚。建立 Faas 链路保障群,拉通上下游各相关业务方、基础平台一起,按照集团的 1-5-10 要求,共同努力做到 1 分钟之内响应线上报警、快速排查;5 分钟之内处理;10 分钟之内恢复。

为了规范接入过程,避免犯错误引发线上故障,我们制定了 Faas 接入规范和 checkList,来帮助业务方快速使用 Faas。

可监控、可灰度、可回滚是硬性要求,除此之外,业务方如果能做到可降级就更好了。我们的 C++ 客户端上云业务,在开始试点阶段,就做好了可降级的准备,如果调用 Faas 端失败,本次调用将会自动降级到本地调用。基本对客户端功能无损,只是会增加一些响应延迟,另外客户端上该功能的版本,可能会比服务端稍微老一点,但是功能是向前兼容的,基本不影响客户端使用。

%title插图%num

Now-我们目前的情况

1. 基础平台建设情况

  • Go/C++ Faas Runtime 开发完成,对接 FC-Ginkgo/CSE、Aone 完成,已发布稳定的 1.0 版本。
  • 做了大量的稳定性建设、优雅下线、性能优化、C 编译器优化,使用了阿里云基础软件部编译器优化团队提供的编译方式来优化 C++ Faas 的编译,性能提升明显。
  • C++/Go Faas 接入鹰眼、sunfire 监控完成,函数具备了可观测性。
  • 池化功能完成,具备秒级弹性的能力。池化 runtime 镜像接入 CSE,扩一个新实例的时间由原来的分钟级变为秒级。

2. 高德的 Serverless 业务落地情况

C++ Faas 和 Go Faas 以及 Node.js Faas 在高德内部已经有大量的应用落地。举几个例子:

%title插图%num

上图中的前两个图是 C++ Faas 开发的业务:长途天气、沿途搜。后两个截图是 Go-Faas 开发的业务:导航 tips、足迹地图。

高德是阿里集团内 Serverless 应用落地规模*大 的BU,已落地的 Serverless 应用,日常峰值超过十万 qps 量级。

3. 主要收益

高德落地了集团内规模*大的 Serverless 应用之后,都有哪些收益呢?

首先*个*重要的收益是:开发提效。我们基于 Serverless 实现的端云一体组件,助力了客户端上云,解除了需要实时的客户端发版依赖问题,提升了客户端的开发迭代效率。基于 Serverless 开发的 BFF 层,提升了 BFF 类场景的开发迭代效率。

第二个收益是:运维提效。利用 Serverless 的自动弹性扩缩容技术,高德应对各种出行高峰就更从容了。例如每年的十一出行节、五一、清明、春节的出行高峰,不再需要运维或者业务开发同学在节前提前扩容,节后再缩容了。高德业务高峰的特点还不同于电商的秒杀场景。出行高峰的流量不是在 1 秒内突然涨起来的,我们目前利用池化技术实现的秒级弹性的能力,完全能满足高德的这个业务场景需求。

第三个收益是:降低成本。高德的业务特点,白天流量大、夜间流量低,高峰值和低谷值差异较大,时间段区分明显。利用 Serverless 在夜间流量低峰时自动缩容技术,*大地降低了服务器资源的成本。

%title插图%num

Next-后续计划

  • FC 弹内函数计算使用优化,和 FC 团队一起持续优化弹内函数计算的性能、稳定性、使用体验。用集团内丰富的大流量业务场景,来不断打磨好 C++/Go Faas Runtime,并*终输出到公有云,普惠数字化转型浪潮中的更多企业。
  • Dapr 落地,解决 outcoming 流量去中心化问题,逐步上线一些 C++/Go Faas,使用 Dapr 的方式调用集团中间件。
  • Faas 混沌工程,故障演练,逃生能力建设。Faas 在新财年也会参与我们 BU 的故障演练,逐一解决演练过程中发现的问题。
  • 接入边缘计算。端云一体的场景下,Faas + 边缘计算,能提供更低的延时,更好的用户体验。

 

消息队列Rabbitmq的交换器类型

消息队列Rabbitmq的交换器类型
一、交换器类型
在rabbitmq中,生产者的消息都是通过交换器来接收,然后再从交换器分发到不同的队列中去,在分发的过程中交换器类型会影响分发的逻辑。
rabitmq中的交换器有4种类型,分别为fanout、direct、topic、headers四种,其中前三种较为常见,后面一种用的比较少。
二、fanout
一般情况下交换器分发会先找出绑定的队列,然后再判断routekey,来决定是否将消息分发到某一个队列中;但如果交换器的类型为fanout,那么交换器就不再判断routekey了,而是将消息直接分发到绑定的队列中去,如下测试代码
Channel channel = connection.createChannel();    //在rabbitmq中创建一个信道
channel.exchangeDeclare(“exchangeName”, “fanout”); //创建一个type为fanout的交换器
channel.queueDeclare(“queueName”);    //创建一个队列
channel.queueBind(“queueName”, “exchangeName”, “routingKey”);   //将队列和交换器绑定
三、direct
在类型为direct的情况下,交换器在分发消息的时候同样会先获取绑定的队列,然后还会再判断routeing;当交换器发现类型为direct判断routeing的规则是完全匹配模式,只有消息完全等于到routeing的时候,才会将消息分发到指定队列;
一个队列是可以指定多个路由键的,我们假设有两个队列,分别是队列一、队列二;在队列一中指定了三个路由键,分别是zhangsan、lisi,wangwu,在队列二中指定了一个队列键lisi,指定多个路由键的代码如下所示:
Channel channel = connection.createChannel();    //在rabbitmq中创建一个信道
channel.exchangeDeclare(“exchangeName”, “direct”); //创建一个type为direct的交换器
channel.queueDeclare(“queueName”);    //创建一个队列
channel.queueBind(“queueName”, “exchangeName”, “zhangsna”);   //绑定并设置路由键
channel.queueBind(“queueName”, “exchangeName”, “lisi”);   //绑定并设置路由键
channel.queueBind(“queueName”, “exchangeName”, “wangwu”);   //绑定并设置路由键
当生产者发送了一条routeting为zhangsan的消息到交换器中,交换器在分发的时候只会把消息分发到队列一里面去,因为交换器在routeting匹配的时候只匹配到了队列一,因此队列二不会收到消息;
当生产者再次发送了一条routeting为lisi的消息到交换器中,交换器在分发的时候会把消息分发到队列一和队列二两个队列里面去,因为交换器在routeting匹配的时候匹配都匹配成功,因此两个队列都收到了消息;
四、topic
在类型为topic的情况下,交换器分发消息的时候也需要同时匹配bindKey和routingKey;但与direct类型不同的是当交换器发现类型为topic时候,判断routeing的规则是模糊匹配模式。
rabitmq自定义了一套匹配规则,在这里我假设生产者发送了一个消息,其中的的routingKey为wiki.imooc.com,那么交换器为topic类型时候,想要获取到这条消息,可以用*号作为通配符,来指定routingKey,分别是*.*.com、*.imooc.*、*wiki.imooc.*;同样也可以使用#作为通配符来指定路由键,例如wiki.#、#.com;
在上面的通配符列子中,我们需要掌握这几点:
路由键以.为分隔符,每一个分隔符的代表一个单词
通配符*匹配一个单词、通配符#可以匹配多个单词
*可以在routingKey和bindKey上使用,#只能用于RoutingKey中
五、headers
类型为headers的交换器与前面三种匹配方式完全不一样,它不依赖与bindingKey和routingKey,而是在绑定队列与交换器的时候指定一个键值对;当交换器在分发消息的时候会先解开消息体里的headers数据,然后判断里面是否有所设置的键值对,如果发现匹配成功,才将消息分发到队列中;这种交换器类型在性能上相对来说较差,在实际工作中很少会用到。
六、小结
从消息分发的性能上来比较:fanout > direct > topic > headers
topic的匹配规则只是用于消费者而不是生产者

Linux下3种常用的网络测速工具

Linux下3种常用的网络测速工具
不管你用的是什么操作系统,网速都是你非常关心的一个性能指标,毕竟,谁都不想看个视频结果网速卡到你怀疑人生。本文介绍三个 Linux 命令行下的网络测速工具,让你随时随地知道你的网络状况。
fast
fast 是 Netflix 提供的一项服务,它不仅可以通过命令行来使用,而且可以直接在 Web 端使用:fast.com。
我们可以通过以下命令来安装这个工具:
$ npm install –global fast-cli
不管是网页端还是命令行,它都提供了*基本的网络下载测速。命令行下*简单的使用方法如下:
$ fast
93 Mbps ↓
从以上结果可以看出,直接使用 fast 命令的话,将只返回网络下载速度。如果你也想获取网络的上传速度,则需要使用 -u 选项。
$ fast -u
⠧ 81 Mbps ↓ / 8.3 Mbps ↑
speedtest
speedtest 是一个更加知名的工具。它是用 Python 写成的,可以使用 apt 或 pip 命令来安装。你可以在命令行下使用,也可以直接将其导入到你的 Python 项目。
安装方式:
$ sudo apt install speedtest-cli
或者
$ sudo pip3 install speedtest-cli
使用的时候,可以直接运行 speedtest 命令即可:
$ speedtest
Retrieving speedtest.net configuration…
Testing from Tencent cloud computing (140.143.139.14)…
Retrieving speedtest.net server list…
Selecting best server based on ping…
Hosted by Henan CMCC 5G (Zhengzhou) [9.69 km]: 28.288 ms
Testing download speed……………………………………………………………………..
Download: 56.20 Mbit/s
Testing upload speed…………………………………………………………………………………………
Upload: 1.03 Mbit/s
从运行结果可以看出,speedtest 命令将直接提供上传/下载速率,测试的过程也是挺快的。你可以编写一个脚本来调用这个命令,然后定期进行网络测试,并在结果保存在一个文件或数据库,这样你就可以实时跟踪你的网络状态。
iPerf
iperf 是一个网络性能测试工具,它可以测试 TCP 和 UDP 带宽质量,可以测量*大 TCP 带宽,具有多种参数和 UDP 特性,可以报告带宽,延迟抖动和数据包丢失。利用 iperf 这一特性,可以用来测试一些网络设备如路由器,防火墙,交换机等的性能。
Debian 系的发行版可以使用如下命令安装 iPerf :
$ sudo apt install iperf
这个工具不仅仅在 Linux 系统下可以用,在 Mac 和 Windows 系统同样可以使用。
如果你想测试网络带宽,则需要两台电脑。这两台电脑需要处于同样的网络,一台作为服务机,另一台作为客户机,并且二者必须都要安装 iPerf 。
可以通过如下命令获取服务器的 IP 地址:
$ ip addr show | grep inet.*brd
    inet 192.168.242.128/24 brd 192.168.242.255 scope global dynamic noprefixroute ens33
我们知道,在局域网里,我们的 ipv4 地址一般是以 192.168 开头的。运行以上命令之后,我们需要记下服务机的地址,后面会用到。
之后,我们再在服务机上启动 iperf 工具:
$ iperf -s
然后,我们就可以等待客户机的接入了。客户机可以使用以下命令来连上服务机:
$ iperf -c 192.168.242.128
通过几秒钟的测试,它就会返回网络传输速率及带宽。

什么是数据结构和算法

什么是数据结构和算法
内容简介
前言
什么是算法
算法无处不在
计算机的“特权”角色
什么是数据结构
*部分第二课预告
1. 前言
程序员应该知道:程序 = 数据结构 + 算法(Program = Data Structure + Algorithm )。
作为一个程序员,如果不了解数据结构和算法,应该会不太好意思出门跟人家打招呼。
在这个课程里,我会带大家以循序渐进、轻松幽默的形式从入门到精通数据结构和算法,相信我们会度过一段非常愉快的时光。
你会发现,入门数据结构和算法,其实一点都不难。
话休絮烦,我们直接进入主题。
2. 什么是算法
算法的英语是 Algorithm。
首先我们来思考一个问题:
什么是算法?
要很准确地回答这个问题并不容易,但其实也没那么难,我不需要用一大堆理论来说清楚什么是算法,况且算法也不仅限于 IT(Information Technology 的简称,表示“信息技术”)编程领域。
所以一个通俗易懂的回答可以是:
算法是以简单概念的形式对如何解决问题的一种精确描述。
所以说:
算法是一种描述(description),且是一种精确的描述。
描述什么?描述如何解决问题。
以什么样的形式来描述?以简单概念的形式。
说到问题,在日常生活中,我们经常需要解决问题。有的人可能每天需要解决很多问题,有的人可能就是问题本身。
下面我就来描述一个生活中的问题,从广义上来考虑“问题”和“算法”的概念。
3. 无所不在的算法
我们可以用一个几乎关乎所有人的问题来开始说明:烹饪。毕竟“民以食为天”。
假设你饿了,你来到厨房,想整点什么吃的,正好你看到了一包方便面,然后你不自觉舔了舔舌头,你想吃方便面了(如果你正好是在睡觉前看到这里,请不要打我。不鼓励大家多吃方便面。这里的例子也可以是煮饭、煮面或者烹饪其他食物),那你应该怎么烹饪它呢?
这是一个简单的过程(这里只说*简单的水煮的方式,请大家不要纠结烹饪的细节,也许你有其他更好的烹饪方便面的方式):
在锅子里倒入适量水
在炉子上点起火来(如果是电磁炉就不用火)
把锅子放在炉子上
等待水开,转中火
把方便面饼放入锅中
煮半分钟
放入所有调料包
煮 1 分钟
出锅
可以看到,我已经以简单概念的形式精确描述了如何解决“煮方便面”这个问题。
所以上面这套流程的描述,你可以把它称为“算法”,这个算法是专门针对“煮方便面”这个问题的。
你会注意到上面的例子中有许多暗含的东西:我说你*初拥有一包方便面,但你也需要锅子、水,等。
我们可能会处于所有这些东西都不可用的特定情况下,然后我们就得使用另一种算法(或许先得自己造口锅出来)。
上面的流程里,我使用的各条指令(步骤)是比较“精确的”,但我们可以精简到更少的指令,或扩充到更多指令。你也许会说,如果要更精确地说,得说明如何把水装进锅子里。
如果要根据这个食谱来烹饪方便面的人不知道如何执行“在锅子里倒入适量水”这一指令,则有必要用更简单的术语解释(例如,需要解释如何使用水龙头)。
类似地,在我们编程时,你使用的算法的精度取决于许多参数:你使用的编程语言,可用的库,等等。
4. 计算机的“特权”角色
如果在日常生活中能找到算法的痕迹,为什么我们却主要在计算机科学(Computer Science)中讨论它呢?
原因很简单:计算机(或电脑)非常擅长执行重复性任务。它们快速,高效,“任劳任怨”,从不喊累。
假设我们可以描述用于计算 3 的平方根的小数的算法(这算法得是人类可以操作的)。利用这个算法,你可以使用纸和笔来计算 3 的平方根的前 7 个小数位(1.7320508)。
但如果需要你计算 3 的平方根的前 10 万个小数位呢?用纸和笔会计算到怀疑人生。这种时候,计算机将变得更加合适。
我们可以设计出不少用于信息处理的算法。说到信息处理,通常有以下几类:
研究
比较
分析
分类
提取
计算机通常在这些方面更加有优势,可以很好地处理大量信息。
你可能已经想到了著名搜索引擎谷歌(*初谷歌正是靠着其搜索算法的实力才能主导市场,成就了今天超高的市值),但这种活动并不仅限于互联网领域。
当你玩即时战略游戏(Real-Time Strategy Game,简称 RTS。例如红警,星际争霸,等等)的时候,如果你下达指令给一个单位,让它移动。此时电脑需要掌握很多的信息(例如地图的结构,单位的起点,单位的终点),它也必须产生新的信息:单位应走的路线。
所以其实算法源于生活(毕竟算法是人想出来的),但是我们通常在 IT 这个领域才讨论算法,因为计算机的特殊性。
5. 什么是数据结构
上面说到了算法,现在我们来聊聊数据结构。数据结构的英语是 Data Structure。data 表示“数据”,structure 表示“结构”。
除了处理信息(数据是信息的符号表示或称载体,信息则是数据的内涵)外,还必须考虑如何存储信息。存储信息的方式可能会对其处理方式产生非常重要的影响。
具体地说,我们可以用字典作为例子。我们可以将字典定义为“单词及其定义的一个集合”(一个单词对应一个定义)。
如果一部字典里的单词是胡乱排序的,这样的字典应该很难使用吧。比如你要找一个单词的定义,你得一页页地翻字典,直到找到那个单词。
按字母顺序来排列单词显然是一种非常有效的解决方案,可以快速找到你所要的单词。因此,这是一种不错的存储信息的方式。
算法(描述方法)和数据结构(描述组织)之间存在非常紧密的联系。简单来说,对信息的存储方式就是数据结构关心的事,对信息的处理方式就是算法关心的事。
通常,某些数据结构对于某些算法的实现至关重要;反之亦然。
例如,如果我们想要在已经按字母顺序排列好的字典中添加一个新单词,我们就不能只是将这个新单词写在字典的*后一页的空白处,而是必须使用算法将其添加到正确的位置。
因此,数据结构的研究与算法的研究是密不可分的,需要同时来学习它们。

SpringBoot 2.x 开发案例之整合MinIo文件服务

SpringBoot 2.x 开发案例之整合MinIo文件服务
Python入门教程100天
专栏收录该内容
129 篇文章6 订阅
订阅专栏
在之前的图床开发中曾使用了分布式文件服务FASTDFS和阿里云的OSS对象存储来存储妹子图。奈何OSS太贵,FASTDFS搭建配置又太繁琐,今天给大家推荐一款*易上手的高性能对象存储服务MinIO。
简介
MinIO 是高性能的对象存储,兼容 Amazon S3接口,充分考虑开发人员的需求和体验;支持分布式存储,具备高扩展性、高可用性;部署简单但功能丰富。官方的文档也很详细。它有多种不同的部署模式(单机部署,分布式部署)。
为什么说 MinIO 简单易用,原因就在于它的启动、运行和配置都很简单。可以通过 docker 方式进行安装运行,也可以下载二进制文件,然后使用脚本运行。
安装
推荐使用 docker 一键安装:
docker run -it -p 9000:9000 –name minio \
-d –restart=always \
-e “MINIO_ACCESS_KEY=admin” \
-e “MINIO_SECRET_KEY=admin123456” \
-v /mnt/minio/data:/data \
-v /mnt/minio/config:/root/.minio \
minio/minio server /data
注意:
密钥必须大于8位,否则会创建失败
文件目录和配置文件一定要映射到主机,你懂得
整合Nginx:
server{
    listen 80;
    server_name minio.cloudbed.vip;
    location /{
        proxy_set_header Host $http_host;
        proxy_pass http://localhost:9000;
    }
    location ~ /\.ht {
        deny  all;
    }
}
这样,通过浏览器访问配置的地址,使用指定的 MINIO_ACCESS_KEY 及 MINIO_SECRET_KEY 登录即可。
简单看了一下,功能还算可以,支持创建Bucket,文件上传、删除、分享、下载,同时可以对Bucket设置读写权限。
整合
Minio支持接入JavaScript、Java、Python、Golang等多种语言,这里我们选择*熟悉的Java语言,使用*流行的框架 SpringBoot 2.x。
pom.xml引入:
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.0.2</version>
</dependency>
application.properties引入:
# MinIo文件服务器
min.io.endpoint = http://minio.cloudbed.vip
min.io.accessKey = admin
min.io.secretKey = admin123456
MinIoProperties.java 配置实体:
/**
 * 实体类
 * 爪哇笔记:https://blog.52itstyle.vip
 */
@Data
@ConfigurationProperties(prefix = “min.io”)
public class MinIoProperties {
    private String endpoint;
    private String accessKey;
    private String secretKey;
}
撸一个工具类:
/**
 * 工具类
 * 爪哇笔记:https://blog.52itstyle.vip
 */
@Component
@Configuration
@EnableConfigurationProperties({MinIoProperties.class})
public class MinIoUtils {
    private MinIoProperties minIo;
    public MinIoUtils(MinIoProperties minIo) {
        this.minIo = minIo;
    }
    private MinioClient instance;
    @PostConstruct
    public void init() {
        try {
            instance = new MinioClient(minIo.getEndpoint(),minIo.getAccessKey(),minIo.getSecretKey());
        } catch (InvalidPortException e) {
            e.printStackTrace();
        } catch (InvalidEndpointException e) {
            e.printStackTrace();
        }
    }
    /**
     * 判断 bucket是否存在
     * @param bucketName
     * @return
     */
    public boolean bucketExists(String bucketName){
        try {
            return instance.bucketExists(bucketName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    /**
     * 创建 bucket
     * @param bucketName
     */
    public void makeBucket(String bucketName){
        try {
            boolean isExist = instance.bucketExists(bucketName);
            if(!isExist) {
                instance.makeBucket(bucketName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 文件上传
     * @param bucketName
     * @param objectName
     * @param filename
     */
    public void putObject(String bucketName, String objectName, String filename){
        try {
            instance.putObject(bucketName,objectName,filename,null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 文件上传
     * @param bucketName
     * @param objectName
     * @param stream
     */
    public void putObject(String bucketName, String objectName, InputStream stream){
        try {
            instance.putObject(bucketName,objectName,stream,null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 删除文件
     * @param bucketName
     * @param objectName
     */
    public void removeObject(String bucketName, String objectName){
        try {
            instance.removeObject(bucketName,objectName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//省略各种CRUD
}
目前SDK不支持文件夹的创建,如果想创建文件夹,只能通过文件的方式上传并创建。
minIoUtils.putObject(“itstyle”,”妹子图/爪哇妹.jpg”,”C:\\爪哇妹.jpg”);
一个实例只能有一个账号,如果想使用多个账号,需要创建多个实例。此外 minio还支持单主机,多块磁盘以及分布式部署,不过对于大部分单体应用来说,单体已经够用了。
小结
夜深人静的时候花了半个多小时就搞定了,是不是很简单,一键傻瓜式安装,丰富的SDK可供选择,小白用户是不是美滋滋。
重要的是她不仅可以作为文件服务,还可以当做私人网盘使用,一举两得岂不美滋滋。

Android开发——内存优化 图片处理

 用缓存避免内存泄漏

很常见的一个例子就是图片的三级缓存结构,分别为网络缓存,本地缓存以及内存缓存。在内存缓存逻辑类中,通常会定义这样的集合类。

[java]
  1. private HashMap<String, Bitmap> mMemoryCache = new HashMap<String, Bitmap>();//String类为该图片对应url  

三级缓存结构过程介绍:

在用户切换到展示图片的界面时,当然是优先判断内存缓存是否为Null,不为空直接展示图片,若为空,同样的逻辑去判断本地缓存(不为空便设置内存缓存并展示图片),本地缓存再为空才会根据该图片的url用网络下载类去下载该图片并展示图片(当然了,下载到图片后会有设置本地缓存以及内存缓存的操作)。

内存泄漏的问题就出现在内存缓存中:只要HashMap对象实例被引用,而Bitmap对象又都是强引用,Bitmap中图片越来越多,即便是内存溢出了,垃圾回收器也不会处理。

 

解决方案:

(1)我们可以选择使用软引用,从而在内存不足时,垃圾回收器更容易回收Bitmap垃圾。

[java]
  1. private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<String, SoftReference<Bitmap>>();  

(2)Android2.3以后,SoftReference不再可靠。垃圾回收期更容易回收它,不再是内存不足时才回收软引用。那么缓存机制便失去了意义。

Google官方建议使用LruCache作为缓存的集合类。其实内部封装了LinkedHashMap。内部原理是一直判断集合大小是否超出给定的*大值,超出就把*早*少使用的对象踢出集合。

[java]
  1. private LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>  
  2. ((int)(Runtime.getRuntime().maxMemory()/8)){   
  3. //用*大内存的1/8分配给这个集合使用  
  4. //让这个集合知道每个图片的大小  
  5. @Override  
  6. protected int sizeOf(String key, Bitmap value){  
  7. int byteCount = value.getRowBytes() * value.getHeight();//计算图片大小,每行字节数*高度  
  8. return byteCount;  
  9.   }
  10. };

 

  优化Bitmap避免内存泄漏

Android中很多控件比如ListView/GridView/ViewPaper通常都会包含很多图片,特别是快速滑动的时候可能加载大量的图片,因此对图片进行优化处理显得尤为重要。

 

  图片质量压缩

 

[java]
  1. public static Bitmap compressImage(Bitmap bitmap){    
  2.     ByteArrayOutputStream baos = new ByteArrayOutputStream();    
  3.     //质量压缩方法,参数100表示不压缩,把压缩后的数据存放到baos中    
  4.     bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);    
  5.     int options = 100;    
  6.     //循环判断如果压缩后图片大小>50kb就继续压缩    
  7.     while ( baos.toByteArray().length/1024 > 50) {    
  8.          //清空baos    
  9.          baos.reset();
  10.          bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
  11.          options -= 10;//每次都减少10    
  12.     }
  13.     //把压缩后的数据baos存放到ByteArrayInputStream中    
  14.     ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());    
  15.     //把ByteArrayInputStream数据生成图片    
  16.     Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);    
  17.     return newBitmap;            
  18. }

 

  图片尺寸裁剪

如果说没有裁剪,下载下来是200*200,而ImageView本身是100*100的,具体原因可以参考这篇文章,这样就浪费了一部分内存。

使用BitmapFactory.Options设置inSampleSize就可以缩小图片。如果该值为2,则缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4(小于等于1不缩放)。具体方法如下:

 

既然有了inSampleSize的概念,我们就要对比实际图片大小和ImageView控件的大小,如果使用内存直接处理实际图片的Bitmap从而得到实际大小的话,就失去了图片尺寸裁剪的意义,因为内存已经被消耗了。因此BitmapFactory.Options提供了inJustDecodeBounds标志位,当它被设置为true后,再使用decode系列方法时,并不会真正的分配内存空间,这样解码出来的Bitmap为null,但是可以计算出原始图片的真实宽高,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。

 

 

[java]
  1. BitmapFactory.Options options = new BitmapFactory.Options();    
  2. options.inJustDecodeBounds = true;    
  3. BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
  4. int imageHeight = options.outHeight;    
  5. int imageWidth = options.outWidth;    
  6. String imageType = options.outMimeType;

 

这里提供了一个calculateInSampleSize()工具方法来帮我们根据实际情况动态计算合适的inSampleSize。

[java]
  1. public static int calculateInSampleSize( //参2和3为ImageView期待的图片大小  
  2.             BitmapFactory.Options options, int reqWidth, int reqHeight) {    
  3.     // 图片的实际大小  
  4.     final int height = options.outHeight;    
  5.     final int width = options.outWidth;    
  6.     //默认值  
  7.     int inSampleSize = 1;    
  8.     //动态计算inSampleSize的值  
  9.     if (height > reqHeight || width > reqWidth) {    
  10.         final int halfHeight = height/2;  
  11.         final int halfWidth = width/2;  
  12.         while( (halfHeight/inSampleSize) >= reqHeight && (halfWidth/inSampleSize) >= reqWidth){  
  13.             inSampleSize *= 2;  
  14.         }
  15.     }
  16.     return inSampleSize;    
  17. }

 

创建一个完整的缩略图方案:

[java]
  1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,    
  2.         int reqWidth, int reqHeight) {    
  3.     final BitmapFactory.Options options = new BitmapFactory.Options();    
  4.     options.inJustDecodeBounds = true;    
  5.     BitmapFactory.decodeResource(res, resId, options);
  6.     // 计算inSampleSize,因为前面已经设置过标志位并调用了decode方法,所以参数option包含了真实宽高信息  
  7.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
  8.     // 别忘记将opts.inJustDecodeBound设置回false,否则获取的bitmap对象还是null   
  9.     options.inJustDecodeBounds = false;    
  10.     //重新加载图片  
  11.     return BitmapFactory.decodeResource(res, resId, options);    
  12. }

 

当我们在使用ImageView进行设置图片资源时:

[java]
  1. mImageView.setImageBitmap( //ImageView所期望的图片大小为100*100像素  
  2.     decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));    

 

  改变图片颜色模式

%title插图%num

Android默认的颜色格式是ARGB_8888,在不要求透明度的情况下可以改成RGB_565,这样每个像素占用的内从可从4byte将为2byte。

 

一张分辨率为1920×1080的图片,如果Bitmap使用ARGB_8888格式显示的话,占用的内存将是1920x1080x4个字节,将近8M内存。

 

  及时回收资源

 

(1)当界面不可见时我们应当将所有和界面相关的资源进行释放。

我们可以在Activity中重写onTrimMemory()方法,通过switch这个方法中的level参数,判断它是不是等于TRIM_MEMORY_UI_HIDDEN,就说明用户已经离开了我们的程序,此时就可以进行UI相关资源释放操作了,如下所示:

[java]
  1. @Override    
  2. public void onTrimMemory(int level) {    
  3.     super.onTrimMemory(level);    
  4.     switch (level) {    
  5.     case TRIM_MEMORY_UI_HIDDEN:    
  6.         // 进行资源释放操作    
  7.         break;    
  8.     }
  9. }

比如Android3.0开始支持的属性动画中有一类无限循环的动画,它会通过View间接持有Activity的引用,如果没有在onDestroy中停止动画(animator.cancel()),就会泄漏当前的Activity。说起动画,还有一点就是减少帧动画的使用。

(2)Google也建议在onStop()方法中释放资源,但是和上面的释放UI资源是有区别的,因为onStop()方法只是当一个Activity不可见的时候就会调用,比如说用户打开了我们程序中的另一个ActivityB。在onStop()方法中适合去关闭一些读写文件的资源、数据库操作相关的资源等等。

但是像UI相关的资源应该一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)这个回调之后才去释放,否则从ActivityB回到ActivityA,UI相关的资源会重新加载。

 

至此关于Android内存泄漏的内容总结完毕。

Android 内存泄漏常见情况4 资源泄漏

资源未关闭或释放导致内存泄露

在使用IOFile流或者SqliteCursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

 

1.数据库的cursor没有关闭。

操作Sqlite数据库时,Cursor是数据库表中每一行的集合,Cursor提供了很多方法,可以很方便的读取数据库中的值,
可以根据索引,列名等获取数据库中的值,通过游标的方式可以调用moveToNext()移到下一行
当我们操作完数据库后,一定要记得调用Cursor对象的close()来关闭游标,释放资源。

2,未关闭InputStream/OutputStream。

3,Bitmap对象不在使用时调用recycle()释放内存

4,BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap

 

Android 内存泄漏常见情况3 注册泄漏

android 中有很多注册和反注册,由于在注册后,上下文自身会被持久化的观察者列表所持有,如果不进行反注册,就会造成内存泄漏

内存泄漏1:Sensor Manager

代码如下:
MainActivity.java

void registerListener() {
       SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
       Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
       sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        registerListener();
        nextActivity();
    }
});

 

为什么?
通过Context调用getSystemService获取系统服务,这些服务运行在他们自己的进程执行一系列后台工作或者提供和硬件交互的接口,如果Context对象需要在一个Service内部事件发生时随时收到通知,则需要把自己作为一个监听器注册进去,这样服务就会持有一个Activity,如果开发者忘记了在Activity被销毁前注销这个监听器,这样就导致内存泄漏。

怎么解决?
在onDestroy方法里注销监听器。

内存泄漏1:未取消注册或回调导致内存泄露

比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个刚播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄露。因此注册广播后在Activity销毁后一定要取消注册。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 接收到广播需要做的逻辑
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }
}

在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用Retrofit+RxJava注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。

WebView造成内存泄露

关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。

另外在查阅WebView内存泄露相关资料时看到这种情况:

Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。

*终的解决方案是:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView。详细分析过程请参考这篇文章:WebView内存泄漏解决方法。

@Override
protected void onDestroy() {
    super.onDestroy();
    // 先从父控件中移除WebView
    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();
}

 

Android 内存泄漏常见情况2 内部类泄漏

线程持久化

Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉

内存泄漏1:AsyncTask

void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
        nextActivity();
    }
});

 

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为什么?
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

怎么解决?
自定义静态AsyncTask类,并且让AsyncTask的周期和Activity周期保持一致,也就是在Activity生命周期结束时要将AsyncTask cancel掉。

内存泄漏2:Handler

非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:

public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessageDelayed(msg,1000);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 做相应逻辑
            }
        }
    };
}

也许有人会说,mHandler并未作为静态变量持有Activity引用,生命周期可能不会比Activity长,应该不一定会导致内存泄露呢,显然不是这样的!

熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandlerActivity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueueLooper都是与线程相关联的,MessageQueueLooper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。

通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑
                }
            }
        }
    }
}

mHandler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。

上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

为什么?

创建的Handler对象为匿名类,匿名类默认持有外部类activity, Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。这时activity被handler持有
handler被message持有,message被messagequeue持有,message queue被loop持有,主线程的loop是全局存在的,这时就造成activity被临时性持久化,造成临时性内存泄漏

怎么解决?
可以由上面的结论看出,产生泄漏的根源在于匿名类持有Activity的引用,因此可以自定义Handler和Runnable类并声明成静态的内部类,来解除和Activity的引用。或者在activity 结束时,将发送的Message移除

内存泄漏3:Thread

代码如下:
MainActivity.java

void spawnThread() {
    new Thread() {
        @Override public void run() {
            while(true);
        }
    }.start();
}

View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
      spawnThread();
      nextActivity();
  }
});

 

为什么?
Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。看到这相信你应该也是心中有答案了吧 : 我在每一个MainActivity中都创建了一个线程,此线程会持有MainActivity的引用,即使退出Activity当前线程因为是直接被GC Root引用所以不会被回收掉,导致MainActivity也无法被GC回收

怎么解决?
当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉

内存泄漏4:Timer Tasks

TimerTimerTask在Android中通常会被用来做一些计时或循环任务,比如实现无限轮播的ViewPager

public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private PagerAdapter mAdapter;
    private Timer mTimer;
    private TimerTask mTimerTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        mTimer.schedule(mTimerTask, 3000, 3000);
    }

    private void init() {
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
        mAdapter = new ViewPagerAdapter();
        mViewPager.setAdapter(mAdapter);

        mTimer = new Timer();
        mTimerTask = new TimerTask() {
            @Override
            public void run() {
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        loopViewpager();
                    }
                });
            }
        };
    }

    private void loopViewpager() {
        if (mAdapter.getCount() > 0) {
            int curPos = mViewPager.getCurrentItem();
            curPos = (++curPos) % mAdapter.getCount();
            mViewPager.setCurrentItem(curPos);
        }
    }

    private void stopLoopViewPager() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer.purge();
            mTimer = null;
        }
        if (mTimerTask != null) {
            mTimerTask.cancel();
            mTimerTask = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopLoopViewPager();
    }
}

当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即cancelTimerTimerTask,以避免发生内存泄漏。

为什么?
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。

怎么解决?
在适当的时机进行Cancel。

内存泄漏5:属性动画造成内存泄露

动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

@Override
protected void onDestroy() {
    super.onDestroy();
    mAnimator.cancel();
}