-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1184 lines (1083 loc) · 180 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>I'm aaapei</title><link href="http://blog.aaapei.com/" rel="alternate"></link><link href="http://blog.aaapei.com/atom.xml" rel="self"></link><id>http://blog.aaapei.com/</id><updated>2017-11-05T11:28:00+08:00</updated><entry><title>Android接入httpdns的另一种思路</title><link href="http://blog.aaapei.com/article/2017/11/androidjie-ru-httpdnsde-ling-yi-chong-si-lu" rel="alternate"></link><published>2017-11-05T11:28:00+08:00</published><updated>2017-11-05T11:28:00+08:00</updated><author><name>郑文</name></author><id>tag:blog.aaapei.com,2017-11-05:/article/2017/11/androidjie-ru-httpdnsde-ling-yi-chong-si-lu</id><summary type="html"><h3>前言</h3>
<p>httpdns是基于Http协议的域名解析服务,用于替代基于UDP协议向运营商Local DNS发起解析请求的传统方式,目标是解决域名劫持和跨网访问等问题。</p>
<h3>httpdns的接入</h3>
<p>传统的使用Local DNS的方式,我们只需要在url中指定hostname;网络库帮我们做了域名解析、ttl缓存管理,我们不需要关心底层的流程。</p>
<p>但当使用httpdns,就需要我们自己向httpdns发起http请求,获取到域名对应的ip。一般商业的httpdns sdk都会提供域名解析成ip的方法,一般的开发者接入httpdns还是需要做以下工作:</p>
<ol>
<li>从URL中提取hostname,通过httpdns sdk获取到对应的IP</li>
<li>将URL中的hostname替换成IP</li>
<li>设置请求Header的Host字段</li>
<li>如果请求的是HTTPS,修改证书的域名验证策略</li>
</ol>
<p>android端有三个网络基础库,具体的接入方式会有差异:</p>
<ul>
<li>apache httpclient</li>
<li>http urlconnection</li>
<li>okhttp</li>
</ul>
<p>对于最后一个okhttp,由于它本身提供的设置外部dns的api,接入最简单,只需要简单的实现okhttp提供的<em>lookup</em>方法即可;但针对apache和urlconnection,就只能采取显式将url中的hostname替换成ip的方式了,然后添加Host头和HTTPS证书验证策略。但由于apache httpclient和urlconnection在设计之初,就没有考虑外部设置dns的策略,进行相关的接入时,总有各种异常case需要处理,比如apache http client在实现上,使用的是url中的ip信息作为cookie存储管理的key …</p></summary><content type="html"><h3>前言</h3>
<p>httpdns是基于Http协议的域名解析服务,用于替代基于UDP协议向运营商Local DNS发起解析请求的传统方式,目标是解决域名劫持和跨网访问等问题。</p>
<h3>httpdns的接入</h3>
<p>传统的使用Local DNS的方式,我们只需要在url中指定hostname;网络库帮我们做了域名解析、ttl缓存管理,我们不需要关心底层的流程。</p>
<p>但当使用httpdns,就需要我们自己向httpdns发起http请求,获取到域名对应的ip。一般商业的httpdns sdk都会提供域名解析成ip的方法,一般的开发者接入httpdns还是需要做以下工作:</p>
<ol>
<li>从URL中提取hostname,通过httpdns sdk获取到对应的IP</li>
<li>将URL中的hostname替换成IP</li>
<li>设置请求Header的Host字段</li>
<li>如果请求的是HTTPS,修改证书的域名验证策略</li>
</ol>
<p>android端有三个网络基础库,具体的接入方式会有差异:</p>
<ul>
<li>apache httpclient</li>
<li>http urlconnection</li>
<li>okhttp</li>
</ul>
<p>对于最后一个okhttp,由于它本身提供的设置外部dns的api,接入最简单,只需要简单的实现okhttp提供的<em>lookup</em>方法即可;但针对apache和urlconnection,就只能采取显式将url中的hostname替换成ip的方式了,然后添加Host头和HTTPS证书验证策略。但由于apache httpclient和urlconnection在设计之初,就没有考虑外部设置dns的策略,进行相关的接入时,总有各种异常case需要处理,比如apache http client在实现上,使用的是url中的ip信息作为cookie存储管理的key,而非请求头的host。
更具体的讨论可以参考<a href="https://help.aliyun.com/document_detail/30140.html?spm=5176.7758308.2.4.MJ84VT">阿里云httpdns</a>。</p>
<p>总之,现有的解法都不能完美的覆盖掉所有场景。如果想使用httpdns,那么建议开发者抛弃掉apache或urlconnection,引入okhttp这样的第三方网络库实现。</p>
<p>但的确会有那么一小撮仍在使用apache或者urlconnection的app,在不对现有网络库进行改造的情况下,有办法实现httpdns的低成本接入吗</p>
<h3>NETD的设计</h3>
<p>我们先看下android的Netd的设计,</p>
<p><img alt="" src="http://www.laiwangyo.com/2016/11/25/Android%20Netd%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/netd%E6%B5%81%E7%A8%8B.png">
在android 4.3之后,DNS解析都向Netd代理的方式来管理,所有进程的dns解析都通过IPC向Netd进行来进行。
受限于权限问题,我们没法对Netd做修改,但也正因为Netd这个设计,我们对当前进程的dns解析结果的改动并不会影响设备上其他应用的dns解析。</p>
<p>顺着apache client/urlconnetion/okhttp的源码往下翻,我们会发现无论何种网络库,最终都需要使用libcore提供<em>InetAddress.getByName()</em> API来进行DNS解析,并最终调用android native层提供的DNS解析C函数。以android5.0为分界,出现了两个分支:</p>
<ul>
<li>
<p>android 5.0以下系统将使用中的<em>getaddrinfo</em>;</p>
<div class="highlight"><pre><span></span>int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result )
</pre></div>
</li>
<li>
<p>android5.0及以上系统中android_getaddrinfoforget,相关的签名如下:</p>
<div class="highlight"><pre><span></span>int android_getaddrinfoforget(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
</pre></div>
</li>
</ul>
<p>这两个函数的参数完全一致,都通过hostname参数来指定查询的域名, result参数来返回域名对应的IP列表。他们都会编译进libjavacore.so这个动态链接库。</p>
<p>相关的源码调用关系有些长,总之得出一个结论:所有网络库的dns解析流程入口都是同一个。</p>
<h3>hook实现</h3>
<p>既然入口是同一个,我们可以针对这个入口做一些hook的事情。<br>
如果围绕java层的<em>InetAddress.getByName()</em>找方案,由于它是普通的静态方法,插件化常见的代理方案都将失效;从虚拟机这层hook,比如说Andfix,又会有遇到虚拟机兼容以及方案过重的问题。</p>
<p>而从native层想办法成本则小了点。比如使用<a href="http://gslab.qq.com/article-162-1.html">ptrace注入</a>,又比如<a href="http://gslab.qq.com/article-163-1.html">elfhook</a>。</p>
<p>具体的流程是,在android 应用刚启动时,我们可以从libjavacore.so中,分别找到<em>getaddrinfo</em>和<em>android_getaddrinfoforget</em>两个函数的指针地址,将其指向我们自定义的<em>get_httpdns_addrinfo</em>。在<em>get_httpdns_addrinfo</em>函数内部,我们调用httpdns的域名解析,将ip填充到struct addrinfo **result结构体中。</p>
<p>利用这种方式,上层网络库是无感知的,仍然按照他本身InetAddress.getByName的逻辑获取域名解析结果,cookie、sni的问题也仍然按照网络库原始的策略管理,从而实现httpdns的无缝接入。</p></content><category term="httpdns"></category></entry><entry><title>也谈MultiDex的优化</title><link href="http://blog.aaapei.com/article/2017/01/ye-tan-multidexde-you-hua" rel="alternate"></link><published>2017-01-17T14:26:00+08:00</published><updated>2017-01-17T14:26:00+08:00</updated><author><name>郑文</name></author><id>tag:blog.aaapei.com,2017-01-17:/article/2017/01/ye-tan-multidexde-you-hua</id><summary type="html"><p>旧文一篇,看到移动开发前线推送了<a href="https://zhuanlan.zhihu.com/p/24305296">MultiDex工作原理分析和优化方案</a>,也将我们的MultiDex启动优化思路分享给社区</p>
<h3>MultiDex存在的问题</h3>
<p>我们经常说的MultiDex,可以分成运行时和编译时两个部分:</p>
<ul>
<li>
<p>编译时的分包机制,将app中的class以某种策略将class分散在多个dex中,目的是减少为了第一个dex也就是main dex中包含的class。</p>
</li>
<li>
<p>运行时: app启动时,虚拟机只加载main dex中的class。app启动以后,使用<em>Multidex.install</em> API,通过反射修改ClassLoader中的dexElements加载其他dex。</p>
</li>
</ul>
<p>MultiDex机制的出现本身是为了避免出现app 65535问题的出现,但随着业务逻辑的增长,以及不合理的模块划分,可能会导致main dex的方法数也超出了65535,这就导致了<em>main dex capacity exceeded</em>异常。</p>
<p>此外,Multidex的接入额外还会对app的启动性能造成影响。Multidex在install时需要加载dex,首次启动时还需要做odex的转换,而这些都是在ui主线程中完成。
根据<a href="https://medium.com/@Macarse/lazy-loading-dex-files-d41f6f37df0e#.3vx8kle8j"> Carlos Sessa</a>的测试,启用multidex后,4.4或以下的设备,app的启动时间平均会增加15%,更严重的情况,甚至在启动时候会出现了黑屏。</p>
<p>目前部分app采取的策略是,放弃掉Multidex的 …</p></summary><content type="html"><p>旧文一篇,看到移动开发前线推送了<a href="https://zhuanlan.zhihu.com/p/24305296">MultiDex工作原理分析和优化方案</a>,也将我们的MultiDex启动优化思路分享给社区</p>
<h3>MultiDex存在的问题</h3>
<p>我们经常说的MultiDex,可以分成运行时和编译时两个部分:</p>
<ul>
<li>
<p>编译时的分包机制,将app中的class以某种策略将class分散在多个dex中,目的是减少为了第一个dex也就是main dex中包含的class。</p>
</li>
<li>
<p>运行时: app启动时,虚拟机只加载main dex中的class。app启动以后,使用<em>Multidex.install</em> API,通过反射修改ClassLoader中的dexElements加载其他dex。</p>
</li>
</ul>
<p>MultiDex机制的出现本身是为了避免出现app 65535问题的出现,但随着业务逻辑的增长,以及不合理的模块划分,可能会导致main dex的方法数也超出了65535,这就导致了<em>main dex capacity exceeded</em>异常。</p>
<p>此外,Multidex的接入额外还会对app的启动性能造成影响。Multidex在install时需要加载dex,首次启动时还需要做odex的转换,而这些都是在ui主线程中完成。
根据<a href="https://medium.com/@Macarse/lazy-loading-dex-files-d41f6f37df0e#.3vx8kle8j"> Carlos Sessa</a>的测试,启用multidex后,4.4或以下的设备,app的启动时间平均会增加15%,更严重的情况,甚至在启动时候会出现了黑屏。</p>
<p>目前部分app采取的策略是,放弃掉Multidex的,而转为插件化的架构。通过将非核心模块的lazy load,来达到启动速度的优化,但我们需要明确的是,并不是所有app都适合插件化架构,为了实现启动加速或热更新将本耦合的业务逻辑硬生生拆解才是本末倒置。</p>
<h3>解决方案</h3>
<h4>Multidex异步化</h4>
<p>在Android的性能优化中,最常见的思路就是异步化,减少UI线程的工作。在应用的交互层面上,app启动时,几乎所有app都会有一个SplashActivity。在界面上展示欢迎页,在后台进行初始化的业务逻辑。这就给我们一个启发,我们可以将系统的初始化逻辑,延迟到我们的业务初始化时间点上。</p>
<p>更加具体的方式是,我们可以将Multidex.install这个操作异步化,保证主线程的正常进行,待dex加载完成后通知SplashActivity跳转到真正的业务主界面。</p>
<p>在MultiDex加载的异步化之后,我们还可以进行第二步,main dex大小的精简。</p>
<h4>Main Dex精简</h4>
<p>我们先了解一下MultiDex分包的原理,Multidex会在入口Application的attachBaseContext,加载second dex,因此multidex分包的基本原则是:保证app启动需要的class放置在main dex上。在android gradle 1.5之后,multidex都通过一个MultidexTransform完成,分包过程可以分为三步:</p>
<ul>
<li>生成manifest_keep.txt</li>
</ul>
<p>MutidexTransform会解析出AndroidManifest.xml中所有的组件类:包括Activity、Service、Receiver以及ContentProvider,这些类将会Application入口类一起放在build/intermediates/multi-dex/{flavor}/{buildType}/manifest_keep.txt中</p>
<ul>
<li>生成maindexlist.txt文件</li>
</ul>
<p>查找manifest_keep.txt中所有类的直接引用类,具体的方式是遍历类的所有字段类以及方法,查看方法的参数和返回值的类型,将其放保存在maindexlist.txt</p>
<ul>
<li>生成main dex</li>
</ul>
<p>将maindexlist.txt文件包含的所有class编译进main dex</p>
<p>从上面的分析中,我们可以确定的是,MultiDex的分包机制并不严密:</p>
<ul>
<li>
<p>MultiDex将AndroidManifest.xml中的所有组件都包含在了manifest_keep.txt。但app在首次启动时,并不需要加载所有的组件,而只是需要入口的activity,供其他app访问的service、contentprovider以及注册获取系统通知的receiver。MainDex中过多的组件信息反而可能导致了app启动过慢。</p>
</li>
<li>
<p>MultidexTransform只查找了manifest_keep.txt中类的直接引用类,间接引用类并没有出现在maindex中,特殊情况下,会出现<a href="http://blog.waynell.com/2015/04/19/android-multidex/">NoClassDefFoundError</a>的异常,这时候开发者需要自行将需要的class添加到maindexlist.txt</p>
</li>
</ul>
<p>针对这两个缺陷,我们的优化思路是MultiDex的分包流程进行优化:</p>
<ul>
<li>
<p>使用SAX自行解析AndroidMainfest.xml,抽取出组件信息,将原始的Manifest_keep.txt内容替换掉,去除启动不需要的Activity组件,保证启动加载的类最小。</p>
</li>
<li>
<p>在gradle中添加multiDexExt扩展块,通过指定类名或通配符来设置必须编译在MainDex中类,在扩展块中指定的类都会被添加到maindexlist.txt文件汇中。</p>
<div class="highlight"><pre><span></span>multiDexExt {
keepClasses += &#39;android.support.v7.app.AppCompatActivity&#39;
keepClasses += &#39;android.support.v7.app.AppCompatDelegate&#39;
keepClasses += &#39;android.support.v7.app.**&#39;
}
</pre></div>
</li>
</ul>
<p>额外需要提的一个细节是,为了保证以上精简生效。我们还需要开启dx工具的minimal-main-dex参数:
<img alt="" src="http://coolpers.github.io/assets/posts/2015-04-08-multidex/dxhelp.png">
这个参数可以保证MainDex只包含maindexlist.txt文件中指定的类。但在gradle1.5到2.2之间,这个参数被默认关闭的,可以参考这篇文章:<a href="http://blog.csdn.net/lizhen3125/article/details/51911989?utm_source=itdadao&amp;utm_medium=referral">Gradle1.5.0之后如何控制dex包内的方法数上限?</a> ,
直到gradle2.2之后,dx的minimal-main-dex才重新开放给了开发者。在gradle 2.0~2.1的版本阶段,我们通过挂载<a href="http://www.infoq.com/cn/articles/javaagent-illustrated/">javaagent</a>的方式对dx过程进行了hook,重新开放minimal-main-dex参数,后面我们再出一篇文章来详细描述这个流程。 </p>
<h3>最后</h3>
<p>在main dex的分包过程中,maindex只包含了组件以及直接引用类。通过我们的优化进一步减少了maindex的大小,因此也增大了<a href="http://blog.waynell.com/2015/04/19/android-multidex/">NoClassDefFoundError</a>的异常的可能,使用以上的优化思路做好测试,一旦发现启动失败,使用multiDexExt重新添加缺失的类型</p></content><category term="multidex"></category></entry><entry><title>网易云课堂的android微专业课程</title><link href="http://blog.aaapei.com/article/2016/08/wang-yi-yun-ke-tang-de-androidwei-zhuan-ye-ke-cheng" rel="alternate"></link><published>2016-08-25T14:26:00+08:00</published><updated>2016-08-25T14:26:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2016-08-25:/article/2016/08/wang-yi-yun-ke-tang-de-androidwei-zhuan-ye-ke-cheng</id><summary type="html"><p>懒癌晚期,一年多没更新blog,近一两月项目以外做了两件事情:</p>
<ul>
<li>
<p>在听云和infoq的<a href="http://www.apmcon.cn/">apmcon</a>大会做了个android 编译器hook的分享,ppt地址在<a href="http://nos.netease.com/knowledge/fd1ba942-d609-4fa4-bdce-e1003febc1ce?download=apmcon_apm%E6%96%B9%E6%A1%88%E6%8E%A2%E7%B4%A2_v0818.pdf">网易APM hook方案探索</a>,介绍了我们在编译期做bytecode植入的一些实践工作,有兴趣的同学可以和我私信交流;</p>
</li>
<li>
<p>和杭研的同事们一起做了在线的Android课程,讲师基本囊括了网易杭研院各部门的大牛,课程针对在校生和1年以下工作经验的新同学。想了解我们工作方式的同学可以看看,通过下面这个链接购买成功的朋友,也可以私信我下,我再返个微信红包给你 :)</p>
</li>
</ul>
<p>android网易云课堂微专业:<a href="http://mooc.study.163.com/smartSpec/detail/1001168002.htm?utm_source=832006&amp;utm_medium=cps&amp;utm_campaign=affiliate">android</a></p>
<p>ios网易云课堂微专业:<a href="http://mooc.study.163.com/smartSpec/detail/1001168001.htm?utm_source=832006&amp;utm_medium=cps&amp;utm_campaign=affiliate">ios</a></p></summary><content type="html"><p>懒癌晚期,一年多没更新blog,近一两月项目以外做了两件事情:</p>
<ul>
<li>
<p>在听云和infoq的<a href="http://www.apmcon.cn/">apmcon</a>大会做了个android 编译器hook的分享,ppt地址在<a href="http://nos.netease.com/knowledge/fd1ba942-d609-4fa4-bdce-e1003febc1ce?download=apmcon_apm%E6%96%B9%E6%A1%88%E6%8E%A2%E7%B4%A2_v0818.pdf">网易APM hook方案探索</a>,介绍了我们在编译期做bytecode植入的一些实践工作,有兴趣的同学可以和我私信交流;</p>
</li>
<li>
<p>和杭研的同事们一起做了在线的Android课程,讲师基本囊括了网易杭研院各部门的大牛,课程针对在校生和1年以下工作经验的新同学。想了解我们工作方式的同学可以看看,通过下面这个链接购买成功的朋友,也可以私信我下,我再返个微信红包给你 :)</p>
</li>
</ul>
<p>android网易云课堂微专业:<a href="http://mooc.study.163.com/smartSpec/detail/1001168002.htm?utm_source=832006&amp;utm_medium=cps&amp;utm_campaign=affiliate">android</a></p>
<p>ios网易云课堂微专业:<a href="http://mooc.study.163.com/smartSpec/detail/1001168001.htm?utm_source=832006&amp;utm_medium=cps&amp;utm_campaign=affiliate">ios</a></p></content><category term="android"></category><category term="apm"></category></entry><entry><title>从DeepLinkDispatch谈谈组件跳转</title><link href="http://blog.aaapei.com/article/2015/07/cong-deeplinkdispatchtan-tan-zu-jian-tiao-zhuan" rel="alternate"></link><published>2015-07-26T14:26:00+08:00</published><updated>2015-07-26T14:26:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2015-07-26:/article/2015/07/cong-deeplinkdispatchtan-tan-zu-jian-tiao-zhuan</id><summary type="html"><h3>前言</h3>
<p>最近airbnb开源了<a href="https://github.com/airbnb/DeepLinkDispatch">DeepLinkDispatch</a>项目,DeepLinkDispatch是一个基于注解的链接跳转库,简单了解完其实现后,想起了Facebook今年5月公布的另一个项目<a href="http://applinks.org/">AppLink</a>,于是有了这篇文章。</p>
<h3>AppLink</h3>
<p>与其说AppLink是一个框架,更不如说他是一个规范。当app内嵌WebView遇到自定义的Schema时,app只能简单的将url转交给系统,或直接显示页面无法加载。AppLink规范旨在解决各个平台的app跳转的问题。第三方网页或者app接入applink后,跳转方可以根据AppLink规范进行<em>精确</em>的目标跳转以及<em>数据传输</em>。
引用官方文档中的例子,example.hmtl:</p>
<div class="highlight"><pre><span></span><span class="nt">&lt;html&gt;</span>
<span class="nt">&lt;head&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:ios:url&quot;</span> <span class="na">content=</span><span class="s">&quot;applinks://docs&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:ios:app_store_id&quot;</span> <span class="na">content=</span><span class="s">&quot;12345&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:ios:app_name&quot;</span> <span class="na">content=</span><span class="s">&quot;App Links&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta …</span></pre></div></summary><content type="html"><h3>前言</h3>
<p>最近airbnb开源了<a href="https://github.com/airbnb/DeepLinkDispatch">DeepLinkDispatch</a>项目,DeepLinkDispatch是一个基于注解的链接跳转库,简单了解完其实现后,想起了Facebook今年5月公布的另一个项目<a href="http://applinks.org/">AppLink</a>,于是有了这篇文章。</p>
<h3>AppLink</h3>
<p>与其说AppLink是一个框架,更不如说他是一个规范。当app内嵌WebView遇到自定义的Schema时,app只能简单的将url转交给系统,或直接显示页面无法加载。AppLink规范旨在解决各个平台的app跳转的问题。第三方网页或者app接入applink后,跳转方可以根据AppLink规范进行<em>精确</em>的目标跳转以及<em>数据传输</em>。
引用官方文档中的例子,example.hmtl:</p>
<div class="highlight"><pre><span></span><span class="nt">&lt;html&gt;</span>
<span class="nt">&lt;head&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:ios:url&quot;</span> <span class="na">content=</span><span class="s">&quot;applinks://docs&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:ios:app_store_id&quot;</span> <span class="na">content=</span><span class="s">&quot;12345&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:ios:app_name&quot;</span> <span class="na">content=</span><span class="s">&quot;App Links&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:android:url&quot;</span> <span class="na">content=</span><span class="s">&quot;applinks://docs&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:android:app_name&quot;</span> <span class="na">content=</span><span class="s">&quot;App Links&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:android:class&quot;</span> <span class="na">content=</span><span class="s">&quot;org.applinks.DocsActivity&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:android:package&quot;</span> <span class="na">content=</span><span class="s">&quot;org.applinks&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;meta</span> <span class="na">property=</span><span class="s">&quot;al:web:url&quot;</span>
<span class="na">content=</span><span class="s">&quot;http://applinks.org/documentation&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
Hello, world!
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</pre></div>
<p>第三方网页在meta中加以上信息,ios或android的webview通过以上信息可以向系统询问第三方app是否安装,进而生成跳转路由。第三方app未安装的情况下,利用al:web:url进行降级跳转。除例子的属性之外,AppLink还提供了一个重要的标签属性:al_applink_data,他的结构:</p>
<div class="highlight"><pre><span></span>{
&quot;target_url&quot;: &quot;http://example.com/docs&quot;,
&quot;extras&quot;: {
&quot;myapp_token&quot;: &quot;t0kEn&quot;
},
&quot;user_agent&quot;: &quot;Bolts iOS 1.1&quot;,
&quot;version&quot;: &quot;1.0&quot;
}
</pre></div>
<p>al_applink_data用于数据传递,以android的intent为例,al:android:package和al:android:class分别标识app的包名以及activity的类名,al:android:url作为intent.setData的参数,al_applink_data的内容放在<a href="http://developer.android.com/reference/android/content/Intent.html#putExtra(java.lang.String, android.os.Bundle)">Bundle</a>数据中。</p>
<p>AppLink使用<a href="https://github.com/BoltsFramework">BoltsFramework</a>工具库中的<em>WebViewAppLinkResolver</em>来进行html的解析,WebViewAppLinkResolver需先从网络中下载到完整的html文本再进行元标签的解析。</p>
<p>AppLink的目标很高大上,然后在国内没什么卵用, 做为Applink的发起者,facebook的app装机量巨大且开放,第三方app愿意进入其生态圈。而国内的app霸主微信,正忙着为自家的各个产品撕逼,封锁竞争对手的app跳转。可想到的场景只剩有多个产品线的几个大厂,利用AppLink进行自家兄弟应用的跳转。</p>
<p>做为小型app的开发者,与其期待facebook的AppLink规范在国内得到普及,不如等待Android M的AppLink可以尽快完善。</p>
<h3>DeepLinkDispatch</h3>
<p>AppLink无卵用,但我们将AppLink结合其他框架。去年info的架构师大会中,淘宝提到了<a href="http://www.infoq.com/cn/presentations/taobao-mobile-client-architecture-road">UI总线</a>的概念,使用跨平台统一的URL来进行Web、Android、IOS的寻址。在android平台上,当本地没有activity来匹配URL时,自动降级成WebView加载。</p>
<p>DeepLinkDispatch是使用url的跳转框架,activity可以通过注解来描述activity的资源定位符,参考一个DeepLinkDispatch的一个示例:</p>
<div class="highlight"><pre><span></span>@DeepLink(&quot;airbnb://example.com/deepLink/{id}&quot;)
public class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (getIntent().getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) {
showToast(&quot;secondactivity&quot;);
}
}
...
}
</pre></div>
<p>DeepLink注解表示MainActivity接受airbnb://example.com/deepLink/{id}规则的跳转。 在编译期,所有activity的DeepLinks注解都会被解析生成实际的跳转规则,生成在<em>DeepLinkActivity</em>类中,DeepLinkActivity是一个预先声明在manifest中的不可见activity,
DeepLinkActivity.onCreate():</p>
<div class="highlight"><pre><span></span> <span class="err">@</span><span class="nx">Override</span>
<span class="kr">protected</span> <span class="k">void</span> <span class="nx">onCreate</span><span class="p">(</span><span class="nx">Bundle</span> <span class="nx">savedInstanceState</span><span class="p">)</span> <span class="p">{</span>
<span class="kr">super</span><span class="p">.</span><span class="nx">onCreate</span><span class="p">(</span><span class="nx">savedInstanceState</span><span class="p">);</span>
<span class="nx">Loader</span> <span class="nx">loader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">com</span><span class="p">.</span><span class="nx">airbnb</span><span class="p">.</span><span class="nx">deeplinkdispatch</span><span class="p">.</span><span class="nx">DeepLinkLoader</span><span class="p">();</span>
<span class="nx">DeepLinkRegistry</span> <span class="nx">registry</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DeepLinkRegistry</span><span class="p">(</span><span class="nx">loader</span><span class="p">);</span>
<span class="nx">Uri</span> <span class="nx">uri</span> <span class="o">=</span> <span class="nx">getIntent</span><span class="p">().</span><span class="nx">getData</span><span class="p">();</span>
<span class="nb">String</span> <span class="nx">uriString</span> <span class="o">=</span> <span class="nx">uri</span><span class="p">.</span><span class="nx">toString</span><span class="p">();</span>
<span class="nx">DeepLinkEntry</span> <span class="nx">entry</span> <span class="o">=</span> <span class="nx">registry</span><span class="p">.</span><span class="nx">parseUri</span><span class="p">(</span><span class="nx">uriString</span><span class="p">);</span>
<span class="p">...</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">Class</span><span class="cp">&lt;?</span><span class="o">&gt;</span> <span class="nx">c</span> <span class="o">=</span> <span class="nx">entry</span><span class="o">.</span><span class="nx">getActivityClass</span><span class="p">();</span>
<span class="nx">Intent</span> <span class="nx">intent</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">entry</span><span class="o">.</span><span class="nx">getType</span><span class="p">()</span> <span class="o">==</span> <span class="nx">DeepLinkEntry</span><span class="o">.</span><span class="nx">Type</span><span class="o">.</span><span class="nx">CLASS</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">intent</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Intent</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">c</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">Method</span> <span class="nx">method</span> <span class="o">=</span> <span class="nx">c</span><span class="o">.</span><span class="nx">getMethod</span><span class="p">(</span><span class="nx">entry</span><span class="o">.</span><span class="nx">getMethod</span><span class="p">(),</span> <span class="nx">Context</span><span class="o">.</span><span class="nx">class</span><span class="p">);</span>
<span class="nx">intent</span> <span class="o">=</span> <span class="p">(</span><span class="nx">Intent</span><span class="p">)</span> <span class="nx">method</span><span class="o">.</span><span class="nx">invoke</span><span class="p">(</span><span class="nx">c</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intent</span><span class="o">.</span><span class="nx">getAction</span><span class="p">()</span> <span class="o">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">intent</span><span class="o">.</span><span class="nx">setAction</span><span class="p">(</span><span class="nx">getIntent</span><span class="p">()</span><span class="o">.</span><span class="nx">getAction</span><span class="p">());</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intent</span><span class="o">.</span><span class="nx">getData</span><span class="p">()</span> <span class="o">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">intent</span><span class="o">.</span><span class="nx">setData</span><span class="p">(</span><span class="nx">getIntent</span><span class="p">()</span><span class="o">.</span><span class="nx">getData</span><span class="p">());</span>
<span class="p">}</span>
<span class="nx">Bundle</span> <span class="nx">parameters</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">getIntent</span><span class="p">()</span><span class="o">.</span><span class="nx">getExtras</span><span class="p">()</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">parameters</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Bundle</span><span class="p">(</span><span class="nx">getIntent</span><span class="p">()</span><span class="o">.</span><span class="nx">getExtras</span><span class="p">());</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">parameters</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Bundle</span><span class="p">();</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="nx">intent</span><span class="o">.</span><span class="nx">putExtras</span><span class="p">(</span><span class="nx">parameters</span><span class="p">);</span>
<span class="nx">intent</span><span class="o">.</span><span class="nx">putExtra</span><span class="p">(</span><span class="nx">DeepLink</span><span class="o">.</span><span class="nx">IS_DEEP_LINK</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nx">startActivity</span><span class="p">(</span><span class="nx">intent</span><span class="p">);</span>
<span class="nx">notifyListener</span><span class="p">(</span><span class="k">false</span><span class="p">,</span> <span class="nx">uri</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">NoSuchMethodException</span> <span class="nx">exception</span><span class="p">)</span> <span class="p">{</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
<span class="nx">finish</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>DeepLinkActivity作为url的统一入口以及路由,定位并启动目标activity,之后立即销毁。</p>
<p>在DeepLinkDispatch下,启动MainActivity的代码就变成了:</p>
<div class="highlight"><pre><span></span> <span class="nt">Intent</span> <span class="nt">intent</span> <span class="o">=</span> <span class="nt">new</span> <span class="nt">Intent</span><span class="o">();</span>
<span class="nt">intent</span><span class="nc">.setData</span><span class="o">(</span><span class="nt">Uri</span><span class="nc">.parse</span><span class="o">(</span><span class="s2">&quot;airbnb://example.com/deepLink/1&quot;</span><span class="o">));</span>
<span class="nt">intent</span><span class="nc">.setAction</span><span class="o">(</span><span class="nt">Intent</span><span class="nc">.ACTION_VIEW</span><span class="o">);</span>
<span class="nt">startActivity</span><span class="o">(</span><span class="nt">intent</span><span class="o">);</span>
</pre></div>
<p>回到UI总线的问题,在DeepLinkDispatch的基础上,我们只需要为DeepLinkActivity做一些简单的改造。在DeepLinkActivity中加上activity启动失败后的工作流,即可完成降级跳转webview的逻辑。</p>
<h3>App跳转</h3>
<p>解决完nativie跳转webview的问题,我们回头看一下facebook的AppLink规范,惊喜的发现它与DeepLinkActivity意外的搭配,AppLink中需要为跳转指定al:android:class,一旦APK的包结构发生了调整,已经发布的网页需做一次重新部署,引入DeepLinkDispatch后,al:android:class的入口统一成了DeepLinkActivity,由DeepLinkDispatch进行二次寻址。额外还带来一个好处,app内嵌webview经常会有拦截自定义url跳转的需求,为了运营活动,不得不为某个url拦截专门发一个版本,采用UI总线的设计,此类需求可以通过动态调整网页AppLink完成。</p>
<h3>最后</h3>
<p>AppLink以及DeepLinkDispatch各自还是有一些缺陷,比如AppLink的BoltsFramework不能和WebView很好的结合,导致html下载多次。App跳转中若需要携带复杂的数据格式,DeepLinkDispatch的url会变得过于冗长。但UI总线的概念可以为native和webview之间的连接提供一些好的思路。</p></content><category term="android"></category><category term="applink"></category></entry><entry><title>booking.com android客户端的bitmap复用</title><link href="http://blog.aaapei.com/article/2015/02/bookingcom-androidke-hu-duan-de-bitmapfu-yong" rel="alternate"></link><published>2015-02-19T00:00:00+08:00</published><updated>2015-02-19T00:00:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2015-02-19:/article/2015/02/bookingcom-androidke-hu-duan-de-bitmapfu-yong</id><summary type="html"><h3>前言</h3>
<p>被鞭炮吵得睡不着觉,rss中找一篇简单的文档翻译下,原文链接:http://blog.booking.com/android-reuse-bitmaps.html,大部分团队应该都做过这个bitmap优化,不过估计设置过BitmapFactory.Options.inTempStorage参数的应该不多 :)</p>
<p>booking.com android客户端在新版本的增加了一个新功能:酒店的图片集合</p>
<p><img alt="此处输入图片的描述" src="http://blog.booking.com/static/android-reuse-bitmaps/hotel_photo_header.png"></p>
<p>不幸的是,增加了这个新功能后,发现这个应用的内存消耗增长了20%。图片集的界面的滑动有明显的卡顿,经定位,我们发现viewpager加载图片时的gc问题造成了以上的问题。由于应用的图片资源多;控件布局层次复杂;数据量较大,造成内存的申请很容易触发GC。</p>
<p>当申请bitmap内存时,logcat输出信息如下:</p>
<div class="highlight"><pre><span></span>GC_FOR_ALLOC freed 3255K, 20% free 21813K/26980K, paused 62ms, total 62ms
GC_FOR_ALLOC freed 710K, 20% free 30242K/37740K, paused 72ms …</pre></div></summary><content type="html"><h3>前言</h3>
<p>被鞭炮吵得睡不着觉,rss中找一篇简单的文档翻译下,原文链接:http://blog.booking.com/android-reuse-bitmaps.html,大部分团队应该都做过这个bitmap优化,不过估计设置过BitmapFactory.Options.inTempStorage参数的应该不多 :)</p>
<p>booking.com android客户端在新版本的增加了一个新功能:酒店的图片集合</p>
<p><img alt="此处输入图片的描述" src="http://blog.booking.com/static/android-reuse-bitmaps/hotel_photo_header.png"></p>
<p>不幸的是,增加了这个新功能后,发现这个应用的内存消耗增长了20%。图片集的界面的滑动有明显的卡顿,经定位,我们发现viewpager加载图片时的gc问题造成了以上的问题。由于应用的图片资源多;控件布局层次复杂;数据量较大,造成内存的申请很容易触发GC。</p>
<p>当申请bitmap内存时,logcat输出信息如下:</p>
<div class="highlight"><pre><span></span>GC_FOR_ALLOC freed 3255K, 20% free 21813K/26980K, paused 62ms, total 62ms
GC_FOR_ALLOC freed 710K, 20% free 30242K/37740K, paused 72ms, total 72ms
GC_FOR_ALLOC freed &lt;1K, 20% free 31778K/39280K, paused 74ms, total 74ms
</pre></div>
<p>通过日志信息可以知道,一个bitmap图片的申请,造成应用约70ms的gc停顿,导致应用程序掉5次左右的帧。为了保证应用的流畅体验,必须保证gc停顿的时间降到16ms以下。
我们决定利用<a href="https://developer.android.com/training/displaying-bitmaps/manage-memory.html#inBitmap">inBitmap</a>参数进行图片资源的复用,不过这个参数必须保证图片的大小一致;幸好,在android4.4之后,二次复用的图片不需要严格遵守这个规则,只需保证不比原图片资源大即可。</p>
<p>基于这个api,我们在viewpager adapter中抽象了一个图片池管理图片复用。当一个imageview移除屏幕以外时,apater管理bitmap的生命周期,将其关联的bitmap buffer内存放置到图片池中而不是直接销毁。</p>
<h3>bitmap的生命周期管理</h3>
<p>为了管理bitmap内存,需要为bitmap进行引用计数,引用技术的接口是这样的;
<img alt="此处输入图片的描述" src="http://blog.booking.com/static/android-reuse-bitmaps/bitmap_pool_uml.png"></p>
<div class="highlight"><pre><span></span><span class="n">package</span> <span class="n">com</span><span class="o">.</span><span class="n">booking</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">bitmap</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">android.graphics.Bitmap</span><span class="p">;</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">A</span> <span class="n">reference</span><span class="o">-</span><span class="n">counted</span> <span class="n">Bitmap</span> <span class="nb">object</span><span class="o">.</span> <span class="n">The</span> <span class="n">Bitmap</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">really</span> <span class="n">recycled</span>
<span class="o">*</span> <span class="n">until</span> <span class="n">the</span> <span class="n">reference</span> <span class="n">counter</span> <span class="n">drops</span> <span class="n">to</span> <span class="n">zero</span><span class="o">.</span>
<span class="o">*/</span>
<span class="n">public</span> <span class="n">interface</span> <span class="n">IManagedBitmap</span> <span class="p">{</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">Get</span> <span class="n">the</span> <span class="n">underlying</span> <span class="p">{</span><span class="nd">@link</span> <span class="n">Bitmap</span><span class="p">}</span> <span class="nb">object</span><span class="o">.</span>
<span class="o">*</span> <span class="n">NEVER</span> <span class="n">call</span> <span class="n">Bitmap</span><span class="o">.</span><span class="n">recycle</span><span class="p">()</span> <span class="n">on</span> <span class="n">this</span> <span class="nb">object</span><span class="o">.</span>
<span class="o">*/</span>
<span class="n">Bitmap</span> <span class="n">getBitmap</span><span class="p">();</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">Decrease</span> <span class="n">the</span> <span class="n">reference</span> <span class="n">counter</span> <span class="ow">and</span> <span class="n">recycle</span> <span class="n">the</span> <span class="n">underlying</span> <span class="n">Bitmap</span>
<span class="o">*</span> <span class="k">if</span> <span class="n">there</span> <span class="n">are</span> <span class="n">no</span> <span class="n">more</span> <span class="n">references</span><span class="o">.</span>
<span class="o">*/</span>
<span class="n">void</span> <span class="n">recycle</span><span class="p">();</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">Increase</span> <span class="n">the</span> <span class="n">reference</span> <span class="n">counter</span><span class="o">.</span>
<span class="o">*</span> <span class="nd">@return</span> <span class="bp">self</span>
<span class="o">*/</span>
<span class="n">IManagedBitmap</span> <span class="n">retain</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<p>其中bitmappool类管理bitmap集合,当不存在bitmap内存时,或新申请,或复用已有内存。
bitmapPool类不直接引用bitmap,而通过IManagedBitmap进行bitmap的引用计数。</p>
<p>由于我们只在主线程进行imageview的创建和销毁,我们被未对BitmapPool进行线程安全同步,如果你需要在后台线程申请位图资源,请自行进行线程同步。</p>
<p>BitmapPool的代码片段是这样的:</p>
<div class="highlight"><pre><span></span><span class="n">package</span> <span class="n">com</span><span class="o">.</span><span class="n">booking</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">bitmap</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">java.util.Stack</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">android.graphics.Bitmap</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">android.os.Handler</span><span class="p">;</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">A</span> <span class="n">pool</span> <span class="n">of</span> <span class="n">fixed</span><span class="o">-</span><span class="n">size</span> <span class="n">Bitmaps</span><span class="o">.</span> <span class="n">Leases</span> <span class="n">a</span> <span class="n">managed</span> <span class="n">Bitmap</span> <span class="nb">object</span>
<span class="o">*</span> <span class="n">which</span> <span class="ow">is</span> <span class="n">tied</span> <span class="n">to</span> <span class="n">this</span> <span class="n">pool</span><span class="o">.</span> <span class="n">Bitmaps</span> <span class="n">are</span> <span class="n">put</span> <span class="n">back</span> <span class="n">to</span> <span class="n">the</span> <span class="n">pool</span>
<span class="o">*</span> <span class="n">instead</span> <span class="n">of</span> <span class="n">actual</span> <span class="n">recycling</span><span class="o">.</span>
<span class="o">*</span>
<span class="o">*</span> <span class="n">WARNING</span><span class="p">:</span> <span class="n">This</span> <span class="k">class</span> <span class="nc">is</span> <span class="n">NOT</span> <span class="n">thread</span> <span class="n">safe</span><span class="p">,</span> <span class="n">intended</span> <span class="k">for</span> <span class="n">use</span>
<span class="o">*</span> <span class="kn">from</span> <span class="nn">the</span> <span class="nn">main</span> <span class="nn">thread</span> <span class="nn">only.</span>
<span class="o">*/</span>
<span class="n">public</span> <span class="k">class</span> <span class="nc">BitmapPool</span> <span class="p">{</span>
<span class="n">private</span> <span class="n">final</span> <span class="nb">int</span> <span class="n">width</span><span class="p">;</span>
<span class="n">private</span> <span class="n">final</span> <span class="nb">int</span> <span class="n">height</span><span class="p">;</span>
<span class="n">private</span> <span class="n">final</span> <span class="n">Bitmap</span><span class="o">.</span><span class="n">Config</span> <span class="n">config</span><span class="p">;</span>
<span class="n">private</span> <span class="n">final</span> <span class="n">Stack</span><span class="o">&lt;</span><span class="n">Bitmap</span><span class="o">&gt;</span> <span class="n">bitmaps</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Stack</span><span class="o">&lt;</span><span class="n">Bitmap</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">private</span> <span class="n">boolean</span> <span class="n">isRecycled</span><span class="p">;</span>
<span class="n">private</span> <span class="n">final</span> <span class="n">Handler</span> <span class="n">handler</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Handler</span><span class="p">();</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">Construct</span> <span class="n">a</span> <span class="n">Bitmap</span> <span class="n">pool</span> <span class="k">with</span> <span class="n">desired</span> <span class="n">Bitmap</span> <span class="n">parameters</span>
<span class="o">*/</span>
<span class="n">public</span> <span class="n">BitmapPool</span><span class="p">(</span><span class="nb">int</span> <span class="n">bitmapWidth</span><span class="p">,</span>
<span class="nb">int</span> <span class="n">bitmapHeight</span><span class="p">,</span>
<span class="n">Bitmap</span><span class="o">.</span><span class="n">Config</span> <span class="n">config</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">this</span><span class="o">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">bitmapWidth</span><span class="p">;</span>
<span class="n">this</span><span class="o">.</span><span class="n">height</span> <span class="o">=</span> <span class="n">bitmapHeight</span><span class="p">;</span>
<span class="n">this</span><span class="o">.</span><span class="n">config</span> <span class="o">=</span> <span class="n">config</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">Destroy</span> <span class="n">the</span> <span class="n">pool</span><span class="o">.</span> <span class="n">Any</span> <span class="n">leased</span> <span class="n">IManagedBitmap</span> <span class="n">items</span> <span class="n">remain</span> <span class="n">valid</span>
<span class="o">*</span> <span class="n">until</span> <span class="n">they</span> <span class="n">are</span> <span class="n">recycled</span><span class="o">.</span>
<span class="o">*/</span>
<span class="n">public</span> <span class="n">void</span> <span class="n">recycle</span><span class="p">()</span> <span class="p">{</span>
<span class="n">isRecycled</span> <span class="o">=</span> <span class="n">true</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">Bitmap</span> <span class="n">bitmap</span> <span class="p">:</span> <span class="n">bitmaps</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bitmap</span><span class="o">.</span><span class="n">recycle</span><span class="p">();</span>
<span class="p">}</span>
<span class="n">bitmaps</span><span class="o">.</span><span class="n">clear</span><span class="p">();</span>
<span class="p">}</span>
<span class="o">/**</span>
<span class="o">*</span> <span class="n">Get</span> <span class="n">a</span> <span class="n">Bitmap</span> <span class="kn">from</span> <span class="nn">the</span> <span class="nn">pool</span> <span class="nn">or</span> <span class="nn">create</span> <span class="nn">a</span> <span class="nn">new</span> <span class="nn">one.</span>
<span class="o">*</span> <span class="nd">@return</span> <span class="n">a</span> <span class="n">managed</span> <span class="n">Bitmap</span> <span class="n">tied</span> <span class="n">to</span> <span class="n">this</span> <span class="n">pool</span>
<span class="o">*/</span>
<span class="n">public</span> <span class="n">IManagedBitmap</span> <span class="n">getBitmap</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">new</span> <span class="n">LeasedBitmap</span><span class="p">(</span><span class="n">bitmaps</span><span class="o">.</span><span class="n">isEmpty</span><span class="p">()</span>
<span class="err">?</span> <span class="n">Bitmap</span><span class="o">.</span><span class="n">createBitmap</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span> <span class="p">:</span> <span class="n">bitmaps</span><span class="o">.</span><span class="n">pop</span><span class="p">());</span>
<span class="p">}</span>
<span class="n">private</span> <span class="k">class</span> <span class="nc">LeasedBitmap</span> <span class="n">implements</span> <span class="n">IManagedBitmap</span> <span class="p">{</span>
<span class="n">private</span> <span class="nb">int</span> <span class="n">referenceCounter</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">private</span> <span class="n">final</span> <span class="n">Bitmap</span> <span class="n">bitmap</span><span class="p">;</span>
<span class="n">private</span> <span class="n">LeasedBitmap</span><span class="p">(</span><span class="n">Bitmap</span> <span class="n">bitmap</span><span class="p">)</span> <span class="p">{</span>
<span class="n">this</span><span class="o">.</span><span class="n">bitmap</span> <span class="o">=</span> <span class="n">bitmap</span><span class="p">;</span>
<span class="p">}</span>
<span class="nd">@Override</span>
<span class="n">public</span> <span class="n">Bitmap</span> <span class="n">getBitmap</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">bitmap</span><span class="p">;</span>
<span class="p">}</span>
<span class="nd">@Override</span>
<span class="n">public</span> <span class="n">void</span> <span class="n">recycle</span><span class="p">()</span> <span class="p">{</span>
<span class="n">handler</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">new</span> <span class="n">Runnable</span><span class="p">()</span> <span class="p">{</span>
<span class="nd">@Override</span>
<span class="n">public</span> <span class="n">void</span> <span class="n">run</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">--</span><span class="n">referenceCounter</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">isRecycled</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bitmap</span><span class="o">.</span><span class="n">recycle</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">bitmaps</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="n">bitmap</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nd">@Override</span>
<span class="n">public</span> <span class="n">IManagedBitmap</span> <span class="n">retain</span><span class="p">()</span> <span class="p">{</span>
<span class="o">++</span><span class="n">referenceCounter</span><span class="p">;</span>
<span class="k">return</span> <span class="n">this</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h3>网络层</h3>
<p>Booking.com客户端的网络通信层使用Vollery框架,默认情况下,Volley通过ImageRequest进行网络图片的bitmap的获取,为了和ImagePool集成,我们实现了一个自定义的ImageRequest(ReusableImageRequest),ReusableImageRequest内部持有一个IManagedBitmap进行bitmap的解码。
为了避免内存泄漏,当ReusableImageRequest被取消时,需要有机制通知IManagedBitmap进行引用释放,因此,我们为ReusableImageRequest扩展了一个onFinished方法。</p>
<p>与volley的结构图是这样的:</p>
<p><img alt="此处输入图片的描述" src="http://blog.booking.com/static/android-reuse-bitmaps/volley_uml.png"></p>
<h3>其他工作</h3>
<p>当我们实现了一个自定义的ImageRequest时,我们还利用
<a href="http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inTempStorage">BitmapFactory.Options.inTempStorage</a>. 参数进行了图片解码的优化。inTempStorege可以预申请一块内存,对所有解码过程中指定相同的内存,以达到减少临时内存的目的。</p></content><category term="bitmap,性能优化"></category></entry><entry><title>facebook新闻页ListView优化</title><link href="http://blog.aaapei.com/article/2015/02/facebookxin-wen-ye-listviewyou-hua" rel="alternate"></link><published>2015-02-01T00:00:00+08:00</published><updated>2015-02-01T00:00:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2015-02-01:/article/2015/02/facebookxin-wen-ye-listviewyou-hua</id><summary type="html"><h3>引言</h3>
<p>原文链接:<a href="https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/">https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/</a>
透漏的信息量不大,且大多数项目并不会遇到facebook这种ListView的场景,不过可以拓展下思路:逻辑单元不一定是视图单元;移动端不要死搬MVC的架构,在市场上仍是中低端机型为主时,还是应该多考虑性能;附上rebbit的关于本文的<a href="http://www.reddit.com/r/androiddev/comments/2tzrqe/fast_rendering_news_feed_on_android/">讨论</a>,有些干货 :)</p>
<h3>基础知识</h3>
<p>android系统每隔16.7ms发出一个渲染信号,通知ui线程进行界面的渲染。为了达到流畅的体验,应用程序需要在这个时间内完成应用逻辑,使系统达到60fps。当一个Listview被添加到布局时,其关联的adapter的getView方法将会被回调。在16.7毫秒这样一个时间单元内,可见listitem单元的getView方法将被按照顺序执行。在大多数情况下,由于其他绘图行为的存在,例如measure和draw,getVIew实际分配到执行时间远低于16ms。一旦listview包含复杂控件时,在16毫秒内不能完成渲染,用户只能看到上一祯的结果,这时就发生了掉帧。</p>
<h3>Facebook新闻页介绍</h3>
<p>Facebook的新闻页是一个复杂的listview控件,如何使它获得流畅的滚动体验一直困扰我们。
首先,新闻页的每一条新闻的可见区域非常大,包含一系列的文本以及照片;其次,新闻的展现类型也很多样,除了文本以及照片,新闻的附件还可包含链接、音频 …</p></summary><content type="html"><h3>引言</h3>
<p>原文链接:<a href="https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/">https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/</a>
透漏的信息量不大,且大多数项目并不会遇到facebook这种ListView的场景,不过可以拓展下思路:逻辑单元不一定是视图单元;移动端不要死搬MVC的架构,在市场上仍是中低端机型为主时,还是应该多考虑性能;附上rebbit的关于本文的<a href="http://www.reddit.com/r/androiddev/comments/2tzrqe/fast_rendering_news_feed_on_android/">讨论</a>,有些干货 :)</p>
<h3>基础知识</h3>
<p>android系统每隔16.7ms发出一个渲染信号,通知ui线程进行界面的渲染。为了达到流畅的体验,应用程序需要在这个时间内完成应用逻辑,使系统达到60fps。当一个Listview被添加到布局时,其关联的adapter的getView方法将会被回调。在16.7毫秒这样一个时间单元内,可见listitem单元的getView方法将被按照顺序执行。在大多数情况下,由于其他绘图行为的存在,例如measure和draw,getVIew实际分配到执行时间远低于16ms。一旦listview包含复杂控件时,在16毫秒内不能完成渲染,用户只能看到上一祯的结果,这时就发生了掉帧。</p>
<h3>Facebook新闻页介绍</h3>
<p>Facebook的新闻页是一个复杂的listview控件,如何使它获得流畅的滚动体验一直困扰我们。
首先,新闻页的每一条新闻的可见区域非常大,包含一系列的文本以及照片;其次,新闻的展现类型也很多样,除了文本以及照片,新闻的附件还可包含链接、音频、视频等。除此之外,新闻还可以被点赞、被转载,导致一个新闻会被其他新闻包含在内。当新闻被大量用户转载时,甚至会出现一条新闻占据两个屏幕的情况。加上android用户的机型多为中低端设备,这使我们在16.7ms内完成新闻页的渲染变的非常困难。</p>
<p><img alt="" src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xpa1/t39.2365-6/10935993_1534797460105141_1373600061_n.png"></p>
<h3>新闻页最初架构</h3>
<p>在2012年,我们将新闻页从web-view转化成本地控件,在最初的那个版本中,基于View-Model-Binder设计模型,我们为新闻listitem创建了一个自定义StoryView类,这个类有一个bindModel方法,该方法用于和数据进行绑定。代码是这样的:</p>
<p><img alt="" src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xap1/t39.2365-6/10935990_1412843022342801_1297058886_n.png">
StoryView的包含的子控件都会有一个bindModel方法,例如HeadVIew通过该方法与其相关的数据进行绑定。 </p>
<p>这种设计,代码非常直观清晰,但他的缺点也很明显:</p>
<ul>
<li>
<p>listview复用机制不能有效的工作</p>
</li>
<li>
<p>逻辑嵌套:采用bindModel绑定控件和数据,业务逻辑与视图逻辑耦合,导致逻辑类层次非常深;</p>
</li>
<li>
<p>布局嵌套非常深:不但导致低效的视图渲染,例如新闻被不停的转载的极端场景下还会导致栈溢出;</p>
</li>
<li>
<p>bindModel方法逻辑过重:bindModel方法在当用户滚动列表时被ui线程回调,由于所有的数据解析都在这个方法内,导致该方法耗时</p>
</li>
</ul>
<p>以上这些问题虽有他们单独的解决方法,例如我们可以自己设计一套回收机制解决storyView复用问题。但基于维护成本和开发时间考虑,我们决定进行一次重构。</p>
<h3>重构方案</h3>
<p>重构工作大约是一年之前开始的,为了解决前一个架构的问题,首先我们决定将一条新闻分隔成多个listview item。例如,新闻的headerview将是一个独立的listitem。这样,我们可以利用android回收机制,HeaderView新闻子控件将被不同的新闻复用。另外,切分成小view也使得内存占用更小,在之前的架构中,Storyview部分的可见会导致这个Storyview被加载到内存中,而现在,粒度更小,只有可见的子控件才会被加载。</p>
<p><img alt="" src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xap1/t39.2365-6/10935983_984256741587871_980206636_n.png"></p>
<p>另一个大的修改是,我们将视图逻辑和数据逻辑分离,StoryView被分离成两个类:
只负责展现的视图类,以及一个Binder类。视图类仅包含set方法(例如HeaderView包含了setTitle,setSubTitle。setProfiePic等等)。Binder类包含了原来的bindMethod的逻辑,binder类包含三个方法:prepare,bind,unbind。
bind方法调用view的set方法设置数据,unbind清理视图数据,prepare方法在cpu空闲期间做一些预初始化工作,例如进行click事件绑定、数据格式化、创建spannable等等,它会在getView方法之前被调用
<img alt="" src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfp1/t39.2365-6/10956894_918624611495337_1619622974_n.png"></p>
<p>我们遇到的技术难点是Binder的设计,由于StoryView被拆分不同的子控件,一条新闻可能会包含多个不同的Binder。而在之前,我们只需要根据视图的树结构进行结构化赋值。因此,我们引进了<em>PartDefinition</em>类,PartDefinition负责维护一条新闻包含哪些子控件、包含Binder的类型以及为新闻创建Binder类,有两种类型的PartDefinition:单个PartDefinition以及PartDefinition集合。</p>
<p><img alt="" src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfa1/t39.2365-6/10935981_1536551233276267_1103658334_n.png"></p>
<p>一个新闻在重构之后的PartDefinition结构是这样的:</p>
<p><img alt="" src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xaf1/t39.2365-6/10935975_856616717694467_1297407005_n.png"></p>
<h3>结论</h3>
<ul>
<li>采取新的架构,内存错误减少了17%,总crash率减少了8%,彻底解决涨溢出问题</li>
<li>渲染时间减少了10%,大新闻场景不再掉帧</li>
<li>精简了原来的自定义回收机制,同时在重构过程中增加了单元测试</li>
</ul></content><category term="listview,性能优化"></category></entry><entry><title>android异步编程</title><link href="http://blog.aaapei.com/article/2014/12/androidyi-bu-bian-cheng" rel="alternate"></link><published>2014-12-21T00:00:00+08:00</published><updated>2014-12-21T00:00:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2014-12-21:/article/2014/12/androidyi-bu-bian-cheng</id><summary type="html"><p>在android开发中,有两条很重要的<a href="http://developer.android.com/guide/components/processes-and-threads.html">编程准则</a></p>
<ul>
<li>不要堵塞ui线程</li>
<li>不要在非ui线程操作ui控件</li>
</ul>
<p>开发者必须这两个遵守单线程模型的准则,将耗时的逻辑转移到非ui线程进行,得出计算结果后,通知ui线程进行数据的展现。本文介绍一下android的异步编程。</p>
<h3>android线程模型</h3>
<p>同hotspot vm一样,在daivlk vm中,采取的是1:1线程模型,每一个android thread对应一个Native Linux thread;linux内核通过cfs(completely fair scheduler)来进行线程调度,在cfs中着影响一个线程时间分配的因素有两个:</p>
<ul>
<li>thread priority</li>
<li>thread group</li>
</ul>
<h4>thread group</h4>
<p>线程的thread group是动态改变的,在android framework层面,android的应用有5个等级,分别是</p>
<ul>
<li>foreground process</li>
<li>visible process</li>
<li>service process</li>
<li>backgroud process</li>
<li>empty process </li>
</ul>
<p>它们的thread …</p></summary><content type="html"><p>在android开发中,有两条很重要的<a href="http://developer.android.com/guide/components/processes-and-threads.html">编程准则</a></p>
<ul>
<li>不要堵塞ui线程</li>
<li>不要在非ui线程操作ui控件</li>
</ul>
<p>开发者必须这两个遵守单线程模型的准则,将耗时的逻辑转移到非ui线程进行,得出计算结果后,通知ui线程进行数据的展现。本文介绍一下android的异步编程。</p>
<h3>android线程模型</h3>
<p>同hotspot vm一样,在daivlk vm中,采取的是1:1线程模型,每一个android thread对应一个Native Linux thread;linux内核通过cfs(completely fair scheduler)来进行线程调度,在cfs中着影响一个线程时间分配的因素有两个:</p>
<ul>
<li>thread priority</li>
<li>thread group</li>
</ul>
<h4>thread group</h4>
<p>线程的thread group是动态改变的,在android framework层面,android的应用有5个等级,分别是</p>
<ul>
<li>foreground process</li>
<li>visible process</li>
<li>service process</li>
<li>backgroud process</li>
<li>empty process </li>
</ul>
<p>它们的thread group如图,<br>
<img alt="Alt pic" src="http://nos.netease.com/knowledge/e2d1dc53-5724-4b1e-b93e-74325a0d70f5"> </p>
<p>在实际的分配中,系统会将90%的cpu时间分配给foregroud thread group,
如果某个应用处于foreground或visible level,那么它创建的所有thread都属于foreground group。
当应用的可见状态被改变时,例如按home健被切入到后台运用,应用从foregroud process变成了backgroud process,应用对应的的thread group也切换到了backgroud group</p>
<h4>thread priority</h4>
<p>线程的优先级通过Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置;</p>
<h3>线程分类</h3>
<p>在一个android应用中,存在三种类型的线程:</p>
<ul>
<li>UIThread,即工作主线程</li>
<li>
<p>Binder Thread:与其他进程进行binder通信的线程,通过一个线程池进行维护;
每个进程维护了一个线程池用于与其他进程的通信,binder thread隐藏在后台,开发者一般不需要关心;</p>
</li>
<li>
<p>backgroud thread,即后台线程;</p>
</li>
</ul>
<p>注意每一个后台线程都是UIThread的子线程,意味着他的线程优先级和ui thread是完全相同的,同时由于他们处在同一个thread group中,那么linux内核对ui线程和后台线程一视同仁;</p>
<p>dalvik有一个最大线程个数的限制,但不意味着应用可以随意生成低于这个限制值的线程个数;由于linux内核平等的对待ui线程和后台线程,一旦滥用后台,当ui线程不能抢占到足够的cpu时间片时,也抛出anr异常;另一方面,由于thread是gc mark的root,过多的线程也会对gc造成影响。</p>
<h3>Thread&amp;&amp;Executor</h3>
<p>Thread和Executor都是jdk原生的异步机制,不再赘述。在使用时,原生的Thread的存在两个缺陷: </p>
<ul>
<li>不可复用,线程的创建、销毁都需要时间开销以及空间开销</li>
<li>无原生的cancel机制,你需要额外的工作去管理的thread生命周期</li>
</ul>
<p>而Executor线程池,解决Thread不可复用,减少thread的重复创建。</p>
<h3>HandlerThread</h3>
<p>HandlerThread是Thread的子类,内部封装了Message/Looper,Message/Looper是一个顺序执行的队列,开发者可以利用这一特性进行异步逻辑之间的组合及交互,因此非常适合做一个状态机:最典型的例子即条码处理库<a href="https://github.com/zxing/zxing/">zxing</a>,摄像头扫描过程中每一个状态都通过Message同步给了ui线程;同样使用HandlerThread,另一个使用度非常高的<a href="https://github.com/loopj/android-async-http">android-async-http</a>则是一个典型的反例,async-http是一个异步网络库,它以callback作为数据协同的方式,导致的结果是代码充斥了不可读的callback嵌套callback。</p>
<h5>适用场景</h5>
<ul>
<li>需要一个长期运行的thread</li>
<li>顺序执行的消息loop<ul>
<li>避免多个按钮同时点击</li>
<li>状态机</li>
<li>细粒度的消息控制</li>
</ul>
</li>
</ul>
<h3>AsyncTask</h3>
<p>AsyncTask是android中最常用的并发机制,api非常简洁,开发者只需简单的继承AsyncTask即可完成异步逻辑以及数据协同,
数据的协同,内部通过Handler/Message进行,异步逻辑doInBackground则利用Executor完成。
必须要了解是,AysncTask是一个全局行为,在不同的组件中创建Asynctask,最终的执行都会在同一个Executor中:</p>
<p><img alt="Alt pic" src="http://nos.netease.com/knowledge/42dccf78-0a92-4d5f-9463-1e77174ca076"> </p>
<p>作为最常用的异步编程解决方案,AsyncTask也是被批评最多的异步机制,原因是其在不同android版本中的不同表现,
在1.6版本之前,AsyncTask的Executor是一个单线程,所有AsyncTaskd都是顺序执行;
在1.6到3.0版本中,Executor维护了一个最多线程池并发执行
android版本发展到3.0后,默认的线程修改成了单线程task,同时提供了一个executeOneExecutor api支持多个并行task,
但这不表示的在3.x版本之后,你的task是默认一定是顺序执行的,它还受taregetSdkVersion的影响,AcitivityThread.java中有这样一段代码</p>
<div class="highlight"><pre><span></span>// If the app is Honeycomb MR1 or earlier, switch its AsyncTask
// implementation to use the pool executor. Normally, we use the
// serialized executor as the default. This has to happen in the
// main thread so the main looper is set right.
if (data.appInfo.targetSdkVersion &lt;= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
</pre></div>
<p>如果targetSDKVersion低于3.2版本,AsyncTask是并行执行的,否则将会被顺序执行,考虑不同版本的表现,在使用AsyncTask时需要规避task之间有依赖执行顺序的逻辑;</p>
<p>另一个经常被批评的点是AsyncTask的全局行为,在组件中使用Asynctask时,经常会将他定义在一个匿名的内部类,这时候一个潜在的内存泄漏就产生了,由于Asynctask的生命周期可能比它关联的组件对象长,导致其关联的组件无法被回收,幸运的是,asynctask提供了cancel机制。在组件生命周期结束后及时调用cancel api,在asynctask未被执行时,可以将asynctask从队列去除;cancel api有一个mayInterruptIfRunning参数:cancel(true) == cancel(false)+interrpt</p>
<h3>IntentService</h3>
<p>IntentService为Service的子类,其内部实现包含了一个HandlerThread,它兼具了service/message looper/hanlder的优势,android官方文档将其列为后台任务的最佳实践(http://developer.android.com/training/best-background.html)</p>
<p>不同与Service,当一个intent被提交,系统会将intent提交到HandlerThread中顺序执行,而非在ui线程执行;
IntentService中工作队列不能被打断,必须等待所有的intent被处理完成之后,intentservcie自动关闭;官方建议通过broadcast机制来进行协同,避免产生耦合;</p>
<h3>Loader/AsyncQueryHandler</h3>
<p>将Loader和AsyncQueryHandler放在一起,主要是因为他们api适合做为数据加载的接口
区别在与Loader内部利用AsyncTask,AsyncQueryHandler使用ThreadHandler
Loader机制在api 11中引入,通过support包的方式支持已有版本,两者都支持:</p>
<ul>
<li>后台加载数据</li>
<li>数据改变时候的回调</li>
<li>生命周期自动管理</li>
</ul>
<h3>延伸阅读</h3>
<p>一些现有的解决方案 </p>
<ul>
<li>
<p><a href="https://github.com/ReactiveX/RxAndroid">rxandroid</a>:函数式响应框架,rxjava的android版本,主要贡献者是<em>JakeWharton</em>大神,提供了异步逻辑的组合、过滤,不过目前暂不适合在生产环境使用,建议关注</p>
</li>
<li>
<p><a href="http://androidannotations.org/">android anotation</a>:如名,基于java注解,提供了一个简化的线程模型,提供编程效率</p>
</li>
<li>
<p><a href="https://github.com/greenrobot/EventBus">EventBus</a>:基于生产/消费者模型的优雅实现</p>
</li>
</ul></content><category term="异步"></category><category term="android"></category></entry><entry><title>dalvik jni解析</title><link href="http://blog.aaapei.com/article/2014/11/dalvik-jnijie-xi" rel="alternate"></link><published>2014-11-09T00:00:00+08:00</published><updated>2014-11-09T00:00:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2014-11-09:/article/2014/11/dalvik-jnijie-xi</id><summary type="html"><p>android开发者在移植第三方native库时,经常会使用到jni. <br>
Java Native Interface(java本地接口),用于java与c/c++代码进行交互.需要了解的是,jni只是jvm的一个规范,各家虚拟机有各自的实现,本文分享一下dalvik中jobject reference的实现.</p>
<h3>jobject reference type</h3>
<p>jni规范中,对jobject定义了两种reference类型:</p>
<ul>
<li>全局引用(global reference)</li>
<li>局部引用(local reference)</li>
</ul>
<p>在JNI Specification中的说明是:</p>
<blockquote>
<p>Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references …</p></blockquote></summary><content type="html"><p>android开发者在移植第三方native库时,经常会使用到jni. <br>
Java Native Interface(java本地接口),用于java与c/c++代码进行交互.需要了解的是,jni只是jvm的一个规范,各家虚拟机有各自的实现,本文分享一下dalvik中jobject reference的实现.</p>
<h3>jobject reference type</h3>
<p>jni规范中,对jobject定义了两种reference类型:</p>
<ul>
<li>全局引用(global reference)</li>
<li>局部引用(local reference)</li>
</ul>
<p>在JNI Specification中的说明是:</p>
<blockquote>
<p>Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references remain valid until they are explicitly freed </p>
</blockquote>
<p>局部引用的生命周期只在当前native函数上下文中有效;全局引用则虚拟机全局生效,直置开发者显式释放;
dalvik额外定义了weak global reference,弱全局引用的<em>weak</em>与java层面中weak reference概念一致:在gc时,会被自动回收;
android4.0版本发布之后,android官方博客有这样一篇文章<a href="http://android-developers.blogspot.com/2011/11/jni-local-reference-changes-in-ics.html">jni local reference changes in ics</a></p>
<blockquote>
<p>The best garbage collectors move objects around. This lets them offer very cheap allocation and bulk deallocation, avoids heap fragmentation, and may improve locality. Moving objects around is a problem if you’ve handed out pointers to them to native code. JNI uses types such as jobject to solve this problem: rather than handing out direct pointers, you’re given an opaque handle that can be traded in for a pointer when necessary. By using handles, when the garbage collector moves an object, it just has to update the handle table to point to the object’s new location. </p>
</blockquote>
<p>意思是说,为了避免内存碎片,虚拟机在gc过程中应该对堆对象进行内存整理,将堆中分散的指针压缩集中在一处;在之前的版本中,jobject是一个直接指针,直接指向了堆中的内存地址,因此无法采用压缩算法;又由于jobject是一个直接指针,之前的版本也未能实现<em>GetObjectRefType</em>接口;所以,我们现在采用对象句柄间接指向堆对像,解决了以上的问题;<br>
那么问题来了,何为对象句柄? 对象句柄又如何区分reference类型?</p>
<h3>IndirectRefTable</h3>
<p>首先明确几个类型的定义:</p>
<div class="highlight"><pre><span></span>typedef void* jobject;
</pre></div>
<p>jobject是一个void指针,在arm下,长32位</p>
<div class="highlight"><pre><span></span>typedef uint16_t u2;
typedef uint32_t u4;
</pre></div>
<p>dalvik虚拟机几个内部类型的定义,u4代表无符号32位</p>
<blockquote>
<p>By using handles, when the garbage collector moves an object, it just has to update the handle table to point to the object’s new location. </p>
</blockquote>
<p>官方博客的文章中提到的handle table,对应到dalvik的实现,就是<em>IndirectRefTable</em><br>
虚拟机维护了一个全局引用表jniGlobalRefTable,为每一个线程都维护一个局部引用表jniLocalRefTable,两者采用此相同的数据结构.IndirectRefTable的表元素称之为<em>IndirectRefSlot</em> </p>
<div class="highlight"><pre><span></span>struct IndirectRefSlot {
Object* obj; /* object pointer itself, NULL if the slot is unused */
u4 serial; /* slot serial number */
};
</pre></div>
<p>IndirectRefSlot只有两个字段,obj即直接对象指针,serial是一个自增的无符号数,主要用于校验对比;<br>
jni.h中将jobject定义成void指针,在虚拟机内部,jobject被表示为<em>IndirectRef</em>,仍然是一个void指针,不过虚拟机并不关心其类型,只需要明确的是,void指针有32位长;</p>
<div class="highlight"><pre><span></span>typedef void* IndirectRef;
</pre></div>
<p>基于以上几个定义,获取一个jobject的流程可以转换成:构造一个IndirectRefSlot对象,添加到IndirectTable表中,获取索引值,并最终返回IndirectRef指针.<br>
索引转换成IndirectRef的函数实现如下:</p>
<div class="highlight"><pre><span></span> static inline IndirectRef toIndirectRef(u4 index, u4 serial, IndirectRefKind kind) {
return reinterpret_cast&lt;IndirectRef&gt;((serial &lt;&lt; 20) | (index &lt;&lt; 2) | kind);
}
</pre></div>
<p>IndirectRef由3部分组成: </p>
<ul>
<li>IndirectRefKind 即global/local/weak global,占最后2位;</li>
<li>index索引 占18位;</li>
<li>serial 校验位</li>
</ul>
<p>通过这样一个位移运算,对象类型和对象表索引放到一个32位指针中;<br>
当获取一个jobject对象类型时,可以直接判断jobject最后两位,但当操作jobject对象时,则需要额外的寻址操作,句柄的装包解包其实也是jni效率低下的原因之一.<br>
有表的添加,自然有表对象的移除,当在IndirectRefTable表中删除一个jni引用时候,会将对应IndirectRefSlot的obj对象置为null,二次添加时,优先添加到空位IndirectRefSlot.</p>
<h3>IndirectRefTable的压栈出栈</h3>
<p>java函数分为普通函数和本地函数,虚拟机栈也分为普通栈和jni栈.虚拟机在编译期可以确定普通函数中局部变量的个数,当一个普通栈被压栈时,压栈的内存大小为栈<em>StackSaveArea</em>本身大小以及局部变量的内存大小; <br>
而jni栈,虚拟机并不知道栈中会有多少个局部变量,jni栈只包含<em>StackSaveArea</em>的内存大小,jobject统一交给IndirectRefTable管理,因此,IndirectRefTable也需要压栈出栈的概念; <br>
StackSaveArea有一个xtra union变量:</p>
<div class="highlight"><pre><span></span>struct StackSaveArea;
....
union {
/* for JNI native methods: bottom of local reference segment */
u4 localRefCookie;
/* for interpreted methods: saved current PC, for exception stack
* traces and debugger traces */
const u2* currentPc;
} xtra;
...
</pre></div>
<p>从注释可以看出,java method模式下,xtra用于表示解释器的pc值;jni method,用于表示<em>bottom of local reference segment</em><br>
对于segment,我们可以理解成一个与栈类似的结构,IndirectRefTable在压栈时,会将IndirectRefTable的<em>IRTSegmentState</em>变量,赋值给xtra.localRefCookie</p>
<div class="highlight"><pre><span></span><span class="nt">union</span> <span class="nt">IRTSegmentState</span> <span class="p">{</span>
<span class="n">u4</span> <span class="n">all</span><span class="p">;</span>
<span class="n">struct</span> <span class="err">{</span>
<span class="n">u4</span> <span class="n">topIndex</span><span class="o">:</span><span class="m">16</span><span class="p">;</span> <span class="c">/* index of first unused entry */</span>
<span class="n">u4</span> <span class="n">numHoles</span><span class="o">:</span><span class="m">16</span><span class="p">;</span> <span class="c">/* #of holes in entire table */</span>
<span class="p">}</span> <span class="nt">parts</span><span class="o">;</span>
<span class="err">}</span>
</pre></div>
<p>注意<em>IRTSegmentState</em>是一个32位长的union,从注释可以看出,前16位用于记录当前IndirectRefTable的索引位,后16位用于记录当前栈之前,IndirectRefTable的空位个数.
采取这种设计的原因是为了快速的出栈,虚拟机可以直接从StackSaveArea中取到上一个栈的topIndex,重置回IndirectRefTable表,而不需要逐一回退.<br>
jni api有一对pushLocalFrame/popLocalFrame函数,也是利用了<em>IRTSegmentState</em>进行本地引用的批量删除.</p>
<h3>垃圾回收</h3>
<p>虽然在官方blog中说明采取间接指针的目的是为了进行内存压缩,但在最后几个dalvik版本中,采取的仍是mark-sweep算法(art模式采取mark-copy,真正实现了对堆内存的整理).mark过程中,虚拟机会分别在mark rootSet以及Mark Thread root阶段对jniGlobalRefTable和jniLocalRefTable进行标记,避免GC回收.</p></content><category term="jni"></category><category term="dalvik"></category></entry><entry><title>blog.aaapei.com</title><link href="http://blog.aaapei.com/article/2014/08/blogaaapeicom" rel="alternate"></link><published>2014-08-09T00:00:00+08:00</published><updated>2014-08-09T00:00:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2014-08-09:/article/2014/08/blogaaapeicom</id><summary type="html"><p>前端时间为了科学上网买了个vps,准备用aaapei这个主域名收回来,用在vps跑一些试验性服务,blog切到二级子域名 blog.aaapei.com</p>
<p>最近一两个月会将aaapei.com的80端口重定向到blog子域名,顺便扯一句,godaddy的重定向服务在国内被禁,所以在vps上又跑了个tornado实例,专门做重定向;google analytics上看月pv也就几百,我一定是想多了 :&lt;</p></summary><content type="html"><p>前端时间为了科学上网买了个vps,准备用aaapei这个主域名收回来,用在vps跑一些试验性服务,blog切到二级子域名 blog.aaapei.com</p>
<p>最近一两个月会将aaapei.com的80端口重定向到blog子域名,顺便扯一句,godaddy的重定向服务在国内被禁,所以在vps上又跑了个tornado实例,专门做重定向;google analytics上看月pv也就几百,我一定是想多了 :&lt;</p></content><category term="域名"></category></entry><entry><title>android中lua脚本的部署</title><link href="http://blog.aaapei.com/article/2014/04/androidzhong-luajiao-ben-de-bu-shu" rel="alternate"></link><published>2014-04-19T23:21:00+08:00</published><updated>2014-04-19T23:21:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2014-04-19:/article/2014/04/androidzhong-luajiao-ben-de-bu-shu</id><summary type="html"><p>“愤怒的小鸟”中,lua脚本是运行时部署在应用的data目录,同时做了脚本加密;<br>
但是如果你的lua脚本有了模块包,脚本就会有多级目录,而android的data目录下是不允许有子目录;<br>
当然,lua脚本也可以在运行时拷贝到外置sd卡中,不过既然放置在不安全的公共目录,就会有被第三方删除或篡改的危险,那么每次启动的时候必须做一次文件校验,同时,也必须对脚本进行加密,避免程序逻辑暴露; </p>
<h3>lua自定义加载器</h3>
<p>lua引擎提供了一个自定义加载器,当lua脚本中调用require时,会回调到自定义加载器的CFunction;利用这样特性,可以尝试取消掉脚本的运行时拷贝,而直接从应用的asset资源目录加载;自定义加载器的原理中,云风的blog中有介绍,<a href="http://blog.codingnow.com/2007/04/user_define_lua_loader.html">以自定义方式加载lua模块</a><br>
直接贴个代码实现 </p>
<div class="highlight"><pre><span></span><span class="kt">void</span> <span class="nf">addAssetLuaLoader</span><span class="p">(</span><span class="n">lua_State</span> <span class="o">*</span><span class="n">L</span><span class="p">,</span> <span class="n">lua_CFunction</span> <span class="n">func</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">func</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="n">lua_getglobal</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">&quot;package&quot;</span><span class="p">);</span>
<span class="n">lua_getfield</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="s">&quot;loaders&quot;</span><span class="p">);</span>
<span class="n">lua_pushcfunction</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">func</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span> <span class="n">i …</span></pre></div></summary><content type="html"><p>“愤怒的小鸟”中,lua脚本是运行时部署在应用的data目录,同时做了脚本加密;<br>
但是如果你的lua脚本有了模块包,脚本就会有多级目录,而android的data目录下是不允许有子目录;<br>
当然,lua脚本也可以在运行时拷贝到外置sd卡中,不过既然放置在不安全的公共目录,就会有被第三方删除或篡改的危险,那么每次启动的时候必须做一次文件校验,同时,也必须对脚本进行加密,避免程序逻辑暴露; </p>
<h3>lua自定义加载器</h3>
<p>lua引擎提供了一个自定义加载器,当lua脚本中调用require时,会回调到自定义加载器的CFunction;利用这样特性,可以尝试取消掉脚本的运行时拷贝,而直接从应用的asset资源目录加载;自定义加载器的原理中,云风的blog中有介绍,<a href="http://blog.codingnow.com/2007/04/user_define_lua_loader.html">以自定义方式加载lua模块</a><br>
直接贴个代码实现 </p>
<div class="highlight"><pre><span></span><span class="kt">void</span> <span class="nf">addAssetLuaLoader</span><span class="p">(</span><span class="n">lua_State</span> <span class="o">*</span><span class="n">L</span><span class="p">,</span> <span class="n">lua_CFunction</span> <span class="n">func</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">func</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="n">lua_getglobal</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">&quot;package&quot;</span><span class="p">);</span>
<span class="n">lua_getfield</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="s">&quot;loaders&quot;</span><span class="p">);</span>
<span class="n">lua_pushcfunction</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">func</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span> <span class="n">i</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">lua_objlen</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">;</span> <span class="o">--</span><span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">lua_rawgeti</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">lua_rawseti</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">lua_rawseti</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="n">lua_setfield</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="s">&quot;loaders&quot;</span><span class="p">);</span>
<span class="n">lua_pop</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>在lua引擎启动时,调用<em>addAssetLuaLoader</em>进行初始化,把你的CFunction加入到到lua的<em>package.loader</em>表中;<br>
其中asset lua Loader的逻辑是这样的 </p>
<div class="highlight"><pre><span></span> <span class="c1">//搜索路径</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">searchpath</span><span class="p">(</span><span class="s">&quot;?.lua;?/init.lua;&quot;</span><span class="p">);</span>
<span class="kt">size_t</span> <span class="n">next</span> <span class="o">=</span> <span class="n">searchpath</span><span class="p">.</span><span class="n">find_first_of</span><span class="p">(</span><span class="s">&quot;;&quot;</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">do</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">next</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="n">next</span> <span class="o">=</span> <span class="n">searchpath</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">prefix</span> <span class="o">=</span> <span class="n">searchpath</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">begin</span><span class="p">,</span> <span class="n">next</span><span class="o">-</span><span class="n">begin</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">prefix</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sc">&#39;.&#39;</span> <span class="o">&amp;&amp;</span> <span class="n">prefix</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">&#39;/&#39;</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">prefix</span> <span class="o">=</span> <span class="n">prefix</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">pos</span> <span class="o">=</span> <span class="n">prefix</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">&quot;?&quot;</span><span class="p">);</span>
<span class="n">chunkName</span> <span class="o">=</span> <span class="n">prefix</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">filename</span><span class="p">);</span>
<span class="c1">//从asset目录读取字节流</span>
<span class="n">chunk</span> <span class="o">=</span> <span class="n">getAssetData</span><span class="p">(</span><span class="n">chunkName</span><span class="p">,</span><span class="o">&amp;</span><span class="n">chunkSize</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">chunk</span><span class="p">){</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">begin</span> <span class="o">=</span> <span class="n">next</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">next</span> <span class="o">=</span> <span class="n">searchpath</span><span class="p">.</span><span class="n">find_first_of</span><span class="p">(</span><span class="s">&quot;;&quot;</span><span class="p">,</span> <span class="n">begin</span><span class="p">);</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">begin</span> <span class="o">&lt;</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">searchpath</span><span class="p">.</span><span class="n">length</span><span class="p">());</span>
<span class="k">if</span> <span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 解开加密lua</span>
<span class="n">loadencryrtbuffer</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">chunk</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">chunkSize</span><span class="p">,</span> <span class="n">chunkName</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
<span class="n">delete</span> <span class="p">[]</span><span class="n">chunk</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>当lua脚本中调用require时,asset lua loader就会回调得到一个fileName,将filename增补后缀名,转化成完整的asset相对文件路径,读取文件字节流;<br>
这段字节流可以直接是经过加密的字节流,也可以是未加密的原始lua脚本,然后通过<em>luaL_loadbuffer</em>调用执行;</p>
<h3>asset资源的读取</h3>
<p>android2.3之后,ndk提供了直接操作asset资源的c api,可以在c中直接读取asset文件</p>
<div class="highlight"><pre><span></span><span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">//filename也就是上个代码块中的chunkName,即lua文件在asset的相对目录</span>
<span class="n">AAsset</span><span class="o">*</span> <span class="n">asset</span> <span class="o">=</span> <span class="n">AAssetManager_open</span><span class="p">(</span><span class="n">gAssetManager</span> <span class="p">,</span><span class="n">filename</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">AASSET_MODE_UNKNOWN</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">asset</span><span class="p">){</span>
<span class="kt">off_t</span> <span class="n">fileSize</span> <span class="o">=</span> <span class="n">AAsset_getLength</span><span class="p">(</span><span class="n">asset</span><span class="p">);</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span><span class="n">fileSize</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">bytesread</span> <span class="o">=</span> <span class="n">AAsset_read</span><span class="p">(</span><span class="n">asset</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">data</span><span class="p">,</span> <span class="n">fileSize</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
<span class="o">*</span><span class="n">size</span> <span class="o">=</span> <span class="n">bytesread</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">AAsset_close</span><span class="p">(</span><span class="n">asset</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">data</span><span class="p">;</span>
</pre></div>
<p>不过lua脚本的位置移动asset目录后,lua脚本常用的代码热更新机制就遇到些许问题,毕竟不可能将热更新代码重新写入到dex文件中;<br>
就时候,机可以考虑将lua脚本进行模块化拆分,需要热更新的脚本部署在sd卡中,其他固化在APK的资源包中,随版本更新进行迭代;<br>
既然有了自定义加载器,动态代码的热更新实现就简单多了。</p></content><category term="android"></category><category term="lua"></category></entry><entry><title>lua在跨语言环境中的内存泄露</title><link href="http://blog.aaapei.com/article/2014/02/luazai-kua-yu-yan-huan-jing-zhong-de-nei-cun-xie-lu" rel="alternate"></link><published>2014-02-19T19:50:00+08:00</published><updated>2014-02-19T19:50:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2014-02-19:/article/2014/02/luazai-kua-yu-yan-huan-jing-zhong-de-nei-cun-xie-lu</id><summary type="html"><p>前段时间在android环境中使用lua,采用的luabridge是luajava,遇到一个内存泄露问题,查了很久,终于定位了;</p>
<p>场景是这样的:<br>
1:在lua中创建了一个java对象,将jobject指针传递给java;对应在luajava中,即传递了一个luaobject给java;java保存了这个对象;<br>
2:在Lua中需要对这个java对象设置一个事件回调,比如说为某个控件setOnClickListener;<br>
其中设置的代码是这样的:</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="n">viewclick_cb</span><span class="o">=</span><span class="p">{}</span>
<span class="k">function</span> <span class="nc">viewclick_cb</span><span class="p">.</span><span class="nf">onClick</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="n">self</span><span class="p">:</span><span class="n">cb</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">listenerProxy</span> <span class="o">=</span> <span class="n">luajava</span><span class="p">.</span><span class="n">createProxy</span><span class="p">(</span><span class="s1">&#39;</span><span class="s">android.view.View$OnClickListener&#39;</span><span class="p">,</span><span class="n">viewclick_cb</span><span class="p">)</span>
<span class="n">javaobjhandler</span><span class="p">:</span><span class="n">setOnClickListener</span><span class="p">(</span><span class="n">ListenerProxy</span><span class="p">)</span>
</pre></div>
<p>lua和java交互时,对象生命周期管理分两种情况:</p>
<blockquote>
<p>1:java对象传递个lua虚拟机,lua虚拟机为java对象创建一个userdata,在globalref中增加一个引用,标记这个java对象正在被使用;同时,为这个userdata设置一个__gc元方法,当lua对象需要被释放时,_gc元方法回调,释放掉java对象的globaref …</p></blockquote></summary><content type="html"><p>前段时间在android环境中使用lua,采用的luabridge是luajava,遇到一个内存泄露问题,查了很久,终于定位了;</p>
<p>场景是这样的:<br>
1:在lua中创建了一个java对象,将jobject指针传递给java;对应在luajava中,即传递了一个luaobject给java;java保存了这个对象;<br>
2:在Lua中需要对这个java对象设置一个事件回调,比如说为某个控件setOnClickListener;<br>
其中设置的代码是这样的:</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="n">viewclick_cb</span><span class="o">=</span><span class="p">{}</span>
<span class="k">function</span> <span class="nc">viewclick_cb</span><span class="p">.</span><span class="nf">onClick</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="n">self</span><span class="p">:</span><span class="n">cb</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="k">end</span>
<span class="kd">local</span> <span class="n">listenerProxy</span> <span class="o">=</span> <span class="n">luajava</span><span class="p">.</span><span class="n">createProxy</span><span class="p">(</span><span class="s1">&#39;</span><span class="s">android.view.View$OnClickListener&#39;</span><span class="p">,</span><span class="n">viewclick_cb</span><span class="p">)</span>
<span class="n">javaobjhandler</span><span class="p">:</span><span class="n">setOnClickListener</span><span class="p">(</span><span class="n">ListenerProxy</span><span class="p">)</span>
</pre></div>
<p>lua和java交互时,对象生命周期管理分两种情况:</p>
<blockquote>
<p>1:java对象传递个lua虚拟机,lua虚拟机为java对象创建一个userdata,在globalref中增加一个引用,标记这个java对象正在被使用;同时,为这个userdata设置一个__gc元方法,当lua对象需要被释放时,_gc元方法回调,释放掉java对象的globaref; </p>
<p>2:lua对象传递个java虚拟机,将lua对象放到LUA_REGISTRYINDEX中,调用luaL_ref得到一个引用交给一个java对象;在这个java对象的finalize中调用luaL_unref释放引用; </p>
</blockquote>
<p>单独看,都没有问题,但当这两个策略同时作用时,就可能产生循环引用;<br>
比如上述代码中,回调函数中self:cb(v)与上下文相关的场景;<br>
java虚拟机和lua虚拟机采取的都是mark-sweep策略,其mark方式都是根节点标记法;两个虚拟机都认为对象存在引用,导致内存泄露;
这个内存泄露问题在其他语言环境中也应该存在; </p>
<p>最后采取的解决方式,采取云风大神这篇<a href="http://blog.codingnow.com/2007/10/lua_c_object_reference.html">文章</a>的思路:<br>
事件回调通过消息机制而不是直接引用的方式,回调函数保存在一个全局weak table中;回调函数关联在一个lua对象中,和lua对象生命周期保持一致;回调事件发生时,java通过一个全局的weak table查询回调函数;</p></content><category term="lua"></category><category term="luajava"></category></entry><entry><title>android bitmap tips</title><link href="http://blog.aaapei.com/article/2014/01/android-bitmap-tips" rel="alternate"></link><published>2014-01-28T17:30:00+08:00</published><updated>2014-01-28T17:30:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2014-01-28:/article/2014/01/android-bitmap-tips</id><summary type="html"><p>大部分android开发者只知道bitmap再使用之后,需要显式的调用一次bitmap.recycle,回收bitmap内存; </p>
<p>这个观点,在android3.0之后其实已经过时了,android3.0将bitmap所引用的位图资源从native heap移到虚拟机的heap空间,bitmap的生命周期由虚拟机管理,开发者只需要保证在应用中不在持有对bitmap的引用,即可保证不发生内存泄漏;位图资源的内存由GC进行回收;<br>
将bitmap的位图内存转移到虚拟机heap后,由于位图资源本身较大,造成gc的概率的增加 </p>
<h3>bitmap proxy</h3>
<p>在一般的应用场景中,bitmap的引用一般是和imageview关联;当大量imageview持有对bitmap的引用时,虚拟机无法释放bitmap的内存,导致oom;</p>
<p>针对这种场景,开发者可以实现一个bitmap的代理类,作为bitmap和imageview的中间层,在imageview不可见时(例如imageview是一个listview的子控件,listview发生滚动/activiy切换到后台),即释放掉对bitamp的引用;当imageview重新可见时,通过代理类重新去加载bitmap,以解耦bitmap和iamgeview的引用关系; </p>
<h3>inBitmap</h3>
<p>android在3.0之后新增了一个BitmapFactory.Options.inBitmap开关,加了这个开关之后,bitmapfactory在加载位图时候,会尝试使用inbitmap指向的已分配在heap中位图空间;而不是重新申请一块内存;从而减少了虚拟机最讨厌的短生命周期大内存对象; 不过这个开关有严格的使用场景,即两个bitamp的位图大小必须一致</p></summary><content type="html"><p>大部分android开发者只知道bitmap再使用之后,需要显式的调用一次bitmap.recycle,回收bitmap内存; </p>
<p>这个观点,在android3.0之后其实已经过时了,android3.0将bitmap所引用的位图资源从native heap移到虚拟机的heap空间,bitmap的生命周期由虚拟机管理,开发者只需要保证在应用中不在持有对bitmap的引用,即可保证不发生内存泄漏;位图资源的内存由GC进行回收;<br>
将bitmap的位图内存转移到虚拟机heap后,由于位图资源本身较大,造成gc的概率的增加 </p>
<h3>bitmap proxy</h3>
<p>在一般的应用场景中,bitmap的引用一般是和imageview关联;当大量imageview持有对bitmap的引用时,虚拟机无法释放bitmap的内存,导致oom;</p>
<p>针对这种场景,开发者可以实现一个bitmap的代理类,作为bitmap和imageview的中间层,在imageview不可见时(例如imageview是一个listview的子控件,listview发生滚动/activiy切换到后台),即释放掉对bitamp的引用;当imageview重新可见时,通过代理类重新去加载bitmap,以解耦bitmap和iamgeview的引用关系; </p>
<h3>inBitmap</h3>
<p>android在3.0之后新增了一个BitmapFactory.Options.inBitmap开关,加了这个开关之后,bitmapfactory在加载位图时候,会尝试使用inbitmap指向的已分配在heap中位图空间;而不是重新申请一块内存;从而减少了虚拟机最讨厌的短生命周期大内存对象; 不过这个开关有严格的使用场景,即两个bitamp的位图大小必须一致</p></content><category term="android"></category></entry><entry><title>jnlua吐槽</title><link href="http://blog.aaapei.com/article/2013/12/jnluatu-cao" rel="alternate"></link><published>2013-12-29T00:00:00+08:00</published><updated>2013-12-29T00:00:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2013-12-29:/article/2013/12/jnluatu-cao</id><summary type="html"><p>纯吐槽 </p>
<p>前端时间对luabridge的性能问题很头痛,后来找到号称jni实现的<em>jnlua</em>, 从原理上说,如果luabridge在动态绑定的逻辑可以只在jni上完成,而不走虚拟机的解释器,性能上可以提升很多,在jnlua的项目主页上还找到了,5.1的android移植版,于是打开beyondcompare迅速合并了个5.2的android移植版,实际运行的时候直接秒退了.翻了下代码,一口老血. </p>
<p>jnlua号称jni实现,但事实上动态绑定的几个api还是通过java实现,由于luabridge的瓶颈基本都在反射那块,和luajava相比,jnlua没有性能上的优势,算是典型的重复造轮子 </p>
<p>至于所谓的5.1的android移植版,它为了兼容android的lib不支持的java.beans.BeanInfo类,删除了相关的JavaModule,DefaultJavaReflector两个类,代价是android移植版不再支持了lua脚本中调用java! </p>
<p>口怕的是,jnlua的项目主页竟然还加了移植版的链接, </p>
<p>虽然应该支持开源项目,但是挂这样一个半成品项目在主页上,还是要说一句 你在外面这么屌,luajava知道吗 </p>
<p>各位想用jnlua的android同学,如果非要用jnlua的话,移植的时候其实是可以引入一些第三方库的,比如<em>openbeans</em> </p>
<p>jnlua的代码结构较luajava层次分的更加清晰一些,api粒度更细;
好吧,个人觉得这种基础架构型的bridge,性能才是决定是否使用的第一要素,继续用luajava吧</p></summary><content type="html"><p>纯吐槽 </p>
<p>前端时间对luabridge的性能问题很头痛,后来找到号称jni实现的<em>jnlua</em>, 从原理上说,如果luabridge在动态绑定的逻辑可以只在jni上完成,而不走虚拟机的解释器,性能上可以提升很多,在jnlua的项目主页上还找到了,5.1的android移植版,于是打开beyondcompare迅速合并了个5.2的android移植版,实际运行的时候直接秒退了.翻了下代码,一口老血. </p>
<p>jnlua号称jni实现,但事实上动态绑定的几个api还是通过java实现,由于luabridge的瓶颈基本都在反射那块,和luajava相比,jnlua没有性能上的优势,算是典型的重复造轮子 </p>
<p>至于所谓的5.1的android移植版,它为了兼容android的lib不支持的java.beans.BeanInfo类,删除了相关的JavaModule,DefaultJavaReflector两个类,代价是android移植版不再支持了lua脚本中调用java! </p>
<p>口怕的是,jnlua的项目主页竟然还加了移植版的链接, </p>
<p>虽然应该支持开源项目,但是挂这样一个半成品项目在主页上,还是要说一句 你在外面这么屌,luajava知道吗 </p>
<p>各位想用jnlua的android同学,如果非要用jnlua的话,移植的时候其实是可以引入一些第三方库的,比如<em>openbeans</em> </p>
<p>jnlua的代码结构较luajava层次分的更加清晰一些,api粒度更细;
好吧,个人觉得这种基础架构型的bridge,性能才是决定是否使用的第一要素,继续用luajava吧</p></content><category term="lua"></category></entry><entry><title>微信的jsbridge实现</title><link href="http://blog.aaapei.com/article/2013/12/wei-xin-de-jsbridgeshi-xian" rel="alternate"></link><published>2013-12-11T19:50:00+08:00</published><updated>2013-12-11T19:50:00+08:00</updated><author><name>zhengwen</name></author><id>tag:blog.aaapei.com,2013-12-11:/article/2013/12/wei-xin-de-jsbridgeshi-xian</id><summary type="html"><p>微信公共平台开放了几个操作webview界面的js接口<br>
示例代码是这样的: </p>
<div class="highlight"><pre><span></span><span class="nx">WeixinJSBridge</span><span class="p">.</span><span class="nx">invoke</span><span class="p">(</span><span class="s1">&#39;getNetworkType&#39;</span><span class="p">,{},</span>
<span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">){</span>
<span class="nx">WeixinJSBridge</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">err_msg</span><span class="p">);</span>
<span class="p">});</span>
</pre></div>
<p>android的webview api中有开放过一个<em>addJavaScriptInterface</em>函数,这个函数的作用是在页面的Window中注入一个JS对象<br>
如果你的应用中使用了这个api,建议先看一下国内安全领域第一人黑哥的这篇文章<a href="http://hi.baidu.com/hi_heige/item/9baf99f063331d58c9f3379a">android webview 漏洞背后的节操</a>, </p>
<p>没耐心的同学直接看结尾的解决方案吧:</p>
<blockquote>
<p>第1个方案是设置信任域,这个问题其实是不太靠谱的,在我之前在kcon里演讲《去年跨过的浏览器》里有很多信任域带来的安全问题<br>
第2个方案是使用 shouldOverrideUrlLoading 的方式,据说这个方案还是比较靠谱的,只是可能代价比较大<br>
第3个方案就是教育那些开发商,没有必要用webview的时候就不要用,不要java与js交互就不要用 </p>
</blockquote>
<p>不过,按黑哥这篇文章的想法,这个漏洞危险等级很低,可以无视之<br>
暂时把安全问题放一边,Js对象的注入,对函数的参数类型有严格要求,它只能传递基本数据类型以及JSON<br>
但微信的JsApi中,参数三是一个函数对象,那他是如果做到的呢</p>
<h3>逆向</h3>
<p>Android原生的机制既然不能支持函数对象的传递,于是猜测微信是否会对原始的api做了一层包装;<br>
先下载weixin …</p></summary><content type="html"><p>微信公共平台开放了几个操作webview界面的js接口<br>
示例代码是这样的: </p>
<div class="highlight"><pre><span></span><span class="nx">WeixinJSBridge</span><span class="p">.</span><span class="nx">invoke</span><span class="p">(</span><span class="s1">&#39;getNetworkType&#39;</span><span class="p">,{},</span>
<span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">){</span>
<span class="nx">WeixinJSBridge</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">err_msg</span><span class="p">);</span>
<span class="p">});</span>
</pre></div>
<p>android的webview api中有开放过一个<em>addJavaScriptInterface</em>函数,这个函数的作用是在页面的Window中注入一个JS对象<br>
如果你的应用中使用了这个api,建议先看一下国内安全领域第一人黑哥的这篇文章<a href="http://hi.baidu.com/hi_heige/item/9baf99f063331d58c9f3379a">android webview 漏洞背后的节操</a>, </p>
<p>没耐心的同学直接看结尾的解决方案吧:</p>
<blockquote>
<p>第1个方案是设置信任域,这个问题其实是不太靠谱的,在我之前在kcon里演讲《去年跨过的浏览器》里有很多信任域带来的安全问题<br>
第2个方案是使用 shouldOverrideUrlLoading 的方式,据说这个方案还是比较靠谱的,只是可能代价比较大<br>
第3个方案就是教育那些开发商,没有必要用webview的时候就不要用,不要java与js交互就不要用 </p>
</blockquote>
<p>不过,按黑哥这篇文章的想法,这个漏洞危险等级很低,可以无视之<br>
暂时把安全问题放一边,Js对象的注入,对函数的参数类型有严格要求,它只能传递基本数据类型以及JSON<br>
但微信的JsApi中,参数三是一个函数对象,那他是如果做到的呢</p>
<h3>逆向</h3>
<p>Android原生的机制既然不能支持函数对象的传递,于是猜测微信是否会对原始的api做了一层包装;<br>
先下载weixin.apk,反编译,全局搜索"WeixinJSBrige",在assets目录找到一个wxjs.js;不知什么原因,微信团队没有对这个js文件进行代码混淆;</p>
<h3>wxjs.js分析</h3>
<p>wxjs.js有两千多行的代码,不过不必担心,其中有一大部分是jquery的实现<br>
直接找我们想要的,先看'WeixinJSBridge' </p>
<div class="highlight"><pre><span></span> <span class="kd">var</span> <span class="nx">__WeixinJSBridge</span> <span class="o">=</span> <span class="p">{</span>
<span class="c1">// public</span>
<span class="nx">invoke</span><span class="o">:</span><span class="nx">_call</span><span class="p">,</span>
<span class="nx">call</span><span class="o">:</span><span class="nx">_call</span><span class="p">,</span>
<span class="nx">on</span><span class="o">:</span><span class="nx">_on</span><span class="p">,</span>
<span class="nx">env</span><span class="o">:</span><span class="nx">_env</span><span class="p">,</span>
<span class="nx">log</span><span class="o">:</span><span class="nx">_log</span><span class="p">,</span>
<span class="nx">_fetchQueue</span><span class="o">:</span> <span class="nx">_fetchQueue</span><span class="p">,</span>
<span class="nx">_handleMessageFromWeixin</span><span class="o">:</span> <span class="nx">_handleMessageFromWeixin</span><span class="p">,</span>
<span class="p">};</span>
</pre></div>
<p>看到方法名可以猜测微信JSBridge的大概的逻辑了;应该是消息队列处理机制,</p>
<p>具体还是看一下微信是怎么实现的</p>
<p>先看_call方法的逻辑:</p>
<div class="highlight"><pre><span></span> <span class="kd">function</span> <span class="nx">_call</span><span class="p">(</span><span class="nx">func</span><span class="p">,</span><span class="nx">params</span><span class="p">,</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">func</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">func</span> <span class="o">!==</span> <span class="s1">&#39;string&#39;</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">params</span> <span class="o">!==</span> <span class="s1">&#39;object&#39;</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">params</span> <span class="o">=</span> <span class="p">{};</span>
<span class="p">};</span>
<span class="kd">var</span> <span class="nx">callbackID</span> <span class="o">=</span> <span class="p">(</span><span class="nx">_callback_count</span><span class="o">++</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">callback</span> <span class="o">===</span> <span class="s1">&#39;function&#39;</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">_callback_map</span><span class="p">[</span><span class="nx">callbackID</span><span class="p">]</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">;</span>
<span class="p">};</span>
<span class="kd">var</span> <span class="nx">msgObj</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;func&#39;</span><span class="o">:</span><span class="nx">func</span><span class="p">,</span><span class="s1">&#39;params&#39;</span><span class="o">:</span><span class="nx">params</span><span class="p">};</span>
<span class="nx">msgObj</span><span class="p">[</span><span class="nx">_MESSAGE_TYPE</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;call&#39;</span><span class="p">;</span>
<span class="nx">msgObj</span><span class="p">[</span><span class="nx">_CALLBACK_ID</span><span class="p">]</span> <span class="o">=</span> <span class="nx">callbackID</span><span class="p">;</span>