-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
2149 lines (952 loc) · 324 KB
/
index.html
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html class="theme-next muse use-motion" lang="">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.1.3" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.3">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.3">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.3">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.3" color="#222">
<meta name="keywords" content="Hexo, NexT" />
<meta property="og:type" content="website">
<meta property="og:title" content="Creative, Challenging and Cogitative">
<meta property="og:url" content="http://houzhi.me/index.html">
<meta property="og:site_name" content="Creative, Challenging and Cogitative">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Creative, Challenging and Cogitative">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Muse',
version: '5.1.3',
sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: 'Author'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="http://houzhi.me/"/>
<title>Creative, Challenging and Cogitative</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">Creative, Challenging and Cogitative</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle"></p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
HOME
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
ARCHIVE
</a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://houzhi.me/2016/10/17/android-messagequeue-sourcecode-analysis/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="houzhi">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Creative, Challenging and Cogitative">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/10/17/android-messagequeue-sourcecode-analysis/" itemprop="url">Android MessageQueue源码分析</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Veröffentlicht am</span>
<time title="Post created" itemprop="dateCreated datePublished" datetime="2016-10-17T00:00:00+08:00">
2016-10-17
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>Android应用开发中离不开Handler,而Handler实际上最终是将Message交给MessageQueue。MessageQueue是Android消息机制的核心,熟悉MessageQueue能够帮助我们更清楚详细地理解Android的消息机制。这篇文章会介绍MessageQueue消息的插入(enqueueMessage)和读取(next),native层的消息机制,以及IdleHandler和SyncBarrier的逻辑原理。源码是基于6.0。</p>
<h3 id="MessageQueue的next与enqueueMessage方法"><a href="#MessageQueue的next与enqueueMessage方法" class="headerlink" title="MessageQueue的next与enqueueMessage方法"></a>MessageQueue的next与enqueueMessage方法</h3><h4 id="MessageQueue-enqueueMessage"><a href="#MessageQueue-enqueueMessage" class="headerlink" title="MessageQueue enqueueMessage"></a>MessageQueue enqueueMessage</h4><p>每次使用Handler发送一个Message的时候,最终会先调用MessageQueue的enqueueMessage方法将Message方法放入到MessageQueue里面。先看Handler的sendMessage方法,其他发送Message的内容也是一样的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div></pre></td><td class="code"><pre><div class="line">public final boolean sendMessage(Message msg)</div><div class="line">{</div><div class="line"> return sendMessageDelayed(msg, 0); // 调用下面这个方法</div><div class="line">}</div><div class="line"></div><div class="line">public final boolean sendMessageDelayed(Message msg, long delayMillis)</div><div class="line">{</div><div class="line"> if (delayMillis < 0) {</div><div class="line"> delayMillis = 0;</div><div class="line"> }</div><div class="line"> return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // 调用下面方法</div><div class="line">}</div><div class="line"></div><div class="line">public boolean sendMessageAtTime(Message msg, long uptimeMillis) {</div><div class="line"> MessageQueue queue = mQueue; //Handler中的mQueue</div><div class="line"> if (queue == null) {</div><div class="line"> RuntimeException e = new RuntimeException(</div><div class="line"> this + " sendMessageAtTime() called with no mQueue");</div><div class="line"> Log.w("Looper", e.getMessage(), e);</div><div class="line"> return false;</div><div class="line"> }</div><div class="line"> return enqueueMessage(queue, msg, uptimeMillis); // 下面方法</div><div class="line">}</div><div class="line"></div><div class="line">private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {</div><div class="line"> msg.target = this;</div><div class="line"> if (mAsynchronous) {</div><div class="line"> msg.setAsynchronous(true);</div><div class="line"> }</div><div class="line"> return queue.enqueueMessage(msg, uptimeMillis); //调用MessageQueue的enqueueMessage</div><div class="line">}</div></pre></td></tr></table></figure>
<p>最后会调用Handler的mQueue的enqueueMessage方法,而Handler的mQueue是从哪里来的呢?在Handler的构造函数中设置的,看默认的情况:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line">public Handler() {</div><div class="line"> this(null, false);</div><div class="line">}</div><div class="line">public Handler(Callback callback, boolean async) {</div><div class="line"> if (FIND_POTENTIAL_LEAKS) {</div><div class="line"> final Class<? extends Handler> klass = getClass();</div><div class="line"> if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&</div><div class="line"> (klass.getModifiers() & Modifier.STATIC) == 0) {</div><div class="line"> Log.w(TAG, "The following Handler class should be static or leaks might occur: " +</div><div class="line"> klass.getCanonicalName());</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> mLooper = Looper.myLooper();</div><div class="line"> if (mLooper == null) {</div><div class="line"> throw new RuntimeException(</div><div class="line"> "Can't create handler inside thread that has not called Looper.prepare()");</div><div class="line"> }</div><div class="line"> mQueue = mLooper.mQueue;</div><div class="line"> mCallback = callback;</div><div class="line"> mAsynchronous = async;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>无参Handler构造函数对应的是当前调用无参Handler构造函数线程的Looper,Looper是一个ThreadLocal变量,也就是说但是每个线程独有的,每个线程调用了Looper.prepare方法后,就会给当前线程设置一个Looper:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">public static void prepare() {</div><div class="line"> prepare(true);</div><div class="line">}</div><div class="line"></div><div class="line">private static void prepare(boolean quitAllowed) {</div><div class="line"> if (sThreadLocal.get() != null) {</div><div class="line"> throw new RuntimeException("Only one Looper may be created per thread");</div><div class="line"> }</div><div class="line"> sThreadLocal.set(new Looper(quitAllowed));</div><div class="line">}</div></pre></td></tr></table></figure>
<p>Looper里面包含了一个MessageQueue, 在Handler的构造函数中,会将当前关联的Looper的MessageQueue赋值给Handler的成员变量mQueue,enqueueMessage的时候就是调用该mQueue的enqueueMessage。关于Handler与Looper可以理解为每个Handler会关联一个Looper,每个线程最多只有一个Looper。Looper创建的时候会创建一个MessageQueue,而发送消息的时候,Handler就会通过调用mQueue.enqueueMessage方法将Message放入它关联的Looper的MessageQueue里面。介绍了Handler与Looper,然后继续看看MessageQueue的enqueueMessage方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div></pre></td><td class="code"><pre><div class="line">boolean enqueueMessage(Message msg, long when) {</div><div class="line"> if (msg.target == null) {</div><div class="line"> throw new IllegalArgumentException("Message must have a target.");</div><div class="line"> }</div><div class="line"> if (msg.isInUse()) {</div><div class="line"> throw new IllegalStateException(msg + " This message is already in use.");</div><div class="line"> }</div><div class="line"></div><div class="line"> synchronized (this) {</div><div class="line"> if (mQuitting) {</div><div class="line"> IllegalStateException e = new IllegalStateException(</div><div class="line"> msg.target + " sending message to a Handler on a dead thread");</div><div class="line"> Log.w(TAG, e.getMessage(), e);</div><div class="line"> msg.recycle();</div><div class="line"> return false;</div><div class="line"> }</div><div class="line"></div><div class="line"> msg.markInUse();</div><div class="line"> msg.when = when;</div><div class="line"> Message p = mMessages;</div><div class="line"> boolean needWake;</div><div class="line"> if (p == null || when == 0 || when < p.when) {</div><div class="line"> // New head, wake up the event queue if blocked.</div><div class="line"> msg.next = p;</div><div class="line"> mMessages = msg;</div><div class="line"> needWake = mBlocked;</div><div class="line"> } else {</div><div class="line"> // Inserted within the middle of the queue. Usually we don't have to wake</div><div class="line"> // up the event queue unless there is a barrier at the head of the queue</div><div class="line"> // and the message is the earliest asynchronous message in the queue.</div><div class="line"> needWake = mBlocked && p.target == null && msg.isAsynchronous();</div><div class="line"> Message prev;</div><div class="line"> for (;;) {</div><div class="line"> prev = p;</div><div class="line"> p = p.next;</div><div class="line"> if (p == null || when < p.when) {</div><div class="line"> break;</div><div class="line"> }</div><div class="line"> if (needWake && p.isAsynchronous()) {</div><div class="line"> needWake = false;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> msg.next = p; // invariant: p == prev.next</div><div class="line"> prev.next = msg;</div><div class="line"> }</div><div class="line"></div><div class="line"> // We can assume mPtr != 0 because mQuitting is false.</div><div class="line"> if (needWake) {</div><div class="line"> nativeWake(mPtr);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> return true;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>整个enqueueMessage方法的过程就是先持有MessageQueue.this锁,然后将Message放入队列中,放入队列的过程是:</p>
<ol>
<li>如果队列为空,或者当前处理的时间点为0(when的数值,when表示Message将要执行的时间点),或者当前Message需要处理的时间点先于队列中的首节点,那么就将Message放入队列首部,否则进行第2步。</li>
<li>遍历队列中Message,找到when比当前Message的when大的Message,将Message插入到该Message之前,如果没找到则将Message插入到队列最后。</li>
<li>判断是否需要唤醒,一般是当前队列为空的情况下,next那边会进入睡眠,需要enqueue这边唤醒next函数。后面会详细介绍</li>
</ol>
<p>执行完后,会释放持有的MessageQueue.this的锁。这样整个enqueueMessage方法算是完了,然后看看读取Message的MessageQueue的next方法。</p>
<h4 id="MessageQueue的next方法"><a href="#MessageQueue的next方法" class="headerlink" title="MessageQueue的next方法"></a>MessageQueue的next方法</h4><p>MessageQueue的next方法是从哪里调用的呢?先看一个线程对Looper的标准用法是:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">class LoopThread extends Thread{</div><div class="line">public Handler mHandler;</div><div class="line"> public void run(){</div><div class="line"> Looper.prepare();</div><div class="line"> mHandler = new Handler() {</div><div class="line"> public void handleMessage(Message msg) {</div><div class="line"> // process incoming messages here</div><div class="line"> }</div><div class="line"> }; </div><div class="line"> Looper.loop();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>prepare方法我们前面已经看过了,就是初始化ThreadLocal变量Looper。loop()方法就是循环读取MessageQueue中Message,然后处理每一个Message。我们看看Looper.loop方法源码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div></pre></td><td class="code"><pre><div class="line">public static void loop() {</div><div class="line"> final Looper me = myLooper();</div><div class="line"> if (me == null) {</div><div class="line"> throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");</div><div class="line"> }</div><div class="line"> final MessageQueue queue = me.mQueue;</div><div class="line"></div><div class="line"> // Make sure the identity of this thread is that of the local process,</div><div class="line"> // and keep track of what that identity token actually is.</div><div class="line"> Binder.clearCallingIdentity();</div><div class="line"> final long ident = Binder.clearCallingIdentity();</div><div class="line"></div><div class="line"> for (;;) {</div><div class="line"> Message msg = queue.next(); // might block 此处就是next方法调用的地方</div><div class="line"> if (msg == null) {</div><div class="line"> // No message indicates that the message queue is quitting.</div><div class="line"> return;</div><div class="line"> }</div><div class="line"></div><div class="line"> // This must be in a local variable, in case a UI event sets the logger</div><div class="line"> Printer logging = me.mLogging;</div><div class="line"> if (logging != null) {</div><div class="line"> logging.println(">>>>> Dispatching to " + msg.target + " " +</div><div class="line"> msg.callback + ": " + msg.what);</div><div class="line"> }</div><div class="line"></div><div class="line"> msg.target.dispatchMessage(msg);</div><div class="line"></div><div class="line"> if (logging != null) {</div><div class="line"> logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);</div><div class="line"> }</div><div class="line"></div><div class="line"> // Make sure that during the course of dispatching the</div><div class="line"> // identity of the thread wasn't corrupted.</div><div class="line"> final long newIdent = Binder.clearCallingIdentity();</div><div class="line"> if (ident != newIdent) {</div><div class="line"> Log.wtf(TAG, "Thread identity changed from 0x"</div><div class="line"> + Long.toHexString(ident) + " to 0x"</div><div class="line"> + Long.toHexString(newIdent) + " while dispatching to "</div><div class="line"> + msg.target.getClass().getName() + " "</div><div class="line"> + msg.callback + " what=" + msg.what);</div><div class="line"> }</div><div class="line"></div><div class="line"> msg.recycleUnchecked();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>整个loop函数大概的过程就是先调用MessageQueue.next方法获取一个Message,然后调用Message的target的dispatchMessage方法来处理Message,Message的target就是发送这个Message的Handler。处理的过程是先看Message的callback有没有实现,如果有,则使用调用callback的run方法,如果没有则看Handler的callback是否为空,如果非空,则使用handler的callback的handleMessage方法来处理Message,如果为空,则调用Handler的handleMessage方法处理。</p>
<p>我们主要看next,从注释来看,next方法可能会阻塞,先看next方法的源码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">Message next() {</div><div class="line"> // Return here if the message loop has already quit and been disposed.</div><div class="line"> // This can happen if the application tries to restart a looper after quit</div><div class="line"> // which is not supported.</div><div class="line"> final long ptr = mPtr; //mPrt是native层的MessageQueue的指针</div><div class="line"> if (ptr == 0) {</div><div class="line"> return null;</div><div class="line"> }</div><div class="line"></div><div class="line"> int pendingIdleHandlerCount = -1; // -1 only during first iteration</div><div class="line"> int nextPollTimeoutMillis = 0;</div><div class="line"> for (;;) {</div><div class="line"> if (nextPollTimeoutMillis != 0) {</div><div class="line"> Binder.flushPendingCommands();</div><div class="line"> }</div><div class="line"></div><div class="line"> nativePollOnce(ptr, nextPollTimeoutMillis); // jni函数</div><div class="line"></div><div class="line"> synchronized (this) {</div><div class="line"> // Try to retrieve the next message. Return if found.</div><div class="line"> final long now = SystemClock.uptimeMillis();</div><div class="line"> Message prevMsg = null;</div><div class="line"> Message msg = mMessages;</div><div class="line"> if (msg != null && msg.target == null) { //target 正常情况下都不会为null,在postBarrier会出现target为null的Message</div><div class="line"> // Stalled by a barrier. Find the next asynchronous message in the queue.</div><div class="line"> do {</div><div class="line"> prevMsg = msg;</div><div class="line"> msg = msg.next;</div><div class="line"> } while (msg != null && !msg.isAsynchronous());</div><div class="line"> }</div><div class="line"> if (msg != null) {</div><div class="line"> if (now < msg.when) {</div><div class="line"> // Next message is not ready. Set a timeout to wake up when it is ready.</div><div class="line"> nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);</div><div class="line"> } else {</div><div class="line"> // Got a message.</div><div class="line"> mBlocked = false;</div><div class="line"> if (prevMsg != null) {</div><div class="line"> prevMsg.next = msg.next;</div><div class="line"> } else {</div><div class="line"> mMessages = msg.next;</div><div class="line"> }</div><div class="line"> msg.next = null;</div><div class="line"> if (DEBUG) Log.v(TAG, "Returning message: " + msg);</div><div class="line"> msg.markInUse();</div><div class="line"> return msg;</div><div class="line"> }</div><div class="line"> } else {</div><div class="line"> // No more messages.</div><div class="line"> nextPollTimeoutMillis = -1; // 等待时间无限长</div><div class="line"> }</div><div class="line"></div><div class="line"> // Process the quit message now that all pending messages have been handled.</div><div class="line"> if (mQuitting) {</div><div class="line"> dispose();</div><div class="line"> return null;</div><div class="line"> }</div><div class="line"></div><div class="line"> // If first time idle, then get the number of idlers to run.</div><div class="line"> // Idle handles only run if the queue is empty or if the first message</div><div class="line"> // in the queue (possibly a barrier) is due to be handled in the future.</div><div class="line"> if (pendingIdleHandlerCount < 0</div><div class="line"> && (mMessages == null || now < mMessages.when)) {</div><div class="line"> pendingIdleHandlerCount = mIdleHandlers.size();</div><div class="line"> }</div><div class="line"> if (pendingIdleHandlerCount <= 0) {</div><div class="line"> // No idle handlers to run. Loop and wait some more.</div><div class="line"> mBlocked = true;</div><div class="line"> continue;</div><div class="line"> }</div><div class="line"></div><div class="line"> if (mPendingIdleHandlers == null) {</div><div class="line"> mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];</div><div class="line"> }</div><div class="line"> mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);</div><div class="line"> }</div><div class="line"></div><div class="line"> // Run the idle handlers.</div><div class="line"> // We only ever reach this code block during the first iteration.</div><div class="line"> for (int i = 0; i < pendingIdleHandlerCount; i++) { //运行idle</div><div class="line"> final IdleHandler idler = mPendingIdleHandlers[i];</div><div class="line"> mPendingIdleHandlers[i] = null; // release the reference to the handler</div><div class="line"></div><div class="line"> boolean keep = false;</div><div class="line"> try {</div><div class="line"> keep = idler.queueIdle();</div><div class="line"> } catch (Throwable t) {</div><div class="line"> Log.wtf(TAG, "IdleHandler threw exception", t);</div><div class="line"> }</div><div class="line"></div><div class="line"> if (!keep) {</div><div class="line"> synchronized (this) {</div><div class="line"> mIdleHandlers.remove(idler);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> // Reset the idle handler count to 0 so we do not run them again.</div><div class="line"> pendingIdleHandlerCount = 0;</div><div class="line"></div><div class="line"> // While calling an idle handler, a new message could have been delivered</div><div class="line"> // so go back and look again for a pending message without waiting.</div><div class="line"> nextPollTimeoutMillis = 0;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>整个next函数的主要是执行步骤是:</p>
<ul>
<li>step1: 初始化操作,如果mPtr为null,则直接返回null,设置nextPollTimeoutMillis为0,进入下一步。</li>
<li>step2: 调用nativePollOnce, nativePollOnce有两个参数,第一个为mPtr表示native层MessageQueue的指针,nextPollTimeoutMillis表示超时返回时间,调用这个nativePollOnce会等待wake,如果超过nextPollTimeoutMillis时间,则不管有没有被唤醒都会返回。-1表示一直等待,0表示立刻返回。下一小节单独介绍这个函数。</li>
<li>step3: 获取队列的头Message(msg),如果头Message的target为null,则查找一个异步Message来进行下一步处理。当队列中添加了同步Barrier的时候target会为null。</li>
<li>step4: 判断上一步获取的msg是否为null,为null说明当前队列中没有msg,设置等待时间nextPollTimeoutMillis为-1。实际上是等待enqueueMessage的nativeWake来唤醒,执行step4。如果非null,则下一步</li>
<li>step5: 判断msg的执行时间(when)是否比当前时间(now)的大,如果小,则将msg从队列中移除,并且返回msg,结束。如果大则设置等待时间nextPollTimeoutMillis为(int) Math.min(msg.when - now, Integer.MAX_VALUE),执行时间与当前时间的差与MAX_VALUE的较小值。执行下一步</li>
<li>step6: 判断是否MessageQueue是否已经取消,如果取消的话则返回null,否则下一步</li>
<li>step7: 运行idle Handle,idle表示当前有空闲时间的时候执行,而运行到这一步的时候,表示消息队列处理已经是出于空闲时间了(队列中没有Message,或者头部Message的执行时间(when)在当前时间之后)。如果没有idle,则继续step2,如果有则执行idleHandler的queueIdle方法,我们可以自己添加IdleHandler到MessageQueue里面(addIdleHandler方法),执行完后,回到step2。</li>
</ul>
<p>需要说的时候,我们平常只是使用Message,但是实际上IdleHandler如果使用的好,应该会达到意想不到的效果,它表示MessageQueue有空闲时间的时候执行一下。然后介绍一下nativePollOnce与nativeWake方法</p>
<h3 id="native层机制"><a href="#native层机制" class="headerlink" title="native层机制"></a>native层机制</h3><p>nativePollOnce与nativeWake是两个jni方法,这两个方法jni实现方法在frameworks/base/core/jni/android_os_MessageQueue.cpp。这个是MessageQueue的native层内容。native层的NativeMessageQueue初始化是在nativeInit方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {</div><div class="line"> NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();</div><div class="line"> if (!nativeMessageQueue) {</div><div class="line"> jniThrowRuntimeException(env, "Unable to allocate native queue");</div><div class="line"> return 0;</div><div class="line"> }</div><div class="line"></div><div class="line"> nativeMessageQueue->incStrong(env);</div><div class="line"> return reinterpret_cast<jlong>(nativeMessageQueue);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>对应的java层方法是nativeInit,在MessageQueue构造函数的时候调用:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">MessageQueue(boolean quitAllowed) {</div><div class="line"> mQuitAllowed = quitAllowed;</div><div class="line"> mPtr = nativeInit();</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>而NativeMessageQueue的构造函数是:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">NativeMessageQueue::NativeMessageQueue() :</div><div class="line"> mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {</div><div class="line"> mLooper = Looper::getForThread();</div><div class="line"> if (mLooper == NULL) {</div><div class="line"> mLooper = new Looper(false);</div><div class="line"> Looper::setForThread(mLooper);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>创建了一个native层的Looper。Looper的源码在system/core/libutils/Looper.cpp。Looper通过epoll_create创建了一个mEpollFd作为epoll的fd,并且创建了一个mWakeEventFd,用来监听java层的wake,同时可以通过Looper的addFd方法来添加新的fd监听。</p>
<h4 id="nativePollOnce"><a href="#nativePollOnce" class="headerlink" title="nativePollOnce"></a>nativePollOnce</h4><p>nativePollOnce是每次调用next方法获取消息的时候调用的:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line">static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,</div><div class="line"> jlong ptr, jint timeoutMillis) {</div><div class="line"> NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);</div><div class="line"> nativeMessageQueue->pollOnce(env, obj, timeoutMillis);</div><div class="line">}</div><div class="line"></div><div class="line">void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {</div><div class="line"> mPollEnv = env;</div><div class="line"> mPollObj = pollObj;</div><div class="line"> mLooper->pollOnce(timeoutMillis);</div><div class="line"> mPollObj = NULL;</div><div class="line"> mPollEnv = NULL;</div><div class="line"></div><div class="line"> if (mExceptionObj) {</div><div class="line"> env->Throw(mExceptionObj);</div><div class="line"> env->DeleteLocalRef(mExceptionObj);</div><div class="line"> mExceptionObj = NULL;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>这个方法的native层方法最终会调用Looper的pollOnce:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div><div class="line">107</div><div class="line">108</div><div class="line">109</div><div class="line">110</div><div class="line">111</div><div class="line">112</div><div class="line">113</div><div class="line">114</div><div class="line">115</div><div class="line">116</div><div class="line">117</div><div class="line">118</div><div class="line">119</div><div class="line">120</div><div class="line">121</div><div class="line">122</div><div class="line">123</div><div class="line">124</div><div class="line">125</div><div class="line">126</div><div class="line">127</div><div class="line">128</div><div class="line">129</div><div class="line">130</div><div class="line">131</div><div class="line">132</div><div class="line">133</div><div class="line">134</div><div class="line">135</div><div class="line">136</div><div class="line">137</div><div class="line">138</div><div class="line">139</div><div class="line">140</div><div class="line">141</div><div class="line">142</div><div class="line">143</div><div class="line">144</div><div class="line">145</div><div class="line">146</div><div class="line">147</div><div class="line">148</div><div class="line">149</div><div class="line">150</div><div class="line">151</div><div class="line">152</div><div class="line">153</div><div class="line">154</div><div class="line">155</div><div class="line">156</div><div class="line">157</div><div class="line">158</div><div class="line">159</div><div class="line">160</div><div class="line">161</div><div class="line">162</div><div class="line">163</div><div class="line">164</div><div class="line">165</div><div class="line">166</div><div class="line">167</div><div class="line">168</div><div class="line">169</div><div class="line">170</div><div class="line">171</div><div class="line">172</div><div class="line">173</div><div class="line">174</div><div class="line">175</div><div class="line">176</div><div class="line">177</div><div class="line">178</div><div class="line">179</div><div class="line">180</div><div class="line">181</div><div class="line">182</div><div class="line">183</div><div class="line">184</div><div class="line">185</div><div class="line">186</div><div class="line">187</div><div class="line">188</div><div class="line">189</div><div class="line">190</div><div class="line">191</div><div class="line">192</div><div class="line">193</div></pre></td><td class="code"><pre><div class="line">int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {</div><div class="line"> int result = 0;</div><div class="line"> for (;;) {</div><div class="line"> while (mResponseIndex < mResponses.size()) {</div><div class="line"> const Response& response = mResponses.itemAt(mResponseIndex++);</div><div class="line"> int ident = response.request.ident;</div><div class="line"> if (ident >= 0) {</div><div class="line"> int fd = response.request.fd;</div><div class="line"> int events = response.events;</div><div class="line"> void* data = response.request.data;</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ pollOnce - returning signalled identifier %d: "</div><div class="line"> "fd=%d, events=0x%x, data=%p",</div><div class="line"> this, ident, fd, events, data);</div><div class="line">#endif</div><div class="line"> if (outFd != NULL) *outFd = fd;</div><div class="line"> if (outEvents != NULL) *outEvents = events;</div><div class="line"> if (outData != NULL) *outData = data;</div><div class="line"> return ident;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> if (result != 0) {</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ pollOnce - returning result %d", this, result);</div><div class="line">#endif</div><div class="line"> if (outFd != NULL) *outFd = 0;</div><div class="line"> if (outEvents != NULL) *outEvents = 0;</div><div class="line"> if (outData != NULL) *outData = NULL;</div><div class="line"> return result;</div><div class="line"> }</div><div class="line"></div><div class="line"> result = pollInner(timeoutMillis);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">int Looper::pollInner(int timeoutMillis) {</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);</div><div class="line">#endif</div><div class="line"></div><div class="line"> // Adjust the timeout based on when the next message is due.</div><div class="line"> if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {</div><div class="line"> nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);</div><div class="line"> int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);</div><div class="line"> if (messageTimeoutMillis >= 0</div><div class="line"> && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {</div><div class="line"> timeoutMillis = messageTimeoutMillis;</div><div class="line"> }</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",</div><div class="line"> this, mNextMessageUptime - now, timeoutMillis);</div><div class="line">#endif</div><div class="line"> }</div><div class="line"></div><div class="line"> // Poll.</div><div class="line"> int result = POLL_WAKE;</div><div class="line"> mResponses.clear();</div><div class="line"> mResponseIndex = 0;</div><div class="line"></div><div class="line"> // We are about to idle.</div><div class="line"> mPolling = true;</div><div class="line"></div><div class="line"> struct epoll_event eventItems[EPOLL_MAX_EVENTS];</div><div class="line"> int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);</div><div class="line"></div><div class="line"> // No longer idling.</div><div class="line"> mPolling = false;</div><div class="line"></div><div class="line"> // Acquire lock.</div><div class="line"> mLock.lock();</div><div class="line"></div><div class="line"> // Rebuild epoll set if needed.</div><div class="line"> if (mEpollRebuildRequired) {</div><div class="line"> mEpollRebuildRequired = false;</div><div class="line"> rebuildEpollLocked();</div><div class="line"> goto Done;</div><div class="line"> }</div><div class="line"></div><div class="line"> // Check for poll error.</div><div class="line"> if (eventCount < 0) {</div><div class="line"> if (errno == EINTR) {</div><div class="line"> goto Done;</div><div class="line"> }</div><div class="line"> ALOGW("Poll failed with an unexpected error, errno=%d", errno);</div><div class="line"> result = POLL_ERROR;</div><div class="line"> goto Done;</div><div class="line"> }</div><div class="line"></div><div class="line"> // Check for poll timeout.</div><div class="line"> if (eventCount == 0) {</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ pollOnce - timeout", this);</div><div class="line">#endif</div><div class="line"> result = POLL_TIMEOUT;</div><div class="line"> goto Done;</div><div class="line"> }</div><div class="line"></div><div class="line"> // Handle all events.</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);</div><div class="line">#endif</div><div class="line"></div><div class="line"> for (int i = 0; i < eventCount; i++) {</div><div class="line"> int fd = eventItems[i].data.fd;</div><div class="line"> uint32_t epollEvents = eventItems[i].events;</div><div class="line"> if (fd == mWakeEventFd) {</div><div class="line"> if (epollEvents & EPOLLIN) {</div><div class="line"> awoken();</div><div class="line"> } else {</div><div class="line"> ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);</div><div class="line"> }</div><div class="line"> } else {</div><div class="line"> ssize_t requestIndex = mRequests.indexOfKey(fd);</div><div class="line"> if (requestIndex >= 0) {</div><div class="line"> int events = 0;</div><div class="line"> if (epollEvents & EPOLLIN) events |= EVENT_INPUT;</div><div class="line"> if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;</div><div class="line"> if (epollEvents & EPOLLERR) events |= EVENT_ERROR;</div><div class="line"> if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;</div><div class="line"> pushResponse(events, mRequests.valueAt(requestIndex));</div><div class="line"> } else {</div><div class="line"> ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "</div><div class="line"> "no longer registered.", epollEvents, fd);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">Done: ;</div><div class="line"></div><div class="line"> // Invoke pending message callbacks.</div><div class="line"> mNextMessageUptime = LLONG_MAX;</div><div class="line"> while (mMessageEnvelopes.size() != 0) {</div><div class="line"> nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);</div><div class="line"> const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);</div><div class="line"> if (messageEnvelope.uptime <= now) {</div><div class="line"> // Remove the envelope from the list.</div><div class="line"> // We keep a strong reference to the handler until the call to handleMessage</div><div class="line"> // finishes. Then we drop it so that the handler can be deleted *before*</div><div class="line"> // we reacquire our lock.</div><div class="line"> { // obtain handler</div><div class="line"> sp<MessageHandler> handler = messageEnvelope.handler;</div><div class="line"> Message message = messageEnvelope.message;</div><div class="line"> mMessageEnvelopes.removeAt(0);</div><div class="line"> mSendingMessage = true;</div><div class="line"> mLock.unlock();</div><div class="line"></div><div class="line">#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS</div><div class="line"> ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",</div><div class="line"> this, handler.get(), message.what);</div><div class="line">#endif</div><div class="line"> handler->handleMessage(message);</div><div class="line"> } // release handler</div><div class="line"></div><div class="line"> mLock.lock();</div><div class="line"> mSendingMessage = false;</div><div class="line"> result = POLL_CALLBACK;</div><div class="line"> } else {</div><div class="line"> // The last message left at the head of the queue determines the next wakeup time.</div><div class="line"> mNextMessageUptime = messageEnvelope.uptime;</div><div class="line"> break;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> // Release lock.</div><div class="line"> mLock.unlock();</div><div class="line"></div><div class="line"> // Invoke all response callbacks.</div><div class="line"> for (size_t i = 0; i < mResponses.size(); i++) {</div><div class="line"> Response& response = mResponses.editItemAt(i);</div><div class="line"> if (response.request.ident == POLL_CALLBACK) {</div><div class="line"> int fd = response.request.fd;</div><div class="line"> int events = response.events;</div><div class="line"> void* data = response.request.data;</div><div class="line">#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS</div><div class="line"> ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",</div><div class="line"> this, response.request.callback.get(), fd, events, data);</div><div class="line">#endif</div><div class="line"> // Invoke the callback. Note that the file descriptor may be closed by</div><div class="line"> // the callback (and potentially even reused) before the function returns so</div><div class="line"> // we need to be a little careful when removing the file descriptor afterwards.</div><div class="line"> int callbackResult = response.request.callback->handleEvent(fd, events, data);</div><div class="line"> if (callbackResult == 0) {</div><div class="line"> removeFd(fd, response.request.seq);</div><div class="line"> }</div><div class="line"></div><div class="line"> // Clear the callback reference in the response structure promptly because we</div><div class="line"> // will not clear the response vector itself until the next poll.</div><div class="line"> response.request.callback.clear();</div><div class="line"> result = POLL_CALLBACK;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> return result;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这个方法超长,但实际上Looper的pollOnce方法主要有5步:</p>
<ol>
<li>调用epoll_wait方法等待所监听的fd的写入,其方法原型如下:<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout)</div></pre></td></tr></table></figure>
</li>
</ol>
<p>调用的方法参数为:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);</div></pre></td></tr></table></figure></p>
<p>eventItems里面就包含了mWakeEvent和通过addFd添加fd时加入的Event。该方法会阻塞,当timeoutMillis(对应java层的nextPollTimeoutMillis)到了时间,该方法会返回,或者eventItems有事件来了,该方法会返回。返回之后就是干下一件事</p>
<ol>
<li>判断有没有event,因为可能是timeoutMillis到了返回的,如果没有直接进行4.</li>
<li>读取eventItems的内容,如果eventItem的fd是mWakeEventFd,则调用awoken方法,读取Looper.wake写入的内容,如果是其他的fd,则使用pushResponse来读取,并且将内容放入Response当中。</li>
<li>处理NativeMessageQueue的消息,这些消息是native层的消息</li>
<li>处理pushResponse写入的内容。</li>
</ol>
<p>里面主要是干了三件事处理wakeEventFd的输入内容,其他fd的输入内容,以及NativeMessageQueue里面的Message。 </p>
<h4 id="nativeWake"><a href="#nativeWake" class="headerlink" title="nativeWake"></a>nativeWake</h4><p>实际上最后就是调用了Looper的wake方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line">//android_os_MessageQueue.cpp</div><div class="line">static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {</div><div class="line"> NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);</div><div class="line"> nativeMessageQueue->wake();</div><div class="line">}</div><div class="line">void NativeMessageQueue::wake() {</div><div class="line"> mLooper->wake();</div><div class="line">}</div><div class="line"></div><div class="line">//Looper.cpp</div><div class="line">void Looper::wake() {</div><div class="line">#if DEBUG_POLL_AND_WAKE</div><div class="line"> ALOGD("%p ~ wake", this);</div><div class="line">#endif</div><div class="line"></div><div class="line"> uint64_t inc = 1;</div><div class="line"> ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));</div><div class="line"> if (nWrite != sizeof(uint64_t)) {</div><div class="line"> if (errno != EAGAIN) {</div><div class="line"> ALOGW("Could not write wake signal, errno=%d", errno);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这样native层的消息队列就算是完了。</p>
<h3 id="SyncBarrier"><a href="#SyncBarrier" class="headerlink" title="SyncBarrier"></a>SyncBarrier</h3><p>我们在next方法里面看到有这么一段代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">if (msg != null && msg.target == null) { //target 正常情况下都不会为null,在postBarrier会出现target为null的Message</div><div class="line"> // Stalled by a barrier. Find the next asynchronous message in the queue.</div><div class="line"> do {</div><div class="line"> prevMsg = msg;</div><div class="line"> msg = msg.next;</div><div class="line"> } while (msg != null && !msg.isAsynchronous());</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>什么时候msg.target会为null呢?有sync barrier消息的时候,实际上msg.target为null表示sync barrier(同步消息屏障)。MessageQueue有一个postSyncBarrier方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line">public int postSyncBarrier() {</div><div class="line"> return postSyncBarrier(SystemClock.uptimeMillis());</div><div class="line">}</div><div class="line"></div><div class="line">private int postSyncBarrier(long when) {</div><div class="line"> // Enqueue a new sync barrier token.</div><div class="line"> // We don't need to wake the queue because the purpose of a barrier is to stall it.</div><div class="line"> synchronized (this) {</div><div class="line"> final int token = mNextBarrierToken++;</div><div class="line"> final Message msg = Message.obtain();</div><div class="line"> msg.markInUse();</div><div class="line"> msg.when = when;</div><div class="line"> msg.arg1 = token;</div><div class="line"></div><div class="line"> Message prev = null;</div><div class="line"> Message p = mMessages;</div><div class="line"> if (when != 0) {</div><div class="line"> while (p != null && p.when <= when) {</div><div class="line"> prev = p;</div><div class="line"> p = p.next;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> if (prev != null) { // invariant: p == prev.next</div><div class="line"> msg.next = p;</div><div class="line"> prev.next = msg;</div><div class="line"> } else {</div><div class="line"> msg.next = p;</div><div class="line"> mMessages = msg;</div><div class="line"> }</div><div class="line"> return token;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>对应有removeSyncBarrier方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div></pre></td><td class="code"><pre><div class="line">public void removeSyncBarrier(int token) {</div><div class="line"> // Remove a sync barrier token from the queue.</div><div class="line"> // If the queue is no longer stalled by a barrier then wake it.</div><div class="line"> synchronized (this) {</div><div class="line"> Message prev = null;</div><div class="line"> Message p = mMessages;</div><div class="line"> while (p != null && (p.target != null || p.arg1 != token)) {</div><div class="line"> prev = p;</div><div class="line"> p = p.next;</div><div class="line"> }</div><div class="line"> if (p == null) {</div><div class="line"> throw new IllegalStateException("The specified message queue synchronization "</div><div class="line"> + " barrier token has not been posted or has already been removed.");</div><div class="line"> }</div><div class="line"> final boolean needWake;</div><div class="line"> if (prev != null) {</div><div class="line"> prev.next = p.next;</div><div class="line"> needWake = false;</div><div class="line"> } else {</div><div class="line"> mMessages = p.next;</div><div class="line"> needWake = mMessages == null || mMessages.target != null;</div><div class="line"> }</div><div class="line"> p.recycleUnchecked();</div><div class="line"></div><div class="line"> // If the loop is quitting then it is already awake.</div><div class="line"> // We can assume mPtr != 0 when mQuitting is false.</div><div class="line"> if (needWake && !mQuitting) {</div><div class="line"> nativeWake(mPtr); // 需要唤醒,因为队首元素是SyncBarrier,队列中有消息但是没有异步消息的时候,next方法同样会阻塞等待。</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>看next方法的源码,每次消息队列中有barrier的时候,next会寻找队列中的异步消息来处理。如果没有异步消息,设置nextPollTimeoutMillis = -1,进入阻塞等待新消息的到来。异步消息主要是系统发送的,而系统中的异步消息主要有触摸事件,按键事件的消息。系统中调用postSyncBarrier和removeSyncBarrier主要实在ViewRootImpl的scheduleTraversals和unscheduleTraversals,以及doTraversals方法中。从源码可以猜到每次调用postSyncBarrier后都会调用removeSyncBarrier,不然同步消息就没法执行了(看next源码理解这一点)。可以看一下scheduleTraversal方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line">//ViewRootImpl.java</div><div class="line">void scheduleTraversals() {</div><div class="line"> if (!mTraversalScheduled) {</div><div class="line"> mTraversalScheduled = true;</div><div class="line"> mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();</div><div class="line"> mChoreographer.postCallback(</div><div class="line"> Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);</div><div class="line"> if (!mUnbufferedInputDispatch) {</div><div class="line"> scheduleConsumeBatchedInput();</div><div class="line"> }</div><div class="line"> notifyRendererOfFramePending();</div><div class="line"> pokeDrawLockIfNeeded();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>实际上MessageQueue的源码一直在变化的,2.3才加入了native层的Message,在4.0.1还没有SyncBarrier,4.1才开始加入SyncBarrier的,而且MessageQueue没有postSyncBarrier方法,只有enqueueSyncBarrier方法,Looper里面有个postSyncBarrier方法。</p>
<h4 id="SyncBarrier的意义"><a href="#SyncBarrier的意义" class="headerlink" title="SyncBarrier的意义"></a>SyncBarrier的意义</h4><p>前面介绍了一下每个版本的特点,我想介绍一种SyncBarrier的意义,我们介绍了使用SyncBarrier主要是ViewRootImpl中的scheduleTraversal的时候,那是跟UI事件相关的,像派发消息会通过发送Message发到主线程:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {</div><div class="line"> SomeArgs args = SomeArgs.obtain();</div><div class="line"> args.arg1 = event;</div><div class="line"> args.arg2 = receiver;</div><div class="line"> Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);</div><div class="line"> msg.setAsynchronous(true);</div><div class="line"> mHandler.sendMessage(msg);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>注意它这里就是使用的异步Message,使用了<code>msg.setAsyncronous(true)</code>。 而SyncBarrier有什么用处呢?我们刚刚介绍的时候,当消息队列的第一个Message的target的时候,表示它是一个SyncBarrier,它会阻拦同步消息,而选择队列中第一个异步消息处理,如果没有则会阻塞。这表示什么呢?这是表示第一个Message是SyncBarrier的时候,会只处理异步消息。而我们前面介绍了InputEvent的时候,它就是异步消息,在有SyncBarrier的时候就会被优先处理。所以在调用了scheduleTraversal的时候,就会只处理触摸事件这些消息了,保证用户体验。保证了触摸事件及时处理,实际上这也能减少ANR。如果这个时候MessageQueue中有很多Message,也能够及时处理那些触摸事件的Message了。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>MessageQueue是Android消息消息机制的内部核心,理解好MessageQueue更能理解好Android应用层的消息逻辑。另外MessageQueue的代码一直在不断地变化,对照不同版本的代码,真的能领略代码改变时的目的,从演变中学习。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://houzhi.me/2016/10/05/hugo-sourcecode-analysis/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="houzhi">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Creative, Challenging and Cogitative">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/10/05/hugo-sourcecode-analysis/" itemprop="url">Hugo源码分析</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Veröffentlicht am</span>
<time title="Post created" itemprop="dateCreated datePublished" datetime="2016-10-05T00:00:00+08:00">
2016-10-05
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p><a href="https://github.com/JakeWharton/hugo" target="_blank" rel="external">Hugo</a>是JakeWharton大神开发的一个通过注解触发的Debug日志库。它是一个非常好的AOP框架,在Debug模式下,Hugo利用aspectj库来进行切面编程,插入日志代码。通过分析Hugo的代码能够对gradle以及aspectj有一个非常好的了解。</p>
<h3 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h3><p>通过使用来看Hugo具体的功能,这样也能够更好的明白Hugo的实现方式。</p>
<p>首先把下面的编译配置文件添加到项目当中:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">buildscript {</div><div class="line"> repositories {</div><div class="line"> mavenCentral()</div><div class="line"> }</div><div class="line"></div><div class="line"> dependencies {</div><div class="line"> classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">apply plugin: 'com.android.application'</div><div class="line">apply plugin: 'com.jakewharton.hugo'</div></pre></td></tr></table></figure>
<p>使用的时候直接使用@DebugLog注解给想要调试的方法就好了,它会打印函数的参数,执行时间,以及返回值:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">@DebugLog</div><div class="line">public String getName(String first, String last) {</div><div class="line"> SystemClock.sleep(15); // Don't ever really do this!</div><div class="line"> return first + " " + last;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">V/Example: ⇢ getName(first="Jake", last="Wharton")</div><div class="line">V/Example: ⇠ getName [16ms] = "Jake Wharton"</div></pre></td></tr></table></figure>
<p>需要指出的,Hugo只会在Debug模式下打印log。DebugLog的源码如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">package hugo.weaving;</div><div class="line"></div><div class="line">import java.lang.annotation.Retention;</div><div class="line">import java.lang.annotation.Target;</div><div class="line">import static java.lang.annotation.ElementType.CONSTRUCTOR;</div><div class="line">import static java.lang.annotation.ElementType.METHOD;</div><div class="line">import static java.lang.annotation.ElementType.TYPE;</div><div class="line">import static java.lang.annotation.RetentionPolicy.CLASS;</div><div class="line"></div><div class="line">@Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)</div><div class="line"></div><div class="line">public @interface DebugLog {</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>就是注解,Target也包含了TYPE,DebugLog也可以添加到类上面:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line">@DebugLog</div><div class="line"></div><div class="line">static class Greeter {</div><div class="line"></div><div class="line"> private final String name;</div><div class="line"></div><div class="line"> Greeter(String name) {</div><div class="line"></div><div class="line"> this.name = name;</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> private String sayHello() {</div><div class="line"></div><div class="line"> return "Hello, " + name;</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="AspectJ"><a href="#AspectJ" class="headerlink" title="AspectJ"></a>AspectJ</h3><p>AspectJ是一个面向切面的框架,它有一个专门的编译器用来生成遵守Java字节编码规范的class文件。在<a href="http://www.eclipse.org/aspectj/downloads.php" target="_blank" rel="external">这里</a>下载安装,实际上它有自己的语法。看个简单的例子:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">// 我们有一个TestService类,TestService.java</div><div class="line"></div><div class="line">public class TestService{</div><div class="line"></div><div class="line"> public void test(){</div><div class="line"></div><div class="line"> System.out.println("test");</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> public static void main(String[]args){</div><div class="line"></div><div class="line"> new TestService().test();</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>现在我们想要给test()方法增加一些东西,比如打印test方法进入的时间。如果用aspectj则可以增加一个文件:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public aspect LogAspect {</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> pointcut logPointcut():execution(void TestService.test());</div><div class="line"></div><div class="line"> void around():logPointcut(){</div><div class="line"></div><div class="line"> System.out.println("start time: " + System.currentTimeMillis());</div><div class="line"></div><div class="line"> proceed();</div><div class="line"></div><div class="line"> System.out.println("end time: " + System.currentTimeMillis());</div><div class="line"></div><div class="line"> }</div></pre></td></tr></table></figure>
<p>通过执行命令 ajc -d . TestService.java LogAspect.java生成TestService.class,然后执行命令java TestService,输出为:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">start time: 1478071231659</div><div class="line"></div><div class="line">test</div><div class="line"></div><div class="line">end time: 1478071231659</div></pre></td></tr></table></figure>
<p>实际上将ajc理解为类似于javac的编译工具就好了,它编译的目标跟javac一样的都是java class文件,只是源文件的语法是符合aspect语法的。可以看看TestService.class反编译后的源码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">org.aspectj.runtime.internal.AroundClosureTestService {</div><div class="line"> TestService() {</div><div class="line"> }</div><div class="line"></div><div class="line"> test() {</div><div class="line"> test_aroundBody1$advice(LogAspect.aspectOf()(AroundClosure))}</div><div class="line"></div><div class="line"> main(String[] args) {</div><div class="line"> (TestService()).test()}</div><div class="line">}</div></pre></td></tr></table></figure>
<p>将aspect理解编译时期对源文件按照指定的描述(aspect语法文件)进行编译,得到进行切入后的字节码文件。详细的介绍可以参看这篇文章<a href="https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/" target="_blank" rel="external">spring aop</a>。</p>
<p>我们看看Hugo项目中是如何使用aspect,实际的aspect部分代码是在hugo-runtime子模块当中。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div><div class="line">107</div><div class="line">108</div><div class="line">109</div><div class="line">110</div><div class="line">111</div><div class="line">112</div><div class="line">113</div><div class="line">114</div><div class="line">115</div><div class="line">116</div><div class="line">117</div><div class="line">118</div><div class="line">119</div><div class="line">120</div><div class="line">121</div><div class="line">122</div><div class="line">123</div><div class="line">124</div><div class="line">125</div><div class="line">126</div><div class="line">127</div><div class="line">128</div><div class="line">129</div><div class="line">130</div><div class="line">131</div><div class="line">132</div><div class="line">133</div><div class="line">134</div><div class="line">135</div><div class="line">136</div><div class="line">137</div><div class="line">138</div><div class="line">139</div><div class="line">140</div><div class="line">141</div><div class="line">142</div><div class="line">143</div><div class="line">144</div><div class="line">145</div><div class="line">146</div><div class="line">147</div><div class="line">148</div><div class="line">149</div><div class="line">150</div><div class="line">151</div><div class="line">152</div><div class="line">153</div><div class="line">154</div><div class="line">155</div><div class="line">156</div><div class="line">157</div><div class="line">158</div><div class="line">159</div><div class="line">160</div><div class="line">161</div><div class="line">162</div><div class="line">163</div><div class="line">164</div><div class="line">165</div><div class="line">166</div><div class="line">167</div><div class="line">168</div><div class="line">169</div><div class="line">170</div><div class="line">171</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">package hugo.weaving.internal;</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">import android.os.Build;</div><div class="line"></div><div class="line">import android.os.Looper;</div><div class="line"></div><div class="line">import android.os.Trace;</div><div class="line"></div><div class="line">import android.util.Log;</div><div class="line"></div><div class="line">import org.aspectj.lang.JoinPoint;</div><div class="line">import org.aspectj.lang.ProceedingJoinPoint;</div><div class="line">import org.aspectj.lang.Signature;</div><div class="line">import org.aspectj.lang.annotation.Around;</div><div class="line"></div><div class="line">import org.aspectj.lang.annotation.Aspect;</div><div class="line"></div><div class="line">import org.aspectj.lang.annotation.Pointcut;</div><div class="line"></div><div class="line">import org.aspectj.lang.reflect.CodeSignature;</div><div class="line"></div><div class="line">import org.aspectj.lang.reflect.MethodSignature;</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">import java.util.concurrent.TimeUnit;</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">@Aspect</div><div class="line"></div><div class="line">public class Hugo {</div><div class="line"></div><div class="line"> private static volatile boolean enabled = true;</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> @Pointcut("within(@hugo.weaving.DebugLog *)")</div><div class="line"> public void withinAnnotatedClass() {}</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")</div><div class="line"> public void methodInsideAnnotatedType() {}</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")</div><div class="line"> public void constructorInsideAnnotatedType() {}</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> @Pointcut("execution(@hugo.weaving.DebugLog * *(..)) || methodInsideAnnotatedType()")</div><div class="line"> public void method() {}</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> @Pointcut("execution(@hugo.weaving.DebugLog *.new(..)) || constructorInsideAnnotatedType()")</div><div class="line"> public void constructor() {}</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> public static void setEnabled(boolean enabled) {</div><div class="line"></div><div class="line"> Hugo.enabled = enabled;</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> @Around("method() || constructor()")</div><div class="line"></div><div class="line"> public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {</div><div class="line"></div><div class="line"> enterMethod(joinPoint);</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> long startNanos = System.nanoTime();</div><div class="line"></div><div class="line"> Object result = joinPoint.proceed();</div><div class="line"></div><div class="line"> long stopNanos = System.nanoTime();</div><div class="line"></div><div class="line"> long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);</div><div class="line"></div><div class="line"> exitMethod(joinPoint, result, lengthMillis);</div><div class="line"></div><div class="line"> return result;</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> private static void enterMethod(JoinPoint joinPoint) {</div><div class="line"></div><div class="line"> if (!enabled) return;</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> // ...</div><div class="line"></div><div class="line"> //组织相关信息到builder当中</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> if (Looper.myLooper() != Looper.getMainLooper()) {</div><div class="line"></div><div class="line"> builder.append(" [Thread:\"").append(Thread.currentThread().getName()).append("\"]");</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> Log.v(asTag(cls), builder.toString());</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {</div><div class="line"></div><div class="line"> final String section = builder.toString().substring(2);</div><div class="line"></div><div class="line"> Trace.beginSection(section);</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) {</div><div class="line"></div><div class="line"> if (!enabled) return;</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {</div><div class="line"></div><div class="line"> Trace.endSection();</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> // ...</div><div class="line"></div><div class="line"> //组织相关信息为builder</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> Log.v(asTag(cls), builder.toString());</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> private static String asTag(Class<?> cls) {</div><div class="line"></div><div class="line"> if (cls.isAnonymousClass()) {</div><div class="line"></div><div class="line"> return asTag(cls.getEnclosingClass());</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> return cls.getSimpleName();</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>上面只是给出的代码去除了相关详细内容,具体代码可以直接看源码<a href="https://github.com/JakeWharton/hugo/blob/master/hugo-runtime/src/main/java/hugo/weaving/internal/Hugo.java" target="_blank" rel="external">Hugo.java</a>。</p>
<p>上面是使用了<strong><em>aspect注解</em></strong>来描述相关插入代码的:</p>
<p>@Aspect 表示这个类由AspectJ处理</p>
<p>@Pointcut 描述切面内容,可以理解为针对哪些方法,类,进行拦截,插入代码。</p>
<p>@Around 实际上拦截方法,这个注解可以同时拦截方法的执行前后,另外有@Before, @After,顾名思义,表示方法执行前跟方法执行后拦截。</p>
<p>关于aspectj,邓凡平的这篇文章<a href="http://blog.csdn.net/innost/article/details/49387395" target="_blank" rel="external">深入理解Android之AOP</a>介绍的挺详细的。aspectj编译器会根据这些描述信息对项目中的源码进行插入。另外还有cglib能够有类似的功能,CGlib是在运行期对类进行动态代理(Proxy.newProxyInstance只能对接口进行动态代理),具体可以Google一下。</p>
<h3 id="gradle代码"><a href="#gradle代码" class="headerlink" title="gradle代码"></a>gradle代码</h3><p>Hugo源码中除了aspect的使用,我觉得另外就是项目的编译控制了,因为Hugo只会在Debug模式下打印日志,而控制只在Debug模式下打印日志是在编译脚本中实现的。Hugo源码中目录树主要是:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">|- hugo-plugin</div><div class="line"></div><div class="line">|- hugo-runtime</div><div class="line"></div><div class="line">|- hugo-annotations</div><div class="line"></div><div class="line">|- hugo-example</div></pre></td></tr></table></figure>
<p>我们使用过程的方式是:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">buildscript {</div><div class="line"> repositories {</div><div class="line"> mavenCentral()</div><div class="line"> }</div><div class="line"></div><div class="line"> dependencies {</div><div class="line"> classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">apply plugin: 'com.android.application'</div><div class="line">apply plugin: 'com.jakewharton.hugo'</div></pre></td></tr></table></figure>
<h4 id="Gradle-实现Debug插入代码"><a href="#Gradle-实现Debug插入代码" class="headerlink" title="Gradle 实现Debug插入代码"></a>Gradle 实现Debug插入代码</h4><p>所以先看com.jakewharton.hugo插件,这个插件的实现是在hugo-plugin当中,hugo-plugin模块的hugo-plugin/src/main/resources/META-INF/gradle-plugins/目录下面有com.jakewharton.hugo.properties,这就表示插件的声明。该文件的内容是:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">implementation-class=hugo.weaving.plugin.HugoPlugin</div></pre></td></tr></table></figure>
<p>指定了插件实现的代码。然后看hugo.weaving.plugin.HugoPlugin的内容(对应hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoPlugin.groovy文件),这是groovy源文件,groovy也是一种编程语言,跟Java差不多。关于gradle插件声明使用可以参看gradle源码目录下面的samples/customPlugin项目(比如~/.gradle/wrapper/dists/gradle-2.10-all/a4w5fzrkeut1ox71xslb49gst/gradle-2.10/samples)。HugoPlugin.groovy的文件内容如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div><div class="line">107</div><div class="line">108</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">class HugoPlugin implements Plugin<Project> {</div><div class="line"></div><div class="line"> @Override void apply(Project project) {</div><div class="line"></div><div class="line"> def hasApp = project.plugins.withType(AppPlugin)</div><div class="line"></div><div class="line"> def hasLib = project.plugins.withType(LibraryPlugin)</div><div class="line"></div><div class="line"> if (!hasApp && !hasLib) {</div><div class="line"></div><div class="line"> throw new IllegalStateException("'android' or 'android-library' plugin required.")</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> final def log = project.logger</div><div class="line"></div><div class="line"> final def variants // variants是构造变种版本,为同一个应用创建不同的版本。</div><div class="line"></div><div class="line"> if (hasApp) {</div><div class="line"></div><div class="line"> variants = project.android.applicationVariants</div><div class="line"></div><div class="line"> } else {</div><div class="line"></div><div class="line"> variants = project.android.libraryVariants</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> project.dependencies { // 声明的项目依赖</div><div class="line"></div><div class="line"> debugCompile 'com.jakewharton.hugo:hugo-runtime:1.2.2-SNAPSHOT'</div><div class="line"></div><div class="line"> // TODO this should come transitively</div><div class="line"></div><div class="line"> debugCompile 'org.aspectj:aspectjrt:1.8.6'</div><div class="line"></div><div class="line"> compile 'com.jakewharton.hugo:hugo-annotations:1.2.2-SNAPSHOT'</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> project.extensions.create('hugo', HugoExtension)</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> variants.all { variant -></div><div class="line"></div><div class="line"> if (!variant.buildType.isDebuggable()) { // 非Debug情况下,直接返回</div><div class="line"></div><div class="line"> log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")</div><div class="line"></div><div class="line"> return;</div><div class="line"></div><div class="line"> } else if (!project.hugo.enabled) { //关闭Hugo</div><div class="line"></div><div class="line"> log.debug("Hugo is not disabled.")</div><div class="line"></div><div class="line"> return;</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> // 使用Hugo的情况下,调用aspect编译,args指定了aspect相关参数。</div><div class="line"></div><div class="line"> JavaCompile javaCompile = variant.javaCompile</div><div class="line"></div><div class="line"> javaCompile.doLast {</div><div class="line"></div><div class="line"> String[] args = [</div><div class="line"></div><div class="line"> "-showWeaveInfo",</div><div class="line"></div><div class="line"> "-1.5",</div><div class="line"></div><div class="line"> "-inpath", javaCompile.destinationDir.toString(),</div><div class="line"></div><div class="line"> "-aspectpath", javaCompile.classpath.asPath,</div><div class="line"></div><div class="line"> "-d", javaCompile.destinationDir.toString(),</div><div class="line"></div><div class="line"> "-classpath", javaCompile.classpath.asPath,</div><div class="line"></div><div class="line"> "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)</div><div class="line"></div><div class="line"> ]</div><div class="line"></div><div class="line"> log.debug "ajc args: " + Arrays.toString(args)</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> MessageHandler handler = new MessageHandler(true);</div><div class="line"></div><div class="line"> new Main().run(args, handler);// 运行aspect</div><div class="line"></div><div class="line"> // ... 省略了log运行结果的代码</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>上面这个HogoPlugin.groovy指定了编译的时候区分Debug和release版本编译。非Debug并且没有disable Hugo的时候,使用aspect给应用中的代码插入。aspect会找到有@Aspect注解的类,然后解析这个类,处理代码。这样整个过程就完了。</p>
<h4 id="Gradle与maven"><a href="#Gradle与maven" class="headerlink" title="Gradle与maven"></a>Gradle与maven</h4><p>在hugo目录下面有个build.gradle,先看一下dependencies:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">buildscript {</div><div class="line"></div><div class="line"> repositories {</div><div class="line"></div><div class="line"> mavenCentral()</div><div class="line"></div><div class="line"> jcenter()</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line"> dependencies {</div><div class="line"></div><div class="line"> classpath 'org.gradle.api.plugins:gradle-nexus-plugin:0.7'</div><div class="line"></div><div class="line"> classpath 'com.android.tools.build:gradle:1.3.1'</div><div class="line"></div><div class="line"> classpath 'org.aspectj:aspectjtools:1.8.6'</div><div class="line"></div><div class="line"> classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>mavenCentral()和jcenter是指定了repositories,也就是远程仓库,gradle编译的时候,可以从这些仓库里面获取引用库的包。</p>
<ul>
<li><p>org.aspectj:aspectjtools : 是aspectj的库</p>
</li>
<li><p>com.github.dcendents:android-maven-gradle-plugin: 修改自maven插件,是一个让maven与android库(arr)相兼容的库。</p>
</li>
<li><p>gradle-nexus-plugin: 是配置和上传组件的gradle插件</p>
</li>
</ul>
<p>build.gradle里面定义了几个Task,我们看一下cleanExample的定义来简单了解一下Task:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">task cleanExample(type: Exec) {</div><div class="line"></div><div class="line"> executable = '../gradlew'</div><div class="line"></div><div class="line"> workingDir = project.file('hugo-example')</div><div class="line"></div><div class="line"> args = [ 'clean' ]</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>Task cleanExample 是清理example项目的task,上述代码使用gradlew脚本,将工作目录设置为hugo-example目录下面,设置gradle的参数为clean,这样就清理hugo-example项目了。</p>
<p>这里只是简单介绍一下gradle,如果不了解gradle,建议先看看gradle的介绍文档,比如<a href="https://avatarqing.gitbooks.io/gradlepluginuserguidechineseverision/content/introduction/README.html" target="_blank" rel="external">Gradle for Android中文</a>,另外就是邓凡平的<a href="http://www.infoq.com/cn/articles/android-in-depth-gradle" target="_blank" rel="external">深入理解Android(一):Gradle详解</a>。</p>
<p>我觉得对于gradle,正确的理解方式是它是基于groovy脚本的一种构建框架,它提供了Android编译的框架及其API。另外groovy种充满了闭包,理解好闭包的概念,然后查看API,这样入手和理解Gradle会很容易明白。</p>
<h3 id="Hugo-改进"><a href="#Hugo-改进" class="headerlink" title="Hugo 改进"></a>Hugo 改进</h3><p>因为Hugo当中DebugLog使用的Retention是CLASS类型,所以打包之后,注解还是会存在,这样release的apk包就会增加一些大小。就拿Hugo的example来说,如果DebugLog的Retention是CLASS,release包大概是3476bytes,如果DebugLog的retention是Source的时候,release包是3444bytes。还是能够减少一点包大小的。目前我还没有完全地弄好这个问题,不过如果是导入library的方式使用Hugo的话,可以这样来弄:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">debugCompile 'com.jakewharton.hugo:hugo-annotations:1.2.2-SNAPSHOT'</div><div class="line">releaseCompile 'com.jakewharton.hugo:hugo-annotations-release:1.2.2-SNAPSHOT'</div></pre></td></tr></table></figure>
<p>hugo-annotations-release里面使用SOURCE retention的DebugLog。这样就能够在debug版本使用CLASS retention的DebugLog,而release版本使用SOURCE retention的DebugLog。不过这种配置只能适合debug和release打包时。如果有更好地idea,我在hugo上面提了个issues:<a href="https://github.com/JakeWharton/hugo/issues/146" target="_blank" rel="external">support release build type use DebugLog with SOURCE Retention</a> ,直接评论。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Hugo是一个比较小的项目,但是里面却包含了优秀的思想以及先进的技术。AOP编程,注解的理解,Gradle编译的理解,Aspect的使用,以及gradle-maven在Hugo项目中都用到了,看一下Hugo的源码是学习这些东西的一个非常好的方式。</p>
<p>参考:</p>
<ol>
<li><p><a href="http://www.jianshu.com/p/fe8d1e8bd63e" target="_blank" rel="external">Spring AOP,AspectJ, CGLIB 有点晕:http://www.jianshu.com/p/fe8d1e8bd63e</a></p>
</li>
<li><p><a href="https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/" target="_blank" rel="external">Spring AOP 实现原理与 CGLIB 应用:https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/</a></p>
</li>
<li><p><a href="http://blog.csdn.net/innost/article/details/49387395" target="_blank" rel="external">深入理解Android之AOP:http://blog.csdn.net/innost/article/details/49387395</a></p>
</li>
<li><p><a href="https://yq.aliyun.com/articles/7104" target="_blank" rel="external">Hugo 探究:https://yq.aliyun.com/articles/7104</a></p>
</li>
<li><p><a href="https://avatarqing.gitbooks.io/gradlepluginuserguidechineseverision/content/introduction/README.html" target="_blank" rel="external">Gradle for Android中文:https://avatarqing.gitbooks.io/gradlepluginuserguidechineseverision/content/introduction/README.html</a></p>
</li>
<li><p><a href="http://www.infoq.com/cn/articles/android-in-depth-gradle" target="_blank" rel="external">深入理解Android(一):Gradle详解:http://www.infoq.com/cn/articles/android-in-depth-gradle</a></p>
</li>
</ol>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://houzhi.me/2016/08/13/android-appwidget-sourcecode2/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="houzhi">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Creative, Challenging and Cogitative">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/08/13/android-appwidget-sourcecode2/" itemprop="url">AppWidget源码分析(2)---updateAppWidget过程分析</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Veröffentlicht am</span>
<time title="Post created" itemprop="dateCreated datePublished" datetime="2016-08-13T00:00:00+08:00">
2016-08-13
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>前面一篇文章,分析了AppWidgetProvider和RemoteView的源码,从中我们可以知道它们的实现原理,AppWidgetProvider是一个BroadcastReceiver,所以它是通过广播接收通知的,收到更新通知后,AppWidgetProvider需要去提供View供远程进程显示,而提供的View则是使用RemoteView来代替,通过RemoteView(是一个Parcelable,可跨进程传输数据类型)来作为媒介去传递给远程进程。由远程进程解析RemoteView,然后显示RemoteView表示的真正的View。在这篇文章将分析这个传输过程。</p>
<h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><p>还是先看一下上一篇给出的一个过程图示:</p>
<p><img src="http://xxxzhi.github.io/images/appwidget1.png" alt="这里写图片描述"></p>
<p>实际上,AppWidgetProvider是通过AppWidgetManager来更新View的,而AppWidgetManager里面是有一个IAppWidgetService,一看就知道这是一个idle生成的,是一个Binder通信。关于Binder通信我之前也写过一篇文章叫<a href="http://blog.houzhi.me/2016/04/23/android-sourcecode-proxy-binder" target="_blank" rel="external">Android源码代理模式—Binder</a>,可以参考一下。而服务端的AppWidgetService是AppWidgetServiceImpl,AppWidgetServiceImpl又会通过一个IAppWidgetHost来跨进程通知AppWidgetHost,AppWidgetHost内部的IAppWidgetHost是AppWidgetHost.Callback。这样就到了显示我们的AppWidget的进程(大部分是Launcher应用)。</p>
<p>分清楚每个部分是在什么进程运行的对于理解整个流程是非常有帮助的,AppWidgetProvider是在我们自己的应用程序进程当中,而AppWidgetService是运行在SystemServer进程,AppWidgetHost则是运行在显示AppWidget的进程中,比如Launcher应用(桌面)。整个流程相当于是跨越了三个进程。</p>
<h2 id="updateAppWidgetIds过程分析"><a href="#updateAppWidgetIds过程分析" class="headerlink" title="updateAppWidgetIds过程分析"></a>updateAppWidgetIds过程分析</h2><p>这是AppWidgetManager的一个接口函数,根据id来更新AppWidget。先把整个更新过程的时序图拿出来看一下:</p>
<p><img src="http://xxxzhi.github.io/images/updateAppWidgetIds.png" alt="这里写图片描述"></p>
<p>从我们普通的调用AppWidgetManager的updateAppWidget看起吧:</p>
<pre><code>appWidgetManager.updateAppWidget(appwidgetids,remoteViews);
</code></pre><ol>
<li>这里进入updateAppWidget方法:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {</div><div class="line"> if (mService == null) {</div><div class="line"> return;</div><div class="line"> }</div><div class="line"> try {</div><div class="line"> mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);</div><div class="line"> }</div><div class="line"> catch (RemoteException e) {</div><div class="line"> throw new RuntimeException("system server dead?", e);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>mService是一个IAppWidgetService类型,初始化是在SystemServiceRegistry(6.0才出现的,之前在ContextImpl里面)里面:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">registerService(Context.APPWIDGET_SERVICE, AppWidgetManager.class,</div><div class="line"> new CachedServiceFetcher<AppWidgetManager>() {</div><div class="line"> @Override</div><div class="line"> public AppWidgetManager createService(ContextImpl ctx) {</div><div class="line"> IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);</div><div class="line"> return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));</div><div class="line"> }});</div></pre></td></tr></table></figure>
<p>注册服务的时候获取APPWIDGET_SERVICE。所以mService.updateAppWidgetIds最后会调用到远程进程,而IAppWidgetService的实现者是AppWidgetServiceImpl,所以最终会调用AppWidgetServiceImpl的updateAppWidgetIds。</p>
<ol>
<li>AppWidgetServiceImpl中的updateAppWidgetIds:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">@Override</div><div class="line">public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,</div><div class="line"> RemoteViews views) {</div><div class="line"> if (DEBUG) {</div><div class="line"> Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());</div><div class="line"> }</div><div class="line"></div><div class="line"> updateAppWidgetIds(callingPackage, appWidgetIds, views, false);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这就是AppWidgetServiceImpl的updateAppWidgetIds方法。程序已经开始进入到AppWidgetServiceImpl所在的进程了,实际上是SystemServer进程。怎么看出AppWidgetServiceImpl是运行在SystemServer进程呢?AppWidgetService类new了一个AppWidgetServiceImpl,并且注册到ServiceManager中:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public class AppWidgetService extends SystemService {</div><div class="line"> private final AppWidgetServiceImpl mImpl;</div><div class="line"></div><div class="line"> public AppWidgetService(Context context) {</div><div class="line"> super(context);</div><div class="line"> mImpl = new AppWidgetServiceImpl(context);</div><div class="line"> }</div><div class="line"></div><div class="line"> @Override</div><div class="line"> public void onStart() {</div><div class="line"> publishBinderService(Context.APPWIDGET_SERVICE, mImpl); //注册mImpl到ServiceManager当中</div><div class="line"> AppWidgetBackupBridge.register(mImpl);</div><div class="line"> }</div><div class="line"></div><div class="line"> @Override</div><div class="line"> public void onBootPhase(int phase) {</div><div class="line"> if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {</div><div class="line"> mImpl.setSafeMode(isSafeMode());</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>而AppWidgetService在SystemServer.java中使用:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private static final String APPWIDGET_SERVICE_CLASS =</div><div class="line"> "com.android.server.appwidget.AppWidgetService";</div></pre></td></tr></table></figure>
<p>SystemServer是通过反射的方式new一个AppWidgetService对象,然后调用它的start函数。很多service类都是这样启动的。</p>
<p>回到updateAppWidgetIds方法,最后它会调用四个参数的updateAppWidgetIds方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,</div><div class="line"> RemoteViews views, boolean partially) {</div><div class="line"> final int userId = UserHandle.getCallingUserId();</div><div class="line"></div><div class="line"> if (appWidgetIds == null || appWidgetIds.length == 0) {</div><div class="line"> return;</div><div class="line"> }</div><div class="line"></div><div class="line"> // Make sure the package runs under the caller uid.</div><div class="line"> mSecurityPolicy.enforceCallFromPackage(callingPackage);</div><div class="line"></div><div class="line"> final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0;</div><div class="line"> if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {</div><div class="line"> throw new IllegalArgumentException("RemoteViews for widget update exceeds"</div><div class="line"> + " maximum bitmap memory usage (used: " + bitmapMemoryUsage</div><div class="line"> + ", max: " + mMaxWidgetBitmapMemory + ")");</div><div class="line"> }</div><div class="line"></div><div class="line"> synchronized (mLock) {</div><div class="line"> ensureGroupStateLoadedLocked(userId);</div><div class="line"></div><div class="line"> final int N = appWidgetIds.length;</div><div class="line"> for (int i = 0; i < N; i++) {</div><div class="line"> final int appWidgetId = appWidgetIds[i];</div><div class="line"></div><div class="line"> // NOTE: The lookup is enforcing security across users by making</div><div class="line"> // sure the caller can only access widgets it hosts or provides.</div><div class="line"> Widget widget = lookupWidgetLocked(appWidgetId,</div><div class="line"> Binder.getCallingUid(), callingPackage);</div><div class="line"></div><div class="line"> if (widget != null) {</div><div class="line"> updateAppWidgetInstanceLocked(widget, views, partially);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>该方法首先会做些安全性检查,以及图片大小限制检查。最后会针对每一个appWidgetId,通过lookupWidgetLocked找到其对应的Widget。</p>
<ol>
<li>Widget是一个带有很多信息的类,我们看看lookupWidgetLocked方法:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {</div><div class="line"> final int N = mWidgets.size();</div><div class="line"> for (int i = 0; i < N; i++) {</div><div class="line"> Widget widget = mWidgets.get(i);</div><div class="line"> if (widget.appWidgetId == appWidgetId</div><div class="line"> && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {</div><div class="line"> return widget;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> return null;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>实际上它是从mWidgets找到对应的Widget,先看看Widget类,它是AppWidgetServiceImpl的非静态内部类:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private static final class Widget {</div><div class="line"> int appWidgetId;</div><div class="line"> int restoredId; // tracking & remapping any restored state</div><div class="line"> Provider provider; // 对应AppWidgetProvider,里面有AppWidgetProvider信息。</div><div class="line"> RemoteViews views; //表示View的RemoteView</div><div class="line"> Bundle options;</div><div class="line"> Host host; //显示的地方</div><div class="line"></div><div class="line"> @Override</div><div class="line"> public String toString() {</div><div class="line"> return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>而是从什么时候把Widget添加到mWidgets的呢?主要有三个地方,一个是绑定AppWidgetProvider跟id时,初始化时加载AppWidget与对应的host;一个是第一次添加AppWidget到桌面时,给AppWidget分配id的时候;一个是restore AppWidget的时候。我们看看分配id时,添加Widget的代码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">@Override</div><div class="line"></div><div class="line">public int allocateAppWidgetId(String callingPackage, int hostId) {</div><div class="line"> final int userId = UserHandle.getCallingUserId();</div><div class="line"></div><div class="line"> // Make sure the package runs under the caller uid.</div><div class="line"> mSecurityPolicy.enforceCallFromPackage(callingPackage);</div><div class="line"></div><div class="line"> synchronized (mLock) {</div><div class="line"> ensureGroupStateLoadedLocked(userId);</div><div class="line"></div><div class="line"> if (mNextAppWidgetIds.indexOfKey(userId) < 0) {</div><div class="line"> mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);</div><div class="line"> }</div><div class="line"></div><div class="line"> final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId); //增量分配一个id,保证不冲突</div><div class="line"></div><div class="line"> // NOTE: The lookup is enforcing security across users by making</div><div class="line"> // sure the caller can only access hosts it owns.</div><div class="line"> HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage); //得到hostid</div><div class="line"> Host host = lookupOrAddHostLocked(id); //根据id获取host</div><div class="line"></div><div class="line"> Widget widget = new Widget();</div><div class="line"> widget.appWidgetId = appWidgetId;</div><div class="line"> widget.host = host;</div><div class="line"></div><div class="line"> host.widgets.add(widget); //把widget添加到host的widgets列表中</div><div class="line"> addWidgetLocked(widget); //添加</div><div class="line"></div><div class="line"> saveGroupStateAsync(userId);</div><div class="line"></div><div class="line"> return appWidgetId;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>实际上这里还没有添加对应的provider,所以在Launcher开发的时候,我们需要先调用allocateAppWidgetId方法,然后调用bindAppWidgetId方法绑定id与AppWidgetProvider。而当View有变化的时候,Host需要接收AppWidgetServiceImpl的通知,如何实现的呢?Host(Launcher应用)会跨进程调用AppWidgetServiceImpl的startListening方法,将AppWidgetHost端的Callback服务传递给AppWidgetServiceImpl:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">// 在AppWidgetHost类当中,AppWidgetHost是Host端的代码</div><div class="line"></div><div class="line">public void startListening() {</div><div class="line"> int[] updatedIds;</div><div class="line"> ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();</div><div class="line"> try {</div><div class="line"> updatedIds = sService.startListening(mCallbacks, mContextOpPackageName, mHostId,</div><div class="line"> updatedViews); //把AppWidgetHost端的mCallbacks传递给AppWidgetService,mCallbacks是Binder对象。</div><div class="line"> }</div><div class="line"> catch (RemoteException e) {</div><div class="line"> throw new RuntimeException("system server dead?", e);</div><div class="line"> }</div><div class="line"></div><div class="line"> final int N = updatedIds.length;</div><div class="line"> for (int i = 0; i < N; i++) {</div><div class="line"> updateAppWidgetView(updatedIds[i], updatedViews.get(i));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在AppWidgetServiceImpl里面会将mCallbacks保存在对应的Host当中。</p>
<ol>
<li>从mWidgets里面找到Widget后,会调用updateAppWidgetInstanceLocked方法来更新Widget。</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,</div><div class="line"> boolean isPartialUpdate) {</div><div class="line"> if (widget != null && widget.provider != null</div><div class="line"> && !widget.provider.zombie && !widget.host.zombie) { // 保证widget有效,并且host也有效</div><div class="line"></div><div class="line"> if (isPartialUpdate && widget.views != null) {</div><div class="line"> // For a partial update, we merge the new RemoteViews with the old. 这里是对于partial update的。</div><div class="line"> widget.views.mergeRemoteViews(views);</div><div class="line"> } else {</div><div class="line"> // For a full update we replace the RemoteViews completely.</div><div class="line"> widget.views = views;</div><div class="line"> }</div><div class="line"></div><div class="line"> scheduleNotifyUpdateAppWidgetLocked(widget, views);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>而scheduleNotifyUpdateAppWidgetLocked 则是使用handler发送一个消息给主线程处理:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {</div><div class="line"> if (widget == null || widget.provider == null || widget.provider.zombie</div><div class="line"> || widget.host.callbacks == null || widget.host.zombie) {</div><div class="line"> return;</div><div class="line"> }</div><div class="line"></div><div class="line"> SomeArgs args = SomeArgs.obtain();</div><div class="line"> args.arg1 = widget.host;</div><div class="line"> args.arg2 = widget.host.callbacks; //callbacks 是host的跨进程调用接口,来自于startListening</div><div class="line"></div><div class="line"> args.arg3 = updateViews;</div><div class="line"> args.argi1 = widget.appWidgetId;</div><div class="line"></div><div class="line"> mCallbackHandler.obtainMessage(</div><div class="line"> CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,</div><div class="line"> args).sendToTarget();</div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>使用mCallbackHandler发送一条MSG_NOTIFY_UPDATE_APP_WIDGET的消息,mCallbackHandler是AppWidgetServiceImpl.CallbackHandler的实例。如果处理,具体就到CallbackHandler的handleMessage方法中,就是一个Handler机制,让代码运行在主线程:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">@Override</div><div class="line">public void handleMessage(Message message) {</div><div class="line"> switch (message.what) {</div><div class="line"> case MSG_NOTIFY_UPDATE_APP_WIDGET: {</div><div class="line"> SomeArgs args = (SomeArgs) message.obj;</div><div class="line"> Host host = (Host) args.arg1;</div><div class="line"> IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;</div><div class="line"> RemoteViews views = (RemoteViews) args.arg3;</div><div class="line"> final int appWidgetId = args.argi1;</div><div class="line"> args.recycle();</div><div class="line"></div><div class="line"> handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);</div><div class="line"> } break;</div><div class="line"></div><div class="line"> ...</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>handleNotifyUpdateAppWidget方法:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,</div><div class="line"> int appWidgetId, RemoteViews views) {</div><div class="line"> try {</div><div class="line"> callbacks.updateAppWidget(appWidgetId, views); //通知AppWidgtHost</div><div class="line"> } catch (RemoteException re) {</div><div class="line"> synchronized (mLock) {</div><div class="line"> Slog.e(TAG, "Widget host dead: " + host.id, re);</div><div class="line"> host.callbacks = null;</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里实际上就是调用IAppWidgetHost类型的updateAppWidget,进行跨进程调用。而callbacks就是在AppWidgetServiceImpl的startListening设置的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">@Override</div><div class="line">public int[] startListening(IAppWidgetHost callbacks, String callingPackage,</div><div class="line"> int hostId, List<RemoteViews> updatedViews) {</div><div class="line"> final int userId = UserHandle.getCallingUserId();</div><div class="line"></div><div class="line"> if (DEBUG) {</div><div class="line"> Slog.i(TAG, "startListening() " + userId);</div><div class="line"> }</div><div class="line"></div><div class="line"> // Make sure the package runs under the caller uid.</div><div class="line"> mSecurityPolicy.enforceCallFromPackage(callingPackage);</div><div class="line"></div><div class="line"> synchronized (mLock) {</div><div class="line"> ensureGroupStateLoadedLocked(userId);</div><div class="line"></div><div class="line"> // NOTE: The lookup is enforcing security across users by making</div><div class="line"> // sure the caller can only access hosts it owns.</div><div class="line"> HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);</div><div class="line"> Host host = lookupOrAddHostLocked(id);</div><div class="line"></div><div class="line"> host.callbacks = callbacks; //设置callbacks</div><div class="line"></div><div class="line"> updatedViews.clear();</div><div class="line"></div><div class="line"> ArrayList<Widget> instances = host.widgets;</div><div class="line"> int N = instances.size();</div><div class="line"> int[] updatedIds = new int[N];</div><div class="line"> for (int i = 0; i < N; i++) {</div><div class="line"> Widget widget = instances.get(i);</div><div class="line"> updatedIds[i] = widget.appWidgetId;</div><div class="line"> updatedViews.add(cloneIfLocalBinder(widget.views));</div><div class="line"> }</div><div class="line"></div><div class="line"> return updatedIds;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>最终updateAppWidget的实现代码是:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">static class Callbacks extends IAppWidgetHost.Stub {</div><div class="line"> private final WeakReference<Handler> mWeakHandler;</div><div class="line"></div><div class="line"> public Callbacks(Handler handler) {</div><div class="line"> mWeakHandler = new WeakReference<>(handler);</div><div class="line"> }</div><div class="line"></div><div class="line"> public void updateAppWidget(int appWidgetId, RemoteViews views) {</div><div class="line"> if (isLocalBinder() && views != null) {</div><div class="line"> views = views.clone();</div><div class="line"> }</div><div class="line"> Handler handler = mWeakHandler.get();</div><div class="line"> if (handler == null) {</div><div class="line"> return;</div><div class="line"> }</div><div class="line"> Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);</div><div class="line"> msg.sendToTarget();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>最后使用Handler发送消息给主线程,然后在handleMessage中有具体的处理程序:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">class UpdateHandler extends Handler {</div><div class="line"> public UpdateHandler(Looper looper) {</div><div class="line"> super(looper);</div><div class="line"> }</div><div class="line"></div><div class="line"> public void handleMessage(Message msg) {</div><div class="line"> switch (msg.what) {</div><div class="line"> case HANDLE_UPDATE: {</div><div class="line"> updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);</div><div class="line"> break;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>然后调用AppWidgetHost的updateAppWidgetView方法:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">void updateAppWidgetView(int appWidgetId, RemoteViews views) {</div><div class="line"> AppWidgetHostView v;</div><div class="line"> synchronized (mViews) {</div><div class="line"> v = mViews.get(appWidgetId);</div><div class="line"> }</div><div class="line"> if (v != null) {</div><div class="line"> v.updateAppWidget(views);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>根据appWidgetId找到对应的AppWidgetHostView,然后调用AppWidgetHostView的updateAppWidget来根据RemoteView来更新AppWidgetHostView:</li>
</ol>
<p></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">/**</div><div class="line">* Process a set of {@link RemoteViews} coming in as an update from the</div><div class="line">* AppWidget provider. Will animate into these new views as needed</div><div class="line">*/</div><div class="line">public void updateAppWidget(RemoteViews remoteViews) {</div><div class="line"></div><div class="line"> boolean recycled = false;</div><div class="line"> View content = null;</div><div class="line"> Exception exception = null;</div><div class="line"></div><div class="line"> // Capture the old view into a bitmap so we can do the crossfade.</div><div class="line"> ... 省去old view to bitmap</div><div class="line"></div><div class="line"> if (remoteViews == null) {</div><div class="line"> if (mViewMode == VIEW_MODE_DEFAULT) {</div><div class="line"> // We've already done this -- nothing to do.</div><div class="line"> return;</div><div class="line"> }</div><div class="line"> content = getDefaultView(); // 默认的View</div><div class="line"> mLayoutId = -1;</div><div class="line"> mViewMode = VIEW_MODE_DEFAULT;</div><div class="line"> } else {</div><div class="line"> // Prepare a local reference to the remote Context so we're ready to</div><div class="line"> // inflate any requested LayoutParams.</div><div class="line"> mRemoteContext = getRemoteContext();</div><div class="line"> int layoutId = remoteViews.getLayoutId();</div><div class="line"></div><div class="line"> // If our stale view has been prepared to match active, and the new</div><div class="line"> // layout matches, try recycling it</div><div class="line"> if (content == null && layoutId == mLayoutId) {</div><div class="line"> try {</div><div class="line"> remoteViews.reapply(mContext, mView, mOnClickHandler);</div><div class="line"> content = mView;</div><div class="line"> recycled = true;</div><div class="line"> if (LOGD) Log.d(TAG, "was able to recycled existing layout");</div><div class="line"> } catch (RuntimeException e) {</div><div class="line"> exception = e;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> // Try normal RemoteView inflation</div><div class="line"> if (content == null) {</div><div class="line"> try {</div><div class="line"> content = remoteViews.apply(mContext, this, mOnClickHandler);</div><div class="line"> if (LOGD) Log.d(TAG, "had to inflate new layout");</div><div class="line"> } catch (RuntimeException e) {</div><div class="line"> exception = e;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> mLayoutId = layoutId;</div><div class="line"> mViewMode = VIEW_MODE_CONTENT;</div><div class="line"> }</div><div class="line"> if (content == null) {</div><div class="line"> if (mViewMode == VIEW_MODE_ERROR) {</div><div class="line"> // We've already done this -- nothing to do.</div><div class="line"> return ;</div><div class="line"> }</div><div class="line"> Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);</div><div class="line"> content = getErrorView(); // 在失败的情况下,会使用ErrorView。</div><div class="line"> mViewMode = VIEW_MODE_ERROR;</div><div class="line"> }</div><div class="line"></div><div class="line"> if (!recycled) {</div><div class="line"> prepareView(content);</div><div class="line"> addView(content);</div><div class="line"> }</div><div class="line"></div><div class="line"> if (mView != content) {</div><div class="line"> removeView(mView);</div><div class="line"> mView = content;</div><div class="line"> }</div><div class="line"></div><div class="line"> if (CROSSFADE) {</div><div class="line"> if (mFadeStartTime < 0) {</div><div class="line"> // if there is already an animation in progress, don't do anything --</div><div class="line"> // the new view will pop in on top of the old one during the cross fade,</div><div class="line"> // and that looks okay.</div><div class="line"> mFadeStartTime = SystemClock.uptimeMillis();</div><div class="line"> invalidate();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>代码有点长,但我觉得值得认真一看,可以从源码中发现当我们的AppWidget在桌面显示异常时究竟可能是什么原因。更新view的整个策略主要就是:</p>
<ol>
<li><p>如果remoteView为空,则看是否已经使用了默认视图,如果已经使用了直接返回,如果没有则使用默认的视图。</p>
</li>
<li><p>如果remoteView不为空,则看layoutid是否跟现在已经使用的视图的layoutid一致,一致则重用旧的视图,调用remoteView.reapply方法重用视图,并且设置视图内容。</p>
</li>
<li><p>如果layoutid跟现使用的视图不一致,则调用remoteView.apply方法得到新的视图。</p>
</li>
<li><p>如果上面的步骤都没有得到视图,则使用错误视图。</p>
</li>
<li><p>如果新的视图与旧的视图不一致,则添加新的视图,删除旧的视图。</p>
</li>
</ol>
<p>其实这里更新视图的策略非常简单,尽量重用已有的视图。里面会有三种视图,一种是我们自己设置的,一种是默认的,一种是有错误的情况的。当我们在桌面上看到我们的AppWidget显示异常时,应该还是有两种不同的表现的,一种是错误情况,一种是是用来额默认的视图,如果remoteView为null的时候会使用默认视图,如果是从remoteView中读取视图失败时,则会使用错误视图。</p>
<p>所以看到错误视图时,我们可能需要考虑remoteView里面的View设置是否合理。如果看到的是默认视图,我们应该想想是否在AppWidgetProvider中调用了AppWidgetManager.updateAppWidget方法,是否remoteView参数为null?可能有的手机两种视图是一样的。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>整个updateAppWidget的分析就到这里了,AppWidget其他的更新也是一样的,整个更新过程跨越了三个进程,而RemoteView作为一种View跨进程传递的媒介。另外我觉得从AppWidget去理解Binder机制的使用可能也是一个非常好的切入点。因为这部分我们在应用开发当中经常使用,而且是View,能够看到效果。接下来我想写一篇关于Binder多线程的理解。我看AppWidget这部分的源码,也是项目中的AppWidget存在问题,熟悉AppWidget才能解决好问题,预测代码中可能潜在的问题。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://houzhi.me/2016/08/09/android-appwidget-api-source-code/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="houzhi">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Creative, Challenging and Cogitative">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/08/09/android-appwidget-api-source-code/" itemprop="url">AppWidget源码分析---接口类</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Veröffentlicht am</span>
<time title="Post created" itemprop="dateCreated datePublished" datetime="2016-08-09T00:00:00+08:00">
2016-08-09
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>最近项目中接触到AppWidget,相对来说这部分比较简单,所以趁着空余时间详细阅读了AppWidget的源码。这篇文章主要是从源码上分析AppWidget中API类的相关原理,相关类的简单功能介绍和实现原理。关于使用,建议看<a href="https://developer.android.com/guide/topics/appwidgets/index.html" target="_blank" rel="external">指导文档</a>。</p>
<h2 id="简述"><a href="#简述" class="headerlink" title="简述"></a>简述</h2><p>AppWidget相关的API类(供我们应用开发者使用的类)主要有:</p>
<p>AppWidgetProvider:继承这个类,来提供Appwidget。</p>
<p>AppWidgetManager:提供了AppWidget的管理接口,比如更新,绑定AppWidget id,根据Component获取AppWidget id等等。</p>
<p>RemoteView:能够跨进程更新的View。</p>
<p>AppWidgetHost:与AppWidgt 服务交互的类,获取App widget,显示出来</p>
<p>AppwidgetHostView:实际上显示出来的View</p>
<p>他们之间的关系简略来讲如下:</p>
<p><img src="http://xxxzhi.github.io/images/appwidget1.png" alt="这里写图片描述"></p>
<p>AppWidget Service 是一些类的集合,是AppWidget的核心服务,在之后的文章会介绍,这篇就不介绍了。下面详细介绍每个类的功能以及实现原理。</p>
<h3 id="AppWidgetProvider"><a href="#AppWidgetProvider" class="headerlink" title="AppWidgetProvider"></a>AppWidgetProvider</h3><p>这是我们在建立AppWidget的时候,需要去实现的类,它有几个重要的方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,</div><div class="line"> int appWidgetId, Bundle newOptions) // 当Widget的布局到新的大小的时候会被调用</div><div class="line"></div><div class="line">public void onDeleted(Context context, int[] appWidgetIds) // 当某个Widget被移除的时候回调</div><div class="line"></div><div class="line">public void onDisabled(Context context) // 当最后一个Widget被移除的时候回调</div><div class="line"></div><div class="line">public void onEnabled(Context context) // 当第一个Widget被添加的时候回调</div><div class="line"></div><div class="line">public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) // 当Widget从缓存的Widget恢复时</div><div class="line"></div><div class="line">public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) //当Widget需要被提供RemoteView的时候,每次添加Widget的时候会调用</div><div class="line"></div><div class="line">public void onReceive(Context context, Intent intent) // 后面介绍</div></pre></td></tr></table></figure>
<p>这几个方法是我们在继承AppWidgetProvider的时候,可以根据自己的需要来实现的方法。注释里面是每个方法被回调的时机。</p>
<p>实际上如果去查看AppWidgetProvider的源码,你会发现AppWidgetProvider是继承自BroadcastReceiver的,也就是说它是一种广播。所以它也有一个onReceive方法,这个方法我前面特意没有说明什么时候调用,其实就是收到广播的时候回调。看看onReceive方法的实现,你会发现前面的那几个方法都是在onReceiver中调用的,每一种方法都对应着一种广播Action:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public void onReceive(Context context, Intent intent) {</div><div class="line"> // Protect against rogue update broadcasts (not really a security issue,</div><div class="line"> // just filter bad broacasts out so subclasses are less likely to crash).</div><div class="line"> String action = intent.getAction();</div><div class="line"> if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {</div><div class="line"> Bundle extras = intent.getExtras();</div><div class="line"> if (extras != null) {</div><div class="line"> int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);</div><div class="line"> if (appWidgetIds != null && appWidgetIds.length > 0) {</div><div class="line"> this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {</div><div class="line"> Bundle extras = intent.getExtras();</div><div class="line"> if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {</div><div class="line"> final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);</div><div class="line"> this.onDeleted(context, new int[] { appWidgetId });</div><div class="line"> }</div><div class="line"> } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {</div><div class="line"> Bundle extras = intent.getExtras();</div><div class="line"> if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)</div><div class="line"> && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {</div><div class="line"> int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);</div><div class="line"> Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);</div><div class="line"> this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),</div><div class="line"> appWidgetId, widgetExtras);</div><div class="line"> }</div><div class="line"> } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {</div><div class="line"> this.onEnabled(context);</div><div class="line"> } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {</div><div class="line"> this.onDisabled(context);</div><div class="line"> } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {</div><div class="line"> Bundle extras = intent.getExtras();</div><div class="line"> if (extras != null) {</div><div class="line"> int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);</div><div class="line"> int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);</div><div class="line"> if (oldIds != null && oldIds.length > 0) {</div><div class="line"> this.onRestored(context, oldIds, newIds);</div><div class="line"> this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>AppWidgetProvider是一个BroadcastReceiver,所以它的<code>onUpdate, onEnabled</code>等方法每次被调用前,都会调用onReceive方法。我们继承AppWidgetProvider,也可以重载onReceiver方法,修改<code>onUpdate</code>等方法被调用的时机,当然并不推荐这么做,破坏接口的语义会非常危险。另外一方面它是一个BroadcastReceiver,所以它就具备BroadcastReceiver的各种特性(需要说明的是AppWidgetProvider要在manifest文件中静态注册,因为系统在安装apk时需要知道应用有哪些方法桌面Widget),它能够注册自定义的ACTION,每次执行的时候是会创建一个新的AppWidgetProvider实例,Context不能绑定服务,执行时间不能超过10秒等等。</p>
<p>AppWidgetProvider是一个BroadcastReceiver,明白这一点其实也就是理解AppWidget实现原理了。在onUpdate中提供了AppWidgetManager参数,这个AppWidgetMananger又是什么呢?下面来介绍一下。</p>
<h2 id="AppWidgetManager"><a href="#AppWidgetManager" class="headerlink" title="AppWidgetManager"></a>AppWidgetManager</h2><p>名如其义,AppWidgetManager你可以理解为AppWidget的管理接口(实际上它只提供了管理AppWidget的部分接口,后面的文章会介绍真正管理AppWidget的接口,也就是AppWidget 的client)。我们可以用AppWidgetManager来更新AppWidget的内容,给AppWidget绑定id。AppWidgetManager是在实现AppWidgetProvider的时候,经常会用到的类,可以用<code>AppWidgetManager.updateAppWidget</code>来更新AppWidget。如果是有AdapterView的AppWidget,可以用<code>AppWidgetManager.notifyAppWidgetViewDataChanged</code>来通知Adapter的变化,用<code>partiallyUpdateAppWidget</code>部分更新Widget。这几个方法是经常在AppWidgetProvider的<code>onUpdate</code>方法中使用的。这几个方法的原型如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId)</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views)</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">public void updateAppWidget(int[] appWidgetIds, RemoteViews views)</div></pre></td></tr></table></figure>
<p>另外可以通过<code>getAppWidgetIds</code>获取AppWidgetProvider组件对应的AppWidget的Id数组,我们经常通过这个方法获取所有的id,然后来更新所有的跟AppWidgetProvider相关的AppWidget。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"></div><div class="line">public int[] getAppWidgetIds(ComponentName provider)</div></pre></td></tr></table></figure>
<p>另外还有<code>bindAppWidgetIdIfAllowed</code>方法和<code>getInstalledProviders</code>,这两个方法主要是在Launcher应用当中使用,分配AppWidget的id,然后用<code>bindAppWidgetIdIfAllowed</code>绑定AppWidgetProvider与id。用<code>getInstalledProviders</code>方法获取已经插入的AppWidgetProvider,可以用来供用户选择添加到桌面。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider,</div><div class="line"> Bundle options)</div><div class="line"></div><div class="line">public List<AppWidgetProviderInfo> getInstalledProviders()</div></pre></td></tr></table></figure>
<p>关于launcher可以参考aosp的源码:<a href="http://arnab.ch/blog/2013/08/how-to-write-custom-launcher-app-in-android/" target="_blank" rel="external">http://arnab.ch/blog/2013/08/how-to-write-custom-launcher-app-in-android/</a>。我们也可以自己开发launcher应用。</p>
<h2 id="RemoteView"><a href="#RemoteView" class="headerlink" title="RemoteView"></a>RemoteView</h2><p>RemoteView顾名思义就是指远程View,通过本地进程修改RemoteView,能够使这些修改通过RemoteView为载体传递到远程进程。RemoteView不仅仅在AppWidget中有使用,Notification中也是使用了RemoteView。下面简单介绍一下RemoteView的实现原理。</p>
<p>RemoteView继承自Parcelable,所以RemoteView本身就是可以跨进程传递的。RemoteView有个内部类叫做Action,它也是继承自Parcelable,对于不同的操作,RemoteView内部实现了不同的Action。</p>
<p>比如点击事件,RemoteView内部有一个SetOnClickPendingIntent,它也是继承自Action,设置点击事件就是将点击事件保存在这里面,把点击事件作为一个Action。可以看看它的源码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private class SetOnClickPendingIntent extends Action {</div><div class="line"> public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {</div><div class="line"> this.viewId = id;</div><div class="line"> this.pendingIntent = pendingIntent;</div><div class="line"> }</div><div class="line"></div><div class="line"> public SetOnClickPendingIntent(Parcel parcel) {</div><div class="line"> viewId = parcel.readInt();</div><div class="line"></div><div class="line"> // We check a flag to determine if the parcel contains a PendingIntent.</div><div class="line"> if (parcel.readInt() != 0) {</div><div class="line"> pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> public void writeToParcel(Parcel dest, int flags) {</div><div class="line"> dest.writeInt(TAG);</div><div class="line"> dest.writeInt(viewId);</div><div class="line"></div><div class="line"> // We use a flag to indicate whether the parcel contains a valid object.</div><div class="line"> dest.writeInt(pendingIntent != null ? 1 : 0);</div><div class="line"> if (pendingIntent != null) {</div><div class="line"> pendingIntent.writeToParcel(dest, 0 /* no flags */);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> @Override</div><div class="line"> public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {</div><div class="line"> final View target = root.findViewById(viewId);</div><div class="line"> if (target == null) return;</div><div class="line"></div><div class="line"> // If the view is an AdapterView, setting a PendingIntent on click doesn't make much</div><div class="line"> // sense, do they mean to set a PendingIntent template for the AdapterView's children?</div><div class="line"> if (mIsWidgetCollectionChild) {</div><div class="line"> Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +</div><div class="line"> "(id: " + viewId + ")");</div><div class="line"> ApplicationInfo appInfo = root.getContext().getApplicationInfo();</div><div class="line"></div><div class="line"> // We let this slide for HC and ICS so as to not break compatibility. It should have</div><div class="line"> // been disabled from the outset, but was left open by accident.</div><div class="line"> if (appInfo != null &&</div><div class="line"> appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {</div><div class="line"> return;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> // If the pendingIntent is null, we clear the onClickListener</div><div class="line"> OnClickListener listener = null;</div><div class="line"> if (pendingIntent != null) {</div><div class="line"> listener = new OnClickListener() {</div><div class="line"> public void onClick(View v) {</div><div class="line"> // Find target view location in screen coordinates and</div><div class="line"> // fill into PendingIntent before sending.</div><div class="line"> final Rect rect = getSourceBounds(v);</div><div class="line"></div><div class="line"> final Intent intent = new Intent();</div><div class="line"> intent.setSourceBounds(rect);</div><div class="line"> handler.onClickHandler(v, pendingIntent, intent);</div><div class="line"> }</div><div class="line"> };</div><div class="line"> }</div><div class="line"> target.setOnClickListener(listener);</div><div class="line"> }</div><div class="line">...</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>其中以Parcel为参数的构造函数相当于是从Parcel当中读取内容,而writeToParcel是将Action写入到Parcel。这两个函数是用来传递Action用的。而apply方法则是将Action解析出来,设置监听(这种监听是远程监听,在onClick方法里面发送一个PendingIntent),具体实现在RemoteView的OnClickHandler中。其他的Action也是类似的。关于Parcel实现原理可以借鉴我之前写的两篇关于Bitmap传输的文章:<a href="http://blog.csdn.net/xxxzhi/article/details/51531098" target="_blank" rel="external"> Android4.0 Bitmap Parcel传输源码分析</a>,<a href="http://blog.csdn.net/xxxzhi/article/details/51490253" target="_blank" rel="external">Android6.0 Bitmap存储以及Parcel传输源码分析</a></p>
<p>RemoteView里面有一个mActions变量,是Action的列表。通过Parcel传递RemoteView的时候,RemoteView会将mActions里面的内容都写入到Parcel中。在readParcel的时候,使用Action的带Parcel参数的构造函数从Parcel里面读取Action,进行设置。Action其实就是一种模板方法模式。RemoteView很多设置是跟普通的View不一样的,RemoteView是一个能够跨进程设置相关内容的,如果需要设置监听函数之类的,只能设置PendingIntent。可以看看RemoteView里面的CREATOR和RemoteView对应的构造方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">/**</div><div class="line">* Parcelable.Creator that instantiates RemoteViews objects</div><div class="line">*/</div><div class="line">public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {</div><div class="line"> public RemoteViews createFromParcel(Parcel parcel) {</div><div class="line"> return new RemoteViews(parcel);</div><div class="line"> }</div><div class="line"></div><div class="line"> public RemoteViews[] newArray(int size) {</div><div class="line"> return new RemoteViews[size];</div><div class="line"> }</div><div class="line">};</div><div class="line"></div><div class="line">...</div><div class="line"></div><div class="line"></div><div class="line"></div><div class="line">public RemoteViews(Parcel parcel) {</div><div class="line"> this(parcel, null);</div><div class="line">}</div><div class="line"></div><div class="line">private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {//实际上在构造函数中读取Parcel的内容,重新创建一个mActions</div><div class="line"> int mode = parcel.readInt();</div><div class="line"></div><div class="line"> // We only store a bitmap cache in the root of the RemoteViews.</div><div class="line"> if (bitmapCache == null) {</div><div class="line"> mBitmapCache = new BitmapCache(parcel);</div><div class="line"> } else {</div><div class="line"> setBitmapCache(bitmapCache);</div><div class="line"> setNotRoot();</div><div class="line"> }</div><div class="line"></div><div class="line"> if (mode == MODE_NORMAL) {</div><div class="line"> mApplication = parcel.readParcelable(null);</div><div class="line"> mLayoutId = parcel.readInt();</div><div class="line"> mIsWidgetCollectionChild = parcel.readInt() == 1;</div><div class="line"></div><div class="line"> int count = parcel.readInt();</div><div class="line"> if (count > 0) {</div><div class="line"> mActions = new ArrayList<Action>(count);</div><div class="line"> for (int i=0; i<count; i++) {</div><div class="line"> int tag = parcel.readInt();</div><div class="line"> switch (tag) {</div><div class="line"> case SetOnClickPendingIntent.TAG:</div><div class="line"> mActions.add(new SetOnClickPendingIntent(parcel));</div><div class="line"> break;</div><div class="line"> case SetDrawableParameters.TAG:</div><div class="line"> mActions.add(new SetDrawableParameters(parcel));</div><div class="line"> break;</div><div class="line"> ...</div><div class="line"> default:</div><div class="line"> throw new ActionException("Tag " + tag + " not found");</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> } else {</div><div class="line"> // MODE_HAS_LANDSCAPE_AND_PORTRAIT</div><div class="line"> mLandscape = new RemoteViews(parcel, mBitmapCache);</div><div class="line"> mPortrait = new RemoteViews(parcel, mBitmapCache);</div><div class="line"> mApplication = mPortrait.mApplication;</div><div class="line"> mLayoutId = mPortrait.getLayoutId();</div><div class="line"> }</div><div class="line"></div><div class="line"> // setup the memory usage statistics</div><div class="line"> mMemoryUsageCounter = new MemoryUsageCounter();</div><div class="line"> recalculateMemoryUsage();</div><div class="line">}</div></pre></td></tr></table></figure>
<p>最后调用RemoteView的apply方法就在远程进程设置了相关内容了。我们AppWidget的远程进程是Launcher应用所在的进程。由AppWidgetHost管理这些。</p>
<h2 id="AppWidgetHost"><a href="#AppWidgetHost" class="headerlink" title="AppWidgetHost"></a>AppWidgetHost</h2><p>这个类是Android提供的供应用与AppWidget service交互的类,我们的AppWidgetProvider提供了Widget,而AppWidgetHost则是读取Widget,将它显示出来。一般在home screen中使用,也就是我们的桌面,Launcher。与之相关的还有个AppWidgetHostView,由AppWidgetHost创建,它与AppWidgetProvider对应。可以看一下AppWidgetHost的createView方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public final AppWidgetHostView createView(Context context, int appWidgetId,</div><div class="line"> AppWidgetProviderInfo appWidget) {</div><div class="line"> AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);</div><div class="line"> view.setOnClickHandler(mOnClickHandler);</div><div class="line"> view.setAppWidget(appWidgetId, appWidget);</div><div class="line"> synchronized (mViews) {</div><div class="line"> mViews.put(appWidgetId, view);</div><div class="line"> }</div><div class="line"> RemoteViews views;</div><div class="line"> try {</div><div class="line"> views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);</div><div class="line"> } catch (RemoteException e) {</div><div class="line"> throw new RuntimeException("system server dead?", e);</div><div class="line"> }</div><div class="line"> view.updateAppWidget(views);</div><div class="line"></div><div class="line"> return view;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这个主要是在launcher应用使用,就不详细介绍了。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这一篇主要是介绍AppWidget相关的一些类,分析里面的源码功能,以及实现方式。通过深入了解它的实现方式才能够更好地使用它,分析遇到的问题。下一篇将从源码角度上介绍一些方法的处理流程。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://houzhi.me/2016/08/04/android-accessibility-introduce/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="houzhi">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Creative, Challenging and Cogitative">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2016/08/04/android-accessibility-introduce/" itemprop="url">Android Accessibility使用及事件流程简介</a></h1>