0%

ANR的原因

  • 后台Service执行超过20s
  • 前台Service执行超过10s
  • 按键或触摸事件超过5s
  • 前台广播超过10s
  • 后台广播超过60s
  • ContentProvider超时

系统针对这些场景都有各自的ANR监控机制

原理分析

Service

ANR的监控是在
ActiveServices#realStartServiceLocked#bumpServiceExecutingLocked#scheduleServiceTimeoutLocked
建立的

从上图能看出Service启动的时候会给AMS发送一个延时消息,如果service执行完成,那么会移除这个消息,否则这个消息执行serviceTimeout这里就会弹出ANR的弹窗

Read more »

更新:

2022-03-01更新AOSP11.0的编译
2017-09-09更新AOSP8.0的编译
2016-05-01更新AOSP6.0编译

配置OSX系统

  1. 安装Xcode
  2. 找到Xcode安装目录,右键打开show package content
  3. 创建/Developer/SDK目录
  4. MacOSX10.11.sdkContents/Developer/Platforms/MacOSX.platform/Developer/SDKs/复制到/Developer/SDK
  5. Xcode.app复制到到/Developer目录下

完成Xcode的配置

  1. 打开Xcode
  2. Xcode的菜单打开Preferences
  3. 选择Location标签
  4. Command Line Tools中选择Xcodd 7.2(具体版本依个人电脑上的Xcode版本而定)
Read more »

编译环境

操作系统: Mac OS 10.13.6
FFMpeg版本: 4.1
NDK版本: android-ndk-r18b
编译器: clang

构建ndk编译链

执行ndk自带的脚本构建编译链,会生成编译环境

1
$NDK/build/tools/make_standalone_toolchain.py --arch arm --api 21 --install-dir /tmp/my-android-toolchain

构建ffmpeg编译环境

修改ffmpeg项目根目录下configure文件将这几个环境变量替换成如下

1
2
3
4
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)'
Read more »

概述

Zookeeper提供了三种选举方式

  • AuthFastLeaderElection
  • 带认证的AuthFastLeaderElection
  • FastLeaderElection

选择机制

选举状态

1
LOOKING, FOLLOWING, LEADING, OBSERVING;
1
2
3
4
5
6
7
8
9
10
11
12
13
synchronized public void startLeaderElection() {
try {
if (getPeerState() == ServerState.LOOKING) {
currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
}
} catch(IOException e) {
RuntimeException re = new RuntimeException(e.getMessage());
re.setStackTrace(e.getStackTrace());
throw re;
}

this.electionAlg = createElectionAlgorithm(electionType);
}
Read more »

概述

Zookeeper是分布式调度服务。在分布式系统当中,当集群中的部分节点出现故障,那么需要通知其他节点。Zookeeper主要就是用来解决这个问题

实例

以上将编写Zookeeper客户端程序,来展示其是如何管理集群中的节点

组和成员

Zookeeper可以看成是一个高可用的文件系统,但它没有文件和文件夹的概念,其是一个树状结构。它的基本单元是znode,其主要用来存储数据。

创建组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

open class ConnectionWatcher : Watcher {

var zk: ZooKeeper? = null
var connectedSignal: CountDownLatch = CountDownLatch(1)

@Throws(IOException::class, InterruptedException::class)
fun connect(hosts: String?) {
zk = ZooKeeper(hosts, CreateGroup.SESSION_TIMEOUT, this)
connectedSignal.await()
}

override fun process(event: WatchedEvent?) {
if (event!!.state == Watcher.Event.KeeperState.SyncConnected) {
connectedSignal.countDown()
}
}

@Throws(InterruptedException::class)
fun close() {
zk!!.close()
}
}

class CreateGroup : ConnectionWatcher() {
companion object {
val SESSION_TIMEOUT: Int = 5000
}

@Throws(KeeperException::class, InterruptedException::class)
fun create(groupName: String?) {
val path = "/" + groupName
val createPath = zk!!.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
System.out.println("Created " + createPath)
}
}
Read more »

简介

ChannelGo的一种类型,其一种可以用来发送或者接受数据的管道。Go使用它来实现并发。Go的并发模型是CSP(通讯顺序进程)。CSP由并发执行的实体所组成,实体之间通过消息进行通讯。发送消息的通道就是Channel,协程就是并发执行的实体

1
2
3
ch := make(chan int, 100)
ch <- v
v := <- ch

Channel类型

Channel必须使用make来创建指定类型的管道,可以指定管道容量,它的操作符是<-或者->,箭头代表数据的流动方向,如果没有指定方向,那么其时双向

1
make(chan int, 100)

如果没有设置容量,那么Channel没有缓存,只有senderreceiver都准备好了,它们的通讯才会发生。如果设置了容量,那么会等到缓存满了sender才会阻塞,只有缓存空了receiver才会阻塞。通过close方法来关闭Channel。可以在多个协程中往一个Channel中发送/接受数据而不需要考虑数据同步。Channel可以作为一个FIFO的队列。接受数据如下

1
v, ok := <-ch

发送/接受数据

1
2
3
4
c := make(chan int)
defer close(c)
go func() { c <- 10 }()
fmt.Println(<-c)

上述往Channel c中发送10,然后从c中取出。如果没有发送任何数据,那么c会一直block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total
}

func testSum() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
c := make(chan int, 10)
defer close(c)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c
fmt.Println(x, y, x+y)
}

上诉x, y会一直等到sum函数中的值算完并发送到c

1
2
3
4
5
6
7
8
9
10
11
c := make(chan int)
go func() {
defer close(c)
for i := 0; i < 10; i++ {
c <- i
}
}()

for i := range c {
fmt.Println(i)
}

可以使用range来迭代取出c关闭前,所有的数据。如果c不被关闭,那么会一直blockrange

select

使用select来管理多路Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func fibonacci(c, quit chan int) {
defer close(c)
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-time.After(5 * time.Second):
println("timeout")
break
case <-quit:
fmt.Println("quit")
return
}
}
}

c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)

上述使用c作为数据体操作,使用quit作为数据结束操作。如果有多个case需要处理多个Channel,那么go会随机选择一个处理。如果没有case处理,那么会选择default处理,如果没有default,那么select会一直block,直到有case需要处理。上述代码如果把quit <- 0注释掉,那么每5秒回打印一次timeout

深入chan源码

chan的源码位于src/runtime/chan.go

数据结构

chan的数据结构是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
type hchan struct {
qcount uint // 队列中的总数据
dataqsiz uint // 环形数组大小
buf unsafe.Pointer // 环形数组
elemsize uint16 //数据类型大小
closed uint32 //channel是否关闭
elemtype *_type // 数据类型
sendx uint // 发送队列索引
recvx uint // 接收队列索引
recvq waitq // 由<-val阻塞在channel上的队列
sendq waitq // 由val<-阻塞在channel队列

// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex //锁,用于保护上述所有字段
}

type waitq struct {
first *sudog
last *sudog
}

type sudog struct {
// The following fields are protected by the hchan.lock of the
// channel this sudog is blocking on. shrinkstack depends on
// this for sudogs involved in channel ops.

g *g

// isSelect indicates g is participating in a select, so
// g.selectDone must be CAS'd to win the wake-up race.
isSelect bool
next *sudog
prev *sudog
elem unsafe.Pointer // data element (may point to stack)

// The following fields are never accessed concurrently.
// For channels, waitlink is only accessed by g.
// For semaphores, all fields (including the ones above)
// are only accessed when holding a semaRoot lock.

acquiretime int64
releasetime int64
ticket uint32
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}

channel由一个环形队列用于存储数据,一个写操作阻塞队列,一个读操作阻塞队列以及一个锁组成。而读写操作的列表由sudog实现

构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func makechan(t *chantype, size int) *hchan {
elem := t.elem

// compiler checks this but be safe.
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}

if size < 0 || uintptr(size) > maxSliceCap(elem.size) || uintptr(size)*elem.size > maxAlloc-hchanSize {
panic(plainError("makechan: size out of range"))
}

// Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.
// buf points into the same allocation, elemtype is persistent.
// SudoG's are referenced from their owning thread so they can't be collected.
// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
var c *hchan
switch {
case size == 0 || elem.size == 0:
// Queue or element size is zero.
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
c.buf = unsafe.Pointer(c)
case elem.kind&kindNoPointers != 0:
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
// Elements contain pointers.
c = new(hchan)
c.buf = mallocgc(uintptr(size)*elem.size, elem, true)
}

c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)

if debugChan {
print("makechan: chan=", c, "; elemsize=", elem.size, "; elemalg=", elem.alg, "; dataqsiz=", size, "\n")
}
return c
}

首先进行元素类型大小限制和对齐限制。检查缓存大小,确保有足够的堆内存分配给这个chan。如果chan类型不含指针,并且大小大于0,那么分配一段内存给它。否则分配默认内存大小给它

send

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
* generic single channel send/recv
* If block is not nil,
* then the protocol will not
* sleep but return if it could
* not complete.
*
* sleep can wake up with g.param == nil
* when a channel involved in the sleep has
* been closed. it is easiest to loop and re-run
* the operation; we'll see that it's now closed.
*/
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
if c == nil {
if !block {
return false
}
gopark(nil, nil, "chan send (nil chan)", traceEvGoStop, 2)
throw("unreachable")
}

if debugChan {
print("chansend: chan=", c, "\n")
}

if raceenabled {
racereadpc(unsafe.Pointer(c), callerpc, funcPC(chansend))
}

// Fast path: check for failed non-blocking operation without acquiring the lock.
//
// After observing that the channel is not closed, we observe that the channel is
// not ready for sending. Each of these observations is a single word-sized read
// (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).
// Because a closed channel cannot transition from 'ready for sending' to
// 'not ready for sending', even if the channel is closed between the two observations,
// they imply a moment between the two when the channel was both not yet closed
// and not ready for sending. We behave as if we observed the channel at that moment,
// and report that the send cannot proceed.
//
// It is okay if the reads are reordered here: if we observe that the channel is not
// ready for sending and then observe that it is not closed, that implies that the
// channel wasn't closed during the first observation.
if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
return false
}

var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}

//锁住当前状态
lock(&c.lock)

//如果当前channel已经关闭,那么直接抛异常
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}

//发现有阻塞的读协程
if sg := c.recvq.dequeue(); sg != nil {
// Found a waiting receiver. We pass the value we want to send
// directly to the receiver, bypassing the channel buffer (if any).
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}

//还有可用的缓存空间
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
qp := chanbuf(c, c.sendx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
}
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}

if !block {
unlock(&c.lock)
return false
}

//当前缓存已满,阻塞当前routine
// Block on the channel. Some receiver will complete our operation for us.
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
mysg.elem = ep
mysg.waitlink = nil
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.waiting = mysg
gp.param = nil
c.sendq.enqueue(mysg)
goparkunlock(&c.lock, "chan send", traceEvGoBlockSend, 3)

// someone woke us up.
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
if gp.param == nil {
if c.closed == 0 {
throw("chansend: spurious wakeup")
}
panic(plainError("send on closed channel"))
}
gp.param = nil
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
mysg.c = nil
releaseSudog(mysg)
return true
}
  1. 首先判断chan是否为空.如果为空,那么会使当前的goroutine进入休眠状态。然而go启动时会检测系统的运行状态。在这里其发现发数据的goroutine和收数据的goroutine都进入了休眠状态,那么系统会直接报错
    all goroutines are asleep - deadlock!

  2. chan非空
    2.1 如果有读goroutine阻塞在channel上,那么直接调用send方法将数据发送给goroutine
    2.2 当前的缓存还有空间,那么将数据放到缓存里。并且调整sendxqcount的大小
    2.3 当前的缓存空间已满,阻塞当前的goroutine

这里数据的传递采用内存复制的方式,将写列表的数据复制到读列表数据上

receive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// raceenabled: don't need to check ep, as it is always on the stack
// or is new memory allocated by reflect.

if debugChan {
print("chanrecv: chan=", c, "\n")
}

if c == nil {
if !block {
return
}
gopark(nil, nil, "chan receive (nil chan)", traceEvGoStop, 2)
throw("unreachable")
}

// Fast path: check for failed non-blocking operation without acquiring the lock.
//
// After observing that the channel is not ready for receiving, we observe that the
// channel is not closed. Each of these observations is a single word-sized read
// (first c.sendq.first or c.qcount, and second c.closed).
// Because a channel cannot be reopened, the later observation of the channel
// being not closed implies that it was also not closed at the moment of the
// first observation. We behave as if we observed the channel at that moment
// and report that the receive cannot proceed.
//
// The order of operations is important here: reversing the operations can lead to
// incorrect behavior when racing with a close.
if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
atomic.Load(&c.closed) == 0 {
return
}

var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}


lock(&c.lock)

//没有数据
if c.closed != 0 && c.qcount == 0 {
if raceenabled {
raceacquire(unsafe.Pointer(c))
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}

//有阻塞的写队列。则直接接受数据
if sg := c.sendq.dequeue(); sg != nil {
// Found a waiting sender. If buffer is size 0, receive value
// directly from sender. Otherwise, receive from head of queue
// and add sender's value to the tail of the queue (both map to
// the same buffer slot because the queue is full).
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}

//直接从队列中取数据
if c.qcount > 0 {
// Receive directly from queue
qp := chanbuf(c, c.recvx)
if raceenabled {
raceacquire(qp)
racerelease(qp)
}
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
typedmemclr(c.elemtype, qp)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.qcount--
unlock(&c.lock)
return true, true
}

if !block {
unlock(&c.lock)
return false, false
}

//缓冲为空,阻塞当前goroutine
// no sender available: block on this channel.
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
mysg.elem = ep
mysg.waitlink = nil
gp.waiting = mysg
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.param = nil
c.recvq.enqueue(mysg)
goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3)

// someone woke us up
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
closed := gp.param == nil
gp.param = nil
mysg.c = nil
releaseSudog(mysg)
return true, !closed
}

close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
func closechan(c *hchan) {
if c == nil {
panic(plainError("close of nil channel"))
}

lock(&c.lock)
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("close of closed channel"))
}

if raceenabled {
callerpc := getcallerpc()
racewritepc(unsafe.Pointer(c), callerpc, funcPC(closechan))
racerelease(unsafe.Pointer(c))
}

c.closed = 1

var glist *g

// release all readers
for {
sg := c.recvq.dequeue()
if sg == nil {
break
}
if sg.elem != nil {
typedmemclr(c.elemtype, sg.elem)
sg.elem = nil
}
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, unsafe.Pointer(c))
}
gp.schedlink.set(glist)
glist = gp
}

// release all writers (they will panic)
for {
sg := c.sendq.dequeue()
if sg == nil {
break
}
sg.elem = nil
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, unsafe.Pointer(c))
}
gp.schedlink.set(glist)
glist = gp
}
unlock(&c.lock)

// Ready all Gs now that we've dropped the channel lock.
for glist != nil {
gp := glist
glist = glist.schedlink.ptr()
gp.schedlink = 0
goready(gp, 3)
}
}

如何当前通道为空或者已经关闭,抛出异常。将读写队列的goroutine放到glist中,然后唤醒glist中所有的goroutine

前言

面向对象语言都会有接口这个概念。go中的接口就是interface。它跟很多别的面向对象的接口很不一样。如java的接口需要使用implement来实现接口

interface定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type I interface{
Get() string
Set(string)
}

type S struct{
val string
}

func (s S) Get() string{
return s.val
}

func (s *S) Set(val string) {
s.val = val
}

上述代码就定义了一个go的接口,包含了一个方法。当然也可以不包含方法。gointerface是一种具有一组方法的类型。如果一个类型实现了一个interface的所有方法,我们就说该类型实现了该接口。其实go中所有的类型都实现了empty interfacego不需要使用关键字来实现interface,只需要实现interface包含的方法即可。上述代码struct S实现了interface I

interface多态

1
2
3
4
5
6
7
8
9
func f(i I) {
i.Set(10)
fmt.Println(i.Get())
}

func main() {
s := S{}
f(&s)
}

interface的一个重要用途就是体现在函数的参数上。函数的参数如果是interface,那么可以传入任意实现了该interface的类型。这就是多态

判断interface类型

go可以使用value, ok := em.(T)来检查类型

1
2
3
if t, ok := i.(*S); ok {
fmt.Println("s implements I", t)
}

上述代码用来检查i是否是*S类型,如果是,那么oktrue。如果需要区分多种类型那么可以使用switch

1
2
3
4
5
6
switch t := i.(type) {
case *T:
fmt.Println("i store *T", t)
case *S:
fmt.Println("i store *S", t)
}

empty interface

如果定义一个函数,其参数是empty interface。那么这个函数可以接受任何类型作为它的参数

1
func doSomething(v interface{}){}

运行时v并不是任意类型,v只是一个interface。之所以可以接受任何类型是go执行时传递到函数的任何类型都被自动转换成interface{}。至于运行中是如何转换的,可以参考interface{}类型的slice是无法接受任何类型的

1
2
3
4
5
6
7
8
9
10
func printAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}

func testInterfaceSlice() {
vals := []string{"stanley", "david", "oscar"}
printAll(vals)
}

上述代码执行会出错,因为go不会为interface{}的slice做自动转换。至于为什么,可以参考

interface{} 会占用两个字长的存储空间,一个是自身的methods数据,一个是指向其存储值的指针,也就是interface变量存储的值,因而slice[]interface{}其长度是固定的N2, 但是[]T的长度是Nsizeof(T),两种 slice 实际存储值的大小是有区别的。

虽然go不能帮我们自动转换,但我们可以手动转换

1
2
3
4
5
var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}

interface receiver

interface定义时并没有严格规定实现者的方法receiver是个value receiver或者pointer receiver。上述代码SSet Receiverpointer,也就是实现I的两个方法的receiver一个是value一个是pointer。如果使用f(s),那么传递给fs的一个拷贝。go中的函数都是按值传递

如果是按pointer调用,go会自动进行转换,因为有了指针,那么总是能够得到指针指向的值,如果是值调用,那么无法得知原始值是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type Animal interface {
Speak() string
}

type Dog struct{}

func (d Dog) Speak() string {
return "wangwang!"
}

type Cat struct{}

func (c Cat) Speak() string {
return "miao"
}

type Pig struct{}

func (p *Pig) Speak() string {
return "keng"
}

type Programmer struct{}

func (p Programmer) Speak() string {
return "Hello, world"
}

func testAnimal() {
animals := []Animal{Dog{}, Cat{}, &Pig{}, Programmer{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}

上述代码Pig使用的是指针,如果使用值会无法运行。按值传递,函数内部对于值的任何改变都不会影响到原始值

Java程序的启动入口是在main方法。因此如果我们要了解SpringBoot的启动流程,那么我们可以从main着手

入口

1
2
3
4
5
6
7
8
9
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}

构造SpringApplication

构造SpringApplication对象。在其内部进行初始化操作

1
2
3
4
5
6
7
8
9
10
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

判断是否是Web程序

首先将当前类添加到启动类中,判断是否是Web程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
public static boolean isPresent(String className, ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}

通过判断javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext能否被加载类确定是否是Web程序

找出所有应用初始化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}

spring.factories文件中找出keyApplicationContextInitializer的类实例并实例化,默认情况下从spring.factories文件中找出的keyApplicationContextInitializer的类有以下五种:

  1. org.springframework.boot.context.config.DelegatingApplicationContextInitializer
  2. org.springframework.boot.context.ContextIdApplicationContextInitializer
  3. org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
  4. org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
  5. org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

找出所有应用程序监听器

spring.factories中找出所有keyApplicationListener的应用程序监听器,过程跟找出应用程序初始化器一致。

默认情况下,被加载的应用程序监听器有

  1. org.springframework.boot.context.config.ConfigFileApplicationListener
  2. org.springframework.boot.context.config.AnsiOutputApplicationListener
  3. org.springframework.boot.logging.LoggingApplicationListener
  4. org.springframework.boot.logging.ClasspathLoggingApplicationListener
  5. org.springframework.boot.autoconfigure.BackgroundPreinitializer
  6. org.springframework.boot.context.config.DelegatingApplicationListener
  7. org.springframework.boot.builder.ParentContextCloserApplicationListener
  8. org.springframework.boot.ClearCachesApplicationListener
  9. org.springframework.boot.context.FileEncodingApplicationListener
  10. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

这些监听器在应用程序启动之后会回调执行对应的逻辑操作

SpringApplication run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}

首先构造StopWatch来统计每个任务的运行时间。

1
2
3
4
5
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}

获取所有的应用程序监听器,然后开始监听。构建DefaultApplicationArguments

1
2
3
4
5
6
7
8
9
10
11
12
13
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}

准备运行环境,获取或创建环境。配置属性,这个时候resources里的属性被读取。配置运行环境(profile)。回调监听器告知环境已经配置完成

1
2
3
4
5
6
7
8
9
10
11
12
13
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

打印Banner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

创建应用程序上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}

// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}

// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
listeners.contextLoaded(context);
}

准备上下文

1
2
3
4
5
6
7
8
9
10
11
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}

刷新上下文

所有的这些过程都会通过SpringApplicationRunListener对外发送SpringApplicationEvent事件,这是一个订阅发布模式

总结

SpringBoot启动时,会加载各种初始化器,监听器,然后会通过订阅发布模式对所有的监听者发送启动过程

2017年即将过去。在此对于过去的一年做一下简单的总结

满意的地方

  • 坚持写博客,坚持逛github
  • 技能树更加全面的开发,接触了更多的前后端技能,朝着T字形人才的方向更进一步
  • 读了很多源码
  • 工作日几乎每天晚上下班之后都会继续保持学习

不满意的地方

  • github虽然一直再更新,但多数都是自己的一些实践
  • 工作上,今年经历了一些不稳定,也从中收获了很多人生经验。有得有失
  • 股票亏了很多
  • 年初准备了考研计划,但之后的一些变动,考研计划被搁置
  • 日本没有没去,希望能够早日找到自己的另一半,携手赴日

2018年的期待

  • 坚持写博客,希望博客的质量能够提高
  • 投入更多的精力在后端领域
  • 读更多的源码
  • 考研
  • 股票能够好起来