数据处理 · 初步的认识关系图(三)

此篇讲一下关系图片中的特征和有向图
一、 关系图特征值

1.最大的度(Max Degree)

在关系图谱中每一个顶点的度是图论算法中非常重要的计算对象。如果某一个节点的度最大,则说明他很有可能是这个关系图中的核心点。

寻找一个关系图谱中带有最大度数的顶点并不困难,只需全部先计算出所有顶点的度,然后找出最大数即可。

当然,光是找出最大的度可不能满足算法的需要。除了找出最大的度数以外,自然还需要知道是哪一个顶点具有最大的度数。所以我们需要准备两个函数,一个用于找出带有最大度数的顶点,而另外一个则用于获取其度数。

//Graph是总的关系图类
class Graph { 
    // ... 
    largestVertex() { 
        const self = this 
        const degrees = self.vertexIds.map(function(vertexId) { 
            return { 
                degree: self.degree(vertexId), id: vertexId 
            } 
        }) 
        return self.getVertex(_.maxBy(degrees, 'degree').id) 
    } 
    maxDegree() { 
        return this.degree(this.largestVertex().id) 
    } 
} 
const vertices = [ 
    new Vertex(1, 'A'), 
    new Vertex(2, 'B'), 
    new Vertex(3, 'C'), 
    new Vertex(4, 'D'), 
    new Vertex(5, 'E') 
    ] 
const edges = [ 
    new Edge(1, 2, 1), 
    new Edge(1, 3, 2), 
    new Edge(2, 4, 1), 
    new Edge(3, 4, 1), 
    new Edge(1, 4, 2), 
    new Edge(4, 5, 3) 
    ] 
const graph = new Graph(vertices, edges) console.log(graph.largestVertex().property) //=> D 
console.log(graph.maxDegree()) //=> 4 

2、自环(Self-Loop)

自环是一种十分抽象的概念,其定义是在一个关系图谱内,某一个顶点存在一条边再与自己相连。我们可以将一个关系图谱中的每一个顶点看作是事件序列中的某一个事件,顶点间的边则表示多种情况下事件之间的连续关系。那么在这种场景下,顶点的自环就可以被解释为某一个事件的连续发生可能性。

在多种事件顺序发生的可能性中,如果某一个事件的连续发生次数(我们可以使用边的属性值表示)越多,则表示该事件的重要性越强。这种概念在一些对用户行为进行分析的应用中十分重要。

而找到自环的边的方法也非常简单,只需要找到那些左右顶点相同的边即可。

class Graph { 
    // ... 
    loops() { 
        const self = this 
        return self.edgeIds 
            .map(function(edgeId) { 
                return self.edges[edgeId] 
            }) 
            .filter(function(edge) { 
                return edge.leftId === edge.rightId 
            }) 
        } 
    } 
    const vertices = [ 
        new Vertex(1, '1'), 
        new Vertex(2, '2'), 
        new Vertex(3, '3') 
        ] 
    const edges = [ 
        new Edge(1, 1, 3), 
        new Edge(1, 2, 1), 
        new Edge(1, 3, 1), 
        new Edge(2, 3, 2) 
        ] 
    const graph = new Graph(vertices, edges) 
    console.log(graph.loops()) //=> [ Edge{ leftId: 1, rightId: 1, property: 3 } ] 

二、有向图

有向图是从原本的一条边即代表两个顶点共同拥有一个平等的关系,变成允许顶点之间存在单向的关系。就好像我们在人际社交中,朋友之间互相认识所以我们是平等的,但是我们也有很多我们只认识他们,他们却不认识我们的人

  1. 有向边

因为我们前面第一篇所定义的边是不存在方向特性的,所以我们直接使用了 leftId 和 rightId 来存储与边相连的两个顶点的信息。而有向图的边是带有方向特征的,虽然我们也可以像数学一样定义一个向量的表达方式就是从左到右的(如 ),直接使用前面所定义的边来表示有向边,但是我们还是需要另外定义一个有向边类型以使用。

class DirectedEdge { 
    constructor(originalId, targetId, property) { 
    this.originalId = originalId 
    this.targetId = targetId 
    this.property = property 
    } 
} 
  1. 有向图 Digraph

因为使用的边不再是无方向特性的边,所以之前所定义的其实是无向图类型,不能直接当做有向图使用了。有向图和无向图最大的区别就是顶点之间从平行的关系变成了有出或入的单边关系。出则代表着某一个顶点存在一个单向的关系指向另外一个顶点,而入则表示某一个顶点被另外一个顶点所指向。

那么前面使用 edgeRelations 来存储顶点与边的关系时则需要加以改动了,我们可以简单地分开 inEdgeRelations 和 outEdgeRelations 来分别存储顶点与入边、出边的关系。

class Digraph { 
    constructor(vertices, edges) { 
        // Vertices 
        this.vertexIds = [];
        this.vertices = {};
        for (let i = 0; i < vertices.length; ++i) { 
            const vertex = vertices[i];
            this.vertexIds.push(vertex.id); 
            this.vertices[vertex.id] = vertex;
        } 
        const edgesWithId = edges.map(function(edge, i) { 
            edge.id = i + 1; 
            return edge; 
        }) 
        // Edges 
        this.edgeIds = []; 
        this.edges = {}; 
        this.inEdgeRelations = {}; 
        this.outEdgeRelations = {}; 
        for (let i = 0; i < edgesWithId.length; ++i) { 
            const edge = edgesWithId[i]; 
            this.edgeIds.push(edge.id);
            this.edges[edge.id] = edge;
            if (typeof this.outEdgeRelations[edge.originalId] === 'undefined') {                 this.outEdgeRelations[edge.originalId] = []; 
            } 
            if (typeof this.inEdgeRelations[edge.targetId] === 'undefined') {                    this.inEdgeRelations[edge.targetId] = []; 
            }                                                                               this.inEdgeRelations[edge.targetId].push(edge.id)                                this.outEdgeRelations[edge.originalId].push(edge.id) 
        } 
    } 
} 

总结:关系图谱在社会人际关系也可以体现出来,可以分为有向图,无向图,实际例子可以举例说明,可能更加清楚关系图谱。

lerna管理好你的packages

前言

最近在研究包管理相关的东西,于是发现了lerna

什么是Lerna

Lerna是一个管理多个node模块的工具,是Babel自己用来维护自己的Monorepo并开源出的一个项目。Lerna现在已经被很多著名的项目组织使用。

Monorepo

Monorepo的全程是monolithic repository,即单体式仓库,与之对应的是Multirepo(multiple repository)。以下是两者的区别。

Monorepo是把所有相关的module放在一个仓库中进行管理,每个module独立发布,优缺点总结如下:

优点
* 版本更新简单便捷,core repo 以及各模块版本发生变化后可以简便的同步更新其他对其有依赖的module
* 管理简单,issue readme pr 都放在一起维护
* changelog 方便维护,所有的修改基于一个commit列表

缺点
* 仓库体积增长迅速,随着module的增多,仓库的体积也会变得庞大。
* 自由度不高,对统一的配套工具要求较高,要适配每个module的要求。

Multirepo相对来说比较传统,没一个组件都单独用一个仓库来维护管理,优缺点如下:

优点
* 每个模块可自行管理,自由度高,可自行选择构建工具等相关工具。
* 每个仓库体积较小

缺点
* 项目管理混乱,issue 经常出现针对其他module的问题,需要更多精力维护。
* changlog 无法关联,无法很好的自动关联各个 module 与 core repo 之间的变动联系
* 版本更新繁琐,如果 core repo 的版本发生了变化,需要对所有的 module 进行依赖 core repo 的更新
* 测试复杂,对多个相关联 module 测试繁琐

Babel为了解决上面的问题,于是诞生了Lerna,Lerna可以通过git和npm帮助我们来优化管理Monorepo的工作流,同时较少开发和构建环境中对大量依赖包复制的时间和控件需求。

初始化一个Lerna工程

一个基本的Lerna仓库结构如下

.
├── lerna.json
├── package.json
└── packages
    ├── module-1
    ├── module-2
    └── module-3

首先要创建一个Lerna项目
我们要全局安装Lerna

npm i -g lerna
mkdir lerna-demo && cd $_
lerna init

Lerna提供两种不同的方式来管理你的项目:Fixed或Independent,默认采用Fixed模式,如果你想采用Independent 模式,只需在执行init命令的时候加上–independent或-i参数即可。

Fixed/Locked 模式(默认)

固定模式下Lerna项目在单一版本线上运行。版本号保存在项目根目录下lerna.json文件中的version下。当你运行lernapublish时,如果一个模块自上次发布版本以后有更新,则它将更新到你将要发布的新版本。这意味着你在需要发布新版本时只需发布一个统一的版本即可。

Independent 模式(–independent)

独立模式下Lerna允许维护人员独立地的迭代各个包版本。每次发布时,你都会收到每个发生更改的包的提示,同时来指定它是patch,minor,major还是自定义类型的迭代。

Lerna 实践

为了能够使lerna发挥最大的作用,根据这段时间使用Lerna的经验,总结出一个最佳实践。下面是一些特性。

  • 采用Independent模式
  • 根据Git提交信息,自动生成changelog
  • eslint规则检查
  • prettier自动格式化代码
  • 提交代码,代码检查hook
  • 遵循semver版本规范

工具整合

在这里引入的工具都是为了解决一个问题,就是工程和代码的规范问题。
* husky
* lint-staged
* prettier
* eslint

Prisma 服务及CLI

前言:本文主要讲述 Primsa 的场景用例,主要优点以及如何将它适配到您的技术栈中。

Prisma 和 GraphQL

Prisma 适用 GraphQL 作为通用数据库抽象,这意味着它将您的数据库转换为GraphQL API,使您能够:

  • 使用 GraphQL queries 和 mutations 来读写数据库

  • 使用 GraphQL subscriptions 来接收数据库事件的实时更新信息

  • 使用 GraphQL SDL 执行迁移和数据建模

当一个 Prisma client 发送请求到 Prisma server,它实际上会生成 GraphQL 操作,这些操作会被发送到 Prisma 的 GraphQL API。然后,client 会将 GraphQL 响应转换成所期望的数据结构,并从调用的方法返回它。

Prisma 服务

数据库的 GraphQL 映射由 Prisma 服务提供,每个服务都为数据库提供相应的 GraphQL CRUD 映射。GraphQL API 是自动生成的,并为所服务的 datamodel 中的每个 model 提供 CRUD 操作。

Prisma 服务在 Prisma server 上运行。Prisma server 可以配置为托管多个 Prisma 服务。

img

Prisma 服务使用两个组件配置:

  • prisma.yml : Prisma 服务的根配置文件(包括服务的端点、服务机密,数据模型文件的路径,……)

  • Datamodel: 在 datamodel 中,您会定义数据模型,Prisma 会使用该模型来为您的数据库生成 GraphQL API。它使用声明性 GraphQL SDL 语法,通常存储在一个名为 datamodel.prisma 的文件中。

一个极简的 prisma.yml 看起来像这样:

endpoint: http://localhost:4466
datamodel: datamodel.prisma
secret: mysecret42

其中:

  • endpoint : 应将服务部署到的 Prisma server 的HTTP endpoint。此端点对外暴露 service 的Prisma API。

  • datamodel : datamodel 文件(生成 GraphQL CRUD / realtime API 的基础)的路径配置

  • secret : 用于确保 service GraphQL API 端点的安全而设置的基于 JWT-based 签名校验的 service 秘钥。如果未设置 secret ,那么这个service 不需要权限校验就可以访问

Prisma CLI安装

可以从 NPM 仓库安装 Prisma CLI 。

NPM

npm install -g prisma

Yarn

yarn global add prisma

概要

$ prisma

GraphQL Database Gateway (https://www.prisma.io)

Usage: prisma COMMAND

Service:
  init            Initialize a new service
  deploy          Deploy service changes (or new service)
  introspect      Introspect database schema(s) of service
  info            Display service information (endpoints, cluster, ...)
  token           Create a new service token
  list            List all deployed services
  delete          Delete an existing service

Data workflows:
  playground      Open service endpoints in GraphQL Playground
  seed            Seed a service with data specified in the prisma.yml
  import          Import data into a service
  export          Export service data to local file
  reset           Reset the stage data

Cloud:
  login           Login or signup to the Prisma Cloud
  logout          Logout from Prisma Cloud
  console         Open Prisma Console in browser
  account         Display account information

Use prisma help [command] for more information about a command.
Docs can be found here: https://bit.ly/prisma-cli-commands

Examples:

- Initialize files for a new Prisma service
  $ prisma init

- Deploy service changes (or new service)
  $ prisma deploy

快速开始

安装完成后,执行以下命令让 Prisma API 启动并运行,然后便可以开始向其发送 queries 和 mutations :

prisma init hello-world
# Select a *demo server* from the interactive prompt
cd hello-world
prisma deploy
prisma playground

您现在可以开始向 Prisma API 发送 queries 和 mutations。有关更全面的演示,请查看“入门”部分。

graphql-config 的使用

Prisma CLI 集成了 graphql-config。如果您的项目使用 .graphqlconfig-file ,您可以使用 prisma 扩展并将其指向您的prisma.yml:

projects:
  prisma:
    schemaPath: prisma.graphql
    extensions:
      prisma: prisma.yml

为 CLI 命令行添加 HTTP 代理

Prisma CLI 支持自定义HTTP代理。当在公司防火墙后面时,这尤其重要。

要激活代理,请提供环境变量 HTTP_PROXYHTTPS_PROXY。该行为与 npm CLI 对该行为的处理非常相似。

可以提供以下环境变量:

  • HTTP_PROXY 或者 http_proxy:http 代理 URL,例如 http://localhost:8080

  • HTTPS_PROXY 或者 https_proxy:https 代理 URL,例如 https://localhost:8080

  • NO_PROXY 或者 no_proxy:要禁用某些 URLs 的代理,请为 NO_PROXY 提供一个 glob 通配符,如 *。

要获得简单的本地代理,您可以使用该 proxy 模块:

npm install -g proxy
DEBUG="*" proxy -p 8080
HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=https://localhost:8080 prisma deploy

参考链接:https://github.com/graphql/graphql-js

Go 程序的性能监控与分析 pprof

Go 开箱就提供了一系列的性能监控 API 以及用于分析的工具, 可以快捷而有效地观察应用程序各个细节的 CPU 与内存使用情况, 包括生成一些可视化的数据.

pprof 数据采样

pprof 采样数据主要有如下方式:

  • runtime/pprof: 采集程序(非 Server)的运行数据进行分析。手动调用runtime.StartCPUProfile或者runtime.StopCPUProfile等 API来生成和写入采样文件,灵活性高
  • net/http/pprof: 采集 HTTP Server 的运行时数据进行分析。通过 http 服务获取Profile采样文件,简单易用,适用于对应用程序的整体监控。通过 runtime/pprof 实现
net/http/pprof

在应用程序中导入import _ “net/http/pprof”,并启动 http server即可:

import _ "net/http/pprof" //执行init函数

net/http/pprof 已经在 init()函数中通过 import 副作用(side effect)完成默认 Handler 的注册

go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()

1个简单的例子:

main.go

package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
    "gitlab.pri.ibanyu.com/devops/go-pprof-example/data"
)

func main() {
    go func() {
        for {
            log.Println(data.Add("https://github.com/devops"))
        }
    }()

    http.ListenAndServe("0.0.0.0:6060", nil)
}

data/d.go,文件内容:

package data

var datas []string

func Add(str string) string {
    data := []byte(str)
    sData := string(data)
    datas = append(datas, sData)

    return sData
}

接着我们需要编译一下这个程序并运行

$ go build .
$ ./go-pprof-example
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops
2019/07/31 21:14:47 https://github.com/devops

之后可通过 http://localhost:6060/debug/pprof 可以看到如下页面:

页面上展示了可用的程序CMD运行采样数据:

  • allocs: 内存分配情况的采样信息
  • goroutine: /debug/pprof/goroutine,获取程序当前所有 goroutine 的堆栈信息
  • heap: /debug/pprof/heap,查看活动对象的内存分配情况。
  • threadcreate: /debug/pprof/threadcreate,查看创建新OS线程的堆栈跟踪
  • block: /debug/pprof/block,阻塞操作情况的采样信息
  • mutex: /debug/pprof/mutex,查看导致互斥锁的竞争持有者的堆栈跟踪
  • cmdline: /debug/pprof/cmdline,获取程序的命令行启动参数
  • profile: /debug/pprof/profile,默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件
  • trace: /debug/pprof/trace 程序运行跟踪信息
runtime/pprof

runtime/pprof提供各种相对底层的 API 用于生成采样数据,一般应用程序更推荐使用net/http/pprof,runtime/pprof 的 API 参考runtime/pprof或 http pprof 实现

通过交互式终端使用

1. profile
$ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60
Fetching profile over HTTP from http://localhost:6060/debug/pprof/profile?seconds=60
Saved profile in /Users/wangyichen/pprof/pprof.samples.cpu.002.pb.gz
Type: cpu
Time: Jul 31, 2019 at 9:26pm (CST)
Duration: 1mins, Total samples = 8.02s (13.33%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

执行该命令后,需等待 60 秒(可调整 seconds 的值),pprof 会进行 CPU Profiling。结束后将默认进入 pprof 的交互式命令模式,可以对分析的结果进行查看或导出。具体可执行 pprof help 查看命令说明.

(pprof) top10
Showing nodes accounting for 7.88s, 98.25% of 8.02s total
Dropped 35 nodes (cum <= 0.04s)
Showing top 10 nodes out of 37
      flat  flat%   sum%        cum   cum%
     5.79s 72.19% 72.19%      6.15s 76.68%  syscall.syscall
     0.42s  5.24% 77.43%      0.75s  9.35%  runtime.notetsleep
     0.35s  4.36% 81.80%      0.36s  4.49%  runtime.exitsyscallfast
     0.34s  4.24% 86.03%      0.34s  4.24%  runtime.nanotime
     0.33s  4.11% 90.15%      0.33s  4.11%  runtime.pthread_cond_timedwait_relative_np
     0.33s  4.11% 94.26%      0.33s  4.11%  runtime.usleep
     0.15s  1.87% 96.13%      0.15s  1.87%  runtime.memmove
     0.09s  1.12% 97.26%      0.09s  1.12%  runtime.memclrNoHeapPointers
     0.07s  0.87% 98.13%      0.07s  0.87%  runtime.pthread_cond_signal
     0.01s  0.12% 98.25%      0.26s  3.24%  gitlab.pri.ibanyu.com/devops/go-pprof-example/data.Add
  • flat:给定函数上运行耗时
  • flat%:同上的 CPU 运行耗时总比例
  • sum%:给定函数累积使用 CPU 总比例
  • cum:当前函数加上它之上的调用运行总耗时
  • cum%:同上的 CPU 运行耗时总比例

最后一列为函数名称,在大多数的情况下,我们可以通过这五列得出一个应用程序的运行情况并优化.

2.heap
Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap
Saved profile in /Users/wangyichen/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.002.pb.gz
Type: inuse_space
Time: Jul 31, 2019 at 9:34pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.27GB, 100% of 1.27GB total
      flat  flat%   sum%        cum   cum%
    1.27GB   100%   100%     1.27GB   100%  gitlab.pri.ibanyu.com/devops/go-pprof-example/data.Add
         0     0%   100%     1.27GB   100%  main.main.func1
(pprof) list data.Add
Total: 1.27GB
ROUTINE ======================== gitlab.pri.ibanyu.com/devops/go-pprof-example/data.Add in /tmp/example/data/d.go
    1.27GB     1.27GB (flat, cum)   100% of Total
         .          .      2:
         .          .      3:var datas []string
         .          .      4:
         .          .      5:func Add(str string) string {
         .          .      6:    data := []byte(str)
  823.03MB   823.03MB      7:    sData := string(data)
  481.98MB   481.98MB      8:    datas = append(datas, sData)
         .          .      9:
         .          .     10:    return sData
         .          .     11:}
  • -inuse_space:分析应用程序的常驻内存占用情况
  • -alloc_objects:分析应用程序的内存临时分配情况
3.allocs
go tool pprof http://localhost:6060/debug/pprof/allocs
Fetching profile over HTTP from http://localhost:6060/debug/pprof/allocs
Saved profile in /Users/wangyichen/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.003.pb.gz
Type: alloc_space
Time: Jul 31, 2019 at 9:37pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 5458.28MB, 99.86% of 5465.68MB total
Dropped 22 nodes (cum <= 27.33MB)
      flat  flat%   sum%        cum   cum%
 3987.74MB 72.96% 72.96%  3987.74MB 72.96%  gitlab.pri.ibanyu.com/devops/go-pprof-example/data.Add
  980.03MB 17.93% 90.89%   980.03MB 17.93%  fmt.Sprintln
  490.51MB  8.97% 99.86%  5458.28MB 99.86%  main.main.func1
         0     0% 99.86%   980.03MB 17.93%  log.Println
4. block

go tool pprof http://localhost:6060/debug/pprof/block

5. mutex

go tool pprof http://localhost:6060/debug/pprof/block

PProf 可视化界面

go-torch 在 Go 1.11 之前是作为非官方的可视化工具存在的, 它可以为监控数据生成火焰图,从 Go 1.11 开始, 火焰图被集成进入 Go 官方的 pprof 库.

go-torch is deprecated, use pprof instead

As of Go 1.11, flamegraph visualizations are available in go tool pprof directly!
$ go tool pprof -http=":8088" [binary] [profile]

在浏览器打开 http://localhost:8081/ui/flamegraph, 就可以看到下面这样的反过来的火焰图.如果出现 Could not execute dot; may need to install graphviz.,就是提示你要安装 graphviz 。谷歌安装即可.

火焰图最大优点是动态的,每一块代表一个函数,颜色的深浅是随机的 ,长度越长代表占用 CPU 时间越长,

然后, pprof 命令行的 top 以及 list 正则也可以在这里边完成, 还有 svg 图形.

通过 PProf 的可视化界面,我们能够更方便、更直观的看到 Go 应用程序的调用链、使用情况等,并且在 View 菜单栏中,还支持如上多种方式的切换。

本文粗略地介绍了 Go 的性能利器 PProf。在特定的场景中,PProf 给定位、剖析问题带了极大的帮助

震惊!前端竟拿go做这种事情

越是火的语言社区越是喜欢玩跨界。最近go语言社区就出了这么一个框架,竟然可以用来写前端页面。你肯定会好奇了,go语言不是后端语言么,怎么可以在浏览器里运行呢。具体怎么回事,让咱们通过一个demo来说明吧。

  1. 首先创建一个空文件夹,可以起名字叫 testapp 或者你喜欢的名字。
  2. 声明一个 go.mod 文件,你需要在里边声明自己的模块,例如:module example.org/someone/testapp
  3. 创建一个名为root.vugu组件文件,为什么是vugu后缀,因为这个框架的名字就叫做 Vugu,里边的内容如下:
<div class="my-first-vugu-comp">
    <button @click="data.Toggle()">Click Me</button>
    <div vg-if="data.Show">Hello World!</div>
</div>

<style>
.my-first-vugu-comp { background: #eee; }
</style>

<script type="application/x-go">
type RootData struct { Show bool }
func (data *RootData) Toggle() { data.Show = !data.Show }
</script>
  1. 创建一个devserver.go文件,内容如下:
// +build ignore

package main

import (
    "log"
    "net/http"
    "os"

    "github.com/vugu/vugu/simplehttp"
)

func main() {
    wd, _ := os.Getwd()
    l := "127.0.0.1:8844"
    log.Printf("Starting HTTP Server at %q", l)
    h := simplehttp.New(wd, true)
    // include a CSS file
    // simplehttp.DefaultStaticData["CSSFiles"] = []string{ "/my/file.css" }
    log.Fatal(http.ListenAndServe(l, h))
}
  1. 启动服务 go run devserver.go
  2. 访问:http:///127.0.0.1:8844
  3. 这个时候网页上会出现Click Me 的字符串,点击后就会出现经典的Hello World!

打开浏览器的控制台,你会发现浏览器加载了一个叫做main.wasm的文件,打开后会发现是一串乱码。这就要提到浏览器的特性WebAssembly了

什么是WebAssembly

WebAssembly 是一种新的字节码格式, 和 JS 需要解释执行不同的是,WebAssembly 字节码和底层机器码很相似可快速装载运行,因此性能相对于 JS 解释执行大大提升。 也就是说 WebAssembly 并不是一门编程语言,而是一份字节码标准,需要用高级编程语言编译出字节码放到 WebAssembly 虚拟机中才能运行, 浏览器厂商需要做的就是根据 WebAssembly 规范实现虚拟机。

WebAssembly 原理

WebAssembly 字节码是一种抹平了不同 CPU 架构的机器码,WebAssembly 字节码不能直接在任何一种 CPU 架构上运行, 但由于非常接近机器码,可以非常快的被翻译为对应架构的机器码,因此 WebAssembly 运行速度和机器码接近,这听上去非常像 Java 字节码。

关于Vugu

  1. 前端语法属于一个Vue的超子集,只实现了基本的组件功能。
  2. 目前还是一个纯试验性质的库
  3. 相比于传统前端框架除了效率高一些,并没有什么优势,因为前端的壁垒不在语言,而是庞大的生态,其他语言要在短期赶上很难。

WebAssembly的展望

既然Vugu目前还处于一个纯探索性质的库,那WebAssembly还有什么用呢?当然有用了,因为WebAssembly性能快的特性,另外在移动端的支持性已经非常好,详见支持列表。已经可以用在一些计算密集的领域了。

  1. 各种加密库
  2. Vue、React、AngularJs框架的核心库,像dom diff 这些运算密集的操作就可以用Webassembly来做。
  3. 游戏引擎,粒子特效、龙骨等运算量大的操作其实都可以把核心库迁移到WebAssembly来做,例如WebAssembly 在白鹭引擎5.0中的实践

现在开始学习WebAssembly

说了WebAssembly这么多的好处,那么我要从哪里开始入手呢?这里给大家推荐几个比较好的学习资源

  1. awesome-wasm awesome系列,万物皆可awesome,哪里不会点哪里,妈妈再也不用担心我的学习。
  2. WebAssembly 现状与实战 IBM Developer上的关于WebAssembly的介绍,建议入门阅读
  3. http://webassembly.org.cn/ 官方WebAssembly的中文镜像。
  4. assemblyscript WebAssembly的js实现,根本上就是把js转成机器码让速度更快

密码学介绍及RSA算法概述

说来惭愧,我大学其实是信息安全专业的,但是啥都没学会,所以自学了web开始当一个搬运工。信息安全专业最重要的一门课就是密码学,下面我凭着残留的记忆还有没扔的大学教材和请教读研大学同学后总结的点,给大家介绍一下这门神奇的学科。

密码学介绍

密码编码学有两个分支,密码分析学和密码使用学,
分析学指破译一种加密方式的技巧,一个加密算法是否可靠都需要分析来证明。

使用学又分为三个分支:对称算法,非对称算法,密码协议。

对称算法是双方共享一个密钥,使用同样的方法进行加密和解密,现代的比如AES,DES,3DES等,古代的斯巴达密码棒和凯撒加密。

非对称算法用户会持有一个公私钥对儿。A想要给B发送信息,就需要A用B的公钥加密,B收到后使用私钥解密。常见的又RSA和椭圆曲线算法。
密码协议主要内容是如何搭配算法实现最优方案,对称与非对称有各自都优缺点,所以要一起使用才最好,最典型的例子就是传输层安全(TLS)方案,所有web浏览器都已使用这个方案,访问https的网站,我们发送信息时需要用服务器给的公钥进行加密,中间还涉及到CA,数字签名等等。

白话RSA算法


(这三个人目前还在世,MD5也是他们搞得)
RSA的出现并不是为了取代对称加密算法,因为RSA的执行需要很多计算,这也是为什么https的网站访问比较慢的原因,真正用来加密大量数据的还是对称加密算法,RSA为对称加密的公钥保驾护航。
RSA的底层原理就是整数的因式分解,两个大素数在乘积上很容易计算,但是对乘积的因式分解确实非常困难的,几乎是不可能完成的。

1.生成密钥

第一小步,bob选择两个质数(除了1和它本身以外不再有其他因数)
p = 5, q = 11
n = p*q = 55
计算p * q的欧拉函数 U(55) = (5-1)(11-1) = 40 (欧拉函数指的是在小于N的数环中于N互质的数字的个数 比如欧拉8 = 4 (1,3,4,7), 欧拉6 = 2 (1,5) )
欧拉函数最终可以推算出公式

点击查看推导过程

// 根据上面的公式js实现的欧拉函数
function isPrime(i) {
        for (var a = 2; a < i; a++) {
            if (i % a == 0) {
                return false;
            }
        }
        return true;
    }
    function getPrimes(n) {
        var current = n;
        for (var b = 2; b <= n; b++) {
            if (isPrime(b) && (n % b) == 0) {
                current *= (1 - 1 / b);
            }
        }
        return Math.round(current);
    }
    getPrimes(55)  // 40

第二小步,bob从1到40选择一个数字e=3

计算e对U(55)的模反逆元d( 费马小定理 ed ≡ 1 (mod φ(n)),自行搜索 )
等价于 3d – 1 = k40 —> 3x – 40y = 1 对这个二元一次方程求解。
使用 带入消元发 计算xy过程如下
40 = 3 * 13 + 1
然后把它们改写成“余数等于”的形式
1 = 40 – 3 * 13
然后一步一步替换,提取公因式得出 x=-13 y=-1 所以d=-13
在整数环{0,40}内-13对应27,所以私钥d为27。

计算出公钥为(n,e) = (55,3),私钥为(n,d) = (55, 27)
为什么说对称加密无法破解,当攻击者拿到公钥n,e,是无法推出私钥d的,因为根据公式ed ≡ 1 (mod φ(n)),算出d必须要知道n的欧拉函数是多少,φ(n)=(p-1)(q-1),如果能将n进行因数分解,就能算出d,可是大整数的因式分解是非常困难的,只有暴力破解。

2.加密与解密

加密公式为 m^e ≡ c (mod n)
解密公式为 c^d ≡ m (mod n)
已知明文m = 14,公钥(55,3)
14^3 = c(mod55)
算出 c = 49
解密
(49^27)mod55 = 14 解密成功
需要注意的一点是浏览器里计算Math.pow(49,27)%55 = 36。49的27次方已经超出了大多数语言的最大安全数值,所以我们在计算的时候需要自己想办法提公因式,结合律交换律之类的。
为了算出正确的14我自己实现了一个方法

Math.bigM = (s,n,m) => {
    let mi_gap = 1
    while(Math.pow(s,mi_gap)<Number.MAX_SAFE_INTEGER){
        mi_gap += 1
    }
    mi_gap -= 1
    let mi_gap_mi = Math.floor(n/mi_gap)
    let mi_gap_mo = n%mi_gap
    return Math.pow(Math.pow(s,mi_gap)%m, mi_gap_mi)*(Math.pow(s,mi_gap_mo)%m)%m
}

rxjs入门学习笔记

之前早就听说并简单看过响应式函数编程,但并没有仔细的学习并写过代码练习,(其实作为一个程序员特别是跟UI打交道的程序员,挺不应该的…响应式函数编程号称是找到了解决UI编程复杂性的方法),今天抽时间学习练习一下(比较浅薄的记录,对rxjs很熟悉的人就不要看了…)。

先了解一些基本概念,
Functional programming: 函数式编程,参考维基百科链接

Reactive Programming: 响应式编程,参考维基百科链接

Functional Reactive Programming: 响应式函数编程, 参考维基百科链接
简单说就是: 函数式编程+响应式编程, 思想来自于微软。在各个语言都有其实现, OC和Swift有ReactCocoa, Java有RxJava, Javascript有Rxjs。

为什么选择Javascript来学习呢?因为我猜Javascript更有可能统一宇宙吧…

先学习一下rxjs中的一些基本概念(当然其他语言的实现中也包含这些基本概念)。

Observable
一组可被观测的值,可以用来表示未来的值或者事件
Observer
观察者,知道如何处理来自Observable的值
Subscription
订阅,表示Observable的执行,Observable只有在subscribe时才开始执行
Operators
纯函数,用来处理和转换Observable发出的事件流
Subject
既是Observer又是Observable,可以用来实现广播的功能
Schedulers
用来集中处理并发,允许我们控制订阅的执行的时机和事件通知的时机

不太想也没办法一开始就把所有概念理解的非常清楚,在对基本的概念有了大致了解的基础上,还是动手来写点代码会更有助于理解一些。

因此考虑实现一个比较常见的业务场景来使用下rxjs: 搜索框输入文字,在输入的过程中,边输入边查询服务器的服务并显示输入提示的信息。
这个场景看似比较简单,但如果完全正确实现,还是有一些细节要考虑:
1. 监听输入框的文本变化
2. 查询服务器搜索提示服务
3. 取得结果并显示出来
4. 为了防止每次输入都去请求服务器造成浪费,需要输入以后延迟一小段时间(例如500ms)再去请求服务器
5. 请求服务器过程是异步的,在请求过程中,如果输入框内容变化了,请求结果不应该显示出来

按这些点分别讲一下实现:
1)监听输入框文本变化事件, 通过查文档,rxjs有一个fromEvent方法可以将dom事件转成Observable
2) 先用Mock的方法模拟一个服务器请求和相应的结果
3) 直接显示结果
4) 仔细查了下,在Operator里有一个throttleTime可以实现延迟执行,在规定时间内只执行1次的需求
5) 请求结果带请求时的输入参数,如果请求时输入参数和返回时输入参数有了变化,则不更新搜索提示

写的比较简单,但实际还是摸索了很久/(ㄒoㄒ)/

源码如下:


<div> <br /> <span>{{recommendResult}}</span> </div> import { fromEvent, asyncScheduler } from 'rxjs'; import { throttleTime, map, switchMap } from 'rxjs/operators'; export default { name: 'HelloWorld', data() { return { recommendResult: '', }; }, mounted() { function mockApiCall(text) { return new Promise((resolve) =&gt; { setTimeout(() =&gt; { resolve({ query: text, recommend: `recommend of ${text}`, }); }, 300); }); } fromEvent(this.$refs.input, 'input') .pipe( throttleTime(500, asyncScheduler, { leading: false, trailing: true }), map(() =&gt; this.$refs.input.value), switchMap(text =&gt; mockApiCall(text)), ) .subscribe((result) =&gt; { if (result.query === this.$refs.input.value) { this.recommendResult = result.recommend; } }); }, };

几个小坑:
1. rxjs 最新的api和之前的差不少,所以网上的一些文章(没标明版本的)不太好参考,还是看官方文档比较靠谱
2. throttleTime 如果不加参数,默认是{ leading: true, trailing: false }这样的效果就是,输入一开始就有一个事件出来,但是最终停止了是没有事件的,但我们想要的是停止的时候需要有一个事件,因此要改成{ leading: false, trailing: true }
3. mock的api请求是基于promise的接口,想融入到Observable的事件流里边,花了一番精力,理解文档,上网搜,才知道用switchMap这个Operator可以实现

看源码,核心的控制整个事件监听和响应的流程的代码一共10行左右,提到的那些小点也都满足了,整个控制流程不会散落在各个函数里边,代码还是比较清晰的,更少的代码代表着更少的bug,不过如果对rx不熟的人理解起代码来或者上手还是比较费劲的。

思考: 第4点其实有更合理的方式,应该是输入停止一小段时间(例如300ms)再去请求服务器,这样如果用户一直在快速输入,其实是不需要任何请求的,如果采用这种方式,如何用rxjs来实现呢?

注: 文中rxjs版本基于6.5.2, 主要参考自官网: rxjs官网链接

详解ios crash分析

原始文档及Demo可以在 git@gitlab.pri.ibanyu.com:cailei5072/d_palfish_ios.git crash-analyze文件夹下找到

一、各工程说明

lib/MyCrashLib

这个是库工程,其中Target Universal 用来生成对外的Fat Framework, 支持 x86_64 (simulator), arm64 (device)

lib/TestMyCrashLib

直接依赖了MyCrashLib工程,库提供方自己测试用

app/MyCrashApp

依赖Fat Framework,每次Framework发版都需要手动更新,模拟了业务方使用三方Framework的场景

二、crash分析

工程设置的一些定义

<

h3>选项 |

<

h3>值
:————————– | :————-
Build Active Architecture Only | NO
Debug Information Format | DWARF with dSYM File
Strip Debug Symbols During Copy | NO

准备工作

  • lipo -info 确认支持x86_64和arm64
  • 找到xcode自带的symbolicatecrash

可以在path中的路径下生成软链接

sudo ln -s /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash /usr/local/bin/symbolicatecrash
  • export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

可以添加到启动文件

# ~/.zshrc

# 在最后一行添加
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
  • app.dSYM 和 lib.dSYM 放到同一个路径下

开始生成

  • 在手机上运行App,分别点击 AppLib,让其crash在不同的地方
  • ./symbolicatecrash xxx.crash的文件路径 xxx.app.dSYM的文件路径 > log.crash
  • mdfind "com_apple_xcode_dsym_uuids == *"检查下列出的.dSYM全不全

验证

dwarfdump -u dwarfdump -u 可以查看 UUID

➜  快速Dump-Crash实验 git:(master) ✗ dwarfdump -u MyCrashApp.app.dSYM
UUID: D2FD226E-7F12-362F-809D-DC2EBB02A67E (arm64) MyCrashApp.app.dSYM/Contents/Resources/DWARF/MyCrashApp

➜  快速Dump-Crash实验 git:(master) ✗ dwarfdump -u MyCrashLib.framework.dSYM
UUID: 3D50094C-0B91-3D5E-8E1E-110F4F7474C6 (arm64) MyCrashLib.framework.dSYM/Contents/Resources/DWARF/MyCrashLib

.crash文件里载入image时应有

Binary Images:
0x102b1c000 - 0x102b23fff MyCrashApp arm64  <d2fd226e7f12362f809ddc2ebb02a67e> /var/containers/Bundle/Application/D26B9146-4F17-4F5A-AE8F-2FF11D6F2B3B/MyCrashApp.app/MyCrashApp
0x102b4c000 - 0x102b53fff MyCrashLib arm64  <3d50094c0b913d5e8e1e110f4f7474c6> /var/containers/Bundle/Application/D26B9146-4F17-4F5A-AE8F-2FF11D6F2B3B/MyCrashApp.app/Frameworks/MyCrashLib.framework/MyCrashLib


Binary Images:
0x100450000 - 0x100457fff MyCrashApp arm64  <d2fd226e7f12362f809ddc2ebb02a67e> /var/containers/Bundle/Application/D26B9146-4F17-4F5A-AE8F-2FF11D6F2B3B/MyCrashApp.app/MyCrashApp
0x100474000 - 0x10047bfff MyCrashLib arm64  <3d50094c0b913d5e8e1e110f4f7474c6> /var/containers/Bundle/Application/D26B9146-4F17-4F5A-AE8F-2FF11D6F2B3B/MyCrashApp.app/Frameworks/MyCrashLib.framework/MyCrashLib

App UUID :

  • D2FD226E-7F12-362F-809D-DC2EBB02A67E

Lib UUID :

  • 3D50094C-0B91-3D5E-8E1E-110F4F7474C6

两次crash虽然载入的内存段不一样,但是UUID和dSYM中的保持一致

<

h3>

注意:每次代码改动后,生成的UUID会变化,所以一定保证release版本的ipa,dSYM,和git的tag保持一致

三、没有耐心?

已经按照上述做了个可以快速验证结果的,放到了 快速Dump-Crash实验

运行命令

symbolicatecrash app.crash . > app.log
symbolicatecrash lib.crash . > lib.log

并查看结果

Lottie简介

一. 什么是Lottie

Lottie 是Airbnb开源的一套跨平台的完整的动画效果解决方案,设计师设计出来的动画,可以通过Bodymovin 插件将设计好的动画导出成 JSON 格式,就可以直接运用在 iOS、Android、Web 和 React Native之上。通过加载Bundled JSON文件或URL,以AE导出的文件为资源,完美实现动画效果。基本所有不涉及复杂交互行为的需求动画都可以通过Lottie实现。
利用Lottie可以很简单的实现复制的动画,如下就是利用Lottie实现的缩放动画

二. 集成Lottie

如何导入Lottie到项目中
在Podfile中添加lottie
pod ‘lottie-ios’, ‘2.5.3’
执行 $ pod install 就完成了 Lottie 库的接入;

三. Lottie简单实用
  • 将AE文件导出成json导入到项目bundle中,如果动画和图片是分离的,需要将图片也一同导入bundle中,
  • 项目中使用的地方引入头文件#import
  • 使用 LOTAnimationView 加载 json 文件
    LOTAnimationView *fishAnimation = [LOTAnimationView animationNamed:fishName];
    fishAnimation.contentMode = UIViewContentModeScaleAspectFit;
    [self.containerView addSubview:fishAnimation];
    fishAnimation.loopAnimation = YES;
    [fishAnimation autoPinEdgesToSuperviewEdges];
    [fishAnimation play];

只需要短短的几行代码,一个复杂的动画就实现了。LOTAnimationView为何有如此强大的功能,我们可以点到头文件去看下具体实现,进去后发现LOTAnimationView继承自LOTView;那么LOTView是什么呢,在LOTAnimationView_Compat文件中发现

在苹果手机和模拟器中,LOTView是UIView的别名,在mac中是NSView的别名。其实LOTAnimationView就是UIView的子类,拥有UIView所有的属性,我们可以设置大小,位置;而且Lottie用的方法也是计算各种bezier path,只不过这些path已经被AE导出的json预先算好了,然后通过框架做插件,交给系统SDK提供的动画框架渲染,所以在性能上也是ok的,只要不是一个页面出现大量的json动画,就不会出现卡顿等现象;

四. json文件包含了那些内容

我们利用LOTAnimationView加载一个json文件,就能出现各种动画,那么json文件里包含了那些内容呢?将json格式化之后

Lottie动画Json结构分为4层:
结构层:可以读取到动画画布的宽高,帧数,背景色,时间,起始关键帧,结束帧等信息。
asset:图片资源信息集合,这里放置的是 制作动画时引用的图片资源。
layers:图层集合,这里可以获取到多少图层,每个图层的开始帧 结束帧等。
shapes:元素集合,可以获取到每个图层都包含多个动画元素。
LOTAnimationView就是通过解析json中的这些字段来展现动画的。

五. Lottie如何解析json

先看LOTAnimationView的初始化方法

+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName {
  return [self animationNamed:animationName inBundle:[NSBundle mainBundle]];
}

+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
  LOTComposition *comp = [LOTComposition animationNamed:animationName inBundle:bundle];
  return [[self alloc] initWithModel:comp inBundle:bundle];
}

可以看到LOTComposition这个类,在这个类中可以看到如下方法

- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
     withAssetBundle:(NSBundle *)bundle {
  NSNumber *width = jsonDictionary[@"w"];
  NSNumber *height = jsonDictionary[@"h"];
  if (width && height) {
    CGRect bounds = CGRectMake(0, 0, width.floatValue, height.floatValue);
    _compBounds = bounds;
  }

  _startFrame = [jsonDictionary[@"ip"] copy];
  _endFrame = [jsonDictionary[@"op"] copy];
  _framerate = [jsonDictionary[@"fr"] copy];

  if (_startFrame && _endFrame && _framerate) {
    NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1;
    NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
    _timeDuration = timeDuration;
  }

  NSArray *assetArray = jsonDictionary[@"assets"];
  if (assetArray.count) {
    _assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
  }

  NSArray *layersJSON = jsonDictionary[@"layers"];
  if (layersJSON) {
    _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
                                            withAssetGroup:_assetGroup
                                             withFramerate:_framerate];
  }

  [_assetGroup finalizeInitializationWithFramerate:_framerate];
}

里面解析了json文件中的”w”,”h”,”ip”,”op”,”fr”,”assets”,”layers”字段;其中”assets”字段是通过LOTAssetGroup解析,”layers”通过LOTLayerGroup解析,LOTAssetGroup负责解析json文件中的图片资源,LOTLayerGroup负责解析Json文件中的动画layer,具体这两个类是如何实现的,后面继续研究…

使用Android Studio Lint静态分析(三)

主要内容

  1. 在终端通过Gradle命令执行Lint检查。
  2. 在编译时进行lint检查。
  • Android Studio中Project目录下有两个文件gradlewgradlew.bat分别是在Mac/Linux系统和Windows系统上的Gradle命令工具。在Mac系统Project目录下执行./gradlew -p ${module_dir} lint命令,就可以执行对指定module按照Android Studio默认支持的扫描规则进行Lint检查。

  • Gradle提供了名为lintOptions的插件对Lint扫描进行个性化配置。

Gradle文件中的Lint配置

在${module_dir}/build.gradle文件android块内使用DSL对象lintOptions对Lint进行配置,配置项可以参考Android Plugin DSL Reference: LintOptions

android {
    lintOptions {
        // true--关闭lint报告的分析进度
        quiet true
        // true--错误发生后停止gradle构建
        abortOnError false
        // true--只报告error
        ignoreWarnings true
        // true--忽略有错误的文件的全/绝对路径(默认是true)
        //absolutePaths true
        // true--检查所有问题点,包含其他默认关闭项
        checkAllWarnings true
        // true--所有warning当做error
        warningsAsErrors true
        // 关闭指定问题检查
        disable 'TypographyFractions','TypographyQuotes'
        // 打开指定问题检查
        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
        // 仅检查指定问题
        check 'NewApi', 'InlinedApi'
        // true--error输出文件不包含源码行号
        noLines true
        // true--显示错误的所有发生位置,不截取
        showAll true
        // 回退lint设置(默认规则)
        lintConfig file("default-lint.xml")
        // true--生成txt格式报告(默认false)
        textReport true
        // 重定向输出;可以是文件或'stdout'
        textOutput 'stdout'
        // true--生成XML格式报告
        xmlReport false
        // 指定xml报告文档(默认lint-results.xml)
        xmlOutput file("lint-report.xml")
        // true--生成HTML报告(带问题解释,源码位置,等)
        htmlReport true
        // html报告可选路径(构建器默认是lint-results.html )
        htmlOutput file("lint-report.html")
        //  true--所有正式版构建执行规则生成崩溃的lint检查,如果有崩溃问题将停止构建
        checkReleaseBuilds true
        // 在发布版本编译时检查(即使不包含lint目标),指定问题的规则生成崩溃
        fatal 'NewApi', 'InlineApi'
        // 指定问题的规则生成错误
        error 'Wakelock', 'TextViewEdits'
        // 指定问题的规则生成警告
        warning 'ResourceAsColor'
        // 忽略指定问题的规则(同关闭检查)
        ignore 'TypographyQuotes'
    }
}

部分配置项的说明

下面前三条所涉及的配置项,后面跟的参数都是issue_id,这些ID值跟lint.xml文件中使用的ID是同一个集合。

  1. 检查结果警报级别四个:fatal、error、warning、ignore。
  2. 检查结果输出方式三种:textReport、htmlReport、xmlReport,每一种都有开关并可以指定输出位置。后两种默认放在${module_dir}/build/reports/目录下。
  3. enable、disable是对某些检查做临时的开关设置。
  4. lintConfig指定一个lint文件,文件的内容格式与使用Android Studio Lint静态分析(二)中的lint.xml一样,文件名不必指定为lint.xml,文件路径传给lintConfig的file做参数。

配置本地编译时执行lint检查

配置Gradle脚本可实现编译Android工程时执行Lint检查:好处是既可以尽早发现问题,又可以有强制性;缺点是对编译速度有一定的影响。

编译Android工程执行的是assemble任务,让assemble依赖lint任务,即可在编译时执行Lint检查;同时配置LintOptions,发现Error级别问题时中断编译。

  • 在Application模块的gradle中加入配置
android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def lintTask = tasks["lint${variant.name.capitalize()}"]
        output.assemble.dependsOn lintTask
    }
}
  • 在library模块的gradle中加入配置
android.libraryVariants.all { variant ->
    variant.outputs.each { output ->
        def lintTask = tasks["lint${variant.name.capitalize()}"]
        output.assemble.dependsOn lintTask
    }
}

参考文章