-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
888 lines (703 loc) · 361 KB
/
atom.xml
File metadata and controls
888 lines (703 loc) · 361 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>jacpy's blog</title>
<subtitle>enjoy mobile development</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://www.jacpy.com/"/>
<updated>2019-11-13T12:35:41.465Z</updated>
<id>http://www.jacpy.com/</id>
<author>
<name>jacpy</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Nodejs使用archiver-zip-encrypted库加密压缩文件时报错</title>
<link href="http://www.jacpy.com/2019/11/07/nodejs-file-data-stream-has-unexpected-number-of-bytes.html"/>
<id>http://www.jacpy.com/2019/11/07/nodejs-file-data-stream-has-unexpected-number-of-bytes.html</id>
<published>2019-11-07T12:35:10.000Z</published>
<updated>2019-11-13T12:35:41.465Z</updated>
<content type="html"><p>前几天在维护一个nodejs写的命令行工具,要增加一个压缩zip文件时加密码功能。压缩文件时使用了<a href="https://www.npmjs.com/package/archiver" target="_blank" rel="noopener">archiver</a>库,加密码使用了<a href="https://www.npmjs.com/package/archiver-zip-encrypted" target="_blank" rel="noopener">archiver-zip-encrypted</a>库。在windows系统上测试时,发现会概率的出现以下异常:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">events.js:174 </span><br><span class="line">throw er; // Unhandled &apos;error&apos; event </span><br><span class="line">^</span><br><span class="line"></span><br><span class="line">Error: file data stream has unexpected number of bytes </span><br><span class="line">at ByteCounter. (</span><br><span class="line">xxx\node_modules\yazl\index.js:162:99) </span><br><span class="line">at ByteCounter.emit (events.js:194:15) </span><br><span class="line">at endReadableNT (_stream_readable.js:1103:12) </span><br><span class="line">at process._tickCallback (internal/process/next_tick.js:63:19) </span><br><span class="line">Emitted &apos;error&apos; event at: </span><br><span class="line">at ByteCounter. (xxx\node_modules\yazl\index.js:162:85) </span><br><span class="line">at ByteCounter.emit (events.js:194:15) </span><br><span class="line">at endReadableNT (_stream_readable.js:1103:12) </span><br><span class="line">at process._tickCallback (internal/process/next_t</span><br></pre></td></tr></table></figure>
<p>我的本机环境是:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm:6.9.0</span><br><span class="line">node: v10.16.3</span><br></pre></td></tr></table></figure>
<p>在另外一个同事的windows系统上测试,他那边是上面异常必现,对应的node版本是v10.15。</p>
<p>具体使用的代码不贴了,基本上是参照官方<a href="https://www.npmjs.com/package/archiver" target="_blank" rel="noopener">demo</a>来写的,压缩完成最后调用代码如下所示:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">archive.finalize().then(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line"> <span class="comment">// 到这里认为是压缩完成,进行后续处理,实际并没有,参照后面分析</span></span><br><span class="line"> anotherProcess();</span><br><span class="line">&#125;).catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line"> <span class="comment">// 压缩出现异常处理...</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>出现异常后一一检查代码和官方demo不一样的地方,并没有发现什么异常之处,网上搜索也没有发现这种异常记录。由于刚接触JS,不是很熟,就从问题开始下手,找到出现问题的代码,开始调试。</p>
<p>错误日志中提示是在<code>yzal/index.js</code>文件中发生异常,找到出现异常的代码如下所示:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pumpFileDataReadStream</span>(<span class="params">self, entry, readStream</span>) </span>&#123;</span><br><span class="line"> <span class="keyword">var</span> crc32Watcher = <span class="keyword">new</span> Crc32Watcher();</span><br><span class="line"> <span class="keyword">var</span> uncompressedSizeCounter = <span class="keyword">new</span> ByteCounter();</span><br><span class="line"> <span class="keyword">var</span> compressor = entry.compress ? <span class="keyword">new</span> zlib.DeflateRaw() : <span class="keyword">new</span> PassThrough();</span><br><span class="line"> <span class="keyword">var</span> compressedSizeCounter = <span class="keyword">new</span> ByteCounter();</span><br><span class="line"> readStream.pipe(crc32Watcher)</span><br><span class="line"> .pipe(uncompressedSizeCounter)</span><br><span class="line"> .pipe(compressor)</span><br><span class="line"> .pipe(compressedSizeCounter)</span><br><span class="line"> .pipe(self.outputStream, &#123;<span class="attr">end</span>: <span class="literal">false</span>&#125;);</span><br><span class="line"> compressedSizeCounter.on(<span class="string">"end"</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> entry.crc32 = crc32Watcher.crc32;</span><br><span class="line"> <span class="keyword">if</span> (entry.uncompressedSize == <span class="literal">null</span>) &#123;</span><br><span class="line"> entry.uncompressedSize = uncompressedSizeCounter.byteCount;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="comment">// 异常从这里抛出来的</span></span><br><span class="line"> <span class="keyword">if</span> (entry.uncompressedSize !== uncompressedSizeCounter.byteCount) <span class="keyword">return</span> self.emit(<span class="string">"error"</span>, <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"file data stream has unexpected number of bytes"</span>));</span><br><span class="line"> &#125;</span><br><span class="line"> entry.compressedSize = compressedSizeCounter.byteCount;</span><br><span class="line"> self.outputStreamCursor += entry.compressedSize;</span><br><span class="line"> writeToOutputStream(self, entry.getDataDescriptor());</span><br><span class="line"> entry.state = Entry.FILE_DATA_DONE;</span><br><span class="line"> pumpEntries(self);</span><br><span class="line"> &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从上面代码可以看出来:<code>uncompressedSizeCounter.byteCount</code>是从<code>pumpFileDataReadStream()</code>函数<code>readStream</code>参数中获取的属性值,而<code>entry.uncompressedSize</code>也是函数的<code>entry</code>参数中获取的属性。接着找<code>pumpFileDataReadStream()</code>函数是从哪里调用的。</p>
<p>通过输出日志得出<code>pumpFileDataReadStream()</code>函数是在以下面的代码中被调用的:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">ZipFile.prototype.addFile = <span class="function"><span class="keyword">function</span>(<span class="params">realPath, metadataPath, options</span>) </span>&#123;</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"> metadataPath = validateMetadataPath(metadataPath, <span class="literal">false</span>);</span><br><span class="line"> <span class="keyword">if</span> (options == <span class="literal">null</span>) options = &#123;&#125;;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> entry = <span class="keyword">new</span> Entry(metadataPath, <span class="literal">false</span>, options);</span><br><span class="line"> self.entries.push(entry);</span><br><span class="line"> fs.stat(realPath, <span class="function"><span class="keyword">function</span>(<span class="params">err, stats</span>) </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (err) <span class="keyword">return</span> self.emit(<span class="string">"error"</span>, err);</span><br><span class="line"> <span class="keyword">if</span> (!stats.isFile()) <span class="keyword">return</span> self.emit(<span class="string">"error"</span>, <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"not a file: "</span> + realPath));</span><br><span class="line"> <span class="comment">// 这里是文件的大小</span></span><br><span class="line"> entry.uncompressedSize = stats.size;</span><br><span class="line"> <span class="keyword">if</span> (options.mtime == <span class="literal">null</span>) entry.setLastModDate(stats.mtime);</span><br><span class="line"> <span class="keyword">if</span> (options.mode == <span class="literal">null</span>) entry.setFileAttributesMode(stats.mode);</span><br><span class="line"> entry.setFileDataPumpFunction(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="comment">// readStream在这里创建的</span></span><br><span class="line"> <span class="keyword">var</span> readStream = fs.createReadStream(realPath);</span><br><span class="line"> entry.state = Entry.FILE_DATA_IN_PROGRESS;</span><br><span class="line"> readStream.on(<span class="string">"error"</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err</span>) </span>&#123;</span><br><span class="line"> self.emit(<span class="string">"error"</span>, err);</span><br><span class="line"> &#125;);</span><br><span class="line"> <span class="comment">// 在这里被调用</span></span><br><span class="line"> pumpFileDataReadStream(self, entry, readStream);</span><br><span class="line"> &#125;);</span><br><span class="line"> pumpEntries(self);</span><br><span class="line"> &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>从上面代码可以看出来<code>entry.uncompressedSize</code>是stats.size,即文件的大小,<code>readStream</code>是创建的文件流。但是在什么情况下两者会不一样呢?感觉只可能在文件还没有读取完,但是是什么原因导致这种情况发生?由于对JS接触的时间不长,没有进行深入分析。最后在抛出异常的上面一行用<code>console.log</code>将两个属性的大小值都输出,代码如下所示:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (entry.uncompressedSize == <span class="literal">null</span>) &#123;</span><br><span class="line"> entry.uncompressedSize = uncompressedSizeCounter.byteCount;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="comment">// 增加日志输出</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"entry size: "</span> + entry.uncompressedSize + <span class="string">", uncompressedSize: "</span> + uncompressedSizeCounter.byteCount);</span><br><span class="line"> <span class="keyword">if</span> (entry.uncompressedSize !== uncompressedSizeCounter.byteCount) <span class="keyword">return</span> self.emit(<span class="string">"error"</span>, <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"file data stream has unexpected number of bytes"</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在<code>archive.finalize()</code>时和<code>output</code>写入流的<code>close</code>事件时(详细参照官方的示例代码),分别加上日志输出,代码如下所示:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">archive.finalize().then(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line"> <span class="comment">// 到这里认为是压缩完成,进行后续处理,实际并没有,参照后面分析</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"finalize"</span>);</span><br><span class="line"> <span class="comment">// anotherProcess();</span></span><br><span class="line">&#125;).catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line"> <span class="comment">// 压缩出现异常</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">output.on(<span class="string">'close'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'close'</span>);</span><br><span class="line"> <span class="comment">// 这个业务函数与上面finalize函数中的是互斥,不会同时存在</span></span><br><span class="line"> anotherProcess();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>最后分别将<code>anotherProcess()</code>函数加到两个异步回调中执行,发现在<code>close</code>事件执行时,两个size输出的大小一致,都是文件的大小。而在<code>finalize</code>场景测试发现<code>uncompressedSize</code>要小于文件的大小。最后将<code>anotherProcess()</code>函数放在<code>close</code>事件回调函数中执行,问题解决。</p>
</content>
<summary type="html">
<p>前几天在维护一个nodejs写的命令行工具,要增加一个压缩zip文件时加密码功能。压缩文件时使用了<a href="https://www.npmjs.com/package/archiver" target="_blank" rel="noopener">archiver</a>库,加密码使用了<a href="https://www.npmjs.com/package/archiver-zip-encrypted" target="_blank" rel="noopener">archiver-zip-encrypted</a>库。在windows系统上测试时,发现会概率的出现以下异常:</p>
</summary>
<category term="js" scheme="http://www.jacpy.com/categories/js/"/>
<category term="nodejs archiver-zip-encrypted" scheme="http://www.jacpy.com/tags/nodejs-archiver-zip-encrypted/"/>
</entry>
<entry>
<title>Android编译minizip库运行时报Z_DATA_ERROR invalid code lengths set错误处理方法</title>
<link href="http://www.jacpy.com/2019/09/26/z-data-error-invalid-code-lengths-set.html"/>
<id>http://www.jacpy.com/2019/09/26/z-data-error-invalid-code-lengths-set.html</id>
<published>2019-09-26T09:56:06.000Z</published>
<updated>2019-09-27T13:29:29.173Z</updated>
<content type="html"><p>这几天在Android上给zip加上aes解密方法,刚开始使用的是<code>zlib</code>库中的<code>minizip</code>代码,发现不支持<code>aes</code>加解密,就换了github上的<a href="https://github.com/nmoinvaz/minizip" target="_blank" rel="noopener">minizip</a>库。等都编译OK,一运行就出现了异常,返回结果为-3,把msg输出如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">invalid code lengths set</span><br></pre></td></tr></table></figure>
<p>搜索了-3,发现对应的是Z_DATA_ERROR。把测试zip包用winrar解密,发现一切正常,不是zip包数据原因。</p>
<p>然后编译了源码中<code>minizip.c</code>文件,使用命令的方式执行了一下生成的<code>minizip</code>文件,发现能正常解密。看了一下<code>minizip</code>源码中的<code>CMakeLists.txt</code>文件,给<code>Android.mk</code>文件中增加了如下参数:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LOCAL_CFLAGS += -DHAVE_PKCRYPT</span><br></pre></td></tr></table></figure>
<p>再编译运行,OK了。</p>
</content>
<summary type="html">
<p>这几天在Android上给zip加上aes解密方法,刚开始使用的是<code>zlib</code>库中的<code>minizip</code>代码,发现不支持<code>aes</code>加解密,就换了github上的<a href="https://github.com/nmoinvaz/minizip" target="_blank" rel="noopener">minizip</a>库。等都编译OK,一运行就出现了异常,返回结果为-3,把msg输出如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">invalid code lengths set</span><br></pre></td></tr></table></figure>
<p>搜索了-3,发现对应的是Z_DATA_ERROR。把测试zip包用winrar解密,发现一切正常,不是zip包数据原因。</p>
<p>然后编译了源码中<code>minizip.c</code>文件,使用命令的方式执行了一下生成的<code>minizip</code>文件,发现能正常解密。看了一下<code>minizip</code>源码中的<code>CMakeLists.txt</code>文件,给<code>Android.mk</code>文件中增加了如下参数:</p>
</summary>
<category term="minizip" scheme="http://www.jacpy.com/categories/minizip/"/>
<category term="Z_DATA_ERROR invalid code lengths set" scheme="http://www.jacpy.com/tags/Z-DATA-ERROR-invalid-code-lengths-set/"/>
</entry>
<entry>
<title>使用jarsigner签名的apk无法在android 4.2及以下版本系统安装</title>
<link href="http://www.jacpy.com/2019/09/19/cannot-install-apk-in-android-17-width-jarsigner.html"/>
<id>http://www.jacpy.com/2019/09/19/cannot-install-apk-in-android-17-width-jarsigner.html</id>
<published>2019-09-19T11:52:15.000Z</published>
<updated>2019-09-20T00:49:12.307Z</updated>
<content type="html"><p>这几天遇到一件怪事,使用加固工具后,打包出来的APK无法在Android 4.2版本的手机上安装,不使用加固打出来的安装包却可以安装。于是找加固厂商询问原因,加固厂商拿了不能安装的文件后,又发了一个安装包让我们测试一下,结果可以了。询问原因,说是我们给的安装包没有签名,加上签名信息就好了。于是用<code>keytool</code>工具看了下原安装文件的签名信息,发现都有。把执行命令包含有签名信息的截图发过去了之后,加固厂商问了使用的签名工具,然后说是用<code>jarsigner</code>工具的问题。于是一查,还真是。</p>
<a id="more"></a>
<p>原来默认打包使用的是<code>gradlew assembleRelease</code>命令,使用这个命令默认使用的是Android SDK中的<code>apksigner</code>命令读取gradle文件中的签名信息进行签名。这就解释了为什么没有加固的安装包可以正常安装。</p>
<p>因为加固后,需要重新签名,这时自动打包在jenkins上使用的是<code>jarsigner</code>命令进行签名。而这个工具是由jdk提供的,而且不同的jdk版本的算法不一致。在jdk7版本开始使用SHA256,而4.2系统版本只支持SHA1,所以导致无法安装。</p>
<p>解决办法是执行<code>jarsigner</code>命令时,使用参数,命令如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jarsigner -keystore debug.keystore -digestalg SHA1 -sigalg SHA1widthRSA -signedjar test_signed.apk test_unsigned.apk</span><br></pre></td></tr></table></figure>
<p>即要加上<code>-digestalg SHA1 -sigalg SHA1widthRSA</code>参数即可。</p>
<p>但是上述方法不推荐,因为在Android 7.0版本以上会使用V2签名,v1签名会有Janus安全漏洞,所以签名apk文件时还是得使用<code>apkSigner</code>比较好。</p>
<p>命令如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar apksigner.jar sign --v1-signing-enabled true --v2-signing-enabled true --ks D:\test.jks --ks-pass pass:storePassword --ks-key-alias keyAlias --key-pass pass:keyPassword --in unsigned.apk --out signed.apk</span><br></pre></td></tr></table></figure>
<p><code>apksigner.jar</code>文件是在Android SDK的build-tool目录中相应版本目录中的lib目录下。注意这两个参数<code>--v1-signing-enabled true --v2-signing-enabled true</code>是可选,如果不加这两个参数,其默认值为true,这样可以在android 7.0版本以上避免Janus安全漏洞。另外还要注意密钥之前要加上<code>pass:</code>前缀。</p>
<p>查看apk签名信息的命令如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar apksigner.jar verify -v --print-certs 1.apk</span><br></pre></td></tr></table></figure>
<p>查看签名文件中的签名信息的命令如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -list -v -keystore D:\test.jks</span><br></pre></td></tr></table></figure>
<p>输入密钥库口令后即可输出签名信息。</p>
</content>
<summary type="html">
<p>这几天遇到一件怪事,使用加固工具后,打包出来的APK无法在Android 4.2版本的手机上安装,不使用加固打出来的安装包却可以安装。于是找加固厂商询问原因,加固厂商拿了不能安装的文件后,又发了一个安装包让我们测试一下,结果可以了。询问原因,说是我们给的安装包没有签名,加上签名信息就好了。于是用<code>keytool</code>工具看了下原安装文件的签名信息,发现都有。把执行命令包含有签名信息的截图发过去了之后,加固厂商问了使用的签名工具,然后说是用<code>jarsigner</code>工具的问题。于是一查,还真是。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="cannot install apk" scheme="http://www.jacpy.com/tags/cannot-install-apk/"/>
</entry>
<entry>
<title>golang执行命令获取执行结果状态</title>
<link href="http://www.jacpy.com/2019/09/18/golang-exec-command-tools.html"/>
<id>http://www.jacpy.com/2019/09/18/golang-exec-command-tools.html</id>
<published>2019-09-18T05:00:27.000Z</published>
<updated>2019-09-18T05:26:30.530Z</updated>
<content type="html"><p>这几天在用golang写一个工具,要执行外部命令工具,而且还要将外部命令工具输出的日志也要输出出来。网上找了一下,资料很多,关键是执行的结果成功或失败状态没找到好的方法获取到。刚开始想的是看错误日志,如果有错误日志,那么就是执行失败。测试的时候发现这样不行,发现有些时候会用error输出日志,但不一定就是执行失败。后来想用日志中的关键字匹配,因为有些命令执行成功或失败都是有关键字输出的,测试发现也不太好。最后没办法,看了一下<code>Cmd.Wait()</code>方法的实现,突然眼前一亮,找到方法了,有一个<code>Cmd.ProcessState</code>结构体可以使用。于是整理了一下,贴一下代码实现:</p>
<a id="more"></a>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Exec</span><span class="params">(name <span class="keyword">string</span>, args ...<span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"> cmd := exec.Command(name, args...)</span><br><span class="line"> stderr, _ := cmd.StderrPipe()</span><br><span class="line"> stdout, _ := cmd.StdoutPipe()</span><br><span class="line"> <span class="keyword">if</span> err := cmd.Start(); err != <span class="literal">nil</span> &#123;</span><br><span class="line"> log.Println(<span class="string">"exec the cmd "</span>, name, <span class="string">" failed"</span>)</span><br><span class="line"> <span class="keyword">return</span> err</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 正常日志</span></span><br><span class="line"> logScan := bufio.NewScanner(stdout)</span><br><span class="line"> <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"> <span class="keyword">for</span> logScan.Scan() &#123;</span><br><span class="line"> log.Println(logScan.Text())</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 错误日志</span></span><br><span class="line"> errBuf := bytes.NewBufferString(<span class="string">""</span>)</span><br><span class="line"> scan := bufio.NewScanner(stderr)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> scan.Scan() &#123;</span><br><span class="line"> s := scan.Text()</span><br><span class="line"> log.Println(<span class="string">"build error: "</span>, s)</span><br><span class="line"> errBuf.WriteString(s)</span><br><span class="line"> errBuf.WriteString(<span class="string">"\n"</span>)</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等待命令执行完</span></span><br><span class="line"> cmd.Wait()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> !cmd.ProcessState.Success() &#123;</span><br><span class="line"> <span class="comment">// 执行失败,返回错误信息</span></span><br><span class="line"> <span class="keyword">return</span> errors.New(errBuf.String())</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>注意:上面代码中没有处理输入的情况,如果待执行的命令需要输入数据,则不适用。</strong></p>
</content>
<summary type="html">
<p>这几天在用golang写一个工具,要执行外部命令工具,而且还要将外部命令工具输出的日志也要输出出来。网上找了一下,资料很多,关键是执行的结果成功或失败状态没找到好的方法获取到。刚开始想的是看错误日志,如果有错误日志,那么就是执行失败。测试的时候发现这样不行,发现有些时候会用error输出日志,但不一定就是执行失败。后来想用日志中的关键字匹配,因为有些命令执行成功或失败都是有关键字输出的,测试发现也不太好。最后没办法,看了一下<code>Cmd.Wait()</code>方法的实现,突然眼前一亮,找到方法了,有一个<code>Cmd.ProcessState</code>结构体可以使用。于是整理了一下,贴一下代码实现:</p>
</summary>
<category term="golang" scheme="http://www.jacpy.com/categories/golang/"/>
<category term="golang command status" scheme="http://www.jacpy.com/tags/golang-command-status/"/>
</entry>
<entry>
<title>jquery跨域问题导致ajax请求直接走error回调函数</title>
<link href="http://www.jacpy.com/2019/09/16/jquery-cors.html"/>
<id>http://www.jacpy.com/2019/09/16/jquery-cors.html</id>
<published>2019-09-16T09:02:48.000Z</published>
<updated>2019-09-16T09:40:02.221Z</updated>
<content type="html"><p>使用jquery的ajax请求时,发现每次都是走error的回调函数,错误提示的日志为<code>{&quot;readyState&quot;:0, status: 0, status:&quot;error&quot;}</code>。抓包请求发现请求正常返回数据,但是js代码却执行error的回调。其他组开发人员反馈的问题描述是在App中使用离线代码没有问题,使用地址访问就有问题。自然而然就想到了跨域问题,一看请求地址,果然。</p>
<p>服务器端接口响应的头增加以下内容就可以解决:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Access-Control-Allow-Origin: http://foo.example</span><br><span class="line">Access-Control-Allow-Methods: POST, GET, OPTIONS</span><br><span class="line">Access-Control-Allow-Headers: X-PINGOTHER, Content-Type</span><br><span class="line">Access-Control-Max-Age: 86400</span><br></pre></td></tr></table></figure>
<p><strong>注意:相应的域名要替换一下,</strong>替换成请求页面的地址,包括端口号。</p>
<p>响应头中返回以上内容就相当于告诉浏览器,这个数据来源可靠,把数据返回到JS中。否则浏览器认为响应数据不安全,直接拦截,不返回正确的数据,而返回错误。</p>
</content>
<summary type="html">
<p>使用jquery的ajax请求时,发现每次都是走error的回调函数,错误提示的日志为<code>{&quot;readyState&quot;:0, status: 0, status:&quot;error&quot;}</code>。抓包请求发现请求正常返回数据,但是js代码却执行error的回调。其他组开发人员反馈的问题描述是在App中使用离线代码没有问题,使用地址访问就有问题。自然而然就想到了跨域问题,一看请求地址,果然。</p>
<p>服务器端接口响应的头增加以下内容就可以解决:</p>
</summary>
<category term="javascript" scheme="http://www.jacpy.com/categories/javascript/"/>
<category term="jquery跨域问题" scheme="http://www.jacpy.com/tags/jquery%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98/"/>
</entry>
<entry>
<title>EventBus实现分析(下)</title>
<link href="http://www.jacpy.com/2018/07/26/eventbus-implement-analysis-2.html"/>
<id>http://www.jacpy.com/2018/07/26/eventbus-implement-analysis-2.html</id>
<published>2018-07-26T01:45:13.000Z</published>
<updated>2019-01-27T01:14:15.839Z</updated>
<content type="html"><p><a href="/2018/07/23/eventbus-implement-analysis-1.html">EventBus实现分析(上)</a>中介绍了<code>EventBus</code>的<code>简单使用</code>和<code>解决什么问题</code>,这一节介绍EventBus的实现细节。</p>
<h2 id="三、实现细节"><a href="#三、实现细节" class="headerlink" title="三、实现细节"></a>三、实现细节</h2><p>前面在介绍EventBus的使用时,也顺带着讲了一些细节。下面先从两个流程开始,一个是注册流程,另一个是发送消息并且处理的流程。</p>
<a id="more"></a>
<h3 id="3-1-注册流程"><a href="#3-1-注册流程" class="headerlink" title="3.1 注册流程"></a>3.1 注册流程</h3><p>注册过程要根据消息类型缓存对应消息接收的方法,缓存的集合是一个HashMap,缓存集合代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Class&lt;?&gt;, CopyOnWriteArrayList&lt;Subscription&gt;&gt; subscriptionsByEventType;</span><br></pre></td></tr></table></figure>
<p>其中键是消息类型的Class对象,值中的<code>org.greenrobot.eventbus.Subscription</code>的对象中包含当前方法所在的对象,即订阅者,还包含订阅的方法。代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Subscription</span> </span>&#123;</span><br><span class="line"> <span class="keyword">final</span> Object subscriber;</span><br><span class="line"> <span class="keyword">final</span> SubscriberMethod subscriberMethod;</span><br><span class="line"> <span class="comment">// ... 其它代码省略</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SubscriberMethod</span> </span>&#123;</span><br><span class="line"> <span class="comment">// 订阅方法</span></span><br><span class="line"> <span class="keyword">final</span> Method method;</span><br><span class="line"> <span class="comment">// 线程模型</span></span><br><span class="line"> <span class="keyword">final</span> ThreadMode threadMode;</span><br><span class="line"> <span class="comment">// 消息类型</span></span><br><span class="line"> <span class="keyword">final</span> Class&lt;?&gt; eventType;</span><br><span class="line"> <span class="comment">// 优先级</span></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> priority;</span><br><span class="line"> <span class="comment">// stick模式</span></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">boolean</span> sticky;</span><br><span class="line"> <span class="comment">/** Used for efficient comparison */</span></span><br><span class="line"> String methodString;</span><br><span class="line"> <span class="comment">// ...其它代码省略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>中间的解析缓存过程如下图所示:</p>
<p><img src="/images/2018/07/eventbus_register_cache.png" alt="解析缓存过程图"></p>
<h3 id="3-2-消息处理流程"><a href="#3-2-消息处理流程" class="headerlink" title="3.2 消息处理流程"></a>3.2 消息处理流程</h3><p>上面的缓存过程在这一步起作用了。</p>
<p>1)暂存消息到消息队列</p>
<p>当调用<code>EventBus#post</code>消息时,先从当前线程中取到消息队列,然后将消息放入到消息队列中,再从消息队列中取消息进行处理。代码如下所示:</p>
<p>org.greenrobot.eventbus.EventBus#post</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">post</span><span class="params">(Object event)</span> </span>&#123;</span><br><span class="line"> <span class="comment">// 每个线程都有一个消息队列,避免同步,提高效率</span></span><br><span class="line"> PostingThreadState postingState = currentPostingThreadState.get();</span><br><span class="line"> List&lt;Object&gt; eventQueue = postingState.eventQueue;</span><br><span class="line"> eventQueue.add(event);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!postingState.isPosting) &#123;</span><br><span class="line"> <span class="comment">// 参数准备</span></span><br><span class="line"> postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();</span><br><span class="line"> postingState.isPosting = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (postingState.canceled) &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EventBusException(<span class="string">"Internal error. Abort state was not reset"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> <span class="keyword">while</span> (!eventQueue.isEmpty()) &#123;</span><br><span class="line"> <span class="comment">// 执行消息</span></span><br><span class="line"> postSingleEvent(eventQueue.remove(<span class="number">0</span>), postingState);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line"> postingState.isPosting = <span class="keyword">false</span>;</span><br><span class="line"> postingState.isMainThread = <span class="keyword">false</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>2)找到消息类型对应方法</p>
<p>这一步主要是从消息类型的HashMap缓存<code>subscriptionsByEventType</code>中取出对应的方法集合。代码如下<code>postSingleEventForEventType</code>方法所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">postSingleEvent</span><span class="params">(Object event, PostingThreadState postingState)</span> <span class="keyword">throws</span> Error </span>&#123;</span><br><span class="line"> Class&lt;?&gt; eventClass = event.getClass();</span><br><span class="line"> <span class="keyword">boolean</span> subscriptionFound = <span class="keyword">false</span>;</span><br><span class="line"> <span class="comment">// eventInheritance默认为true</span></span><br><span class="line"> <span class="keyword">if</span> (eventInheritance) &#123;</span><br><span class="line"> <span class="comment">// 查找所有event类型</span></span><br><span class="line"> List&lt;Class&lt;?&gt;&gt; eventTypes = lookupAllEventTypes(eventClass);</span><br><span class="line"> <span class="keyword">int</span> countTypes = eventTypes.size();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> h = <span class="number">0</span>; h &lt; countTypes; h++) &#123;</span><br><span class="line"> Class&lt;?&gt; clazz = eventTypes.get(h);</span><br><span class="line"> <span class="comment">// 遍历event的类型,针对每种类型都会处理</span></span><br><span class="line"> subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ... 省略部分代码</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">postSingleEventForEventType</span><span class="params">(Object event, PostingThreadState postingState, Class&lt;?&gt; eventClass)</span> </span>&#123;</span><br><span class="line"> CopyOnWriteArrayList&lt;Subscription&gt; subscriptions;</span><br><span class="line"> <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line"> subscriptions = subscriptionsByEventType.get(eventClass);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">if</span> (subscriptions != <span class="keyword">null</span> &amp;&amp; !subscriptions.isEmpty()) &#123;</span><br><span class="line"> <span class="keyword">for</span> (Subscription subscription : subscriptions) &#123;</span><br><span class="line"> postingState.event = event;</span><br><span class="line"> postingState.subscription = subscription;</span><br><span class="line"> <span class="keyword">boolean</span> aborted = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> postToSubscription(subscription, event, postingState.isMainThread);</span><br><span class="line"> aborted = postingState.canceled;</span><br><span class="line"> &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line"> <span class="comment">// ... 省略部分代码</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...省略部分代码</span></span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在上面<code>postSingleEvent</code>方法中使用一个<code>eventInheritance</code>参数,这个参数默认为true,也就是说消息类型对应的父类都会被找出来。上一节的示例中,如果有订阅方法的参数是<code>Object</code>,post消息是<code>String</code>,这时候对应处理消息的方法参数是<code>Object</code>的方法也会被执行;如果是false,则参数为<code>Object</code>的方法不会执行,只有参数为<code>String</code>的方法会执行。这个参数是在<code>EventBusBuilder</code>中设置后,创建<code>EventBus</code>对象时使用。</p>
<p>3)执行对应方法</p>
<p><code>postToSubscription</code>是将消息加入执行列表中还是直接执行,这个是根据线程模型来的,前面有说过。下面就不贴代码了。</p>
<h2 id="四、优化"><a href="#四、优化" class="headerlink" title="四、优化"></a>四、优化</h2><p>在最后将消息加入到消息队列中的时候,<code>EventBus</code>在处理消息时,做了优化处理。</p>
<h3 id="4-1-主线程执行消息"><a href="#4-1-主线程执行消息" class="headerlink" title="4.1 主线程执行消息"></a>4.1 主线程执行消息</h3><p>当消息需要在UI线程中执行时,消息可能会被加入到消息队列中,也就是加入到<code>mainThreadPoster</code>队列中。代码如下所示:</p>
<p>org.greenrobot.eventbus.EventBus#postToSubscription</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">postToSubscription</span><span class="params">(Subscription subscription, Object event, <span class="keyword">boolean</span> isMainThread)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">switch</span> (subscription.subscriberMethod.threadMode) &#123;</span><br><span class="line"> <span class="keyword">case</span> MAIN:</span><br><span class="line"> <span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line"> invokeSubscriber(subscription, event);</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> mainThreadPoster.enqueue(subscription, event);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">// 部分代码省略...</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在主线程中执行消息代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">HandlerPoster</span> <span class="keyword">extends</span> <span class="title">Handler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> PendingPostQueue queue;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> maxMillisInsideHandleMessage;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> EventBus eventBus;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> handlerActive;</span><br><span class="line"></span><br><span class="line"> HandlerPoster(EventBus eventBus, Looper looper, <span class="keyword">int</span> maxMillisInsideHandleMessage) &#123;</span><br><span class="line"> <span class="keyword">super</span>(looper);</span><br><span class="line"> <span class="keyword">this</span>.eventBus = eventBus;</span><br><span class="line"> <span class="comment">// 这个值是10,即10ms</span></span><br><span class="line"> <span class="keyword">this</span>.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;</span><br><span class="line"> queue = <span class="keyword">new</span> PendingPostQueue();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">enqueue</span><span class="params">(Subscription subscription, Object event)</span> </span>&#123;</span><br><span class="line"> PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);</span><br><span class="line"> <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line"> queue.enqueue(pendingPost);</span><br><span class="line"> <span class="keyword">if</span> (!handlerActive) &#123;</span><br><span class="line"> handlerActive = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (!sendMessage(obtainMessage())) &#123;</span><br><span class="line"> <span class="comment">// 触发handleMessage方法被调用</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EventBusException(<span class="string">"Could not send handler message"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleMessage</span><span class="params">(Message msg)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">boolean</span> rescheduled = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> <span class="keyword">long</span> started = SystemClock.uptimeMillis();</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line"> PendingPost pendingPost = queue.poll();</span><br><span class="line"> <span class="keyword">if</span> (pendingPost == <span class="keyword">null</span>) &#123;</span><br><span class="line"> <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line"> <span class="comment">// Check again, this time in synchronized</span></span><br><span class="line"> pendingPost = queue.poll();</span><br><span class="line"> <span class="keyword">if</span> (pendingPost == <span class="keyword">null</span>) &#123;</span><br><span class="line"> handlerActive = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> eventBus.invokeSubscriber(pendingPost);</span><br><span class="line"> <span class="keyword">long</span> timeInMethod = SystemClock.uptimeMillis() - started;</span><br><span class="line"> <span class="keyword">if</span> (timeInMethod &gt;= maxMillisInsideHandleMessage) &#123;</span><br><span class="line"> <span class="comment">// 处理消息超过10ms</span></span><br><span class="line"> <span class="keyword">if</span> (!sendMessage(obtainMessage())) &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EventBusException(<span class="string">"Could not send handler message"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> rescheduled = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 结束循环</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line"> handlerActive = rescheduled;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里做了一个优化处理是当<code>EventBus</code>UI线程中待执行的消息队列<code>queue</code>中的消息执行耗时超过<code>maxMillisInsideHandleMessage</code>,即10ms,就会停止执行<code>queue</code>消息队列中的消息,不阻碍App中UI线程中的其他消息执行。等App中的UI线程中的消息执行完后,再执行到这里的<code>handleMessage</code>方法。在<code>handleMessage</code>方法中的<code>sendMessage(obtainMessage())</code>操作就是这个意思。</p>
<h3 id="4-2-后台线程执行消息"><a href="#4-2-后台线程执行消息" class="headerlink" title="4.2 后台线程执行消息"></a>4.2 后台线程执行消息</h3><p>同样,<code>EventBus</code>执行后台线程(非UI线程)中的消息队列中的消息时,也使用了优化。<code>org.greenrobot.eventbus.EventBus#backgroundPoster</code>为后台线程的消息队列,</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">BackgroundPoster</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> PendingPostQueue queue;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> EventBus eventBus;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">boolean</span> executorRunning;</span><br><span class="line"></span><br><span class="line"> BackgroundPoster(EventBus eventBus) &#123;</span><br><span class="line"> <span class="keyword">this</span>.eventBus = eventBus;</span><br><span class="line"> queue = <span class="keyword">new</span> PendingPostQueue();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">enqueue</span><span class="params">(Subscription subscription, Object event)</span> </span>&#123;</span><br><span class="line"> PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);</span><br><span class="line"> <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line"> <span class="comment">// 先加入到队列中</span></span><br><span class="line"> queue.enqueue(pendingPost);</span><br><span class="line"> <span class="keyword">if</span> (!executorRunning) &#123;</span><br><span class="line"> executorRunning = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 最后会在线程池中的线程中执行run方法</span></span><br><span class="line"> eventBus.getExecutorService().execute(<span class="keyword">this</span>);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line"> <span class="comment">// 如果消息队列中有消息,则立及返回,否则会等1秒钟,再从消息队列取消息</span></span><br><span class="line"> PendingPost pendingPost = queue.poll(<span class="number">1000</span>);</span><br><span class="line"> <span class="keyword">if</span> (pendingPost == <span class="keyword">null</span>) &#123;</span><br><span class="line"> <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line"> <span class="comment">// Check again, this time in synchronized</span></span><br><span class="line"> pendingPost = queue.poll();</span><br><span class="line"> <span class="keyword">if</span> (pendingPost == <span class="keyword">null</span>) &#123;</span><br><span class="line"> executorRunning = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="comment">// 执行对应订阅的方法</span></span><br><span class="line"> eventBus.invokeSubscriber(pendingPost);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line"> Log.w(<span class="string">"Event"</span>, Thread.currentThread().getName() + <span class="string">" was interruppted"</span>, e);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line"> executorRunning = <span class="keyword">false</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>while循环中的<code>queue.poll</code>方法如下所示:</p>
<p>org.greenrobot.eventbus.PendingPostQueue</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">synchronized</span> PendingPost <span class="title">poll</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> PendingPost pendingPost = head;</span><br><span class="line"> <span class="keyword">if</span> (head != <span class="keyword">null</span>) &#123;</span><br><span class="line"> head = head.next;</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="keyword">null</span>) &#123;</span><br><span class="line"> tail = <span class="keyword">null</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> pendingPost;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">synchronized</span> PendingPost <span class="title">poll</span><span class="params">(<span class="keyword">int</span> maxMillisToWait)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="keyword">null</span>) &#123;</span><br><span class="line"> <span class="comment">// 线程等待</span></span><br><span class="line"> wait(maxMillisToWait);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="comment">// 这里调用上面的poll方法</span></span><br><span class="line"> <span class="keyword">return</span> poll();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面这种做法避免了线程切换带来的开销。也就是说,如果两次<code>post</code>消息间隔是在1秒内,则有可能这两次消息都是在同一个线程中执行,而不是两个线程。但不知道为什么这个值是1秒。</p>
<h2 id="五、问题"><a href="#五、问题" class="headerlink" title="五、问题"></a>五、问题</h2><h3 id="5-1-订阅方法继承问题"><a href="#5-1-订阅方法继承问题" class="headerlink" title="5.1 订阅方法继承问题"></a>5.1 订阅方法继承问题</h3><h5 id="5-1-1-新版本订阅方法的继承问题"><a href="#5-1-1-新版本订阅方法的继承问题" class="headerlink" title="5.1.1 新版本订阅方法的继承问题"></a>5.1.1 新版本订阅方法的继承问题</h5><ul>
<li>子类override的方法,<code>@subscribe</code>注解不管是写在子类方法上还是基类的方法上,子类方法都会被执行。</li>
</ul>
<p>前提条件是订阅对象使用的是子类对象,因为是遵循java多态语法规则,子类override基类中的方法,所以调用的是子类中的方法。如果子类方法中调用了<code>super</code>,则会调用基类方法。如果没有调用,则会完全override基类方法,基类方法不会执行。</p>
<ul>
<li>基类方法上使用<code>@subscribe</code>注解后,子类override的方法则不需要注解标记,即使加了效果也是一样的。</li>
</ul>
<h5 id="5-1-2-旧版本的继承问题"><a href="#5-1-2-旧版本的继承问题" class="headerlink" title="5.1.2 旧版本的继承问题"></a>5.1.2 旧版本的继承问题</h5><p>旧版本解析订阅方法都是以<code>onEvent</code>开头的,规则更简单一点,override方法完全按照java多态语法来。</p>
<h3 id="5-2-订阅方法重载问题"><a href="#5-2-订阅方法重载问题" class="headerlink" title="5.2 订阅方法重载问题"></a>5.2 订阅方法重载问题</h3><p>重载的方法如果也标记为订阅方法,消息类型都兼容,则都会执行。什么意思呢?就像前面的举例,如果post的是<code>String</code>类型,重载的参数是<code>String</code>和<code>Object</code>类型,则<code>Object</code>类型的方法也会执行。如果post的是<code>Object</code>类型,那么<code>String</code>参数的订阅方法不会被执行,<code>Object</code>参数的订阅方法会被执行。</p>
<p>为什么都订阅的重载方法都会执行呢?因为它们都订阅了啊,我是认真的。</p>
<h2 id="六、写在最后"><a href="#六、写在最后" class="headerlink" title="六、写在最后"></a>六、写在最后</h2><p>最后总结一下,用<code>EventBus</code>要注意<code>register</code>方法和<code>unregister</code>方法配对使用,否则容易导致内存泄露。<code>EventBus</code>要灵活使用,建议在项目某一解耦层使用,如果大面积使用会导致项目缺乏逻辑性,使用不当会导致代码臃肿,所以注意使用场景。</p>
<p>(全文完)</p>
</content>
<summary type="html">
<p><a href="/2018/07/23/eventbus-implement-analysis-1.html">EventBus实现分析(上)</a>中介绍了<code>EventBus</code>的<code>简单使用</code>和<code>解决什么问题</code>,这一节介绍EventBus的实现细节。</p>
<h2 id="三、实现细节"><a href="#三、实现细节" class="headerlink" title="三、实现细节"></a>三、实现细节</h2><p>前面在介绍EventBus的使用时,也顺带着讲了一些细节。下面先从两个流程开始,一个是注册流程,另一个是发送消息并且处理的流程。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="EventBus 分析" scheme="http://www.jacpy.com/tags/EventBus-%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>EventBus实现分析(上)</title>
<link href="http://www.jacpy.com/2018/07/23/eventbus-implement-analysis-1.html"/>
<id>http://www.jacpy.com/2018/07/23/eventbus-implement-analysis-1.html</id>
<published>2018-07-23T04:59:17.000Z</published>
<updated>2018-07-30T09:04:22.423Z</updated>
<content type="html"><p>最近看了一下<code>EventBus</code>实现的源码,分享一下自己的心得体会。</p>
<p><code>EventBus</code>这个库实现很简单,如官方所说一样,库体积很小。将组件之间的通信通过解耦的方式表现的淋漓尽致,废话不多说,<code>EventBus</code>优点官网上列举了一堆,有兴趣的可以去看一下<a href="http://greenrobot.org/eventbus/" target="_blank" rel="noopener">http://greenrobot.org/eventbus/</a>。</p>
<p>分享主要有以下几个方面:</p>
<a id="more"></a>
<p>1)简单使用</p>
<blockquote>
<ul>
<li>简单的说明EventBus的使用</li>
</ul>
</blockquote>
<p>2)解决什么问题</p>
<blockquote>
<ul>
<li>比如解决了组件之间解耦问题,线程切换问题。</li>
</ul>
</blockquote>
<p>3)实现细节</p>
<blockquote>
<ul>
<li>EventBus是怎样注册订阅者,怎样通过post将消息发送到订阅者。</li>
</ul>
</blockquote>
<p>4)优化</p>
<blockquote>
<ul>
<li>EventBus中使用的一些细节优化,考虑很周全。</li>
</ul>
</blockquote>
<p>5)问题</p>
<blockquote>
<ul>
<li>针对EventBus中使用的一些问题。</li>
</ul>
</blockquote>
<p>中间可能会介绍新旧版本的区别,新旧版本指的是3.0及以后的版本和以前的版本。要注意的是新旧版本不仅是版本号的区别,包名也不一样。旧版本的包名是<code>de.greenrobot.even</code>,新版本的包名是<code>org.greenrobot.eventbu</code>。</p>
<h2 id="一、简单使用"><a href="#一、简单使用" class="headerlink" title="一、简单使用"></a>一、简单使用</h2><h3 id="1-1-新版本使用"><a href="#1-1-新版本使用" class="headerlink" title="1.1 新版本使用"></a>1.1 新版本使用</h3><ul>
<li>注册消息订阅者,代码如下所示:</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EbNewActivity</span> <span class="keyword">extends</span> <span class="title">BaseNewActivity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(@Nullable Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_eb_new);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 注册必须在Fragment初始化之前,因为订阅消息发送是在Fragment中</span></span><br><span class="line"> EventBus.getDefault().register(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line"> getSupportFragmentManager()</span><br><span class="line"> .beginTransaction()</span><br><span class="line"> .add(R.id.frame_content, EbNewFragment.newInstance())</span><br><span class="line"> .commit();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Subscribe</span>(threadMode = ThreadMode.MAIN)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventBusMainEvent</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> Log.d(<span class="string">"onEventBusMainEvent"</span>, Thread.currentThread().getName() + <span class="string">","</span> + msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 默认是ThreadMode.POSTING,注意这里参数是Object,与其它不同</span></span><br><span class="line"> <span class="meta">@Subscribe</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventBusPostEvent</span><span class="params">(Object msg)</span> </span>&#123;</span><br><span class="line"> Log.i(<span class="string">"onEventBusPostEvent"</span>, Thread.currentThread().getName() + <span class="string">","</span> + msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Subscribe</span>(threadMode = ThreadMode.BACKGROUND)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventBusBackgroundEvent</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> Log.w(<span class="string">"onEventBusBgEvent"</span>, Thread.currentThread().getName() + <span class="string">","</span> + msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Subscribe</span>(threadMode = ThreadMode.ASYNC)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventBusAsyncEvent</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> Log.e(<span class="string">"onEventBusAsyncEvent"</span>, Thread.currentThread().getName() + <span class="string">","</span> + msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDestroy</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onDestroy();</span><br><span class="line"> <span class="comment">// 注销,否则会导致Activity内存泄露</span></span><br><span class="line"> EventBus.getDefault().unregister(<span class="keyword">this</span>);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>activity_eb_new.xml</code>布局文件很简单,里面只有一个<code>LinearLayout</code>,代码如下所示:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">LinearLayout</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:orientation</span>=<span class="string">"vertical"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:id</span>=<span class="string">"@+id/frame_content"</span></span></span><br><span class="line"><span class="tag"> /&gt;</span></span><br></pre></td></tr></table></figure>
<p><code>@Subscribe</code>注解除了指定<code>EventBus</code>的线程模型,还可以指定订阅者的优先级以及是否为<code>sticky</code>模式。</p>
<p>优先级的意思是当<code>EventBus#post</code>消息时,根据订阅的方法的优先级来执行相应的方法。优先级高的即<code>priority</code>的值越大的,先收到订阅消息。在注册订阅者时,就会根据这个<code>priority</code>的大小对顺序进行排序。代码如下所示:</p>
<p>org.greenrobot.eventbus.EventBus#subscribe</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Object subscriber, SubscriberMethod subscriberMethod)</span> </span>&#123;</span><br><span class="line"> Class&lt;?&gt; eventType = subscriberMethod.eventType;</span><br><span class="line"> Subscription newSubscription = <span class="keyword">new</span> Subscription(subscriber, subscriberMethod);</span><br><span class="line"> CopyOnWriteArrayList&lt;Subscription&gt; subscriptions = subscriptionsByEventType.get(eventType);</span><br><span class="line"> <span class="keyword">if</span> (subscriptions == <span class="keyword">null</span>) &#123;</span><br><span class="line"> subscriptions = <span class="keyword">new</span> CopyOnWriteArrayList&lt;&gt;();</span><br><span class="line"> subscriptionsByEventType.put(eventType, subscriptions);</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">if</span> (subscriptions.contains(newSubscription)) &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EventBusException(<span class="string">"Subscriber "</span> + subscriber.getClass() + <span class="string">" already registered to event "</span></span><br><span class="line"> + eventType);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> size = subscriptions.size();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;= size; i++) &#123;</span><br><span class="line"> <span class="comment">// 根据优先级进行排序</span></span><br><span class="line"> <span class="keyword">if</span> (i == size || subscriberMethod.priority &gt; subscriptions.get(i).subscriberMethod.priority) &#123;</span><br><span class="line"> subscriptions.add(i, newSubscription);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>stick</code>模式是指当<code>EventBus#postSticky</code>消息时,该消息会被缓存,当下次调用<code>EventBus#register</code>注册订阅者后,会把相应接收消息的方法都执行一遍。</p>
<p>可能描述太抽象,举个例子:一个项目组在一个房间封闭开发项目,其中测试人员和UI设计都不在。这个时候产品经理跑过来说,改个需求,这时候只有开发人员都知道。产品经理口头通知改需求就相当于<code>post</code>,发送的消息只有已经注册过接收者接收。产品经理把要改的需求打印到一张纸上,然后粘在门上,并向房间里面的开发人员描述了修改的需求。测试人员和UI设计回来之后,发现粘在门上的需求变更通知,也都知道了。产品经理把修改的需求粘在门上并向室内开发人员描述修改的需求这个过程就相当于<code>postSticky</code>,后面注册的接收者也会收到这个消息,先注册的就不用说了。</p>
<p><code>postSticky</code>可以用来做延迟接收消息。下面看下源码的实现:</p>
<p>postSticky操作:</p>
<p>1)缓存消息</p>
<p>org.greenrobot.eventbus.EventBus#postSticky</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postSticky</span><span class="params">(Object event)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">synchronized</span> (stickyEvents) &#123;</span><br><span class="line"> <span class="comment">// 这里会缓存消息</span></span><br><span class="line"> stickyEvents.put(event.getClass(), event);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="comment">// Should be posted after it is putted, in case the subscriber wants to remove immediately</span></span><br><span class="line"> post(event);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>2)接收消息</p>
<p>org.greenrobot.eventbus.EventBus#register</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(Object subscriber)</span> </span>&#123;</span><br><span class="line"> Class&lt;?&gt; subscriberClass = subscriber.getClass();</span><br><span class="line"> List&lt;SubscriberMethod&gt; subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);</span><br><span class="line"> <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line"> <span class="keyword">for</span> (SubscriberMethod subscriberMethod : subscriberMethods) &#123;</span><br><span class="line"> <span class="comment">// 这里处理订阅的方法</span></span><br><span class="line"> subscribe(subscriber, subscriberMethod);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Object subscriber, SubscriberMethod subscriberMethod)</span> </span>&#123;</span><br><span class="line"> <span class="comment">// ... 中间代码省略</span></span><br><span class="line"> <span class="keyword">if</span> (subscriberMethod.sticky) &#123;</span><br><span class="line"> <span class="comment">// 重点看这里</span></span><br><span class="line"> <span class="keyword">if</span> (eventInheritance) &#123;</span><br><span class="line"> <span class="comment">// Existing sticky events of all subclasses of eventType have to be considered.</span></span><br><span class="line"> <span class="comment">// Note: Iterating over all events may be inefficient with lots of sticky events,</span></span><br><span class="line"> <span class="comment">// thus data structure should be changed to allow a more efficient lookup</span></span><br><span class="line"> <span class="comment">// (e.g. an additional map storing sub classes of super classes: Class -&gt; List&lt;Class&gt;).</span></span><br><span class="line"> Set&lt;Map.Entry&lt;Class&lt;?&gt;, Object&gt;&gt; entries = stickyEvents.entrySet();</span><br><span class="line"> <span class="keyword">for</span> (Map.Entry&lt;Class&lt;?&gt;, Object&gt; entry : entries) &#123;</span><br><span class="line"> Class&lt;?&gt; candidateEventType = entry.getKey();</span><br><span class="line"> <span class="keyword">if</span> (eventType.isAssignableFrom(candidateEventType)) &#123;</span><br><span class="line"> Object stickyEvent = entry.getValue();</span><br><span class="line"> <span class="comment">// 执行消息会到这里</span></span><br><span class="line"> checkPostStickyEventToSubscription(newSubscription, stickyEvent);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> Object stickyEvent = stickyEvents.get(eventType);</span><br><span class="line"> <span class="comment">// 执行消息会到这里</span></span><br><span class="line"> checkPostStickyEventToSubscription(newSubscription, stickyEvent);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkPostStickyEventToSubscription</span><span class="params">(Subscription newSubscription, Object stickyEvent)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (stickyEvent != <span class="keyword">null</span>) &#123;</span><br><span class="line"> <span class="comment">// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)</span></span><br><span class="line"> <span class="comment">// --&gt; Strange corner case, which we don't take care of here.</span></span><br><span class="line"> <span class="comment">// 执行消息或者加入消息队列中</span></span><br><span class="line"> postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>postToSubscription</code>方法中会根据线程模型来执行订阅的方法。也就是后注册进来的订阅者也会收到之前的<code>postStick</code>消息。</p>
<ul>
<li>线程模型</li>
</ul>
<p><code>EventBus</code>的线程模型有四种,分别如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> ThreadMode &#123;</span><br><span class="line"> POSTING,</span><br><span class="line"> MAIN,</span><br><span class="line"> BACKGROUND,</span><br><span class="line"> ASYNC</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>POSTING</code>表示在当前线程执行,也就是<code>post</code>方法在哪个线程中执行,对应的接收消息的方法就在哪个线程执行;<br><code>MAIN</code>表示接收的消息在UI线程中执行;<br><code>BACKGROUND</code>表示接收的消息在非UI线程中执行,如果当前是在非UI线程中执行<code>post</code>,则会在当前线程中执行接收消息的方法;如果是在UI线程中执行<code>post</code>方法,则会在非UI线程中执行接收消息的方法。<br><code>ASYNC</code>表示在与执行<code>post</code>线程不同的另外一个非UI线程中执行接收消息的方法。</p>
<p>上面的解释可能不太好理解,上面的代码日志输出如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">onEventBusAsyncEvent: pool-1-thread-1,onAttach</span><br><span class="line">onEventBusMainEvent: main,onAttach</span><br><span class="line">onEventBusBgEvent: pool-1-thread-2,onAttach</span><br><span class="line">onEventBusPostEvent: main,onAttach</span><br></pre></td></tr></table></figure>
<p>对比下日志前面的线程名称就清楚了。</p>
<ul>
<li>发送消息</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EbNewFragment</span> <span class="keyword">extends</span> <span class="title">Fragment</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> EbNewFragment <span class="title">newInstance</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> EbNewFragment fragment = <span class="keyword">new</span> EbNewFragment();</span><br><span class="line"> <span class="keyword">return</span> fragment;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAttach</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onAttach(context);</span><br><span class="line"> <span class="comment">// 发送消息</span></span><br><span class="line"> EventBus.getDefault().post(<span class="string">"onAttach"</span>);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>因为在<code>Activity</code>中注册的方法接收的消息类型是<code>String</code>和<code>Object</code>(eventInheritance参数为true,第二篇会解释),所以这里<code>post(&quot;onAttach&quot;)</code>后,那些方法都会收到消息。</p>
<h3 id="1-2-旧版本使用"><a href="#1-2-旧版本使用" class="headerlink" title="1.2 旧版本使用"></a>1.2 旧版本使用</h3><p>老版本接收消息的订阅方法都是以<code>onEvent</code>开头,代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EbOldActivity</span> <span class="keyword">extends</span> <span class="title">Activity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(@Nullable Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_eb_old);</span><br><span class="line"> EventBus.getDefault().register(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line"> getSupportFragmentManager()</span><br><span class="line"> .beginTransaction()</span><br><span class="line"> .add(R.id.frame_content, EbOldFragment.newInstance())</span><br><span class="line"> .commit();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEvent</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> log(<span class="string">"onEvent"</span>, msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventMainThread</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> log(<span class="string">"onEventMainThread"</span>, msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventBackgroundThread</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> log(<span class="string">"onEventBackgroundThread"</span>, msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEventAsync</span><span class="params">(String msg)</span> </span>&#123;</span><br><span class="line"> log(<span class="string">"onEventAsync"</span>, msg);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDestroy</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">super</span>.onDestroy();</span><br><span class="line"> EventBus.getDefault().unregister(<span class="keyword">this</span>);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>activity_eb_old.xml</code>布局文件内容与上面的<code>activity_eb_new.xml</code>布局文件类似。</p>
<p><code>onEvent</code>后缀表示线程对应的线程模型,对应关系看下面的源码就清楚了:</p>
<p><code>de.greenrobot.event.SubscriberMethodFinder#findSubscriberMethods</code>部分代码:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (methodName.startsWith(ON_EVENT_METHOD_NAME)) &#123;</span><br><span class="line"> <span class="keyword">int</span> modifiers = method.getModifiers();</span><br><span class="line"> <span class="keyword">if</span> ((modifiers &amp; Modifier.PUBLIC) != <span class="number">0</span> &amp;&amp; (modifiers &amp; MODIFIERS_IGNORE) == <span class="number">0</span>) &#123;</span><br><span class="line"> Class&lt;?&gt;[] parameterTypes = method.getParameterTypes();</span><br><span class="line"> <span class="keyword">if</span> (parameterTypes.length == <span class="number">1</span>) &#123;</span><br><span class="line"> <span class="comment">// ON_EVENT_METHOD_NAME为onEvent</span></span><br><span class="line"> String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());</span><br><span class="line"> ThreadMode threadMode;</span><br><span class="line"> <span class="keyword">if</span> (modifierString.length() == <span class="number">0</span>) &#123;</span><br><span class="line"> <span class="comment">// onEvent方法</span></span><br><span class="line"> threadMode = ThreadMode.PostThread;</span><br><span class="line"> &#125; <span class="keyword">else</span> <span class="keyword">if</span> (modifierString.equals(<span class="string">"MainThread"</span>)) &#123;</span><br><span class="line"> <span class="comment">// onEventMainThread方法</span></span><br><span class="line"> threadMode = ThreadMode.MainThread;</span><br><span class="line"> &#125; <span class="keyword">else</span> <span class="keyword">if</span> (modifierString.equals(<span class="string">"BackgroundThread"</span>)) &#123;</span><br><span class="line"> <span class="comment">// onEventBackgroundThread方法</span></span><br><span class="line"> threadMode = ThreadMode.BackgroundThread;</span><br><span class="line"> &#125; <span class="keyword">else</span> <span class="keyword">if</span> (modifierString.equals(<span class="string">"Async"</span>)) &#123;</span><br><span class="line"> <span class="comment">// onEventAsync方法</span></span><br><span class="line"> threadMode = ThreadMode.Async;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">if</span> (skipMethodVerificationForClasses.containsKey(clazz)) &#123;</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EventBusException(<span class="string">"Illegal onEvent method, check for typos: "</span> + method);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从上面源码中也可以看出来注册的订阅方法有一些要求:</p>
<ul>
<li>方法必须是public,不能是抽象和静态的;</li>
<li>方法参数只能有一个。</li>
<li>其它方法不能以<code>onEvent</code>开头,或者必须通过<code>EventBusBuilder</code>来创建<code>EventBus</code>实例,并且调用<code>EventBusBuilder#skipMethodVerificationFor</code>方法注册class,以忽略当前包含其它以<code>onEvent</code>开头的方法。</li>
</ul>
<p>整个调用流程如下图所示:</p>
<p><img src="/images/2018/07/eventbus_register_post.png" alt="EventBus注册、发送消息流程"></p>
<p>OK,简单使用介绍完了,下面看看解决了什么问题。</p>
<h2 id="二、解决什么问题"><a href="#二、解决什么问题" class="headerlink" title="二、解决什么问题"></a>二、解决什么问题</h2><p>通过上面的使用,很明显知道<code>EventBus</code>解决了什么问题。</p>
<h3 id="2-1-通信解耦"><a href="#2-1-通信解耦" class="headerlink" title="2.1 通信解耦"></a>2.1 通信解耦</h3><p>通过<code>EventBus.register</code>来注册订阅者,通过注解反射的方式提取接收消息的方法,再通过<code>EventBus.post</code>发送消息,从接收消息方法的缓存列表中找到对应消息接收者进行消息处理。这个时候也是通过反射调用方法。</p>
<p>所以这里是通过反射的方式来做到解耦。</p>
<h3 id="2-2-线程切换"><a href="#2-2-线程切换" class="headerlink" title="2.2 线程切换"></a>2.2 线程切换</h3><p>线程模型在老版本上是通过后缀来区别,新版本是以注解的方式来指定的,这样为线程切换提供很大的便利。<code>EventBus</code>自己提供了一个线程池来管理线程,<code>ASYNC</code>和<code>BACKGROUND</code>标记的方法都是在同一个线程池中的线程来执行的。</p>
<p>另外就是<code>EventBus</code>还提供了一个消息队列,发送的消息是先进消息队列还是立及执行,这个是根据线程模型来的。部分源码如下所示:</p>
<p>org.greenrobot.eventbus.EventBus#postToSubscription</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">postToSubscription</span><span class="params">(Subscription subscription, Object event, <span class="keyword">boolean</span> isMainThread)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">switch</span> (subscription.subscriberMethod.threadMode) &#123;</span><br><span class="line"> <span class="keyword">case</span> POSTING:</span><br><span class="line"> <span class="comment">// 立及执行</span></span><br><span class="line"> invokeSubscriber(subscription, event);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> MAIN:</span><br><span class="line"> <span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line"> <span class="comment">// 立及执行</span></span><br><span class="line"> invokeSubscriber(subscription, event);</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="comment">// UI线程的消息队列</span></span><br><span class="line"> mainThreadPoster.enqueue(subscription, event);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> BACKGROUND:</span><br><span class="line"> <span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line"> <span class="comment">// 后台线程的消息队列</span></span><br><span class="line"> backgroundPoster.enqueue(subscription, event);</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="comment">// 立及执行</span></span><br><span class="line"> invokeSubscriber(subscription, event);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> ASYNC:</span><br><span class="line"> <span class="comment">// 异步线程的消息队列</span></span><br><span class="line"> asyncPoster.enqueue(subscription, event);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Unknown thread mode: "</span> + subscription.subscriberMethod.threadMode);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>消息队列有三种,UI线程执行消息的消息队列、后台线程的消息队列、异步线程的消息队列。</p>
<hr>
<p>这部分写到这里,未完待续。</p>
<p>第二篇看这里<a href="/2018/07/26/eventbus-implement-analysis-2.html">EventBus实现分析(下)</a></p>
</content>
<summary type="html">
<p>最近看了一下<code>EventBus</code>实现的源码,分享一下自己的心得体会。</p>
<p><code>EventBus</code>这个库实现很简单,如官方所说一样,库体积很小。将组件之间的通信通过解耦的方式表现的淋漓尽致,废话不多说,<code>EventBus</code>优点官网上列举了一堆,有兴趣的可以去看一下<a href="http://greenrobot.org/eventbus/" target="_blank" rel="noopener">http://greenrobot.org/eventbus/</a>。</p>
<p>分享主要有以下几个方面:</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="EventBus 分析" scheme="http://www.jacpy.com/tags/EventBus-%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>android 安全检测工具drozer环境搭建</title>
<link href="http://www.jacpy.com/2018/07/05/android-security-check-tool-drozer.html"/>
<id>http://www.jacpy.com/2018/07/05/android-security-check-tool-drozer.html</id>
<published>2018-07-05T00:52:47.000Z</published>
<updated>2018-07-06T05:16:03.708Z</updated>
<content type="html"><p>最近在处理公司App的安全问题,跟第三方公司人员沟通,提到有使用<code>drozer</code>工具检测。装了下这个工具,在这里记录一些坑。</p>
<p><code>drozer</code>是一个python编写的开源工具,在<code>github</code>上面有源码,地址:<a href="https://github.com/mwrlabs/drozer" target="_blank" rel="noopener">https://github.com/mwrlabs/drozer</a>。其功能很强大,可以检测App中的四大组件的导出情况、Intent拒绝服务、SQL注入风险以及可以自定义Module等。尤其是自定义Module功能,自己按格式编写一个Module功能,就可以获取终端设备及App的信息。</p>
<p><code>drozer</code>工具有两部分:一是PC端,另一部分是终端APK。</p>
<a id="more"></a>
<p>环境搭建步骤有两种方式,一种是使用github上面的源码自己编译安装;另一种是官方提供了一些常用操作系统的安装包,可以直接安装。 下面以windows环境编译为例:</p>
<h2 id="一、源码编译安装"><a href="#一、源码编译安装" class="headerlink" title="一、源码编译安装"></a>一、源码编译安装</h2><h3 id="1-1-编译环境搭建"><a href="#1-1-编译环境搭建" class="headerlink" title="1.1 编译环境搭建"></a>1.1 编译环境搭建</h3><p>编译前安装的环境与<code>github</code>上README所说的环境要一致,一定要一致!!!</p>
<h4 id="1-1-1-jdk版本问题"><a href="#1-1-1-jdk版本问题" class="headerlink" title="1.1.1 jdk版本问题"></a>1.1.1 jdk版本问题</h4><p>如果JDK版本不一致,编译时会提示以下错误,导致编译APK失败。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">UNEXPECTED TOP-LEVEL EXCEPTION:</span><br><span class="line">com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)</span><br><span class="line"> at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)</span><br><span class="line"> at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)</span><br><span class="line"> at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)</span><br><span class="line"> at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processClass(Main.java:665)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processFileBytes(Main.java:634)</span><br><span class="line"> at com.android.dx.command.dexer.Main.access$600(Main.java:78)</span><br><span class="line"> at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:572)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:170)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processOne(Main.java:596)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processAllFiles(Main.java:498)</span><br><span class="line"> at com.android.dx.command.dexer.Main.runMonoDex(Main.java:264)</span><br><span class="line"> at com.android.dx.command.dexer.Main.run(Main.java:230)</span><br><span class="line"> at com.android.dx.command.dexer.Main.main(Main.java:199)</span><br><span class="line"> at com.android.dx.command.Main.main(Main.java:103)</span><br><span class="line">...while parsing ByteStreamReader.class</span><br></pre></td></tr></table></figure>
<p>目前编译的版本是<code>2.4.3</code>,github网站<code>README</code>说明使用JDK版本为1.7。但官方网站上的说明文档还是老版本,使用的是JDK 1.6。所以这里要注意一下。</p>
<h4 id="1-1-2-安装pip"><a href="#1-1-2-安装pip" class="headerlink" title="1.1.2 安装pip"></a>1.1.2 安装pip</h4><p>pip是python库的管理工具,通过它可以很方便下载一些其他库,编译<code>drozer</code>所需要的<code>Protobuf</code>、<code>Pyopenssl</code>、<code>Twisted</code>等库都可以通过它来下载。</p>
<p>下载地址是:<a href="https://pypi.python.org/pypi/pip#downloads" target="_blank" rel="noopener">https://pypi.python.org/pypi/pip#downloads</a>,下载后解压,在命令行<code>cd</code>到解压目录,然后执行<code>python setup.py install</code>即可以安装<code>pip</code>。</p>
<p>安装成功后,将<code>C:\Python27\Scripts</code>目录配置到系统的环境变量中。重新启动命令行, 执行<code>pip install protobuf</code>命令就可以安装<code>protobuf</code>库,其他库类似。</p>
<h3 id="1-2-安装工具"><a href="#1-2-安装工具" class="headerlink" title="1.2 安装工具"></a>1.2 安装工具</h3><p>上述过程编译成功后,会在<code>dist</code>目录生成<code>msi</code>文件。</p>
<h4 id="1-2-1-安装msi"><a href="#1-2-1-安装msi" class="headerlink" title="1.2.1 安装msi"></a>1.2.1 安装msi</h4><p>安装<code>msi</code>文件下一步就可以,但下面这个页面不要选错,否则都不知道上哪儿找<code>drozer.bat</code>文件。如果找到源码中的这个文件,在后面连接成功后,执行<code>list</code>命令会显示空白,即没有命令工具可以使用。</p>
<p><img src="/images/2018/07/drozer_setup_path_select.png" alt="安装选项"></p>
<h4 id="1-2-2-安装APK"><a href="#1-2-2-安装APK" class="headerlink" title="1.2.2 安装APK"></a>1.2.2 安装APK</h4><p>APK官方直接给出了下载地址:<a href="https://github.com/mwrlabs/drozer/releases/download/2.3.4/drozer-agent-2.3.4.apk" target="_blank" rel="noopener">https://github.com/mwrlabs/drozer/releases/download/2.3.4/drozer-agent-2.3.4.apk</a>,下载下来后,直接使用<code>adb install drozer-agent-2.3.4.apk</code>。这一步没什么好说的,只要将<code>adb</code>命令配置到环境变量,就可以直接执行<code>adb</code>命令。</p>
<h3 id="1-3-工具使用"><a href="#1-3-工具使用" class="headerlink" title="1.3 工具使用"></a>1.3 工具使用</h3><h4 id="1-3-1-启动APK"><a href="#1-3-1-启动APK" class="headerlink" title="1.3.1 启动APK"></a>1.3.1 启动APK</h4><p>工具使用之前,要先启动在终端安装的App<code>drozer Agent</code>,点击底部的<code>Embedded Server</code>,进入到新页面后滑动顶部的控件,将其置为<code>Enabled</code>状态。如下图所示:</p>
<p><img src="/images/2018/07/drozer_app_enabled.png" alt="Enabled状态"></p>
<h4 id="1-3-2-PC连接远程服务"><a href="#1-3-2-PC连接远程服务" class="headerlink" title="1.3.2 PC连接远程服务"></a>1.3.2 PC连接远程服务</h4><p>这一步按官方说明来:</p>
<ul>
<li>转发端口</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb forward tcp:31415 tcp:31415</span><br></pre></td></tr></table></figure>
<ul>
<li>连接应用</li>
</ul>
<p>如果是模拟器,直接执行:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">drozer.bat console connect</span><br></pre></td></tr></table></figure>
<p>如果是物理机,需要知道连接Wi-Fi的IP地址,启动命令的PC端网络要和物理机的网络在同一内网中。执行命令需要带上IP:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">drozer.bat console connect --server 192.168.0.10</span><br></pre></td></tr></table></figure>
<p>我用物理机没有连接成功,用模拟就OK,所以如果网络环境复杂,建议使用模拟器。</p>
<p>连接成功如下图所示:</p>
<p><img src="/images/2018/07/drozer_console_connect.png" alt="drozer连接成功"></p>
<p>执行<code>list</code>命令显示的部分命令列表如下图所示:</p>
<p><img src="/images/2018/07/drozer_console_list.png" alt="drozer list"></p>
<p>如果<code>drozer.bat console connect</code>执行失败,出现如下提示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Traceback (most recent call last):</span><br><span class="line">File &quot;bin/drozer&quot;, line 30, in </span><br><span class="line">import(&quot;drozer.cli.%s&quot; % (sys.argv[1]))</span><br><span class="line">ImportError: No module named drozer.cli.console</span><br></pre></td></tr></table></figure>
<p>则是python找不到<code>drozer.cli.console</code>库,在系统的环境变量中增加<code>PYTHONPATH</code>变量,路径为上面安装<code>msi</code>文件时指定的路径中的子目录<code>site-packages</code>路径。如果环境变量中已经有这个变量,则增加一个路径,中间使用英文逗号分隔。如下所示:</p>
<p><img src="/images/2018/07/drozer_python_path.png" alt="PYTHONPATH环境变量"></p>
<h4 id="1-3-3-执行命令"><a href="#1-3-3-执行命令" class="headerlink" title="1.3.3 执行命令"></a>1.3.3 执行命令</h4><p>命令执行可以参考<a href="https://www.cnblogs.com/goodhacker/p/3906180.html" target="_blank" rel="noopener">利用drozer进行Android渗透测试</a>。</p>
<h2 id="二、可执行文件安装"><a href="#二、可执行文件安装" class="headerlink" title="二、可执行文件安装"></a>二、可执行文件安装</h2><p>如果不想自己编译,PC端可执行文件以及APK可以从这个官方地址<a href="https://labs.mwrinfosecurity.com/tools/drozer" target="_blank" rel="noopener">https://labs.mwrinfosecurity.com/tools/drozer</a>下载。其他安装步骤和操作步骤与上面步骤一样,要注意一下编译生成的<code>msi</code>文件的安装路径。</p>
</content>
<summary type="html">
<p>最近在处理公司App的安全问题,跟第三方公司人员沟通,提到有使用<code>drozer</code>工具检测。装了下这个工具,在这里记录一些坑。</p>
<p><code>drozer</code>是一个python编写的开源工具,在<code>github</code>上面有源码,地址:<a href="https://github.com/mwrlabs/drozer" target="_blank" rel="noopener">https://github.com/mwrlabs/drozer</a>。其功能很强大,可以检测App中的四大组件的导出情况、Intent拒绝服务、SQL注入风险以及可以自定义Module等。尤其是自定义Module功能,自己按格式编写一个Module功能,就可以获取终端设备及App的信息。</p>
<p><code>drozer</code>工具有两部分:一是PC端,另一部分是终端APK。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="android security" scheme="http://www.jacpy.com/tags/android-security/"/>
</entry>
<entry>
<title>okhttp too many follow-up requests 21</title>
<link href="http://www.jacpy.com/2018/05/18/okhttp-too-many-follow-up-requests-21.html"/>
<id>http://www.jacpy.com/2018/05/18/okhttp-too-many-follow-up-requests-21.html</id>
<published>2018-05-18T05:12:17.000Z</published>
<updated>2018-05-19T00:38:08.601Z</updated>
<content type="html"><p>博客好长时间没有更新了,因为年底和年初,你懂的,希望以后进入轨。</p>
<p>前几天在开发过程中遇到这样一个HTTP请求错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">okhttp too many follow-up requests: 21</span><br></pre></td></tr></table></figure>
<p>看到<code>okhttp</code>反应过来是底层请求出现问题了,以前从来没有遇到过,很好奇。在github上面<a href="https://github.com/square/okhttp" target="_blank" rel="noopener">okhttp</a>库搜索了一下上面的关键字,找到下面的代码:</p>
<a id="more"></a>
<p><img src="/images/2018/05/okhttp_too_many_follow_up_requests.png" alt="too many follow-up requests exception"></p>
<p>其中<code>MAX_FOLLOW_UPS</code>常量的值为<code>20</code>,代码如下所示,并且还有注释说明:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,</span><br><span class="line"> * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.</span><br><span class="line"> */</span><br><span class="line">private static final int MAX_FOLLOW_UPS = 20;</span><br></pre></td></tr></table></figure>
<p>上面描述各种浏览器重定向次数说明,<code>okhttp</code>里面使用的是最多允许20次。</p>
<p>google上搜索发现有人说是<code>okhttp</code>的bug,低版本会出现这个bug,高版本已经修复,一看自己的版本,已经是最新版本。</p>
<p>用<code>Fiddler</code>抓了下包,看到如下请求:</p>
<p><img src="/images/2018/05/http_dead_recycle.png" alt="循环302请求"></p>
<p>一直302跳转,每三个一循环,也就是说客户端<code>okhttp</code>请求302跳转超过20次就抛出异常,如果不是<code>okhttp</code>异常处理,估计是死循环了。</p>
<p>问服务器端开发,说要先调用一次授权接口,最近才加的功能。</p>
<p>话说出现这种问题不是服务器端逻辑有问题?一直允许这个死循环的302跳转?如果把okhttp的源码修改一下,无限循环下去,不会造成安全性问题?跟服务器端沟通,无果。</p>
</content>
<summary type="html">
<p>博客好长时间没有更新了,因为年底和年初,你懂的,希望以后进入轨。</p>
<p>前几天在开发过程中遇到这样一个HTTP请求错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">okhttp too many follow-up requests: 21</span><br></pre></td></tr></table></figure>
<p>看到<code>okhttp</code>反应过来是底层请求出现问题了,以前从来没有遇到过,很好奇。在github上面<a href="https://github.com/square/okhttp" target="_blank" rel="noopener">okhttp</a>库搜索了一下上面的关键字,找到下面的代码:</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="okhttp" scheme="http://www.jacpy.com/tags/okhttp/"/>
</entry>
<entry>
<title>android使用相机扫描出现内存溢出解决过程</title>
<link href="http://www.jacpy.com/2018/02/28/android-camera-out-of-memory-solution.html"/>
<id>http://www.jacpy.com/2018/02/28/android-camera-out-of-memory-solution.html</id>
<published>2018-02-28T00:39:15.000Z</published>
<updated>2018-05-18T09:55:13.824Z</updated>
<content type="html"><p>博客好长时间没有更新了,由于前段时间疯狂赶项目,有些问题没有来得及时整理,后面慢慢总结一下。</p>
<p>前段时间在优化相机扫描二维码时,遇到了一个内存溢出问题,内存被用完了,日志中出现<code>E/dalvikvm-heap: Out of memory on a 2366828-byte allocation</code>错误提示,App也没有崩溃,只是相机不再输出拍摄到的内容。使用<code>Zxing</code>做过二维码扫描的同学都知道,被扫描到的内容会不停的被处理,并识别出二维码内容,如果有。而此时内存被用完了的现象是:相应的回调处理识别二维码内容的方法没有被调用。于是展开了排查。</p>
<a id="more"></a>
<p>首先打开了ADM(Android Device Monitor),如果是AS 3.0以下,可以直接打开<code>Android Monitor</code>,点击<code>Dump Java Heap</code>按钮保存即可。然后启动App,选中当前App的进程,点击上面工具栏中的<code>Update Heap</code>按钮,在右侧的<code>Heap</code>Tab页中就可以看到当前App的内存使用情况。如下图所示:</p>
<p><img src="/images/2018/02/memory_analysis_1.png" alt="App内存分析"></p>
<p><code>Update Heap</code>按钮即为上图中标记为<code>1</code>的位置。</p>
<p>从上图中可以看出,<code>byte[]</code>对象占用内存较多,而内存一直增长的时候也就是这里的内存在增长。如图中标记为3的位置。<code>byte[]</code>对象一般是Bitmap对象,这个要看内存中的<code>Depth</code>。</p>
<p>当App被限制的内存快要被使用完时(一般手机都会对单个App的内存使用限制,当App内无法再分配足够内存时,就会出现OOM),如图中标记为2的位置,点击右边的<code>Dump HPROF file</code>按钮,即可将内存信息保存为<code>hprof</code>文件。</p>
<p>再在<code>Android Studio</code>中Open保存的hprof文件,如下图所示:</p>
<p><img src="/images/2018/02/memory_analysis_2.png" alt="App内存使用情况"></p>
<p>在左侧点击<code>byte[]</code>行,可以在右侧看到对应的内存中的对象,发现<code>byte[1382400]</code>对象比较多,每个占用内存空间1M多。看到这个对象就反应过来了,扫描处理的对象是一个byte数组,而且长度是1382400。于是想到是不是JNI中内存没有释放。</p>
<p>到C++代码中排查了一下,byte数组从java层传到C++层时,使用<code>GetByteArrayElements</code>获取操作的数组后,并没有调用<code>ReleaseByteArrayElements</code>释放。修改代码如下所示:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">JNIEXPORT jbyteArray JNICALL Java_zxing_utils_ImageUtils_demo</span><br><span class="line">(JNIEnv *env, jclass jcls, jbyteArray jba, jint jlength) &#123;</span><br><span class="line"> jbyte* raw = env-&gt;GetByteArrayElements(jba, <span class="literal">NULL</span>);</span><br><span class="line"> <span class="comment">// 操作raw代码省略...</span></span><br><span class="line"> <span class="comment">// 增加释放内存代码</span></span><br><span class="line"> env-&gt;ReleaseByteArrayElements(jba, raw, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>再编译运行,没有出现OOM了,问题解决。</p>
</content>
<summary type="html">
<p>博客好长时间没有更新了,由于前段时间疯狂赶项目,有些问题没有来得及时整理,后面慢慢总结一下。</p>
<p>前段时间在优化相机扫描二维码时,遇到了一个内存溢出问题,内存被用完了,日志中出现<code>E/dalvikvm-heap: Out of memory on a 2366828-byte allocation</code>错误提示,App也没有崩溃,只是相机不再输出拍摄到的内容。使用<code>Zxing</code>做过二维码扫描的同学都知道,被扫描到的内容会不停的被处理,并识别出二维码内容,如果有。而此时内存被用完了的现象是:相应的回调处理识别二维码内容的方法没有被调用。于是展开了排查。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="Out Of Memory" scheme="http://www.jacpy.com/tags/Out-Of-Memory/"/>
</entry>
<entry>
<title>boost编译和CLion配置</title>
<link href="http://www.jacpy.com/2017/11/11/boost-compile-and-clion-config.html"/>
<id>http://www.jacpy.com/2017/11/11/boost-compile-and-clion-config.html</id>
<published>2017-11-11T14:54:44.000Z</published>
<updated>2017-11-21T05:26:05.738Z</updated>
<content type="html"><p>前几天编译<code>boost</code>库<code>1.65.1</code>版本,遇到一些问题,主要是与环境和版本有关系,如果不知道这些细节,很难找出问题。这里记录一下。</p>
<h2 id="一、boost库编译"><a href="#一、boost库编译" class="headerlink" title="一、boost库编译"></a>一、boost库编译</h2><p>在windows下编译<code>boost</code>库可以参照这里:<a href="https://gist.github.com/sim642/29caef3cc8afaa273ce6" target="_blank" rel="noopener">https://gist.github.com/sim642/29caef3cc8afaa273ce6</a> ,整个过程配置都可以按照上面来,但是要注意下最新版本的<code>MinGW</code>,同时还要注意是64位的还是32位的。</p>
<p>因使用的<code>boost</code>版本是目前最新版本的,所以要求内置<code>gcc</code>编译器的<code>MinGW</code>也要是最新版本的,否则在执行<code>b2</code>命令设置<code>toolset=gcc</code>时,会提示找不到<code>gcc</code>。另外还要注意系统是32位还是64位,对应的gcc也要相同的版本。</p>
<a id="more"></a>
<p>如果中间编译遇到如下问题:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">For MinGW make to work correctly sh.exe must NOT be in your path.</span><br></pre></td></tr></table></figure>
<p>解决办法是,先保证<code>MinGW</code>的环境变量配置正确,然后关闭命令行,重新执行编译命令。如果还提示这个错误,找到环境变量中<code>Git</code>的bin目录,把这个环境配置删除。原因是<code>Git</code>配置的环境变量中的<code>sh.exe</code>与<code>MinGW</code>中的<code>sh.exe</code>冲突,去掉<code>Git</code>的即可。</p>
<h2 id="二、CLion配置"><a href="#二、CLion配置" class="headerlink" title="二、CLion配置"></a>二、CLion配置</h2><p>因为<code>CLion</code>使用的是<code>CMake</code>编译,所以需要配置<code>CMakeList.txt</code>文件。使用目前最新版本<code>2017.2</code>版本时,默认使用的<code>CMake</code>版本是<code>3.8</code>,而<code>CMake</code>的官方网站上最新的版本已经是<code>3.10</code>了。这段文字一直在强调一个–<code>版本</code>,所以这里的版本很重要,原因是如果配置都OK,会提示下面的错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Imported targets not available for Boost version</span><br></pre></td></tr></table></figure>
<p>这个时候就要考虑换<code>CMake</code>版本了,下面是在<code>stackoverflow</code>上面找到的<code>boost</code>库与<code>CMake</code>版本的对应关系:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Boost 1.63 requires CMake 3.7 or newer</span><br><span class="line">Boost 1.64 requires CMake 3.8 or newer</span><br><span class="line">Boost 1.65 and 1.65.1 require CMake 3.9.3 or newer</span><br></pre></td></tr></table></figure>
<p>参见这里:<a href="https://stackoverflow.com/questions/42123509/cmake-finds-boost-but-the-imported-targets-not-available-for-boost-version" target="_blank" rel="noopener">https://stackoverflow.com/questions/42123509/cmake-finds-boost-but-the-imported-targets-not-available-for-boost-version</a></p>
<p>所以需要修改<code>CLion</code>的默认<code>CMake</code>的版本,从<code>CMake</code>的官网上下载最新的<code>CMake</code>,然后安装,然后在设置的<code>Build, Excutation, Deployment</code>中的第一个<code>ToolChains</code>中设置<code>CMake</code>的版本。但是,但是使用<code>CMake executable</code>的<code>Custom</code>选择最新版本的<code>CMake</code>路径无效。下面的<code>CMake</code>不提示版本,显示无效。目测是<code>CLion</code>的bug,解决办法是找到<code>CLion</code>的安装目录,然后将<code>cmake</code>目录下面的内容复制一份备份,然后把最新<code>CMake</code>安装目录下面的对应内容替换<code>CLion</code>安装目录下面的<code>cmake</code>里面的内容。然后再切回<code>Bundled Cmake</code>就可以使用最新版本了。</p>
<p>下面附一份<code>CMakeList.txt</code>的配置:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.9)</span><br><span class="line">project(cppDemo)</span><br><span class="line"></span><br><span class="line">set(CMAKE_CXX_FLAGS &quot;$&#123;CMAKE_CXX_FLAGS&#125; -std=c++14 -static-libgcc -static-libstdc++&quot;)</span><br><span class="line"></span><br><span class="line">set(SOURCE_FILES main.cpp digital_converter.h digital_converter.cpp)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">add_executable(cppDemo $&#123;SOURCE_FILES&#125;)</span><br><span class="line"></span><br><span class="line">if (WIN32)</span><br><span class="line"></span><br><span class="line"> set(BOOST_ROOT &quot;D:/programs/boost_1_65_1/boost&quot;)</span><br><span class="line"> set(Boost_INCLUDE_DIR, $&#123;BOOST_ROOT&#125;/include/boost-1_65_1)</span><br><span class="line"> set(BOOST_LIBRARYDIR $&#123;BOOST_ROOT&#125;/lib)</span><br><span class="line"> set(Boost_USE_STATIC_LIBS ON)</span><br><span class="line"> set(Boost_MULTITHREADED ON)</span><br><span class="line"> set(Boost_USE_STATIC_RUNTIME OFF)</span><br><span class="line"> set(Boost_VERSION 106501)</span><br><span class="line"> set(Boost_COMPILER &quot;-mgw72&quot;) # 注意这里是编译后的boost库中包含的字符串,72是MinGW的版本号,根据自己的版本修改,看下自己编译出来的库的名称就知道了</span><br><span class="line"></span><br><span class="line"> MESSAGE(STATUS &quot;boost root dir: &quot; : $&#123;BOOST_LIBRARYDIR&#125;)</span><br><span class="line"></span><br><span class="line"> find_package(Boost 1.65.1 REQUIRED COMPONENTS</span><br><span class="line"> filesystem) # 注意windows下面编译出来的boost库名称中间都有-mgw72,所以需要在前面指定,不然这里提示找不到filesystem</span><br><span class="line"> add_definitions($&#123;Boost_LIB_DIAGNOSTIC_DEFINITIONS&#125;)</span><br><span class="line"></span><br><span class="line"> include_directories($&#123;Boost_INCLUDE_DIR&#125;)</span><br><span class="line"></span><br><span class="line"> target_link_libraries(cppDemo $&#123;Boost_LIBRARIES&#125;)</span><br><span class="line"></span><br><span class="line">endif (WIN32)</span><br></pre></td></tr></table></figure>
<p>看来C++使用<code>CLion</code>工具的人比较少,估计都是在windows下面用<code>VC</code>了。但感觉<code>VS</code>太大了,不想装,所以选择<code>CLion</code>。用这些小众的工具,遇到问题都不好查资料,需要自己一点点的摸索,以后还不知道会遇到什么问题。</p>
<p>以上。</p>
</content>
<summary type="html">
<p>前几天编译<code>boost</code>库<code>1.65.1</code>版本,遇到一些问题,主要是与环境和版本有关系,如果不知道这些细节,很难找出问题。这里记录一下。</p>
<h2 id="一、boost库编译"><a href="#一、boost库编译" class="headerlink" title="一、boost库编译"></a>一、boost库编译</h2><p>在windows下编译<code>boost</code>库可以参照这里:<a href="https://gist.github.com/sim642/29caef3cc8afaa273ce6" target="_blank" rel="noopener">https://gist.github.com/sim642/29caef3cc8afaa273ce6</a> ,整个过程配置都可以按照上面来,但是要注意下最新版本的<code>MinGW</code>,同时还要注意是64位的还是32位的。</p>
<p>因使用的<code>boost</code>版本是目前最新版本的,所以要求内置<code>gcc</code>编译器的<code>MinGW</code>也要是最新版本的,否则在执行<code>b2</code>命令设置<code>toolset=gcc</code>时,会提示找不到<code>gcc</code>。另外还要注意系统是32位还是64位,对应的gcc也要相同的版本。</p>
</summary>
<category term="Cpp" scheme="http://www.jacpy.com/categories/Cpp/"/>
<category term="boost clion" scheme="http://www.jacpy.com/tags/boost-clion/"/>
</entry>
<entry>
<title>android InvocationTargetException异常浅析</title>
<link href="http://www.jacpy.com/2017/10/27/android-InvocationTargetException-real.html"/>
<id>http://www.jacpy.com/2017/10/27/android-InvocationTargetException-real.html</id>
<published>2017-10-27T15:01:11.000Z</published>
<updated>2017-10-27T15:52:10.000Z</updated>
<content type="html"><p>前几天处理一个很奇葩的问题,解决过程可谓一波三折啊,处理起来却很有意思,这里记录一下。</p>
<p>事情的起因是同事写了一个在android系统调用系统相机的拍照的功能,将路径通过Intent传过去,照片拍好后保存到路径,通过路径取这张照片。</p>
<p>突然有一天点这拍照功能无效了,直接crash。一看异常日志,发现抛出的是<code>ActivityNotFoundException</code>。第一反应是以为是Activity没有注册或者打包出现问题了,于是把Apk反编译,在AndroidManifest.xml中检查了下注册,在dex文件中也找了下相关的类,结果发现都没有问题。</p>
<a id="more"></a>
<p>再细看日志,发现和项目中的插件框有关,报错是跟插件框架中的一个启动Activity的方法有关。因为Hook了系统的<code>Instrumention</code>,所以执行启动Activity时,是由框架中这个Hook来接管Activity的启动。于是找到源码,看了下代码实现,部分代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">Method method = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line"> Class&lt;Instrumentation&gt; cl = (Class&lt;Instrumentation&gt;) <span class="keyword">this</span>.getClass()</span><br><span class="line"> .getClassLoader().loadClass(<span class="string">"android.app.Instrumentation"</span>);</span><br><span class="line"> method = cl.getDeclaredMethod(<span class="string">"execStartActivity"</span>, <span class="keyword">new</span> Class[] &#123;</span><br><span class="line"> Context.class, IBinder.class, IBinder.class, Activity.class,</span><br><span class="line"> Intent.class, Integer.TYPE, android.os.Bundle.class &#125;);</span><br><span class="line"> method.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> ActivityResult r = (ActivityResult) method.invoke(</span><br><span class="line"> originalInstrumentation, <span class="keyword">new</span> Object[] &#123; who, contextThread, token,</span><br><span class="line"> target, intent, requestCode, aBundle &#125;);</span><br><span class="line"> <span class="keyword">return</span> r;</span><br><span class="line">&#125; <span class="keyword">catch</span> (NoSuchMethodException e) &#123;</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">catch</span> (IllegalArgumentException e) &#123;</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">catch</span> (InvocationTargetException e) &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ActivityNotFoundException();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>异常是由<code>throw new ActivityNotFoundException();</code>这行代码抛出来的,抛出的原因是执行<code>method.invoke()</code>方法失败了。抱着怀疑的态度验证了下,在抛出异常之前加了LOG输出,果然有LOG输出,确认是这里出现了问题。没有弄明白写这个框架的人为什么要使用这样的障眼法不让<code>InvocationTargetException</code>的异常信息直接输出,而是抛出一个<code>ActivityNotFoundException</code>。</p>
<p>最后网上查了下这个异常类,发现这个异常类是一个包装的异常类,通过查看其源码也可以看得到,其内部有一个真实的异常。这也不难理解,因为执行反射的方法,可能会抛出各种异常。按照异常尽可能具体的原则,不能直接抛出一个Exception了事,所以就有了这个包装一个异常的<code>InvocationTargetException</code>类。要输出这个真实的异常,可以直接调用该类的<code>getTargetException().printStackTrace();</code>即可输出真实的异常信息。</p>
<p>输出的异常信息如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">android.os.FileUriExposedException: file:///sdcard/tmp exposed beyond app through ClipData.Item.getUri()</span><br><span class="line"> at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)</span><br><span class="line"> at android.net.Uri.checkFileUriExposed(Uri.java:2346)</span><br><span class="line"> at android.content.ClipData.prepareToLeaveProcess(ClipData.java:845)</span><br><span class="line"> at android.content.Intent.prepareToLeaveProcess(Intent.java:8957)</span><br><span class="line"> at android.content.Intent.prepareToLeaveProcess(Intent.java:8942)</span><br><span class="line"> at android.app.Instrumentation.execStartActivity(Instrumentation.java:1519)</span><br><span class="line"> at java.lang.reflect.Method.invoke(Native Method)</span><br><span class="line"> at android.view.View.performClick(View.java:5639)</span><br><span class="line"> at android.view.View$PerformClick.run(View.java:22446)</span><br><span class="line"> at android.os.Handler.handleCallback(Handler.java:754)</span><br><span class="line"> at android.os.Handler.dispatchMessage(Handler.java:95)</span><br><span class="line"> at android.os.Looper.loop(Looper.java:160)</span><br><span class="line"> at android.app.ActivityThread.main(ActivityThread.java:6252)</span><br><span class="line"> at java.lang.reflect.Method.invoke(Native Method)</span><br><span class="line"> at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:895)</span><br><span class="line"> at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:785)</span><br><span class="line"> java.lang.reflect.InvocationTargetException</span><br><span class="line"> at java.lang.reflect.Method.invoke(Native Method)</span><br><span class="line"> at android.view.View.performClick(View.java:5639)</span><br><span class="line"> at android.view.View$PerformClick.run(View.java:22446)</span><br><span class="line"> at android.os.Handler.handleCallback(Handler.java:754)</span><br><span class="line"> at android.os.Handler.dispatchMessage(Handler.java:95)</span><br><span class="line"> at android.os.Looper.loop(Looper.java:160)</span><br><span class="line"> at android.app.ActivityThread.main(ActivityThread.java:6252)</span><br><span class="line"> at java.lang.reflect.Method.invoke(Native Method)</span><br><span class="line"> at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:895)</span><br><span class="line"> at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:785)</span><br><span class="line"> Caused by: android.os.FileUriExposedException: file:///sdcard/tmp exposed beyond app through ClipData.Item.getUri()</span><br><span class="line"> at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)</span><br><span class="line"> at android.net.Uri.checkFileUriExposed(Uri.java:2346)</span><br><span class="line"> at android.content.ClipData.prepareToLeaveProcess(ClipData.java:845)</span><br><span class="line"> at android.content.Intent.prepareToLeaveProcess(Intent.java:8957)</span><br><span class="line"> at android.content.Intent.prepareToLeaveProcess(Intent.java:8942)</span><br><span class="line"> at android.app.Instrumentation.execStartActivity(Instrumentation.java:1519)</span><br><span class="line"> ... 20 more</span><br></pre></td></tr></table></figure></p>
<p><code>FileUriExposedException</code>这个异常还是头一次遇到,于是又查了下,在<code>stackoverflow</code>上看到别个的回答:<a href="https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed。" target="_blank" rel="noopener">https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed。</a><br>遇到这个问题的人还真不少,出现这样的问题是由于android 7.0的系统机制问题,为了安全起见,一个<code>App</code>要访问另外一个<code>App</code>的外部存储目录,需要被访问的<code>App</code>来授权读写权限,所以需要进行版本适配。</p>
<p>在定位问题的时候中间发生一个插曲,写拍照功能的同事用他写的库在demo里面了跑了一下,发现正常,没有任何异常出现。而在项目的App中却出现了问题,所以就说不是他的问题,怀疑是插件框架的问题。因为是我接手了插件框架,所以就开始排查这个问题。</p>
<p><code>stackoverflow</code>上面有人回答也说了,如果把编译的<code>targetVersion</code>改成24或以上,遇到这种场景就会报错,也给了解决方法,官方的说明和解决方案参照这里:<a href="https://developer.android.google.cn/reference/android/support/v4/content/FileProvider.html" target="_blank" rel="noopener">https://developer.android.google.cn/reference/android/support/v4/content/FileProvider.html</a></p>
<p>找到原因后,叫写这个拍照功能的同事把<code>targetVersion</code>修改一下,果然,demo中问题就出现了。</p>
<p>中间还发生一个插曲,传的路径是使用<code>Uri</code>传过去的,如果使用<code>Uri.parse(String)</code>这个方法将路径字符串直接传过去就不会报错;如果使用<code>Uri.parseFile(File)</code>就会出现crash,android 7.0系统的安全机制是识别<code>file://</code>这样的schema,而对普通的路径直接放行。检查的系统源码如下所示:</p>
<blockquote>
<ul>
<li>android.net.Uri.java</li>
</ul>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * If this is a &#123;<span class="doctag">@code</span> file://&#125; Uri, it will be reported to</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@link</span> StrictMode&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">checkFileUriExposed</span><span class="params">(String location)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"file"</span>.equals(getScheme()) &amp;&amp; !getPath().startsWith(<span class="string">"/system/"</span>)) &#123;</span><br><span class="line"> StrictMode.onFileUriExposed(<span class="keyword">this</span>, location);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>以上。</p>
</content>
<summary type="html">
<p>前几天处理一个很奇葩的问题,解决过程可谓一波三折啊,处理起来却很有意思,这里记录一下。</p>
<p>事情的起因是同事写了一个在android系统调用系统相机的拍照的功能,将路径通过Intent传过去,照片拍好后保存到路径,通过路径取这张照片。</p>
<p>突然有一天点这拍照功能无效了,直接crash。一看异常日志,发现抛出的是<code>ActivityNotFoundException</code>。第一反应是以为是Activity没有注册或者打包出现问题了,于是把Apk反编译,在AndroidManifest.xml中检查了下注册,在dex文件中也找了下相关的类,结果发现都没有问题。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="InvocationTargetException ActivityNotFoundException android.os.FileUriExposedException" scheme="http://www.jacpy.com/tags/InvocationTargetException-ActivityNotFoundException-android-os-FileUriExposedException/"/>
</entry>
<entry>
<title>golang中append函数和copy函数操作slice的不同用法</title>
<link href="http://www.jacpy.com/2017/10/26/golang-difference-between-append-and-copy-function.html"/>
<id>http://www.jacpy.com/2017/10/26/golang-difference-between-append-and-copy-function.html</id>
<published>2017-10-26T04:50:00.000Z</published>
<updated>2017-10-26T15:39:31.000Z</updated>
<content type="html"><p>前几天使用<code>golang</code>时,遇到一个很奇怪的问题,原因是对<code>golang</code>不熟悉,所以记录一下。</p>
<p>在使用<code>append()</code>函数给<code>slice</code>中添加元素时,<code>slice</code>的初始大小可以为0,也就是<code>len</code>可以为0。每次向<code>slice</code>中<code>append</code>的时候,如果容量<code>cap</code>不够,会自动对<code>slice</code>进行扩容,也就是改变<code>slice</code>的<code>cap</code>的大小。</p>
<a id="more"></a>
<p>而在使用<code>copy()</code>函数操作<code>slice</code>时,如果<code>slice</code>的大小为0时,会不添加任何元素,不会自动增加<code>slice</code>的容量大小。这里要注意。</p>
<p>下面是从golang官网源码库中看到的实现代码,如下所示:</p>
<blockquote>
<ul>
<li>runtime/slice.go</li>
</ul>
</blockquote>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">slicecopy</span><span class="params">(to, fm slice, width <span class="keyword">uintptr</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line"> <span class="keyword">if</span> fm.<span class="built_in">len</span> == <span class="number">0</span> || to.<span class="built_in">len</span> == <span class="number">0</span> &#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> n := fm.<span class="built_in">len</span></span><br><span class="line"> <span class="keyword">if</span> to.<span class="built_in">len</span> &lt; n &#123;</span><br><span class="line"> n = to.<span class="built_in">len</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> width == <span class="number">0</span> &#123;</span><br><span class="line"> <span class="keyword">return</span> n</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> raceenabled &#123;</span><br><span class="line"> callerpc := getcallerpc(unsafe.Pointer(&amp;to))</span><br><span class="line"> pc := funcPC(slicecopy)</span><br><span class="line"> racewriterangepc(to.array, <span class="keyword">uintptr</span>(n*<span class="keyword">int</span>(width)), callerpc, pc)</span><br><span class="line"> racereadrangepc(fm.array, <span class="keyword">uintptr</span>(n*<span class="keyword">int</span>(width)), callerpc, pc)</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">if</span> msanenabled &#123;</span><br><span class="line"> msanwrite(to.array, <span class="keyword">uintptr</span>(n*<span class="keyword">int</span>(width)))</span><br><span class="line"> msanread(fm.array, <span class="keyword">uintptr</span>(n*<span class="keyword">int</span>(width)))</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> size := <span class="keyword">uintptr</span>(n) * width</span><br><span class="line"> <span class="keyword">if</span> size == <span class="number">1</span> &#123; <span class="comment">// common case worth about 2x to do here</span></span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> is this still worth it with new memmove impl?</span></span><br><span class="line"> *(*<span class="keyword">byte</span>)(to.array) = *(*<span class="keyword">byte</span>)(fm.array) <span class="comment">// known to be a byte pointer</span></span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> memmove(to.array, fm.array, size)</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> n</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从上面的代码可以看出来,如果<code>slice</code>的<code>len</code>值为0,则直接<code>return</code>,不会进行复制操作。</p>
<p>下面是示例代码,验证一下:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"regexp"</span></span><br><span class="line"> <span class="string">"log"</span></span><br><span class="line"> <span class="string">"unsafe"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line"> log.SetFlags(log.Lshortfile|log.LstdFlags)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">sliceTest</span><span class="params">(list []<span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(list) &gt; <span class="number">0</span> &#123;</span><br><span class="line"> log.Println(<span class="string">"func pos 0:"</span>, &amp;list[<span class="number">0</span>])</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> log.Println(<span class="string">"func pointer:"</span>, unsafe.Pointer(&amp;list))</span><br><span class="line"> log.Println(<span class="string">"------"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">copyTest</span><span class="params">()</span></span> &#123;</span><br><span class="line"> list := <span class="built_in">make</span>([]<span class="keyword">int</span>, <span class="number">0</span>)</span><br><span class="line"> log.Println(<span class="string">"pointer:"</span>, unsafe.Pointer(&amp;list))</span><br><span class="line"> sliceTest(list)</span><br><span class="line"> log.Println(<span class="string">"++++++++++++++"</span>)</span><br><span class="line"></span><br><span class="line"> list = <span class="built_in">append</span>(list, <span class="number">1</span>)</span><br><span class="line"> log.Println(<span class="string">"pos 0:"</span>, &amp;list[<span class="number">0</span>])</span><br><span class="line"> log.Println(<span class="string">"pointer:"</span>, unsafe.Pointer(&amp;list))</span><br><span class="line"> sliceTest(list)</span><br><span class="line"> log.Println(<span class="string">"++++++++++++++"</span>)</span><br><span class="line"></span><br><span class="line"> list = <span class="built_in">append</span>(list, <span class="number">2</span>)</span><br><span class="line"> log.Println(<span class="string">"pos 0:"</span>, &amp;list[<span class="number">0</span>])</span><br><span class="line"> log.Println(<span class="string">"pointer:"</span>, unsafe.Pointer(&amp;list))</span><br><span class="line"> sliceTest(list)</span><br><span class="line"> log.Println(<span class="string">"++++++++++++++"</span>)</span><br><span class="line"></span><br><span class="line"> copy1 := <span class="built_in">make</span>([]<span class="keyword">int</span>, <span class="number">0</span>)</span><br><span class="line"> <span class="built_in">copy</span>(copy1, list)</span><br><span class="line"> log.Println(<span class="string">"copy1:"</span>, copy1)</span><br><span class="line"></span><br><span class="line"> copy2 := <span class="built_in">make</span>([]<span class="keyword">int</span>, <span class="number">2</span>)</span><br><span class="line"> <span class="built_in">copy</span>(copy2, list)</span><br><span class="line"> log.Println(<span class="string">"copy2:"</span>, copy2)</span><br><span class="line"></span><br><span class="line"> copy3 := <span class="built_in">make</span>([]<span class="keyword">int</span>, <span class="number">0</span>, <span class="number">2</span>)</span><br><span class="line"> <span class="built_in">copy</span>(copy3, list)</span><br><span class="line"> log.Println(<span class="string">"copy3:"</span>, copy3)</span><br><span class="line"> log.Println()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"> copyTest()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>输出:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">pointer: 0xc42000a060</span><br><span class="line">func pointer: 0xc42000a080</span><br><span class="line">------</span><br><span class="line">++++++++++++++</span><br><span class="line">pos 0: 0xc4200160d0</span><br><span class="line">pointer: 0xc42000a060</span><br><span class="line">func pos 0: 0xc4200160d0</span><br><span class="line">func pointer: 0xc42000a0a0</span><br><span class="line">------</span><br><span class="line">++++++++++++++</span><br><span class="line">pos 0: 0xc4200160f0</span><br><span class="line">pointer: 0xc42000a060</span><br><span class="line">func pos 0: 0xc4200160f0</span><br><span class="line">func pointer: 0xc42000a0c0</span><br><span class="line">------</span><br><span class="line">++++++++++++++</span><br><span class="line">copy1: []</span><br><span class="line">copy2: [1 2]</span><br><span class="line">copy3: []</span><br></pre></td></tr></table></figure></p>
<p>从上面的日志可以看出来,copy1和copy3都是空,没有进行复制操作;每次进行扩容的时候,<code>pos 0:</code>的地址都会发生变化。另外还可以看出来,当把<code>slice</code>传给一个函数时,对<code>slice</code>的结构体发生的值传递,而<code>slice</code>中指向数据内容的地址没有变,即上面<code>pos 0:</code>和<code>func pos 0:</code>输出的地址是一致。</p>
<p>同样可以在<code>runtime/slice.go</code>源码中的<code>makeslice()</code>函数中可以看到创建的<code>slice</code>结构体,如下所示:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> slice <span class="keyword">struct</span> &#123;</span><br><span class="line"> array unsafe.Pointer</span><br><span class="line"> <span class="built_in">len</span> <span class="keyword">int</span></span><br><span class="line"> <span class="built_in">cap</span> <span class="keyword">int</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>也就是说当把<code>slice</code>直接传给函数时,会对这个<code>slice</code>结构体内容进行复制,而<code>array</code>的地址不会变,始终指向数组内容的首元素地址。</p>
<p>另外看这个<code>runtime/slice.go</code>的源码时,还发现一个有趣的细节,代码如下所示:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">growslice</span><span class="params">(et *_type, old slice, <span class="built_in">cap</span> <span class="keyword">int</span>)</span> <span class="title">slice</span></span> &#123;</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> newcap := old.<span class="built_in">cap</span></span><br><span class="line"> doublecap := newcap + newcap</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">cap</span> &gt; doublecap &#123;</span><br><span class="line"> newcap = <span class="built_in">cap</span></span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">if</span> old.<span class="built_in">len</span> &lt; <span class="number">1024</span> &#123;</span><br><span class="line"> newcap = doublecap</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">for</span> newcap &lt; <span class="built_in">cap</span> &#123;</span><br><span class="line"> newcap += newcap / <span class="number">4</span></span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在<code>slice</code>进行扩容的时候,如果其容量小于<code>1024</code>,则容量增加一倍;否则容量以<code>1.25</code>倍的数量增加。</p>
<p>以上golang源码是基于1.9.1版本。</p>
</content>
<summary type="html">
<p>前几天使用<code>golang</code>时,遇到一个很奇怪的问题,原因是对<code>golang</code>不熟悉,所以记录一下。</p>
<p>在使用<code>append()</code>函数给<code>slice</code>中添加元素时,<code>slice</code>的初始大小可以为0,也就是<code>len</code>可以为0。每次向<code>slice</code>中<code>append</code>的时候,如果容量<code>cap</code>不够,会自动对<code>slice</code>进行扩容,也就是改变<code>slice</code>的<code>cap</code>的大小。</p>
</summary>
<category term="go" scheme="http://www.jacpy.com/categories/go/"/>
<category term="append copy slice" scheme="http://www.jacpy.com/tags/append-copy-slice/"/>
</entry>
<entry>
<title>Okhttp使用注意事项</title>
<link href="http://www.jacpy.com/2017/09/28/okhttp-used-attentions.html"/>
<id>http://www.jacpy.com/2017/09/28/okhttp-used-attentions.html</id>
<published>2017-09-28T07:10:42.000Z</published>
<updated>2017-10-15T03:30:22.000Z</updated>
<content type="html"><p>前几天在使用<code>Okhttp</code>时遇到两个坑,这里记录一下。一个是在请求头中不需要设置<code>Accept-Encoding</code>为<code>gzip</code>,使用<code>Okhttp</code>默认的就好;第二个是添加到<code>Okhttp</code>请求头里面的键值对不能为空。</p>
<a id="more"></a>
<h2 id="默认不需要设置Accept-Encoding为gzip"><a href="#默认不需要设置Accept-Encoding为gzip" class="headerlink" title="默认不需要设置Accept-Encoding为gzip"></a>默认不需要设置Accept-Encoding为gzip</h2><p><code>Okhttp</code>中如果外部请求没有在请求里面设置<code>Accept-Encoding</code>值和<code>Range</code>值时,会在请求头里增加设置<code>Accept-Encoding: gzip</code>,如果外部设置这个<code>Accept-Encoding</code>参数,则不会设置。如果是<code>Okhttp</code>自己设置的参数,并且服务器端响应的头里包含有<code>Content-Encoding</code>值为<code>gzip</code>,则会使用<code>Gzip</code>解压。如果是外部设置的,即使服务器端响应头里面包含有<code>Content-Encoding</code>值为<code>gzip</code>,也不会使用<code>Gzip</code>解压,需要自己去解压。</p>
<p>下面是官方代码,在<code>okhttp3.internal.http.BridgeInterceptor#intercept(Chain chain)</code>方法中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing</span></span><br><span class="line"> <span class="comment">// the transfer stream.</span></span><br><span class="line"> <span class="keyword">boolean</span> transparentGzip = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (userRequest.header(<span class="string">"Accept-Encoding"</span>) == <span class="keyword">null</span> &amp;&amp; userRequest.header(<span class="string">"Range"</span>) == <span class="keyword">null</span>) &#123;</span><br><span class="line"> transparentGzip = <span class="keyword">true</span>;</span><br><span class="line"> requestBuilder.header(<span class="string">"Accept-Encoding"</span>, <span class="string">"gzip"</span>);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> List&lt;Cookie&gt; cookies = cookieJar.loadForRequest(userRequest.url());</span><br><span class="line"> <span class="keyword">if</span> (!cookies.isEmpty()) &#123;</span><br><span class="line"> requestBuilder.header(<span class="string">"Cookie"</span>, cookieHeader(cookies));</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (userRequest.header(<span class="string">"User-Agent"</span>) == <span class="keyword">null</span>) &#123;</span><br><span class="line"> requestBuilder.header(<span class="string">"User-Agent"</span>, Version.userAgent());</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> Response networkResponse = chain.proceed(requestBuilder.build());</span><br><span class="line"></span><br><span class="line"> HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());</span><br><span class="line"></span><br><span class="line"> Response.Builder responseBuilder = networkResponse.newBuilder()</span><br><span class="line"> .request(userRequest);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (transparentGzip</span><br><span class="line"> &amp;&amp; <span class="string">"gzip"</span>.equalsIgnoreCase(networkResponse.header(<span class="string">"Content-Encoding"</span>))</span><br><span class="line"> &amp;&amp; HttpHeaders.hasBody(networkResponse)) &#123;</span><br><span class="line"> GzipSource responseBody = <span class="keyword">new</span> GzipSource(networkResponse.body().source());</span><br><span class="line"> Headers strippedHeaders = networkResponse.headers().newBuilder()</span><br><span class="line"> .removeAll(<span class="string">"Content-Encoding"</span>)</span><br><span class="line"> .removeAll(<span class="string">"Content-Length"</span>)</span><br><span class="line"> .build();</span><br><span class="line"> responseBuilder.headers(strippedHeaders);</span><br><span class="line"> responseBuilder.body(<span class="keyword">new</span> RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>注意整个逻辑是由<code>transparentGzip</code>变量来控制的。刚开始不知道有这种逻辑,使用了<code>Okhttp</code>做请求,然后在请求头里设置了<code>Accept-Encoding</code>值为<code>gzip</code>,于是使用<code>okhttp3.ResponseBody#string()</code>方法来获取服务器端的响应字符串时,结果是乱码。去排查原因时发现上面这段逻辑。</p>
<h2 id="设置请求头信息时,值不能为空"><a href="#设置请求头信息时,值不能为空" class="headerlink" title="设置请求头信息时,值不能为空"></a>设置请求头信息时,值不能为空</h2><p>在使用<code>Okhttp</code>请求时,由于设置请求的值为null值,所以App直接闪退,一看错误日志,发现了原因。原来源码里面有拦截操作,具体代码在<code>okhttp3.Headers#checkNameAndValue(String name, String value)</code>方法中,如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkNameAndValue</span><span class="params">(String name, String value)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (name == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">"name == null"</span>);</span><br><span class="line"> <span class="keyword">if</span> (name.isEmpty()) <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"name is empty"</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>, length = name.length(); i &lt; length; i++) &#123;</span><br><span class="line"> <span class="keyword">char</span> c = name.charAt(i);</span><br><span class="line"> <span class="keyword">if</span> (c &lt;= <span class="string">'\u0020'</span> || c &gt;= <span class="string">'\u007f'</span>) &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(Util.format(</span><br><span class="line"> <span class="string">"Unexpected char %#04x at %d in header name: %s"</span>, (<span class="keyword">int</span>) c, i, name));</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">"value for name "</span> + name + <span class="string">" == null"</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>, length = value.length(); i &lt; length; i++) &#123;</span><br><span class="line"> <span class="keyword">char</span> c = value.charAt(i);</span><br><span class="line"> <span class="keyword">if</span> ((c &lt;= <span class="string">'\u001f'</span> &amp;&amp; c != <span class="string">'\t'</span>) || c &gt;= <span class="string">'\u007f'</span>) &#123;</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(Util.format(</span><br><span class="line"> <span class="string">"Unexpected char %#04x at %d in %s value: %s"</span>, (<span class="keyword">int</span>) c, i, name, value));</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果添加请求的键或值为<code>null</code>,都会抛出异常,而且还要求键的长度不能为0,值的要求是可见字符,可以是<code>\t</code>。否则都会抛出异常。</p>
<p>解决方法是对请求时添加到头里面的内容过滤下就可以了。</p>
</content>
<summary type="html">
<p>前几天在使用<code>Okhttp</code>时遇到两个坑,这里记录一下。一个是在请求头中不需要设置<code>Accept-Encoding</code>为<code>gzip</code>,使用<code>Okhttp</code>默认的就好;第二个是添加到<code>Okhttp</code>请求头里面的键值对不能为空。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="okhttp" scheme="http://www.jacpy.com/tags/okhttp/"/>
</entry>
<entry>
<title>Fresco使用HttpURLConnection支持加载https链接图片</title>
<link href="http://www.jacpy.com/2017/09/28/fresco-used-httpurlconnection-support-https-image-loader.html"/>
<id>http://www.jacpy.com/2017/09/28/fresco-used-httpurlconnection-support-https-image-loader.html</id>
<published>2017-09-28T07:09:41.000Z</published>
<updated>2017-09-30T16:38:03.000Z</updated>
<content type="html"><p>前几天使用<code>Fresco</code>加载图片时遇到图片加载不出来的问题,然后调试了一下,发现出现如下错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javax.net.ssl.SSLException: Connection closed by peer</span><br></pre></td></tr></table></figure>
<p>debug时的截图如下图所示:</p>
<a id="more"></a>
<p><img src="/images/2017/09/javax_net_ssl_sslexception.png" alt="SSLException截图"></p>
<h2 id="查找原因"><a href="#查找原因" class="headerlink" title="查找原因"></a>查找原因</h2><p>上面的截图是在给<code>Fresco</code>中的展示图片的类<code>SimpleDraweeView</code>调用如下代码时出现的:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SimpileDraweeView sdv = ...;</span></span><br><span class="line">sdv.setController(Fresco.newDraweeControllerBuilder().setControllerListener(<span class="keyword">new</span> BaseControllerListener&lt;ImageInfo&gt;() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onFinalImageSet</span><span class="params">(String id, ImageInfo imageInfo, Animatable animatable)</span> </span>&#123;</span><br><span class="line"> File file = getCacheFile(request);</span><br><span class="line"> <span class="keyword">if</span> (file != <span class="keyword">null</span> &amp;&amp; callback != <span class="keyword">null</span>) &#123;</span><br><span class="line"> callback.onImageLoadFinish(file);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onFailure</span><span class="params">(String id, Throwable throwable)</span> </span>&#123;</span><br><span class="line"> <span class="comment">// 这里throwable就是javax.net.ssl.SSLException: Connection closed by peer</span></span><br><span class="line"> <span class="keyword">super</span>.onFailure(id, throwable);</span><br><span class="line"> <span class="keyword">if</span> (callback != <span class="keyword">null</span>) &#123;</span><br><span class="line"> callback.onFailure(uri, throwable);</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;)</span><br><span class="line"> .setImageRequest(request)</span><br><span class="line"> .build());</span><br></pre></td></tr></table></figure>
<p>看到上面的错误提示,马上反应是不是因为<code>https</code>证书的原因?原来请求的图片链接是<code>https</code>地址。到网上一搜,果然,<code>Fresco</code>默认不支持<code>https</code>,需要自己设置。一看网上的解决方法,都是使用<code>Okhttp</code>库来解决的,由于项目比较老,还没有使用<code>Okhttp</code>库,总不能为了解决这个问题而引入一个库吧?感觉代价太大了。所以跑去看了一下<code>Okhttp</code>默认请求加载图片的实现。</p>
<p><code>Okhttp</code>库加载图片的网络请求处理在这个类<code>com.facebook.imagepipeline.producers.HttpUrlConnectionNetworkFetcher</code>中,关键的请求处理是在<code>downloadFrom(Uri uri, int maxRedirects)</code>这个方法中,代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> HttpURLConnection <span class="title">downloadFrom</span><span class="params">(Uri uri, <span class="keyword">int</span> maxRedirects)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line"> HttpURLConnection connection = openConnectionTo(uri);</span><br><span class="line"> <span class="keyword">int</span> responseCode = connection.getResponseCode(); <span class="comment">// 请求是在这一步发出的,在这之前设置HTTPS即可</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isHttpSuccess(responseCode)) &#123;</span><br><span class="line"> <span class="keyword">return</span> connection;</span><br><span class="line"></span><br><span class="line"> &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isHttpRedirect(responseCode)) &#123;</span><br><span class="line"> String nextUriString = connection.getHeaderField(<span class="string">"Location"</span>);</span><br><span class="line"> connection.disconnect();</span><br><span class="line"></span><br><span class="line"> Uri nextUri = (nextUriString == <span class="keyword">null</span>) ? <span class="keyword">null</span> : Uri.parse(nextUriString);</span><br><span class="line"> String originalScheme = uri.getScheme();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (maxRedirects &gt; <span class="number">0</span> &amp;&amp; nextUri != <span class="keyword">null</span> &amp;&amp; !nextUri.getScheme().equals(originalScheme)) &#123;</span><br><span class="line"> <span class="keyword">return</span> downloadFrom(nextUri, maxRedirects - <span class="number">1</span>);</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> String message = maxRedirects == <span class="number">0</span></span><br><span class="line"> ? error(<span class="string">"URL %s follows too many redirects"</span>, uri.toString())</span><br><span class="line"> : error(<span class="string">"URL %s returned %d without a valid redirect"</span>, uri.toString(), responseCode);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IOException(message);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> connection.disconnect();</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IOException(String</span><br><span class="line"> .format(<span class="string">"Image URL %s returned HTTP code %d"</span>, uri.toString(), responseCode));</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@VisibleForTesting</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> HttpURLConnection <span class="title">openConnectionTo</span><span class="params">(Uri uri)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line"> URL url = <span class="keyword">new</span> URL(uri.toString());</span><br><span class="line"> <span class="keyword">return</span> (HttpURLConnection) url.openConnection();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>从上面的代码可以看出来,请求是从<code>int responseCode = connection.getResponseCode();</code>这行代码发出去的,但是又没有对<code>https</code>的证书进行处理,所以在这之前增加处理即可。于是想到继承这个类来扩展处理<code>https</code>的情况,结果从上面的代码来看,想从继承的角度增加<code>https</code>的处理是不可能的。干脆把整个类的代码复制一份,修改类名为<code>HttpsUrlConnectionNetworkFetcher</code>,修改<code>downloadFrom</code>这个方法,增加<code>https</code>证书处理,代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HttpsUrlConnectionNetworkFetcher</span> <span class="keyword">extends</span> <span class="title">BaseNetworkFetcher</span>&lt;<span class="title">FetchState</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 中间代码忽略...</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> HttpURLConnection <span class="title">downloadFrom</span><span class="params">(Uri uri, <span class="keyword">int</span> maxRedirects)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line"> HttpURLConnection connection = openConnectionTo(uri);</span><br><span class="line"> <span class="comment">// 增加https支持</span></span><br><span class="line"> String scheme = uri.getScheme();</span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"https"</span>.equals(scheme)) &#123;</span><br><span class="line"> HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection;</span><br><span class="line"> SSLContext sslContext = SSLContextUtils.getSSLContext();</span><br><span class="line"> <span class="keyword">if</span> (sslContext != <span class="keyword">null</span>) &#123;</span><br><span class="line"> SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();</span><br><span class="line"> httpsURLConnection.setSSLSocketFactory(sslSocketFactory);</span><br><span class="line"> httpsURLConnection.setHostnameVerifier(SSLContextUtils.getHostnameVerifier());</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> responseCode = connection.getResponseCode(); </span><br><span class="line"> <span class="comment">// 其它代码忽略...</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>SSLContextUtils</code>类中忽略对证书的处理,信任所有证书,这种做法很不安全,仅供参考。代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.util.Log;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.security.SecureRandom;</span><br><span class="line"><span class="keyword">import</span> java.security.cert.X509Certificate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.net.ssl.HostnameVerifier;</span><br><span class="line"><span class="keyword">import</span> javax.net.ssl.SSLContext;</span><br><span class="line"><span class="keyword">import</span> javax.net.ssl.SSLSession;</span><br><span class="line"><span class="keyword">import</span> javax.net.ssl.TrustManager;</span><br><span class="line"><span class="keyword">import</span> javax.net.ssl.X509TrustManager;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * SSL工具类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SSLContextUtils</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> HostnameVerifier hostnameVerifier = <span class="keyword">new</span> HostnameVerifier() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">verify</span><span class="params">(String hostname, SSLSession session)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> HostnameVerifier <span class="title">getHostnameVerifier</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> <span class="keyword">return</span> hostnameVerifier;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SSLContext <span class="title">getSSLContext</span><span class="params">()</span> </span>&#123;</span><br><span class="line"> SSLContext sslContext = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line"> sslContext = SSLContext.getInstance(<span class="string">"TLS"</span>);</span><br><span class="line"> sslContext.init(<span class="keyword">null</span>, <span class="keyword">new</span> TrustManager[]&#123;<span class="keyword">new</span> X509TrustManager() &#123;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">checkClientTrusted</span><span class="params">(X509Certificate[] chain, String authType)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">checkServerTrusted</span><span class="params">(X509Certificate[] chain, String authType)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> X509Certificate[] getAcceptedIssuers() &#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> X509Certificate[<span class="number">0</span>];</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;&#125;, <span class="keyword">new</span> SecureRandom());</span><br><span class="line"> &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"> Log.e(<span class="string">"SSLContextUtils"</span>, e.getMessage(), e);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> sslContext;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最后需要把这个自定义的类的通过调用<code>ImagePipelineConfig.Builder#setNetworkFetcher()</code>方法设置进来,指定加载图片时使用新的网络请求策略。具体代码如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)</span><br><span class="line"> .setDownsampleEnabled(<span class="keyword">true</span>)</span><br><span class="line"> .setNetworkFetcher(<span class="keyword">new</span> HttpsUrlConnectionNetworkFetcher()) <span class="comment">// 这里设置自定义类</span></span><br><span class="line"> .build();</span><br><span class="line">Fresco.initialize(context, config);</span><br></pre></td></tr></table></figure>
<p>再重新运行App,图片加载出来了,问题解决。</p>
</content>
<summary type="html">
<p>前几天使用<code>Fresco</code>加载图片时遇到图片加载不出来的问题,然后调试了一下,发现出现如下错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javax.net.ssl.SSLException: Connection closed by peer</span><br></pre></td></tr></table></figure>
<p>debug时的截图如下图所示:</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="fresco HttpURLConnection" scheme="http://www.jacpy.com/tags/fresco-HttpURLConnection/"/>
</entry>
<entry>
<title>sublime使用markdownEdit设置</title>
<link href="http://www.jacpy.com/2017/09/22/config-4-sublime-markdownedit.html"/>
<id>http://www.jacpy.com/2017/09/22/config-4-sublime-markdownedit.html</id>
<published>2017-09-22T11:12:12.000Z</published>
<updated>2017-09-22T12:13:31.000Z</updated>
<content type="html"><p>前几天<code>sublime</code>发布正式版本,mac下面一直没有发现跟windows下的<code>notepad++</code>相媲美的工具,于是装了下<code>sublime</code>玩玩,也试了下<code>markdown</code>插件,结果发现太坑爹了,于是走上了不归路。</p>
<p>装完<code>markdown</code>插件后,打开<code>md</code>文件,发现界面很丑,MD风格都被吓跑了。如下图所示:</p>
<a id="more"></a>
<p><img src="/images/2017/09/sublime_markdown_pre.png" alt="配置前的样式"></p>
<p>经过一翻折腾,MD终于又回来了,结果图如下所示:</p>
<p><img src="/images/2017/09/sublime_markdown_post.png" alt="配置后的样式"></p>
<p>修改的配置如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> &quot;color_scheme&quot;: &quot;Packages/Material Theme/schemes/Material-Theme-Darker.tmTheme&quot;, // 修改主题为MD主题,此时发现背景色会改变,不再是白色</span><br><span class="line"> &quot;line_numbers&quot;: true, // 显示行号</span><br><span class="line"> &quot;highlight_line&quot;: true, // 光标所在行高亮</span><br><span class="line"> &quot;wrap_width&quot;: 0, // 默认是80个字符就换行,这里改成0,使用默认值,sublime默认的配置就是这个值</span><br><span class="line"> &quot;draw_centered&quot;: false, // 不自动居中,如果发现一行中内容不多,就会出现上面截图中左侧到行号之前有大面积空白</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>怎么修改配置呢?</p>
<p>先注意<code>sublime</code>的右下角有一个<code>Markdown GFM</code>标识,然后依次<code>Preference</code>-&gt;<code>Package Settings</code>-&gt;<code>Markdown Edting</code>-&gt;<code>Markdown GFM Settings-User</code>,操作如下图所示:</p>
<p><img src="/images/2017/09/sublime_markdown_preference.png" alt="配置后的样式"></p>
<p>会打开一个标题为<code>Markdown.sublime-settings--MarkdownEditing</code>文件,把上面的配置内容复制进去,保存后重启<code>sublime</code>,再打开md文件样式就已经改变了。</p>
</content>
<summary type="html">
<p>前几天<code>sublime</code>发布正式版本,mac下面一直没有发现跟windows下的<code>notepad++</code>相媲美的工具,于是装了下<code>sublime</code>玩玩,也试了下<code>markdown</code>插件,结果发现太坑爹了,于是走上了不归路。</p>
<p>装完<code>markdown</code>插件后,打开<code>md</code>文件,发现界面很丑,MD风格都被吓跑了。如下图所示:</p>
</summary>
<category term="sublime" scheme="http://www.jacpy.com/categories/sublime/"/>
<category term="markdown设置 sublime" scheme="http://www.jacpy.com/tags/markdown%E8%AE%BE%E7%BD%AE-sublime/"/>
</entry>
<entry>
<title>android installation failed with message INSTALL_FAILED_TEST_ONLY</title>
<link href="http://www.jacpy.com/2017/09/21/android-installation-failed-with-message-INSTALL-FAILED-TEST-ONLY-md.html"/>
<id>http://www.jacpy.com/2017/09/21/android-installation-failed-with-message-INSTALL-FAILED-TEST-ONLY-md.html</id>
<published>2017-09-21T05:28:12.000Z</published>
<updated>2017-09-22T12:24:10.000Z</updated>
<content type="html"><p>这几天一直在试用Android Studio Beta版本,在给6.0的手机安装调试包是遇到了下面的问题,一运行AS就弹出<code>INSTALL_FAILED_TEST_ONLY</code>对话框提示,然后点<code>OK</code>,在6.0手机上提示<code>应用程序未安装</code>,但是在4.4手机上运行却没有对话框提示,可以正常运行,奇了怪了,跑去搜索了一下。</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Installation failed with message INSTALL_FAILED_TEST_ONLY.</span><br><span class="line">It is possible that this issue is resolved by uninstalling an existing version of the apk if it is present, and then re-installing.</span><br><span class="line"></span><br><span class="line">WARNING: Uninstalling will remove the application data!</span><br><span class="line"></span><br><span class="line">Do you want to uninstall the existing application?</span><br></pre></td></tr></table></figure>
<p>结果找到了这篇文章<code>http://www.cnblogs.com/bluestorm/p/6934433.html</code>,上面说<code>classpath</code>与<code>distributionUrl</code>要一一对应:</p>
<p><strong>在AS 2.3上面:</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">classpath &apos;com.android.tools.build:gradle:2.3.2&apos;</span><br><span class="line"></span><br><span class="line">对应:</span><br><span class="line"></span><br><span class="line">distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip</span><br></pre></td></tr></table></figure>
<p><strong>在AS 3.0上面:</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">classpath &apos;com.android.tools.build:gradle:3.0.0-alpha2`</span><br><span class="line"></span><br><span class="line">对应:</span><br><span class="line"></span><br><span class="line">distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip</span><br></pre></td></tr></table></figure>
<p>由于自己当前使用的是AS 3.0 beta5版本,</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">classpath: &apos;com.android.tools.build:gradle:3.0.0-beta5&apos;</span><br><span class="line">distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip</span><br></pre></td></tr></table></figure>
<p>所以认为也是对的,按照上面的文章所说的对应关系。</p>
<p>发现没有解决问题,在<code>stackoverflow</code>上面找到这个链接<code>https://stackoverflow.com/questions/25274296/adb-install-fails-with-install-failed-test-only</code>,说是在启动的时候加上<code>-t</code>参数,结果试了下,发现还是弹框提示,问题没有解决。</p>
<hr>
<p>思路又回到之前的解决方案上面,难道是对应关系问题?于是改成以下对应关系:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">classpath &apos;com.android.tools.build:gradle:2.3.3&apos;</span><br><span class="line">distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip</span><br></pre></td></tr></table></figure>
<p>一运行,没弹框了,问题解决了。果真是对应关系问题。</p>
</content>
<summary type="html">
<p>这几天一直在试用Android Studio Beta版本,在给6.0的手机安装调试包是遇到了下面的问题,一运行AS就弹出<code>INSTALL_FAILED_TEST_ONLY</code>对话框提示,然后点<code>OK</code>,在6.0手机上提示<code>应用程序未安装</code>,但是在4.4手机上运行却没有对话框提示,可以正常运行,奇了怪了,跑去搜索了一下。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="INSTALL_FAILED_TEST_ONLY" scheme="http://www.jacpy.com/tags/INSTALL-FAILED-TEST-ONLY/"/>
</entry>
<entry>
<title>Android命令行安装APK时报错:INSTALL_FAILED_UID_CHANGED</title>
<link href="http://www.jacpy.com/2017/09/07/android-install-apk-INSTALL-FAILED-UID-CHANGED.html"/>
<id>http://www.jacpy.com/2017/09/07/android-install-apk-INSTALL-FAILED-UID-CHANGED.html</id>
<published>2017-09-07T05:13:24.000Z</published>
<updated>2017-09-07T06:29:02.000Z</updated>
<content type="html"><p>前几天使用命令行<code>adb install</code>安装APK文件时,安装失败,遇到了这个错误提示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INSTALL_FAILED_UID_CHANGED</span><br></pre></td></tr></table></figure>
<p>突然反应过来了,刚刚使用手机的<code>root</code>权限看app的缓存数据,命令行还在那个<code>/data/data/com.xxx.app</code>目录下,由于命令行的占用,所以这个包名路径没有被删除掉。赶紧退出到<code>/data/data/</code>目录下面,<code>ls</code>一看,这个包名路径还存在,用<code>rm -rf</code>一删除,再安装试下,成功了。</p>
<a id="more"></a>
<p>回想之前调试微信支付功能,调的头大,经常卸载安装微信。结果有一次就安装失败了,提示<code>应用已经安装</code>,结果同样去<code>/data/data</code>目录下面一看,结果微信的包名路径还在。然后执行<code>rm -rf</code>删除,删除失败,跑去删除子目录,都能删除,最后有一个目录删除不了。心想这跟U盘一样,出现坏道了?微信不可能创建一个目录还不让删除的。结果一直提示删除失败,这<code>root</code>权限都使不上劲。最后没办法了,只好换台手机调试。</p>
<p>自己的测试手机上装不了微信,这个事一直挂在心上。只要能把包名路径给删除了,就可以装微信了,想着怎么删除。突然有一天,想到看能不能把这个目录移出去,然后再来删除包名路径。于是使用<code>mv</code>命令将这个目录移走,然后再删除包名,删除成功了,再删除那个目录,还是删除不掉,算了,不管了。一安装微信,装成功了。大喜!终于又可以装上微信了。</p>
</content>
<summary type="html">
<p>前几天使用命令行<code>adb install</code>安装APK文件时,安装失败,遇到了这个错误提示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INSTALL_FAILED_UID_CHANGED</span><br></pre></td></tr></table></figure>
<p>突然反应过来了,刚刚使用手机的<code>root</code>权限看app的缓存数据,命令行还在那个<code>/data/data/com.xxx.app</code>目录下,由于命令行的占用,所以这个包名路径没有被删除掉。赶紧退出到<code>/data/data/</code>目录下面,<code>ls</code>一看,这个包名路径还存在,用<code>rm -rf</code>一删除,再安装试下,成功了。</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="install apk INSTALL_FAILED_UID_CHANGED" scheme="http://www.jacpy.com/tags/install-apk-INSTALL-FAILED-UID-CHANGED/"/>
</entry>
<entry>
<title>gradle failed to create MD5 hash for file</title>
<link href="http://www.jacpy.com/2017/09/07/gradle-failed-to-create-md5-hash-for-file.html"/>
<id>http://www.jacpy.com/2017/09/07/gradle-failed-to-create-md5-hash-for-file.html</id>
<published>2017-09-07T05:03:59.000Z</published>
<updated>2017-09-07T06:29:17.000Z</updated>
<content type="html"><p>前几天在使用gradle编译项目插件,突然遇到这个错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">FAILURE: Build failed with an exception.</span><br><span class="line"></span><br><span class="line">* What went wrong:</span><br><span class="line">Execution failed for task &apos;:compileReleaseJavaWithJavac&apos;.</span><br><span class="line">&gt; Failed to create MD5 hash for file &apos;E:\workspace\as\app\libs\xxx.jar&apos;.</span><br><span class="line"></span><br><span class="line">* Try:</span><br><span class="line">Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.</span><br><span class="line"></span><br><span class="line">BUILD FAILED</span><br></pre></td></tr></table></figure>
<a id="more"></a>
<p>然后在对应目录找了下这个文件,发现没有这个<code>jar</code>文件。于是去<code>build.gradle</code>文件中的<code>depenencies</code>下面找到了这个<code>provided files</code>依赖,然后把这个<code>provided files</code>行删除,然后重新编译就OK了。</p>
<p>发现使用<code>compile files</code>和<code>provided files</code>引用文件时,如果引用的文件不存,都会报这个错误。删除就好了。</p>
</content>
<summary type="html">
<p>前几天在使用gradle编译项目插件,突然遇到这个错误:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">FAILURE: Build failed with an exception.</span><br><span class="line"></span><br><span class="line">* What went wrong:</span><br><span class="line">Execution failed for task &apos;:compileReleaseJavaWithJavac&apos;.</span><br><span class="line">&gt; Failed to create MD5 hash for file &apos;E:\workspace\as\app\libs\xxx.jar&apos;.</span><br><span class="line"></span><br><span class="line">* Try:</span><br><span class="line">Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.</span><br><span class="line"></span><br><span class="line">BUILD FAILED</span><br></pre></td></tr></table></figure>
</summary>
<category term="gradle" scheme="http://www.jacpy.com/categories/gradle/"/>
<category term="failed to create MD5 hash for file" scheme="http://www.jacpy.com/tags/failed-to-create-MD5-hash-for-file/"/>
</entry>
<entry>
<title>android听云升级后编译报错</title>
<link href="http://www.jacpy.com/2017/08/03/network-bench-update-compile-error-md.html"/>
<id>http://www.jacpy.com/2017/08/03/network-bench-update-compile-error-md.html</id>
<published>2017-08-03T01:00:09.000Z</published>
<updated>2017-08-07T05:16:41.000Z</updated>
<content type="html"><h2 id="升级遇到的问题"><a href="#升级遇到的问题" class="headerlink" title="升级遇到的问题"></a>升级遇到的问题</h2><p>项目中由于需要,集成了听云的SDK,最近把听云的SDK版本从<code>2.4.4</code>升级到<code>2.5.7</code>,重新运行时,在编译过程中报了如下错误:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">[NBSAgent.error] Error:com.networkbench.agent.compile.a: NetworkBench NewLens Android Agent Error: Your agent and class rewriter versions do not match: agent = 2.4.4 class rewriter = 2.5.7. You probably need to update one of these components.</span><br><span class="line">java.lang.RuntimeException: com.networkbench.agent.compile.a: NetworkBench NewLens Android Agent Error: Your agent and class rewriter versions do not match: agent = 2.4.4 class rewriter = 2.5.7. You probably need to update one of these components.</span><br><span class="line"> at com.networkbench.agent.compile.NBSStubPreMain$e.a(SourceFile:517)</span><br><span class="line"> at com.networkbench.agent.compile.NBSStubPreMain$e.a(SourceFile:296)</span><br><span class="line"> at com.networkbench.agent.compile.NBSStubPreMain$e$1$1.invoke(SourceFile:328)</span><br><span class="line"> at com.networkbench.agent.compile.NBSStubPreMain$e.invoke(SourceFile:425)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processClass(Main.java)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processFileBytes(Main.java:723)</span><br><span class="line"> at com.android.dx.command.dexer.Main.access$1200(Main.java:85)</span><br><span class="line"> at com.android.dx.command.dexer.Main$FileBytesConsumer.processFileBytes(Main.java:1653)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processOne(Main.java:677)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processAllFiles(Main.java:569)</span><br><span class="line"> at com.android.dx.command.dexer.Main.runMultiDex(Main.java:366)</span><br><span class="line"> at com.android.dx.command.dexer.Main.run(Main.java:275)</span><br><span class="line"> at com.android.dx.command.dexer.Main.main(Main.java:245)</span><br><span class="line"> at com.android.dx.command.Main.main(Main.java:106)</span><br><span class="line">Caused by: com.networkbench.agent.compile.a: NetworkBench NewLens Android Agent Error: Your agent and class rewriter versions do not match: agent = 2.4.4 class rewriter = 2.5.7. You probably need to update one of these components.</span><br><span class="line"> at com.networkbench.agent.compile.a.e.a(SourceFile:91)</span><br><span class="line"> at com.networkbench.agent.compile.a.h.a(SourceFile:43)</span><br><span class="line"> at com.networkbench.agent.compile.b.f.a(SourceFile:644)</span><br><span class="line"> at com.networkbench.agent.compile.b.f.a(SourceFile:439)</span><br><span class="line"> at com.networkbench.agent.compile.NBSStubPreMain$e.a(SourceFile:479)</span><br><span class="line"> ... 16 more</span><br><span class="line"></span><br><span class="line">Dex: Error converting bytecode to dex:</span><br><span class="line">Cause: java.lang.RuntimeException: Exception parsing classes</span><br><span class="line"> UNEXPECTED TOP-LEVEL EXCEPTION:</span><br><span class="line"> java.lang.RuntimeException: Exception parsing classes</span><br><span class="line"> at com.android.dx.command.dexer.Main.processClass(Main.java:760)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processFileBytes(Main.java:723)</span><br><span class="line"> at com.android.dx.command.dexer.Main.access$1200(Main.java:85)</span><br><span class="line"> at com.android.dx.command.dexer.Main$FileBytesConsumer.processFileBytes(Main.java:1653)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)</span><br><span class="line"> at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processOne(Main.java:677)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processAllFiles(Main.java:569)</span><br><span class="line"> at com.android.dx.command.dexer.Main.runMultiDex(Main.java:366)</span><br><span class="line"> at com.android.dx.command.dexer.Main.run(Main.java:275)</span><br><span class="line"> at com.android.dx.command.dexer.Main.main(Main.java:245)</span><br><span class="line"> at com.android.dx.command.Main.main(Main.java:106)</span><br><span class="line"> Caused by: java.lang.NullPointerException</span><br><span class="line"> at com.android.dx.util.ByteArray.&lt;init&gt;(ByteArray.java:76)</span><br><span class="line"> at com.android.dx.cf.direct.DirectClassFile.&lt;init&gt;(DirectClassFile.java:206)</span><br><span class="line"> at com.android.dx.command.dexer.Main.parseClass(Main.java:769)</span><br><span class="line"> at com.android.dx.command.dexer.Main.access$1500(Main.java:85)</span><br><span class="line"> at com.android.dx.command.dexer.Main$ClassParserTask.call(Main.java:1700)</span><br><span class="line"> at com.android.dx.command.dexer.Main.processClass(Main.java:755)</span><br><span class="line"> ... 12 more</span><br><span class="line"> </span><br><span class="line">1 error; aborting</span><br><span class="line">:app:transformClassesWithDexForDebug FAILED</span><br><span class="line">:app:networkBenchNewLensDeinstrumentTask</span><br><span class="line">[NBSAgent.debug] NetworkBench begin</span><br><span class="line"></span><br><span class="line">FAILURE: Build failed with an exception.</span><br><span class="line"></span><br><span class="line">* What went wrong:</span><br><span class="line">Execution failed for task &apos;:app:transformClassesWithDexForDebug&apos;.</span><br><span class="line">&gt; com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException: Error while executing java process with main class com.android.dx.command.Main with arguments &#123;--dex --num-threads=4 --multi-dex --main-dex-list /data/jenkins/workspace/app/build/intermediates/multi-dex/debug/maindexlist.txt --output /data/jenkins/workspace/app/build/intermediates/transforms/dex/debug/folders/1000/1f/main /data/jenkins/workspace/app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar&#125;</span><br><span class="line"></span><br><span class="line">* Try:</span><br><span class="line">Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.</span><br><span class="line"></span><br><span class="line">BUILD FAILED</span><br></pre></td></tr></table></figure>
<h2 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h2><p>遇到了上面的编译错误问题,先google了一下,没有人提出了这样的错误,也没有解决方案,而且从搜索的结果来看,用的人也不多。这问题只能是自己解决了,从上面的错误提示来看,这个错误是听云中抛出来的。也就是听云干预了编译过程。从这个<code>NetworkBench NewLens Android Agent Error: Your agent and class rewriter versions do not match: agent = 2.4.4 class rewriter = 2.5.7. You probably need to update one of these components.</code>错误提示上来看,说是版本没有对应起来。的确,听云在<code>project</code>下面的<code>build.gradle</code>中的<code>dependencies</code>块中的<code>classpath</code>中和<code>module</code>下面的<code>build.gradle</code>中的<code>dependencies</code>块中的<code>compile</code>两个地方都要指定一个版本SDK。一个是用来干预编译过程,植入代码;一个则是要植入的代码。</p>
<p>引入方式如下代码所示:</p>
<p><code>project</code>目录下面的<code>build.gradle</code>:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line"></span><br><span class="line"> classpath &apos;com.networkbench.newlens.agent.android:agent-gradle-plugin:2.5.7&apos;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>app</code>目录下面的<code>build.gradle</code>:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line"> </span><br><span class="line"> compile &apos;com.networkbench.newlens.agent.android:nbs.newlens.agent:2.5.7&apos;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>那为什么会有上面有错误?按道理说两者都具备就可以了,猜是听云的版本兼容性问题,也就是<code>2.5.7</code>不兼容<code>2.4.4</code>版本。也就是上面说的<code>classpath</code>与<code>compile</code>版本要一一对应,版本号必须保持一致。但修改时的版本号是保持一致的呀?问题出在哪儿?缓存问题?</p>
<p>于是先<code>clean</code>,然后再重新<code>build了</code>一下,再运行时,发现问题依旧。没折了,我把版本号改回去<code>2.4.4</code>总行吧?结果还是编译报错,无语了,试了几次,还是这样,现在感觉到自己进入到进退两难的地步了。最后重新启动<code>AS</code>,再重新运行了,问题解决了,编译时不报错了,真是太惊喜,太意外了。</p>
<p>OK,本地问题解决了,服务器的自动构建上又出现问题。按照解决本地问题的思路,不可能重启服务器吧?仔细一想,还是缓存问题?问了运维,说每次构建都会自动清除<code>build</code>目录中的内容。但心想,与<code>build</code>目录的内容无关,自己解决问题时都删了好几次,问题依旧没有解决。突然想起平时经常被<code>kill</code>的<code>java.exe</code>进程,这个进程里面会缓存一些东西,编译时经常会看到<code>Starting a Gradle Daemon</code>这样的提示,<code>AS</code>要提高编译速度,肯定会在这个进程里面缓存一些东西,不然这个<code>java.exe</code>进程内存占用会有几百M甚至超过1个G?</p>
<p>跑去跟运维说,你把这个<code>java</code>进程先<code>kill</code>,然后再重新构建。果然,<code>kill</code>进程之后再重新自动构建就OK了。后来想了一下,自己本地重启<code>AS</code>也是重新启动这个<code>java</code>进程。</p>
<h2 id="为什么要用听云"><a href="#为什么要用听云" class="headerlink" title="为什么要用听云"></a>为什么要用听云</h2><p>听云是收费的,但也有免费的版本,免费的版本最多只能看三天的数据。我看重听云的最大一个好处是可以监控网络情况,比如说哪些接口失败率比较高、请求耗时等,这样可以做一些优化。</p>
<p>但听云有一个不好的地方是,对代码进行侵入,有时候在报错的堆栈中看到了听云的堆栈信息。如果项目结构设计不好,用的第三方库太多,估计听云的代码会插入的到处都是,这里没有整理出数据来。</p>
<p>另外听去的另外一个ANR日志收集的功能没有做好,把<code>traces.txt</code>日志文件中的有用的信息基本都过虑掉了,只留下一些堆栈信息。</p>
</content>
<summary type="html">
<h2 id="升级遇到的问题"><a href="#升级遇到的问题" class="headerlink" title="升级遇到的问题"></a>升级遇到的问题</h2><p>项目中由于需要,集成了听云的SDK,最近把听云的SDK版本从<code>2.4.4</code>升级到<code>2.5.7</code>,重新运行时,在编译过程中报了如下错误:</p>
</summary>
<category term="android" scheme="http://www.jacpy.com/categories/android/"/>
<category term="听云" scheme="http://www.jacpy.com/tags/%E5%90%AC%E4%BA%91/"/>
</entry>
</feed>