-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
3092 lines (2895 loc) · 519 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>lightSky'Blog</title>
<link href="/atom.xml" rel="self"/>
<link href="http://www.lightskystreet.com/"/>
<updated>2016-11-22T01:32:23.000Z</updated>
<id>http://www.lightskystreet.com/</id>
<author>
<name>lightSky</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>设计模式基础</title>
<link href="http://www.lightskystreet.com/2016/11/23/design-summary/"/>
<id>http://www.lightskystreet.com/2016/11/23/design-summary/</id>
<published>2016-11-22T16:00:00.000Z</published>
<updated>2016-11-22T01:32:23.000Z</updated>
<content type="html"><![CDATA[<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>最近在负责一个新项目的基础搭建,过程中涉及到一些模块的划分、设计,之前也确实没有系统的学习过设计模式相关的知识,因此就花了些时间看了下设计模式,记录下来。本篇为设计模式的一些基础概念,但我觉得受益匪浅,当你掌握了基本概念和一些理论,才能更好地去从更高的层次去理解、设计一个模块,一些新知识的学习也是如此,Rx、RN,这些框架的思想都很重要,当然最终还是要回归到实践,才能更深地理解和掌握某一个新技术和新知识点。后面会有设计模式相关的文章,除了一些设计模式的具体介绍也会对一些优秀开源库从设计方面做一些分析,有兴趣的,可以关注下。</p>
<h2 id="一-设计模式的分类"><a href="#一-设计模式的分类" class="headerlink" title="一 设计模式的分类"></a>一 设计模式的分类</h2><h3 id="目的准则"><a href="#目的准则" class="headerlink" title="目的准则"></a>目的准则</h3><p>模依据其目的可分为创建型 (Creational)、 结构型(Structural) 、 或行为型(Behavioral) 三种。</p>
<ul>
<li>创建型模式与对象的创建有关</li>
<li>结构型模式处理类或对象的组合</li>
<li>行为型模式对类或对象怎样交互和怎样分配职责进行描述</li>
</ul>
<h3 id="范围准则"><a href="#范围准则" class="headerlink" title="范围准则"></a>范围准则</h3><p>指定模式主要是用于类还是用于对象</p>
<ul>
<li>类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了</li>
<li>对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性</li>
<li>创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一 个对象中</li>
</ul>
<a id="more"></a>
<p><img src="/img/designpattern/pattern-category.png"> </p>
<h2 id="二-描述对象的实现"><a href="#二-描述对象的实现" class="headerlink" title="二 描述对象的实现"></a>二 描述对象的实现</h2><h3 id="2-1-类继承与接口实现的比较"><a href="#2-1-类继承与接口实现的比较" class="headerlink" title="2.1.类继承与接口实现的比较"></a>2.1.类继承与接口实现的比较</h3><p>一个类定义了对象的实现,同时也定义了对象的内部状态和操作的实现。</p>
<p>对象类型只与它的接口有关,接口即是对象能响应请求的集合。一个对象可以有多个类型,不同的对象可以有相同的类型。</p>
<p>类继承根据一个对象的实现定义另一个对象的实现。简而言之,它是代码和表示的共享机制。而接口继实现(或类型化)描述一个对象什么时候能替换另一个对象。</p>
<h3 id="2-2-针对接口编程,而不是针对实现编程"><a href="#2-2-针对接口编程,而不是针对实现编程" class="headerlink" title="2.2.针对接口编程,而不是针对实现编程"></a>2.2.<strong>针对接口编程,而不是针对实现编程</strong></h3><p>类继承是通过复用父类功能扩展应用功能的基本机制,创建和使用都比较简单。但为了让继承体系拥有相同接口的对象的能力很重要,因为多态依赖这种能力,实现动态绑定。为了达到这种能力,可以通过抽象类来实现,所有从抽象类导出的类将共享该抽象类的接口。</p>
<p>只根据抽象类中定义的接口来操纵对象有以下两个好处:</p>
<ul>
<li>使用者无须知道对象的特定类型,只须对象有他们所期望的接口</li>
<li>使用者无须知道对象是用什么类来实现的,他们只须知道定义接口的抽象类</li>
</ul>
<p>这将极大地减少子系统实现之间的相互依赖关系,也产生了可复用的面向对象设计的原则:</p>
<blockquote>
<p>针对接口编程,而不是针对实现编程</p>
</blockquote>
<p>通常我们不应该将变量声明为某个特定的具体类对象,而是让它遵从抽象类所定义的接口。</p>
<p>当你不得不在系统的某个地方实例化具体的类 (即指定一个特定的实现 )时,可采用创建型模式。创建型模可以确保我们的系统针对接口编程,而不是针对实现。</p>
<h2 id="三-运用复用机制"><a href="#三-运用复用机制" class="headerlink" title="三 运用复用机制"></a>三 运用复用机制</h2><p>理解对象、接口、类和继承之类的概念并不难,问题的关键在于如何运它们写出灵活的、可复的软件。 </p>
<p> </p>
<h3 id="3-1-继承和组合的比较"><a href="#3-1-继承和组合的比较" class="headerlink" title="3.1 继承和组合的比较"></a>3.1 继承和组合的比较</h3><p>面向对象系统中功能复用的两种最常用技术是<strong>类继承</strong>和<strong>对象组合(object composition)</strong>。</p>
<h4 id="类继承"><a href="#类继承" class="headerlink" title="类继承"></a>类继承</h4><p>类继承允许你根据现有的类来定义一个类的实现。这种通过生成子类的复用通常被称为<strong>白箱复用</strong>(white-box reuse)。“白箱”是相对可视性而言:在继承方 式中,父类的内部细节对子类可见。</p>
<ul>
<li><p>优点</p>
<p>类继承是在编译时刻静态定义的,且可直接使用,也可以方便的实现复用。当子类定义了部分操作时,它也能影响子类的操作。</p>
<p></p>
</li>
</ul>
<ul>
<li><p>缺点</p>
<p><strong>静态的</strong></p>
<p>因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。</p>
<p></p>
<p><strong>破坏了封装性</strong></p>
<p>父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,导致父类实现中的任何变化必然导致子类发生变化。</p>
</li>
</ul>
<h4 id="对象组合"><a href="#对象组合" class="headerlink" title="对象组合"></a>对象组合</h4><p>对象组合是类继承之外的另一种复用选择。复杂的功能可以通过组装或组合对象来获得。</p>
<p>这种复用风格被称为<strong>黑箱复用</strong> (black-box reuse),因为对象的内部细节是不可见的。</p>
<ul>
<li><p>优点</p>
<p><strong>良好的封装性</strong></p>
<p>组合要求对象遵守彼此的接口约定,而这些接口并不妨碍你将一个对象和其他对象 一起使用。因为对象只能通过接口访问,所以并不破坏封装性。而且由于对象的实现是基于接口写的,所以实现上存在较少的依赖关系。</p>
<p></p>
<p><strong>较小规模的类层次</strong></p>
<p>对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装并被集中在单个任务上。因此,基于对象组合的设计会有更多的对象 (而有较少的类),这样类和类继承层次会保持较小规模,并且不太可能增长为不可控的庞然大物。</p>
<p></p>
<p><strong>动态性</strong></p>
<p>对象组合是通过获得对其他对象的引用而在<strong>运行时刻动态</strong>定义的。且系统的行为将依赖于对象间的关系而不是被定义在某个类中,只要类型一致,运行时刻还可以用一个对象来替代另一个对象。</p>
</li>
</ul>
<ul>
<li><p>缺点</p>
<p>动态的、 高度参数化的软件比静态软件更难于理解</p>
<p>因为存在间接性,运行效率低效</p>
<p></p>
</li>
</ul>
<p><strong>基于以上优点,在实际开发中,因优先使用组合、而不是继承</strong></p>
<h3 id="3-2-委托"><a href="#3-2-委托" class="headerlink" title="3.2 委托"></a>3.2 委托</h3><p>委托(delegation) 是一种组合方式,它使组合具有与继承同样的复用能力。</p>
<p>在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者(delegate)。这类似于子类将请求交给它的父类处理。</p>
<ul>
<li><p>优点</p>
<p>在于它便于<strong>运行时刻</strong>组合对象操作以及改变这些操作的组合方式。</p>
</li>
<li><p>缺点</p>
<p>与对象组合以取得软件灵活性的技术一样,动态的、 高度参数化的软件比静态软件更难于理解</p>
<p>因为存在间接性,运行效率较低</p>
</li>
</ul>
<p>委托的效率与上下有关,也与开发者的个人经验有关。委托最适于符合特定的情形,即标准模式的情形。有一些模式使用了委托,如 <code>State</code> 、<code>Strategy</code>和<code>Visitor</code></p>
<p>委托是对象组合的特例。它告诉你对象组合作为一个代码复用机制可以替代继承。</p>
<h2 id="四-设计应支持变化"><a href="#四-设计应支持变化" class="headerlink" title="四 设计应支持变化"></a>四 设计应支持变化</h2><p>为了避免后期的重构和重新设计,构建健壮性的系统,我们在写代码之前必须考虑系统在它的生命周期内会发生怎样的变化。设计模式可以确保系统能以特定方式变化,从而帮助我们避免重新设计系统。每一个设计模式可以确保系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮。</p>
<p>下面阐述一些导致重新设计的一般原因,以及解决这些问题的设计模式:</p>
<p><strong>1) 通过显式地指定一个类来创建对象</strong></p>
<p>这种创建对象的方式使得我们的代码受限于特定实现而不是接口。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象。</p>
<p>设计模式: AbstractFactory,FactoryMethod,Prototype</p>
<p><strong>2) 对特殊操作的依赖</strong> </p>
<p>当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便地改变响应请求的方法。</p>
<p>设计模式: ChainofResposibility,Command</p>
<p><strong>3) 对硬件和平台的依赖</strong></p>
<p>依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新。所以设计系统时限制其平台相关性就很重要了。</p>
<p>设计模式: AbstractFactory ,Bridge</p>
<p><strong>4) 对对象表示或实现的依赖</strong> </p>
<p>知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。</p>
<p>设计模式: AbstractFactory ,Bridge,Memento,Proxy</p>
<p><strong>5) 算法依赖</strong></p>
<p>算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。</p>
<p>设计模式: Builder 、Interator、Strategy、TemplateMethod、Visitor</p>
<p><strong>6) 紧耦合</strong></p>
<p>紧耦合的类很难独立地被复用,因为它们是互相依赖的。要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统很难学习、移植和维护。</p>
<p>松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。</p>
<p>设计模式: AbstractFactory ,Command , Facade, Mediator,Observer ,Chain of Responsibility</p>
<p><strong>7) 通过生成子类来扩充功能</strong></p>
<p>通常很难通过定义子类来定制对象。每一个新类都有固定的实现开销(初始化、终止处理等)。定义子类还需要对父类有深入的了解。并且子类方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。</p>
<p>对象组合技术和委托技术是继承之外组合对象行为的另一种灵活方法。新的功能可以通过以新的方式组合已有对象来实现。但是,如果过多地使用对象组合会使设计难于理解。</p>
<p>设计模式: Bridge, Chain of Responsibility ,Composite, Decorator,Observer , Strategy</p>
<p><strong>8) 不能方便地对类进行修改</strong></p>
<p>有时你需要修改一个类,但是却没有源代码。或者可能对类的任何改变会要求修改许多已存在的其他子类。</p>
<p>设计模式: Adapter, Decorator , Visitor </p>
<h2 id="五-如何选择设计模式"><a href="#五-如何选择设计模式" class="headerlink" title="五 如何选择设计模式"></a>五 如何选择设计模式</h2><p>我觉得首先就是对设计模式有一些了解,明白它们的分类、意图、使用场景,然后对遇到的问题,场景进行抽象,找出变化的地方,然后选择适当的设计模式将这些变化进行封装。</p>
<p>当然,设计模式不应该被滥用。因为通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更复杂并 /或牺牲了一定的性能。一个设计模式只有当它提供的灵活性是真正需要的时候,才有必要使用。</p>
<p>下图列出了设计模式允许独立变化的方面,你可以改变它们而不会导致重新设计:</p>
<p><img src="/img/designpattern/pattern-variety.png"> </p>
]]></content>
<summary type="html">
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>最近在负责一个新项目的基础搭建,过程中涉及到一些模块的划分、设计,之前也确实没有系统的学习过设计模式相关的知识,因此就花了些时间看了下设计模式,记录下来。本篇为设计模式的一些基础概念,但我觉得受益匪浅,当你掌握了基本概念和一些理论,才能更好地去从更高的层次去理解、设计一个模块,一些新知识的学习也是如此,Rx、RN,这些框架的思想都很重要,当然最终还是要回归到实践,才能更深地理解和掌握某一个新技术和新知识点。后面会有设计模式相关的文章,除了一些设计模式的具体介绍也会对一些优秀开源库从设计方面做一些分析,有兴趣的,可以关注下。</p>
<h2 id="一-设计模式的分类"><a href="#一-设计模式的分类" class="headerlink" title="一 设计模式的分类"></a>一 设计模式的分类</h2><h3 id="目的准则"><a href="#目的准则" class="headerlink" title="目的准则"></a>目的准则</h3><p>模依据其目的可分为创建型 (Creational)、 结构型(Structural) 、 或行为型(Behavioral) 三种。</p>
<ul>
<li>创建型模式与对象的创建有关</li>
<li>结构型模式处理类或对象的组合</li>
<li>行为型模式对类或对象怎样交互和怎样分配职责进行描述</li>
</ul>
<h3 id="范围准则"><a href="#范围准则" class="headerlink" title="范围准则"></a>范围准则</h3><p>指定模式主要是用于类还是用于对象</p>
<ul>
<li>类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了</li>
<li>对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性</li>
<li>创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一 个对象中</li>
</ul>
</summary>
<category term="设计模式" scheme="http://www.lightskystreet.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="设计模式" scheme="http://www.lightskystreet.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>multidex与插件化</title>
<link href="http://www.lightskystreet.com/2016/11/11/android-dex-plugin/"/>
<id>http://www.lightskystreet.com/2016/11/11/android-dex-plugin/</id>
<published>2016-11-10T16:00:00.000Z</published>
<updated>2016-11-11T02:01:42.000Z</updated>
<content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>随着应用越来越大,很多大型应用都会遇到方法数的爆棚以及方法信息存储区的问题,该篇文章主要以这两种问题为背景,介绍dex拆分、加载以及插件化方案的一些技术点。</p>
<h3 id="65536-与-INSTALL-FAILED-DEXOPT"><a href="#65536-与-INSTALL-FAILED-DEXOPT" class="headerlink" title="65536 与 INSTALL_FAILED_DEXOPT"></a>65536 与 INSTALL_FAILED_DEXOPT</h3><ol>
<li><p>生成的apk在android 2.3或之前的机器上无法安装,提示<code>INSTALL_FAILED_DEXOPT</code></p>
</li>
<li><p>方法数量过多,编译时出错,提示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536</div></pre></td></tr></table></figure>
</li>
</ol>
<p><strong>Android 2.3 INSTALL_FAILED_DEXOPT 的问题</strong></p>
<p>该问题由dexopt的LinearAlloc限制引起的, 在Gingerbread或者以下系统LinearAllocHdr分配空间只有5M大小的。</p>
<blockquote>
<p>Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。 目前Android 4.x提高到了8MB或16MB,在很大程度上解决了这个问题。</p>
</blockquote>
<p><strong>超过最大方法数限制的问题</strong></p>
<p>该问题是由DEX文件格式限制。一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是Android打包的DEX过程中, 单个DEX文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536;</p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a><strong>解决方案</strong></h3><ul>
<li>Android系统的LinearAlloc空间大小,在高版本上已经提升了,所以在一定程度上,不会出现这个问题,Facebook则采用了一种hack的方式,直接修改虚拟机的LinearAlloc空间大小:<a href="https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920/" target="_blank" rel="external">Dalvik patch for Facebook for Android</a></li>
<li>拆分Dex,官方有提供方案<a href="https://developer.android.com/tools/building/multidex.html" target="_blank" rel="external">Google Multidex</a>,让一个apk里支持多个.dex文件,这样就可以突破65536的限制,但该方案有一定的弊端,下面会提到</li>
<li>插件化,将整个工程的划分为Host+多个插件的形式,这样就可以将方法分散到各个插件中,按需加载各个模块。从软件工程的角度来看,这种方案会更好,动态部署,并行开发,更高的编译效率,但这种方案对已有的大项目来讲,重构起来影响非常大,实现成本也相对较高</li>
<li>使用ProGuard等其它优化工具清除项目中无用方法,第三方库中的方法进行清除处理,这个可以作为优化的手段,但对于大型应用上,并不能从根本上解决方法数的问题</li>
</ul>
<p>上面也提到,在高版本上,LinearAlloc方法区的空间已经扩大了很多,所以LinearAlloc的问题很少会再出现。采用dex的拆分以及插件化后并按需加载,对避免LinearAlloc的问题也更有益。</p>
<a id="more"></a>
<h3 id="各个公司所采取的方案"><a href="#各个公司所采取的方案" class="headerlink" title="各个公司所采取的方案"></a><strong>各个公司所采取的方案</strong></h3><ul>
<li>美团点评、手Q、微信采用dex拆分方案</li>
</ul>
<ul>
<li>携程采用插件化方案</li>
</ul>
<ul>
<li>手淘近期发布的Atlas,采用了容器化的概念,所有业务的bundle运行在一个容器中,按需加载,也可以看做是一种高级的插件化</li>
</ul>
<p>下面主要介绍一下dex方案和插件化的主要方案流程和遇到的问题</p>
<h2 id="Dex方案"><a href="#Dex方案" class="headerlink" title="Dex方案"></a>Dex方案</h2><h3 id="如何拆分Dex"><a href="#如何拆分Dex" class="headerlink" title="如何拆分Dex"></a><strong>如何拆分Dex</strong></h3><p>官方已经推出<a href="https://developer.android.com/tools/building/multidex.html" target="_blank" rel="external">Google Multidex</a>方,但官方的问题在于:如果DEX文件过大时,处理时间就会越长,很容易引发ANR。因此通常会采取动态加载dex的方式,并尽量保持首次加载的mainDex中的类尽量少。</p>
<p><strong>mainDex</strong>:主要是一些Android的基础组件和类,比如<code>Application</code>、<code>ContentProvider</code>、<code>Service</code>、<code>Receiver</code>以及必要的<code>Activity</code>和其依赖集。如果mainDex还包括应用的首页等界面,那么也要将相关的依赖都放到mainDex中。</p>
<h3 id="如何动态加载Dex"><a href="#如何动态加载Dex" class="headerlink" title="如何动态加载Dex"></a><strong>如何动态加载Dex</strong></h3><p>dex拆分完了,也解决了首次加载mainDex的问题,那么如何动态加载其它dex呢?比如,加入我们在mainDex中放入了首页,这时候,点击某一个入口,而该入口的业务放在了其它的Dex中,这时候如何加载呢?</p>
<p>通常的处理肯定都是先判断是否已经加载过,如果没加载就先展示一个友好的loading界面然后去加载。思路确实是这样,具体的实现因人而异。</p>
<p><strong>微信、QQ空间的方案</strong></p>
<p>在app进入首页之前,在异步线程中加载</p>
<p>加载逻辑这边主要判断是否已经dexopt,若已经dexopt,即放在attachBaseContext加载,反之放于地球中用线程加载。怎么判断?其实很低级,因为在微信中,若判断revision改变,即将dex以及dexopt目录清空。只需简单判断两个目录dex名称、数量是否与配置文件的一致。</p>
<p><strong>美团点评的方案</strong></p>
<p>按需加载</p>
<p>在Activity的启动过程,修改Instrumentation中的逻辑,因为Instrumentation有关Activity启动相关的方法大概有:execStartActivity、newActivity等等,在这些方法中添加代码逻辑进行判断这个Class是否加载了,如果加载则直接启动这个Activity,如果没有加载完成则启动一个等待的Activity显示给用户,然后在这个Activity中等待后台Secondary DEX加载完成,完成后自动跳转到用户实际要跳转的Activity。</p>
<h2 id="插件化方案"><a href="#插件化方案" class="headerlink" title="插件化方案"></a><strong>插件化方案</strong></h2><p>携程采用了这种方案,个人觉得从开发的角度来看,该方案其实是一种比较干净的方案。每个业务团队开发独立的apk,互不干涉,较高的编译效率。因此,解决的不仅仅是方法数和LinearAlloc的问题,插件化的难点在于资源的编译和加载,最终也会涉及到multidex。下面就简单介绍一下这种方案的一些关键技术点。</p>
<p>我们知道一个Android应用主要以APK方式安装,每一个APK主要的部分为代码和资源,如何编译进去,以及如何加载它们是关键的两个问题,所以插件化主要会涉及到两个阶段的问题:</p>
<ul>
<li>编译期:资源和代码的编译</li>
<li>运行时:资源和代码的加载</li>
</ul>
<h3 id="编译期"><a href="#编译期" class="headerlink" title="编译期"></a>编译期</h3><h4 id="资源的编译"><a href="#资源的编译" class="headerlink" title="资源的编译"></a>资源的编译</h4><p>采用AAPT(资源编译依赖一个强大的命令行工具)对资源的编译流程进行改造。</p>
<ul>
<li>可以对一个已存在的apk包作为依赖资源参与编译,解决资源编译问题</li>
</ul>
<ul>
<li>通过aapt可以给每个子apk中的资源分配不同头字节PackageID,解决多个项目的资源冲突问题</li>
</ul>
<h4 id="代码的编译"><a href="#代码的编译" class="headerlink" title="代码的编译"></a>代码的编译</h4><p>对Java代码的编译大家比较熟悉,只需要注意以下几个问题即可:</p>
<ul>
<li><p>classpath</p>
<p>Java源码编译中需要找齐所有依赖项,classpath就是用来指定去哪些目录、文件、jar包中寻找依赖。</p>
</li>
<li><p>混淆</p>
<p>安全需要,Android工程通常都会被混淆,混淆的原理和配置可参考Proguard手册。</p>
</li>
</ul>
<h3 id="运行期"><a href="#运行期" class="headerlink" title="运行期"></a><strong>运行期</strong></h3><h4 id="资源加载"><a href="#资源加载" class="headerlink" title="资源加载"></a>资源加载</h4><p>平时使用到的资源都是通过AssetManager类和Resources类来访问的。获取它们的方法位于Context类中,因此复写这两个方法即可达到访问指定资源的目的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">private final Resources mResources;</div><div class="line"></div><div class="line">@Override</div><div class="line">public AssetManager getAssets() {</div><div class="line"> return getResources().getAssets();</div><div class="line">}</div><div class="line"></div><div class="line">@Override</div><div class="line">public Resources getResources() {</div><div class="line"> return mResources;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>另外通过要需要接管所有Activity、Service等组件的创建(以便对Resources类进行替换)。具体可以通过篡改<code>mInstrumentation</code>为自己的<code>InstrumentationHook</code>,<code>mInstrumentation</code>是负责创建<code>Activity</code>等组件的类,每次创建Activity的时候把它的mResources类替换为DelegateResources。更多关于Activity的创建过程可以搜索相关资料,需要对Activity的创建和启动进行定制的时候,都会涉及到该部分的知识。</p>
<h4 id="代码加载"><a href="#代码加载" class="headerlink" title="代码加载"></a><strong>代码加载</strong></h4><p>这部分其实就是上面的dex装载方案,就不多介绍了</p>
<h3 id="插件化的好处"><a href="#插件化的好处" class="headerlink" title="插件化的好处"></a>插件化的好处</h3><ul>
<li>各业务的代码和项目控制上做到了高内聚低耦合,极大降低了沟通成本,提高了工作效率</li>
<li>解决了方法数的问题</li>
<li>大大提高编译速度</li>
<li>按需加载,启动速度优化,告别黑屏和启动ANR</li>
<li>可以作为hotFix方案,不仅支持class,也支持资源fix</li>
</ul>
<p>携程的插件化方案也已经开源:<a href="https://github.com/CtripMobile/DynamicAPK" target="_blank" rel="external">https://github.com/CtripMobile/DynamicAPK</a></p>
<p>其它的插件化方案也有多种,具体的可以参考Trinea的文章<a href="http://www.trinea.cn/android/android-plugin/" target="_blank" rel="external">Android 插件化 动态升级</a></p>
<h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>简单的总结下,这里介绍了一些中大型应用会遇到的空间和LinearAlloc空间的问题,最难的是资源的处理和装载,需要利用android提供的一些编译工具对编译流程进行改造。绕不过的是multidex,也会涉及到Activity等组件的初始化流程,Activity的启动过程这块的知识也是比较重要。本篇文章只是对目前插件化和dex的实现方案做了简要的概述,更细节的部分还是建议看各个公司发布的相关文章。</p>
<h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><p><a href="http://tech.meituan.com/mt-android-auto-split-dex.html" target="_blank" rel="external">美团Android DEX自动拆包及动态加载简介</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MzAwMTcwNTE0NA==&mid=400217391&idx=1&sn=86181541ce0164156dfab135ed99bb5c&scene=0&key=b410d3164f5f798e61a5d4afb759fa38371c8b119384c6163a30c28163b4d4d5f59399f2400800ec842f1d0e0ffb84af&ascene=0&uin=MjExMjQ&pass_ticket=Nt5Jaa28jjFxcQO9o%2BvQiXX%2B0iXG5DlZlHNW97Fk1Ew%3D" target="_blank" rel="external">携程插件化和动态加载实践</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207151651&idx=1&sn=9eab282711f4eb2b4daf2fbae5a5ca9a&scene=4#wechat_redirect" target="_blank" rel="external">Android拆分与加载Dex的多种方案对比</a></p>
<p><a href="http://www.trinea.cn/android/android-plugin/" target="_blank" rel="external">Android 插件化 动态升级</a></p>
]]></content>
<summary type="html">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>随着应用越来越大,很多大型应用都会遇到方法数的爆棚以及方法信息存储区的问题,该篇文章主要以这两种问题为背景,介绍dex拆分、加载以及插件化方案的一些技术点。</p>
<h3 id="65536-与-INSTALL-FAILED-DEXOPT"><a href="#65536-与-INSTALL-FAILED-DEXOPT" class="headerlink" title="65536 与 INSTALL_FAILED_DEXOPT"></a>65536 与 INSTALL_FAILED_DEXOPT</h3><ol>
<li><p>生成的apk在android 2.3或之前的机器上无法安装,提示<code>INSTALL_FAILED_DEXOPT</code></p>
</li>
<li><p>方法数量过多,编译时出错,提示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536</div></pre></td></tr></table></figure>
</li>
</ol>
<p><strong>Android 2.3 INSTALL_FAILED_DEXOPT 的问题</strong></p>
<p>该问题由dexopt的LinearAlloc限制引起的, 在Gingerbread或者以下系统LinearAllocHdr分配空间只有5M大小的。</p>
<blockquote>
<p>Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。 目前Android 4.x提高到了8MB或16MB,在很大程度上解决了这个问题。</p>
</blockquote>
<p><strong>超过最大方法数限制的问题</strong></p>
<p>该问题是由DEX文件格式限制。一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是Android打包的DEX过程中, 单个DEX文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536;</p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a><strong>解决方案</strong></h3><ul>
<li>Android系统的LinearAlloc空间大小,在高版本上已经提升了,所以在一定程度上,不会出现这个问题,Facebook则采用了一种hack的方式,直接修改虚拟机的LinearAlloc空间大小:<a href="https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920/">Dalvik patch for Facebook for Android</a></li>
<li>拆分Dex,官方有提供方案<a href="https://developer.android.com/tools/building/multidex.html">Google Multidex</a>,让一个apk里支持多个.dex文件,这样就可以突破65536的限制,但该方案有一定的弊端,下面会提到</li>
<li>插件化,将整个工程的划分为Host+多个插件的形式,这样就可以将方法分散到各个插件中,按需加载各个模块。从软件工程的角度来看,这种方案会更好,动态部署,并行开发,更高的编译效率,但这种方案对已有的大项目来讲,重构起来影响非常大,实现成本也相对较高</li>
<li>使用ProGuard等其它优化工具清除项目中无用方法,第三方库中的方法进行清除处理,这个可以作为优化的手段,但对于大型应用上,并不能从根本上解决方法数的问题</li>
</ul>
<p>上面也提到,在高版本上,LinearAlloc方法区的空间已经扩大了很多,所以LinearAlloc的问题很少会再出现。采用dex的拆分以及插件化后并按需加载,对避免LinearAlloc的问题也更有益。</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="编译" scheme="http://www.lightskystreet.com/categories/Android/%E7%BC%96%E8%AF%91/"/>
<category term="Android,编译" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E7%BC%96%E8%AF%91/"/>
</entry>
<entry>
<title>Android性能优化-减小图片下载大小</title>
<link href="http://www.lightskystreet.com/2016/10/19/android-optimize-image-download-size/"/>
<id>http://www.lightskystreet.com/2016/10/19/android-optimize-image-download-size/</id>
<published>2016-10-19T02:00:29.000Z</published>
<updated>2016-10-19T14:03:21.000Z</updated>
<content type="html"><![CDATA[<p>原文链接 <a href="https://developer.android.com/topic/performance/network-xfer.html" target="_blank" rel="external">https://developer.android.com/topic/performance/network-xfer.html</a></p>
<h3 id="内容概要"><a href="#内容概要" class="headerlink" title="内容概要"></a><strong>内容概要</strong></h3><ol>
<li><p>理解图片的格式 PNG JPG WebP</p>
</li>
<li><p>如何选择一种图片格式</p>
</li>
<li><p>确定准确质量值</p>
</li>
<li><p>服务端的尺寸大小</p>
<p></p>
</li>
</ol>
<h1 id="减小图像下载大小"><a href="#减小图像下载大小" class="headerlink" title="减小图像下载大小"></a>减小图像下载大小</h1><p>大多数流量传输都包含图像。 因此,你制作的下载的图片越小,就会为用户提供更好的网络体验。 本篇文章提供了让你的图像文件更小以及更加网络友好的指导。</p>
<h2 id="理解图像的格式"><a href="#理解图像的格式" class="headerlink" title="理解图像的格式"></a>理解图像的格式</h2><p>Android应用通常使用以下某种或多种文件格式的图片:PNG,JPG和WebP。 对于每种格式,你都可以采取措施缩小图片大小。</p>
<h3 id="PNG"><a href="#PNG" class="headerlink" title="PNG"></a>PNG</h3><p>缩小PNG文件的关键是减少构成图像的每行像素中使用的唯一颜色数。通过使用更少的颜色,可以提高在其它管道中的压缩能力。</p>
<p>减少独唯一颜色的数量会取得显着效果的原因是,PNG压缩效果基于一个水平方向相邻像素颜色变化程度的函数。因此,减少PNG图像每行中唯一颜色数量可以减少文件大小。</p>
<p>当决定是否采用这种策略时,你应该记住,减少唯一颜色的数量实际上等于对图像应用了有损编码。然而,编码工具可能不会判断一个细小的偏差导致多糟糕的视觉效果。因此,为了保证有效压缩和可接受的图像质量的平衡,你应该手动执行此工作,</p>
<p>有两个特别有用的方法你可以采取:优化索引格式和应用矢量量化。</p>
<h4 id="优化索引格式"><a href="#优化索引格式" class="headerlink" title="优化索引格式"></a>优化索引格式</h4><p>任何减少颜色的尝试都应该首先尝试优化颜色,以便将图像导出为PNG时可以使用INDEXED格式。 INDEXED颜色模式会选择最佳的256色彩,并用索引将所有像素值替换到调色板中。 结果是从1600万色彩减少到仅256色彩:等同于从每像素3(没有透明度)或4(具有透明度)字节减少到每像素1字节。这种变化向减少文件迈出了重大的一步。</p>
<a id="more"></a>
<p><img src="/img/android-optimize/indexed-image.png"> </p>
<p>图片 1.一个图像在转换为INDEXED的前后对照</p>
<p>Figure 2 展示了图像1中的调色板</p>
<p><img src="/img/android-optimize/platte.png"> </p>
<p>将图像表示为调色图像对于显着改善文件大小有很大帮助,因此,如果你的大多数图片都可以转换,可以好好研究一下。</p>
<p>当然,并不是每个图像都可以用256种色彩准确表示。 例如,某些图像可能需要257,310,512或912种颜色才能正确显示。 在这种情况下,矢量化会可能会有帮助。</p>
<h4 id="矢量化"><a href="#矢量化" class="headerlink" title="矢量化"></a>矢量化</h4><p>矢量化(VQ)也许是对创建索引图像的过程更好地描述。 VQ会对多维数进行舍入处理。 在此过程中,你图片中的所有颜色都将根据相似性进行分组。 对于给定的组,该组中的所有颜色都将被单个<em>中心点</em>值替换,这样可以最大程度地减少该单元格中的颜色错误(如果使用Voronoi术语,则为“site”)。 在图3中,绿色点表示输入颜色,而红色点是替换输入颜色的中心点。 每个单元格由蓝线界定。</p>
<p><img src="/img/android-optimize/vector-quantization.gif"> </p>
<p>图3 为图像中的颜色进行矢量化</p>
<p>将VQ应用于图像会减少了唯一颜色的数量,用视觉质量上“相当接近”的单一颜色替换每组颜色。</p>
<p>此技术也允许你为图像定义唯一颜色的最大数量。 例如,图4显示了1670万种颜色(每像素24位,或bpp)的鹦鹉头像,旁边是一个只使用16(3bpp)唯一颜色的版本。</p>
<p><img src="/img/android-optimize/vq.png"> </p>
<p>你可以明显看到有一些质量的损失; 大多数渐变颜色已经被替换,赋予图像带状效果。 因此该图片需要超过16种唯一的色彩。</p>
<p>在管道中设置VQ的步骤可以帮助你对图像中使用的唯一色彩的真实数量有更好地了解,并且可以帮助你显著地减小它们。 有很多可用的工具来帮助你实现此技术。</p>
<h3 id="JPG"><a href="#JPG" class="headerlink" title="JPG"></a>JPG</h3><p>如果你使用的是JPG图片,这里有几种只做很小的改变就可以显著节省文件大小的方法:</p>
<ul>
<li>通过不同的编码方法生成较小的文件(不影响质量)。</li>
<li>稍微调整质量以得到更好的压缩。</li>
</ul>
<p>执行这些策略通常可以将文件大小减少高达25%。</p>
<p>选择工具时,请记住照片导出工具会将不必要的元数据(如GPS信息)插入到图像中。为了最小化,你可以尝试利用现有工具删除这些信息。 </p>
<h3 id="WebP"><a href="#WebP" class="headerlink" title="WebP"></a>WebP</h3><p>WebP是Android 4.2.1(API 17)支持的较新的图像格式。 这种格式为网络图像提供了卓越的无损和有损压缩。 使用WebP,开发人员可以创建更小,更丰富的图像。 WebP无损图像文件平均比PNG小 <a href="https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study#conclusions" target="_blank" rel="external">26% </a>。 这些图像文件还支持透明度(也称为alpha通道),成本只有 <a href="https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study#results" target="_blank" rel="external">22%</a> 的字节。</p>
<p>WebP有损图像比同等 <a href="https://en.wikipedia.org/wiki/Structural_similarity" target="_blank" rel="external">SSIM</a> 质量指数下的JPG图像小 <a href="https://developers.google.com/speed/webp/docs/webp_study#experiment_1_webp_vs_jpeg_at_equal_ssim_index" target="_blank" rel="external">25-34%</a> 。 对于可接受有损RGB压缩的场景,有损WebP还能支持透明度,产生的文件大小通常比PNG小3倍。</p>
<p>有关WebP的详细信息,请访问 <a href="https://developers.google.com/speed/webp/" target="_blank" rel="external">WebP</a>。</p>
<h2 id="选择一种格式"><a href="#选择一种格式" class="headerlink" title="选择一种格式"></a>选择一种格式</h2><p>不同的图像格式适用于不同类型的图像。 JPG和PNG的压缩过程有很大差别,产生的效果差别也很大。</p>
<p>决定使用PNG还是JPG,通常归结于图像本身的复杂性。图5显示了两个图像,由于开发者应用了不同的压缩方案而导致很大的差别。左侧的图像有许多小细节,因此使用JPG压缩效率更高。右侧的图像,很多区域具有相同颜色,使用PNG压缩更有效。 </p>
<p><img src="/img/android-optimize/jpg-vs-png.png"> </p>
<p>图5 JPG和PNG的适用场景</p>
<p>WebP格式可以支持有损和无损模式,是PNG和JPG的理想替代品。唯一要记住的是,它只对运行Android 4.2.1(API 17)及更高版本的设备提供支持。不过幸运的是,<a href="https://developer.android.com/about/dashboards/index.html#Platform" target="_blank" rel="external">大多数设备</a>都满足这一要求。</p>
<p>图6提供了一个简单的图示来帮助你决定使用哪种压缩方案。</p>
<p><img src="/img/android-optimize/selector.png"> </p>
<p>Figure 6. 如何决定一种压缩方案</p>
<h2 id="确定最佳质量值"><a href="#确定最佳质量值" class="headerlink" title="确定最佳质量值"></a>确定最佳质量值</h2><p>有几种技术可以用来实现压缩和图像质量之间的正确权衡。有一种技术使用标量值,因此仅适用于JPG和WebP。另一种技术利用了Butteraugli库,并可用于所有图像格式。</p>
<h3 id="标量值(仅限JPG和WebP)"><a href="#标量值(仅限JPG和WebP)" class="headerlink" title="标量值(仅限JPG和WebP)"></a>标量值(仅限JPG和WebP)</h3><p>JPG和WebP的强大来自于你可以使用标量值来平衡质量和文件大小。诀窍是找出图像的正确的质量值。太低的质量水平虽然产生文件小,但以牺牲图像质量为代价。太高的质量水平会增加文件大小,对用户不友好。</p>
<p>最直接的解决方案是选择一些非最大值进行尝试。但是,请注意,质量值对每个图像的影响不同。例如,75%的质量在大多数图像上可能看起来很好,但在另一些图片上效果可能不好。你应该使用图像的代表性样本对选择的最大值进行测试。此外,要确保你是对原始图像执行所有测试,而不是压缩版本。</p>
<p>对于每天上传和发送数百万个JPG的大型媒体应用程序来说,手动调整每个资源是不切实际的。你可以根据图像类别指定几个不同的质量级别来解决这个问题。例如,可以将缩略图的质量设置为35%,因为较小的图像隐藏了更多的压缩伪影(伪影不是很好理解)。</p>
<h3 id="Butteraugli"><a href="#Butteraugli" class="headerlink" title="Butteraugli"></a>Butteraugli</h3><p>Butteraugli是一个库,用于测试图像的视觉误差阈值:观察者开始注意到图像质量下降的点。换句话说,这个项目试图将压缩图像的失真量化。</p>
<p>Butteraugli允许你定义视觉质量的目标,然后运行PNG,JPG,WebP有损和WebP无损压缩。然后,你可以选择文件大小和Butteraugli级别之间效果最佳的图像。图7示出了在视觉失真以至于用户可以感知到之前,如何使用Butteraugli来找到最低的JPG质量水平;最终文件大小减少了大约65%。</p>
<p><img src="/img/android-optimize/butteraugli.png"> </p>
<p>图7.应用Butteraugli技术之前和之后的图像。</p>
<p>Butteraugli允许你基于输出或输入进行处理。也就是说,你可以在用户感觉到图像出现明显失真之前找到最低的质量设置,或者你可以依次设置图像失真级别去观察对应的质量水平。</p>
<h2 id="服务端的尺寸"><a href="#服务端的尺寸" class="headerlink" title="服务端的尺寸"></a>服务端的尺寸</h2><p>在服务器上仅存有一种分辨率的图像的方式是很便利的。当设备访问映像时,服务器直接返回给设备该分辨率的图片。</p>
<p>虽然这个解决方案方便了开发者,但对用户来说可能很痛苦,因为这种方案迫使用户下载比实际需要更多的数据。你应该存储多个大小的图像,并根据合适的使用场景提供不同的图像。例如,对于缩略图,服务应该提供缩略图图像而不是全尺寸的版本,这样可以节省很多网络带宽。</p>
<p>这种方法对于下载速度是有利的,并且降低了使用有限或按数据量收费用户的成本。图像在设备和主存储器上也会占用更少的空间。在大图像(例如4K图像)的情况下,这种方式还减少了设备在加载图像之前调整大小的时间。</p>
<p>实现此方法需要有一个后端图像服务,以提供具有适当缓存的各种分辨率的图像。有几种现有的服务可以帮助你,例如,<a href="https://cloud.google.com/appengine/" target="_blank" rel="external">App Engine</a> 自带了调整图片大小的功能。</p>
]]></content>
<summary type="html">
<p>原文链接 <a href="https://developer.android.com/topic/performance/network-xfer.html">https://developer.android.com/topic/performance/network-xfer.html</a></p>
<h3 id="内容概要"><a href="#内容概要" class="headerlink" title="内容概要"></a><strong>内容概要</strong></h3><ol>
<li><p>理解图片的格式 PNG JPG WebP</p>
</li>
<li><p>如何选择一种图片格式</p>
</li>
<li><p>确定准确质量值</p>
</li>
<li><p>服务端的尺寸大小</p>
<p></p>
</li>
</ol>
<h1 id="减小图像下载大小"><a href="#减小图像下载大小" class="headerlink" title="减小图像下载大小"></a>减小图像下载大小</h1><p>大多数流量传输都包含图像。 因此,你制作的下载的图片越小,就会为用户提供更好的网络体验。 本篇文章提供了让你的图像文件更小以及更加网络友好的指导。</p>
<h2 id="理解图像的格式"><a href="#理解图像的格式" class="headerlink" title="理解图像的格式"></a>理解图像的格式</h2><p>Android应用通常使用以下某种或多种文件格式的图片:PNG,JPG和WebP。 对于每种格式,你都可以采取措施缩小图片大小。</p>
<h3 id="PNG"><a href="#PNG" class="headerlink" title="PNG"></a>PNG</h3><p>缩小PNG文件的关键是减少构成图像的每行像素中使用的唯一颜色数。通过使用更少的颜色,可以提高在其它管道中的压缩能力。</p>
<p>减少独唯一颜色的数量会取得显着效果的原因是,PNG压缩效果基于一个水平方向相邻像素颜色变化程度的函数。因此,减少PNG图像每行中唯一颜色数量可以减少文件大小。</p>
<p>当决定是否采用这种策略时,你应该记住,减少唯一颜色的数量实际上等于对图像应用了有损编码。然而,编码工具可能不会判断一个细小的偏差导致多糟糕的视觉效果。因此,为了保证有效压缩和可接受的图像质量的平衡,你应该手动执行此工作,</p>
<p>有两个特别有用的方法你可以采取:优化索引格式和应用矢量量化。</p>
<h4 id="优化索引格式"><a href="#优化索引格式" class="headerlink" title="优化索引格式"></a>优化索引格式</h4><p>任何减少颜色的尝试都应该首先尝试优化颜色,以便将图像导出为PNG时可以使用INDEXED格式。 INDEXED颜色模式会选择最佳的256色彩,并用索引将所有像素值替换到调色板中。 结果是从1600万色彩减少到仅256色彩:等同于从每像素3(没有透明度)或4(具有透明度)字节减少到每像素1字节。这种变化向减少文件迈出了重大的一步。</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="性能优化" scheme="http://www.lightskystreet.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Android,性能优化" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>Android性能优化-线程性能优化</title>
<link href="http://www.lightskystreet.com/2016/10/18/android-optimize-thread/"/>
<id>http://www.lightskystreet.com/2016/10/18/android-optimize-thread/</id>
<published>2016-10-17T16:00:00.000Z</published>
<updated>2016-10-18T12:40:41.000Z</updated>
<content type="html"><![CDATA[<p>原文链接:<a href="https://developer.android.com/topic/performance/threads.html" target="_blank" rel="external">Better Performance through Threading</a> </p>
<h1 id="线程的性能"><a href="#线程的性能" class="headerlink" title="线程的性能"></a>线程的性能</h1><p>熟练使用Android上的线程可以帮助你提高应用程序的性能。 本篇文章讨论了使用线程的几个方面:使用UI或主线程; 应用程序生命周期和线程优先级之间的关系; 以及平台提供的帮助管理线程复杂性的方法。 在每一部分,本篇都描述了潜在的陷阱以及如何避免它们的策略。</p>
<h2 id="主线程"><a href="#主线程" class="headerlink" title="主线程"></a>主线程</h2><p>当用户启动你的应用程序时,Android会创建一个新的 <a href="https://developer.android.com/guide/components/fundamentals.html" target="_blank" rel="external">Linux process</a> 以及一个执行线程。 这个main线程,也称为UI线程,负责屏幕上发生的一切。 了解其工作原理可以帮助你使用主线程设计你的应用程序以获得最佳性能。</p>
<h3 id="内部细节"><a href="#内部细节" class="headerlink" title="内部细节"></a>内部细节</h3><p>主线程具有非常简单的设计:它的唯一工作就是从线程安全的工作队列中取出并执行工作块,直到应用程序被终止。 框架从各个地方生成一些这些工作块。 这些地方包括与生命周期信息,用户事件(如输入)或来自其他应用程序和进程的事件相关联的回调。 此外,应用程序还可以在不使用框架的情况下显式地将工作块加入队列。</p>
<p>应用程序执行的<a href="https://www.youtube.com/watch?v=qk5F6Bxqhr4&index=1&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE" target="_blank" rel="external">任何代码块</a>都会被绑定到一个事件回调上,例如输入,布局填充或绘制。 当某个时间触发一个事件时,事件发生的所在线程会将事件加入到主线程的消息队列。 之后主线程可以处理该事件。</p>
<p>当发生动画或屏幕更新时,系统试图每16ms左右执行一个工作块(负责绘制屏幕),以便以<a href="https://www.youtube.com/watch?v=CaMTIgxCSqU&index=62&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE" target="_blank" rel="external">每秒60帧</a>的速度平滑地渲染。 为了让系统达到这个目标,一些操作必须发生在主线程上。 但是,当主线程的消息队列包含太多或太耗时的任务,为了让主线程能够在16ms内完成工作,你应将这些任务移到工作线程中去。 如果主线程不能在16ms内完成执行的代码块,则用户可能感觉到卡顿或UI响应较慢。 如果主线程阻塞大约5秒钟,系统将显示“<a href="https://developer.android.com/training/articles/perf-anr.html" target="_blank" rel="external">(ANR)</a>”对话框,允许用户直接关闭应用程序。</p>
<p>从主线程移除多个或耗时的任务,以便它们不会干扰到平滑渲染和对用户输入的快速响应,是你在应用程序中采用线程的最大原因。</p>
<h2 id="线程和UI对象的引用"><a href="#线程和UI对象的引用" class="headerlink" title="线程和UI对象的引用"></a>线程和UI对象的引用</h2><p>按照设计,<a href="https://www.youtube.com/watch?v=tBHPmQQNiS8&index=3&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE" target="_blank" rel="external">Android UI对象不是线程安全的</a>。 应用程序应该在主线程上创建,使用和销毁UI对象。 如果尝试修改或甚至引用除主线程之外的线程中的UI对象,结果可能是异常,静默失败,崩溃和其他未定义的错误行为。</p>
<p>UI对象引用导致的问题可以划分为两种:显式引用和隐式引用。</p>
<a id="more"></a>
<h3 id="显示引用"><a href="#显示引用" class="headerlink" title="显示引用"></a>显示引用</h3><p>许多非主线程上的任务在最后都会更新UI对象。 但是,如果某一个线程访问视图层级中的对象,可能会导致应用的不稳定性:如果工作线程修改了同时被任何其他线程引用的对象属性(这里都是指UI对象),则结果是不可预测的。</p>
<p>假设一个应用程序在工作线程上直接引用UI对象。 这个UI对象可能包含对一个<code>View</code>的引用; 但在工作完成之前,该View被从视图层次结构中删除了。 如果该引用将View对象保留在内存中并对其设置属性,用户并不会看到此对象,因为一旦对象的引用消失,应用程序就会删除该对象。</p>
<p>再举另一个例子,View对象(被工作线程引用)持有包含它们的Activity的引用。 如果该Activity被销毁了,但仍有一个工作的线程直接或间接引用它 - 垃圾收集器将不会回收Activity,直到该工作线程执行完成。</p>
<p>在某些Activity生命周期事件(如屏幕旋转)发生时,某些线程工作可能正在运行。 系统将无法执行垃圾回收,直到正在进行的工作完成。 因此,在内存中可能会有两个Activity对象,直到垃圾回收发生。</p>
<p>考虑到以上场景,我们建议你的应用程序的工作线程中不应该包含对UI对象的显式引用。 避免此类引用可帮助你避免这些类型的内存泄漏,同时避免线程竞争。</p>
<p>在所有情况下,应用程序应该只在主线程上更新UI对象。 如果有多个任务希望更新实际的UI,你应该制定一个策略,允许多个线程交互,最终将结果返回到主线程。</p>
<h3 id="隐式引用"><a href="#隐式引用" class="headerlink" title="隐式引用"></a>隐式引用</h3><p>在以下代码片段中可以看到带有线程对象代码的常见设计缺陷:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">public class MainActivity extends Activity {</div><div class="line"> // …...</div><div class="line"> public class MyAsyncTask extends AsyncTask {</div><div class="line"> @Override protected String doInBackground(Void... params) {...}</div><div class="line"> @Override protected void onPostExecute(String result) {...}</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这段代码的缺陷是将线程对象MyAsyncTask声明为一些Activity的内部类。 这种声明创建一个对Activity对象隐式引用。 因此,该对象持有对Activity的引用,直到线程工作完成,这样会导致所引用的Activity延迟销毁。 这种延迟会给内存带来更大的压力。</p>
<p>解决该问题的直接解决方案是在自己的文件中定义重载类实例,从而移除对Activity的隐式引用。</p>
<p>另一个解决方案是将AsyncTask声明为静态内部类。 这样做也可以消除隐式引用问题,因为静态内部类与普通内部类不同:普通内部类实例需要外部类的实例才可以实例化,并且可以直接访问其包含的方法和字段。 相比之下,静态内部类不需要引用外部类实例,因此它不包含对外部类成员的引用。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">public class MainActivity extends Activity {</div><div class="line"> // …...</div><div class="line"> Static public class MyAsyncTask extends AsyncTask {</div><div class="line"> @Override protected String doInBackground(Void... params) {...}</div><div class="line"> @Override protected void onPostExecute(String result) {...}</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="线程和应用程序以及Activity的生命周期"><a href="#线程和应用程序以及Activity的生命周期" class="headerlink" title="线程和应用程序以及Activity的生命周期"></a>线程和应用程序以及Activity的生命周期</h2><p>应用程序生命周期会对应用程序中线程的工作产生影响。 在Activity被销毁后,你可能需要决定一个线程是否应该持久化。 还应该注意线程优先级和Activity是否在前台或后台运行之间的关系。</p>
<h3 id="持久化的线程"><a href="#持久化的线程" class="headerlink" title="持久化的线程"></a>持久化的线程</h3><p>线程的生命周期大于生成它们的Activity的生命周期。 不管Activity的创建或销毁,线程继续执行,不会被打断。 在一些情况下,这种持久性是不期望的。</p>
<p>考虑一种情况,某个Activity发起了一组线程工作任务,但在工作线程执行完之前该Activity被销毁了。 应用程序应该如何处理那些还在执行的任务?</p>
<p>如果这些任务将来会去更新不再存在的UI,那么这些任务就不应该继续工作。例如,如果该任务是从数据库加载用户信息并更新视图,那么该线程就是不需要的。</p>
<p>相比之下,如果任务组不是完全和UI相关的,还是很有用的。例如,任务组可能在等待下载图片,并将其缓存到磁盘,然后去更新相关的<code>View</code>对象。尽管View对象不再存在,下载和缓存图像的行为仍然是有帮助的,因为用户有可能还会回到这个被销毁的Activity。</p>
<p>手动管理所有线程对象的生命周期可能非常复杂。如果你不能正确地管理它们,你的应用程序可能会遭受内存竞争和性能问题。 <a href="https://developer.android.com/guide/components/loaders.html" target="_blank" rel="external">Loaders</a> 是解决这个问题的一种方案。 <a href="https://developer.android.com/guide/components/loaders.html" target="_blank" rel="external">Loaders</a> 有助于异步加载数据,当configuration变化时仍旧会持久化信息。</p>
<h3 id="线程的优先级"><a href="#线程的优先级" class="headerlink" title="线程的优先级"></a>线程的优先级</h3><p>如<a href="https://developer.android.com/guide/topics/processes/process-lifecycle.html" target="_blank" rel="external">进程和应用生命周期</a>中所述,应用程序线程接收的优先级部分取决于应用在其生命周期所处的阶段。 在应用程序中创建和管理线程时,设置其优先级很重要,这样可以让线程在正确的时间获得正确的优先级。 如果设置太高,你的线程可能会打断UI线程和渲染线程,导致你的应用程序丢帧。 如果设置太低,可能会导致你的异步任务(如图像加载)比它们实际需要的慢。</p>
<p>每次你创建一个线程,你应该调用 <code>setThreadPriority()</code>方法。 系统的线程调度器程会优先选择优先级较高的线程,并根据需要权衡这些优先级,最终完成所有的工作。 通常,<a href="https://www.youtube.com/watch?v=NwFXVsM15Co&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&index=9" target="_blank" rel="external">前台组线程大约占用来设备总执行时间的95%</a>,而后台组大约占5%。</p>
<p>系统也会通过Process类为每个线程分配其自己的优先级值。</p>
<p>默认情况下,系统将线程的优先级设置为与创建它的线程相同的优先级和组成员资格。 但是,你可以通过使用<code>setThreadPriority()</code>明确调整线程优先级。</p>
<p> <code>Process</code>类通过提供一组常量来帮助你降低分配优先级的复杂性,你可以使用这些常量来设置线程优先级。 例如,<a href="https://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_DEFAULT" target="_blank" rel="external">THREAD_PRIORITY_DEFAULT</a> 表示线程的默认值。 对于不那么紧急执行的工作线程,你应将其优先级设置为<a href="https://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_BACKGROUND" target="_blank" rel="external">THREAD_PRIORITY_BACKGROUND</a> 。</p>
<p>你也可以使用 <a href="https://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_LESS_FAVORABLE" target="_blank" rel="external">THREAD_PRIORITY_LESS_FAVORABLE</a> 和 <a href="https://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_MORE_FAVORABLE" target="_blank" rel="external">THREAD_PRIORITY_MORE_FAVORABLE</a> 常量作为增量值来设定相对优先顺序。 所有这些枚举状态和修饰符的列表出现在<code>THREAD_PRIORITY_AUDIO</code> 类的参考文档中。 有关管理线程的更多信息,请参阅有 <code>Thread</code> and <code>Process</code> 的参考文档。</p>
<p><a href="https://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_AUDIO" target="_blank" rel="external">https://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_AUDIO</a></p>
<h2 id="线程的帮助类"><a href="#线程的帮助类" class="headerlink" title="线程的帮助类"></a>线程的帮助类</h2><p>框架为线程提供了和java相同的类和原始类,例如<code>Thread</code> 和<code>Runnable</code> 类。 为了帮助减少与开发Android线程应用程序相关的门槛,框架提供了一组帮助类。 每个助手类在性能上都有一些细微差别,以便去处理特定的线程问题。 对错误的场景使用了错误的类可能会导致性能问题。</p>
<h3 id="AsyncTask-类"><a href="#AsyncTask-类" class="headerlink" title="AsyncTask 类"></a>AsyncTask 类</h3><p><code>AsyncTask</code>类是一个简单,有用的原始类,可以帮你快速将工作从主线程移动到工作线程。 例如,输入事件可能会触发需要加载bitmap的UI更新。<code>AsyncTask</code>对象可以将bitmap加载和解码任务放到备用线程; 一旦处理完成,<code>AsyncTask</code>对象会返回到主线程上去更新UI。</p>
<p>当使用<code>AsyncTask</code>时,有几个重要的性能方面的问题要记住。 首先,默认情况下,应用程序将其创建的所有<code>AsyncTask</code>对象推送到单个线程中。 因此,它们以串行方式执行,和主线程类似,特别耗时的工作组会阻塞队列。 因此,我们建议你只使用<code>AsyncTask</code>处理持续时间短于5ms的任务。</p>
<p><code>AsyncTask</code>对象也会导致常见的隐式引用问题。 而且,<code>AsyncTask</code>对象也存在显式引用的风险,但通常这种问题比较容易解决。 例如,AsyncTask可能需要对UI对象的引用,以便在<code>AsyncTask</code>回调到主线程时更新UI对象。 在这种情况下,可以使用<code>WeakReference</code>存储对所需UI对象的引用,在<code>AsyncTask</code>回调到主线程时先访问一次该UI对象。 但你需要注意,持有某个对象的<code>WeakReference</code>并不会使该对象变为线程安全的; <code>WeakReference</code>只提供了一个处理显式引用和垃圾回收问题的方法。</p>
<h3 id="HandlerThread-类"><a href="#HandlerThread-类" class="headerlink" title="HandlerThread 类"></a>HandlerThread 类</h3><p>虽然<code>AsyncTask</code>很有用,但对于你的线程问题,它可能<a href="https://www.youtube.com/watch?v=adPLIAnx9og&index=5&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE" target="_blank" rel="external">不会总是正确的解决方案</a>。相反,你可能需要一种更传统的方法来在长时间运行的线程上执行一个工作块,并且有一些能力来手动管理该工作流。</p>
<p>我们考虑从Camera对象获取预览帧的场景。当你注册了相机预览事件,将会在<code>onPreviewFrame()</code>回调中收到它们,该回调会在调用它的事件线程上触发。如果这个回调在UI线程上触发,处理大量像素数组的任务将干扰渲染和事件处理工作。<code>AsyncTask</code>也会有同样的问题,<code>AsyncTask</code>会串行地执行任务,容易受阻塞(这个高版本已经使用线程池了)。</p>
<p>这种情况使用<code>HandlerThread</code>更合适:<code>HandlerThread</code>实际上是一个长时间运行的线程,它从队列中抓取工作,并对其进行操作。在这个例子中,当你的应用程序将<code>Camera.open()</code>命令委托给HandlerThread上的一个工作块时,相关的<code>onPreviewFrame()</code>回调会落在HandlerThread上,而不是UI或AsyncTask线程。所以,如果你要对像素进行长时间的操作,这可能是一个更好的解决方案。</p>
<p>当你的应用程序使用<code>HandlerThread</code>创建一个线程时,不要忘记<a href="https://www.youtube.com/watch?v=NwFXVsM15Co&index=9&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE" target="_blank" rel="external">根据工作类型设置线程的优先级</a>。 记住,CPU只能并行处理少数线程。当所有其他线程都在争取资源时, 设置优先级有助于系统知道如何正确的调度这项工作。</p>
<h3 id="ThreadPoolExecutor-类"><a href="#ThreadPoolExecutor-类" class="headerlink" title="ThreadPoolExecutor 类"></a>ThreadPoolExecutor 类</h3><p>有些类型的工作是高度并行,分布式的。例如,为8百万像素图像的每个8×8块计算滤波。创建这种量级的工作,AsyncTask和HandlerThread都不合适。 AsyncTask的单线程性质将所有线程池工作转换为线性系统。另一方面,使用HandlerThread类将需要程序员手动管理一组线程之间的负载平衡。</p>
<p>这种情况,使用<code>ThreadPoolExecutor</code>类来处理会更容易。该类可以管理一组线程的创建,优先级设置,并权衡分配到这些线程的任务如何处理。随着工作负载增加或减少,该类会自动启动或销毁线程来适应工作负载。</p>
<p>此类还可以帮助你的应用程序创建最佳线程数。当在构造一个<code>ThreadPoolExecutor</code>对象时,可以设置最小和最大线程数。随着<code>ThreadPoolExecutor</code>的负载增加,该类将考虑初始化的最小和最大线程数,并考虑待处理的工作量。基于这些因素,<code>ThreadPoolExecutor</code>决定在任何给定时间应该有多少线程存活。</p>
<h4 id="你应该创建多少线程?"><a href="#你应该创建多少线程?" class="headerlink" title="你应该创建多少线程?"></a>你应该创建多少线程?</h4><p>虽然从软件层面来看,你的代码有能力创建数百个线程,但这样做可能会造成性能问题。 CPU只有并行处理少量线程的能力;以上提到的都会遇到<a href="https://www.youtube.com/watch?v=NwFXVsM15Co&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&index=9" target="_blank" rel="external">优先级和调度问题</a>。因此,只创建与你的工作负载需要的线程是很重要的。</p>
<p>实际上,许多因素都会对优先级和调度有影响,但你可以选择一个值(比如初始值设为4),并通过 <a href="https://developer.android.com/topic/performance/%E2%80%9D/studio/profile/systrace-commandline.html%E2%80%9D" target="_blank" rel="external">Systrace</a> 进行测试。通过试错的方式来确定可以使用而又不会产生问题的最小线程数。</p>
<p>你需要考虑创建多少线程的另一个原因是线程不是免费的:它们占用内存。每个线程最少消耗64k内存。如果设备上安装了许多应用,该值就会快速添加,特别是在调用栈显著增长的情况下。</p>
<p>许多系统进程和第三方库经常调度自己的线程池。如果你的应用程序可以重用现有的线程池,则此重用能够减少内存和处理资源的竞争来帮助提高性能。</p>
]]></content>
<summary type="html">
<p>原文链接:<a href="https://developer.android.com/topic/performance/threads.html">Better Performance through Threading</a> </p>
<h1 id="线程的性能"><a href="#线程的性能" class="headerlink" title="线程的性能"></a>线程的性能</h1><p>熟练使用Android上的线程可以帮助你提高应用程序的性能。 本篇文章讨论了使用线程的几个方面:使用UI或主线程; 应用程序生命周期和线程优先级之间的关系; 以及平台提供的帮助管理线程复杂性的方法。 在每一部分,本篇都描述了潜在的陷阱以及如何避免它们的策略。</p>
<h2 id="主线程"><a href="#主线程" class="headerlink" title="主线程"></a>主线程</h2><p>当用户启动你的应用程序时,Android会创建一个新的 <a href="https://developer.android.com/guide/components/fundamentals.html">Linux process</a> 以及一个执行线程。 这个main线程,也称为UI线程,负责屏幕上发生的一切。 了解其工作原理可以帮助你使用主线程设计你的应用程序以获得最佳性能。</p>
<h3 id="内部细节"><a href="#内部细节" class="headerlink" title="内部细节"></a>内部细节</h3><p>主线程具有非常简单的设计:它的唯一工作就是从线程安全的工作队列中取出并执行工作块,直到应用程序被终止。 框架从各个地方生成一些这些工作块。 这些地方包括与生命周期信息,用户事件(如输入)或来自其他应用程序和进程的事件相关联的回调。 此外,应用程序还可以在不使用框架的情况下显式地将工作块加入队列。</p>
<p>应用程序执行的<a href="https://www.youtube.com/watch?v=qk5F6Bxqhr4&amp;index=1&amp;list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">任何代码块</a>都会被绑定到一个事件回调上,例如输入,布局填充或绘制。 当某个时间触发一个事件时,事件发生的所在线程会将事件加入到主线程的消息队列。 之后主线程可以处理该事件。</p>
<p>当发生动画或屏幕更新时,系统试图每16ms左右执行一个工作块(负责绘制屏幕),以便以<a href="https://www.youtube.com/watch?v=CaMTIgxCSqU&amp;index=62&amp;list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">每秒60帧</a>的速度平滑地渲染。 为了让系统达到这个目标,一些操作必须发生在主线程上。 但是,当主线程的消息队列包含太多或太耗时的任务,为了让主线程能够在16ms内完成工作,你应将这些任务移到工作线程中去。 如果主线程不能在16ms内完成执行的代码块,则用户可能感觉到卡顿或UI响应较慢。 如果主线程阻塞大约5秒钟,系统将显示“<a href="https://developer.android.com/training/articles/perf-anr.html">(ANR)</a>”对话框,允许用户直接关闭应用程序。</p>
<p>从主线程移除多个或耗时的任务,以便它们不会干扰到平滑渲染和对用户输入的快速响应,是你在应用程序中采用线程的最大原因。</p>
<h2 id="线程和UI对象的引用"><a href="#线程和UI对象的引用" class="headerlink" title="线程和UI对象的引用"></a>线程和UI对象的引用</h2><p>按照设计,<a href="https://www.youtube.com/watch?v=tBHPmQQNiS8&amp;index=3&amp;list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">Android UI对象不是线程安全的</a>。 应用程序应该在主线程上创建,使用和销毁UI对象。 如果尝试修改或甚至引用除主线程之外的线程中的UI对象,结果可能是异常,静默失败,崩溃和其他未定义的错误行为。</p>
<p>UI对象引用导致的问题可以划分为两种:显式引用和隐式引用。</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="性能优化" scheme="http://www.lightskystreet.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Android,性能优化" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>Android性能优化-减小APK大小</title>
<link href="http://www.lightskystreet.com/2016/10/17/android-optimize-apk/"/>
<id>http://www.lightskystreet.com/2016/10/17/android-optimize-apk/</id>
<published>2016-10-16T16:00:00.000Z</published>
<updated>2016-10-16T13:46:25.000Z</updated>
<content type="html"><![CDATA[<p>原文链接:<a href="https://developer.android.com/topic/performance/reduce-apk-size.html" target="_blank" rel="external">Reduce APK Size</a> </p>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。</p>
<h2 id="一-理解APK的结构"><a href="#一-理解APK的结构" class="headerlink" title="一 理解APK的结构"></a>一 理解APK的结构</h2><p>在讨论如何减小apk大小之前,理解apk的结构很有必要。一个APK文件包括一个ZIP 文件,该ZIP包含app的所有文件。包括java 字节码文件,资源文件和一个包含了编译后的资源文件。APK包含以下目录:</p>
<ul>
<li><code>META-INF/</code>:包含了<code>CERT.SF</code> 和 <code>CERT.RSA</code> 签名文件, 以及 <code>MANIFEST.MF</code>manifest 文件.</li>
<li><code>assets/</code>: 包含了app的assets,app可以通过 <code>AssetManager</code> 对象获取到这些资源</li>
<li><code>res/</code>: 包含了那些没有被编译到 <code>resources.arsc</code>的资源</li>
<li><code>lib/</code>: 包含了用于软件处理器的编译代码,该目录包含一个子目录,针对不同平台: <code>armeabi</code>, <code>armeabi-v7a</code>, <code>arm64-v8a</code>, <code>x86</code>, <code>x86_64</code>, and <code>mips</code>.</li>
</ul>
<p>一个APK也包含了下面的文件,但只有 <code>AndroidManifest.xml</code> 是强制性的</p>
<ul>
<li><p><code>resources.arsc</code>: </p>
<p>包含了编译后的资源。该文件包含了 <code>res/values/</code>文件夹下的所有XML内容。打包工具抽取了XML内容,将它编译成二进制格式,并且进行了压缩。该内容包括language strings和styles,以及未直接包含在<code>resources.arsc</code> 文件中的内容路径。比如layout文件和图片。</p>
</li>
<li><p><code>classes.dex</code>: </p>
<p>包含可以被Dalvik/ART 识别,以dex文件格式编译后的代码</p>
</li>
<li><p><code>AndroidManifest.xml</code>: </p>
<p>包含了Android核心mainfest文件。该文件罗列了app名字,版本,访问权限,和引用的library文件。该文件采用二进制XML格式。</p>
<p></p>
</li>
</ul>
<h2 id="二-减少资源的数量和大小"><a href="#二-减少资源的数量和大小" class="headerlink" title="二 减少资源的数量和大小"></a>二 减少资源的数量和大小</h2><p>APK的大小对app的加载速度以及内存的使用和电量消耗都有影响。一种减小APK大小的最简单方法就是减少APK的资源文件数量和大小。也可以移除那些app不再使用的资源,或者使用可扩展的 <code>Drawable</code> 对象替代图片文件。这部分讨论了这些方法,以及其它几种减小app资源以便最终达到减小APK总体大小的其它方法。</p>
<h3 id="移除无用资源"><a href="#移除无用资源" class="headerlink" title="移除无用资源"></a>移除无用资源</h3><p>使用 <a href="https://developer.android.com/studio/write/lint.html" target="_blank" rel="external"><code>lint</code></a> 工具,AndroidStudio中的一个静态的代码分析工具。可以检测<code>res/</code> 目录下那些没有被引用的资源. 当 <code>lint</code>工具发现了项目中潜在的无用资源,就会打印类似如下的信息:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">res/layout/preferences.xml: Warning: The resource R.layout.preferences appears</div><div class="line"> to be unused [UnusedResources]</div></pre></td></tr></table></figure>
<p><strong>注意:</strong> <code>lint</code> 工具不能够扫描<code>assets/</code> 目录, assets 资源是通过反射的方式引用的,或者app中引用的其它library 文件。但lint并不会移除资源,它只会提示它们的存在。</p>
<p>你引入的Libraries有可能引入了无用的资源。Gradle可以通过在 <code>build.gradle</code> 文件中开启<a href="https://developer.android.com/studio/build/shrink-code.html" target="_blank" rel="external"><code>shrinkResources</code></a> 来帮你自动的移除这些资源:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">android {</div><div class="line"> // Other settings</div><div class="line"></div><div class="line"> buildTypes {</div><div class="line"> release {</div><div class="line"> minifyEnabled true</div><div class="line"> shrinkResources true</div><div class="line"> proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>为了使用 <code>shrinkResources</code>,你应该开启code shrinking,在build处理期间, <a href="https://developer.android.com/studio/build/shrink-code.html" target="_blank" rel="external">ProGuard</a> 首先会移除无用的代码但是会保留无用的资源。之后Gradle会移除无用的资源。</p>
<p>关于ProGuard和使用Android Studio帮助你减小APK大小的更多信息,可以参考 <a href="https://developer.android.com/studio/build/shrink-code.html" target="_blank" rel="external">Shrink Your Code and Resources</a>.</p>
<p>在Android Gradle Plugin 0.7以及更高的版本中,你可以声明app支持的配置。Gradle通过 <code>resConfig</code> 和 <code>resConfigs</code> flavors 和 <code>defaultConfig</code> 选项把这些信息传递给构建系统。构建系统会阻止来自其它不受支持的资源出现在APK中,从而减少APK的大小。更多信息可以参考 <a href="https://developer.android.com/studio/build/shrink-code.html#unused-alt-resources" target="_blank" rel="external">Remove unused alternative resources</a>。</p>
<h3 id="最小化使用Libraries中的资源"><a href="#最小化使用Libraries中的资源" class="headerlink" title="最小化使用Libraries中的资源"></a>最小化使用Libraries中的资源</h3><p>在开发App的时候,通常会使用外部libraries去提升app的可用性和功能扩展。比如,你可能会引用<a href="https://developer.android.com/topic/libraries/support-library/index.html" target="_blank" rel="external">Android Support Library</a>去提升老设备的用户体验。或者使用 <a href="https://developers.google.com/android/guides/overview" target="_blank" rel="external">Google Play Services</a> 为app提供自动翻译。</p>
<p>如果一个library被设计用于桌面服务,那么就可能包含很多app不需要的对象和方法。为了只保留library中app需要的代码,如果license许可的话,你需要编辑library文件。你也可以使用一个移动端友好的替代库。</p>
<p><strong>注意:</strong> <a href="https://developer.android.com/studio/build/shrink-code.html" target="_blank" rel="external">ProGuard</a> 可以清除一些从library中导入的不需要的代码。但是不会移除一个 library的内部依赖。</p>
<h3 id="只支持特定的分辨率"><a href="#只支持特定的分辨率" class="headerlink" title="只支持特定的分辨率"></a>只支持特定的分辨率</h3><p>Android支持非常大的设备集,包括各种屏幕密度。 在Android 4.4(API级别19)及更高版本中,框架支持各种分辨率:ldpi,mdpi,tvdpi,hdpi,xhdpi,xxhdpi和xxxhdpi。 虽然Android支持所有这些分辨率,但你不需要导出光栅化资源到每种分辨率。</p>
<p>如果你知道只有一小部分用户使用特定分辨率的设备,请考虑是否需要支持这些分辨率。 如果你不包括特定屏幕密度的资源,Android会自动缩放最初为其他屏幕密度设计的现有资源。</p>
<p>如果您的应用只需要缩放的图片,您可以通过在drawable-nodpi /中使用图片的单个版本来节省更多空间。 我们建议每个应用至少包含一个xxhdpi图片版本。</p>
<p>更多屏幕分辨率的信息,可以参考 <a href="https://developer.android.com/about/dashboards/index.html#Screens" target="_blank" rel="external">Screen Sizes and Densities</a>.</p>
<h3 id="减少动画帧数"><a href="#减少动画帧数" class="headerlink" title="减少动画帧数"></a>减少动画帧数</h3><p>逐帧动画可能会大幅增加APK的大小。 图1中展示了一个帧动画被分成多个PNG文件的情况。 每个图像是动画中的一帧。</p>
<p>对于添加到动画中的每一帧,都会增加APK中存储的图片数量。 在图1中,图像在应用程序中以30 FPS动画。 如果图像仅以15FPS动画化,则动画将仅需要所需帧的数目的一半。</p>
<p> <img src="/img/android-optimize/anim-frame.png"> </p>
<h3 id="Use-Drawable-Objects"><a href="#Use-Drawable-Objects" class="headerlink" title="Use Drawable Objects"></a>Use Drawable Objects</h3><p>一些图像不需要静态图像资源; 框架可以在运行时动态地绘制图像。 相反,Drawable对象(XML中的)可能只会占用APK中的一小部分空间。 此外,XML形式的Drawable对象可以生成符合MaterialDesign指南的单色图像。</p>
<h3 id="减少资源"><a href="#减少资源" class="headerlink" title="减少资源"></a>减少资源</h3><p>你可能为同一种图像的不同形式都提供了独立的资源,例如同一图像的有色,阴影或旋转版本。 但是,我们建议你重复使用相同的资源,在运行时根据需要进行自定义。</p>
<p>Android提供了几个工具来更改资源的颜色,可以在Android 5.0(API级别21)以及更高版本上使用android:tint和tintMode属性。 对于较低版本的平台,请使用ColorFilter类。</p>
<p>您还可以节约那些只是某一种资源做了旋转的资源。 以下代码段提供了一个例子,通过简单地将原始图像旋转180度,将“展开”箭头转换为“折叠”箭头图标:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><?xml version="1.0" encoding="utf-8"?></div><div class="line"><rotate xmlns:android="http://schemas.android.com/apk/res/android"</div><div class="line"> android:drawable="@drawable/ic_arrow_expand"</div><div class="line"> android:fromDegrees="180"</div><div class="line"> android:pivotX="50%"</div><div class="line"> android:pivotY="50%"</div><div class="line"> android:toDegrees="180" /></div></pre></td></tr></table></figure>
<h3 id="从代码中渲染"><a href="#从代码中渲染" class="headerlink" title="从代码中渲染"></a>从代码中渲染</h3><p>You can also reduce your APK size by procedurally rendering your images. Procedural rendering frees up space because you no longer store an image file in your APK.</p>
<p>你也可以通过程序对图像渲染来减少APK大小。 通过程序渲染可以节约空间是因为不需要在APK中存储图像文件。</p>
<h3 id="压缩PNG文件"><a href="#压缩PNG文件" class="headerlink" title="压缩PNG文件"></a>压缩PNG文件</h3><p>aapt工具可以在构建过程期间优化放置在res / drawable /中的图像资源,以及无损压缩。 例如,aapt工具可以将不需要多于256种颜色的真彩色PNG转换为具有调色板的8位PNG。 这样做会产生质量相同但占用内存较小的映像。</p>
<p>但请记得aapt有以下限制:</p>
<ul>
<li>aapt工具不会收缩asset/文件夹中包含的PNG文件。</li>
<li>图像文件需要使用256个或更少的颜色的aapt工具来优化它们。</li>
<li>aapt工具可能会填充已压缩的PNG文件。 为了防止这种情况,您可以在Gradle中使用cruncherEnabled标志为PNG文件禁用此过程:</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">aaptOptions {</div><div class="line"> cruncherEnabled = false</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="压缩PNG和JPG文件"><a href="#压缩PNG和JPG文件" class="headerlink" title="压缩PNG和JPG文件"></a>压缩PNG和JPG文件</h3><p>你可以使用像 <a href="http://pmt.sourceforge.net/pngcrush/" target="_blank" rel="external">pngcrush</a>, <a href="https://pngquant.org/" target="_blank" rel="external">pngquant</a>, 或<a href="https://github.com/google/zopfli" target="_blank" rel="external">zopflipng</a>等工具来减少PNG文件大小,而不会损失图像质量。 所有这些工具都可以减少PNG文件大小,同时保持图像质量。</p>
<p>pngcrush工具特别有效:此工具在PNG过滤器和zlib(Deflate)参数上迭代,使用每个过滤器和参数的组合来压缩图像。 然后选择产生最小压缩输出的配置。</p>
<p>对于JPEG文件,您可以使用 <a href="http://www.elektronik.htw-aalen.de/packjpg/" target="_blank" rel="external">packJPG</a> 等工具将JPEG文件压缩为更紧凑的形式。</p>
<h3 id="使用WebP-文件格式"><a href="#使用WebP-文件格式" class="headerlink" title="使用WebP 文件格式"></a>使用WebP 文件格式</h3><p>除了使用PNG或JPEG文件,你还可以使用WebP的图像文件。 WebP格式提供有损压缩(如JPEG)和透明度(如PNG),但可以提供比JPEG或PNG更好的压缩。</p>
<p>但是,使用WebP文件格式有一些显着的缺点。 首先,在低于Android 3.2(API级别13)的平台版本中不支持WebP。 第二,系统解码WebP比PNG文件需要更长的时间。</p>
<p><strong>注意</strong>:Google Play只接受使用PNG格式的图标。 如果你打算通过Google Play发布应用,图标就不能使用其他文件格式(如JPEG或WebP)。</p>
<h3 id="使用矢量图形"><a href="#使用矢量图形" class="headerlink" title="使用矢量图形"></a>使用矢量图形</h3><p>你可以使用矢量图形创建分辨率独立的图标和其他可伸缩媒体。 使用这些图形可以大大减少APK体积。 矢量图像在Android中表示为VectorDrawable对象。 使用VectorDrawable对象,100字节的文件可以生成屏幕大小的清晰图像。</p>
<p>然而,系统渲染每个VectorDrawable对象需要大量的时间,较大的图像则需要更长的时间才能出现在屏幕上。 因此,只有在显示小图像时才考虑使用这些矢量图形。</p>
<p>有关使用VectorDrawable对象的更多信息,请参考 <a href="https://developer.android.com/training/material/drawables.html" target="_blank" rel="external">Working with Drawables</a>.</p>
<h2 id="三-减少Native和Java代码"><a href="#三-减少Native和Java代码" class="headerlink" title="三 减少Native和Java代码"></a>三 减少Native和Java代码</h2><h3 id="删除不必要的生成代码"><a href="#删除不必要的生成代码" class="headerlink" title="删除不必要的生成代码"></a>删除不必要的生成代码</h3><p>确保你能够理解那些任何自动生成的代码部分。 例如,许多协议缓冲工具生成过多的方法和类,可以使应用程序的大小增加一倍或两倍。</p>
<h3 id="删除枚举"><a href="#删除枚举" class="headerlink" title="删除枚举"></a>删除枚举</h3><p>单个枚举可能为应用程序的classes.dex文件添加大小为1.0到1.4 KB的大小。 对于复杂的系统或者共享库,这种增加可能比较快迅速。 如果可能,请考虑使用@IntDef注解和<a href="https://developer.android.com/studio/build/shrink-code.html" target="_blank" rel="external">ProGuard</a> 来除去枚举并将它们转换为整数。 这种类型转换保留了枚举的所有类型安全的好处。</p>
<h3 id="减少本地二进制文件的大小"><a href="#减少本地二进制文件的大小" class="headerlink" title="减少本地二进制文件的大小"></a>减少本地二进制文件的大小</h3><p>如果你的应用使用本地代码和Android NDK,你还可以通过优化这些代码来减小应用的大小。 两个有用的方式是删除debug标记,不提取本地库。</p>
<h4 id="删除Debug符号"><a href="#删除Debug符号" class="headerlink" title="删除Debug符号"></a>删除Debug符号</h4><p>如果你的应用程序正在开发中并仍需要调试,那么使用debug标记很有意义。 使用Android NDK中提供的 <code>arm-eabi-strip</code> 工具从本地库中删除不必要的调试标记。 之后,再编译release版本。</p>
<h4 id="避免提取本地库"><a href="#避免提取本地库" class="headerlink" title="避免提取本地库"></a>避免提取本地库</h4><p>将.so文件存储在APK中未压缩的文件中,并在应用清单的``元素中将android:extractNativeLibs标记设置为false。 这将防止PackageManager在安装过程中将.so文件从APK复制到文件系统,并且有一个额外的好处,会让app的差分更新变得更小。</p>
<h2 id="四-维护多个精简版APK"><a href="#四-维护多个精简版APK" class="headerlink" title="四 维护多个精简版APK"></a>四 维护多个精简版APK</h2><p>你的APK可能包含用户下载但从不使用的内容,例如区域或语言信息。 为了让用户提供最小化的下载,你可以将应用细分为多个APK,并根据屏幕尺寸或GPU纹理支持等因素进行区分。</p>
<p>当用户下载您的应用时,其设备会根据设备的功能和设置接收正确的APK。 这样,设备不会接收设备没有的功能的资源。 例如,如果用户拥有的是hdpi设备,那么他们不需要你为更高分辨率设备提供的xxxhdpi资源。</p>
<p>更多信息请参考<a href="https://developer.android.com/studio/build/configure-apk-splits.html" target="_blank" rel="external">Configure APK Splits</a> and <a href="https://developer.android.com/training/multiple-apks/index.html" target="_blank" rel="external">Maintaining Multiple APKs</a>.</p>
]]></content>
<summary type="html">
<p>原文链接:<a href="https://developer.android.com/topic/performance/reduce-apk-size.html">Reduce APK Size</a> </p>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。</p>
<h2 id="一-理解APK的结构"><a href="#一-理解APK的结构" class="headerlink" title="一 理解APK的结构"></a>一 理解APK的结构</h2><p>在讨论如何减小apk大小之前,理解apk的结构很有必要。一个APK文件包括一个ZIP 文件,该ZIP包含app的所有文件。包括java 字节码文件,资源文件和一个包含了编译后的资源文件。APK包含以下目录:</p>
<ul>
<li><code>META-INF/</code>:包含了<code>CERT.SF</code> 和 <code>CERT.RSA</code> 签名文件, 以及 <code>MANIFEST.MF</code>manifest 文件.</li>
<li><code>assets/</code>: 包含了app的assets,app可以通过 <code>AssetManager</code> 对象获取到这些资源</li>
<li><code>res/</code>: 包含了那些没有被编译到 <code>resources.arsc</code>的资源</li>
<li><code>lib/</code>: 包含了用于软件处理器的编译代码,该目录包含一个子目录,针对不同平台: <code>armeabi</code>, <code>armeabi-v7a</code>, <code>arm64-v8a</code>, <code>x86</code>, <code>x86_64</code>, and <code>mips</code>.</li>
</ul>
<p>一个APK也包含了下面的文件,但只有 <code>AndroidManifest.xml</code> 是强制性的</p>
<ul>
<li><p><code>resources.arsc</code>: </p>
<p>包含了编译后的资源。该文件包含了 <code>res/values/</code>文件夹下的所有XML内容。打包工具抽取了XML内容,将它编译成二进制格式,并且进行了压缩。该内容包括language strings和styles,以及未直接包含在<code>resources.arsc</code> 文件中的内容路径。比如layout文件和图片。</p>
</li>
<li><p><code>classes.dex</code>: </p>
<p>包含可以被Dalvik/ART 识别,以dex文件格式编译后的代码</p>
</li>
<li><p><code>AndroidManifest.xml</code>: </p>
<p>包含了Android核心mainfest文件。该文件罗列了app名字,版本,访问权限,和引用的library文件。该文件采用二进制XML格式。</p>
<p></p>
</li>
</ul>
<h2 id="二-减少资源的数量和大小"><a href="#二-减少资源的数量和大小" class="headerlink" title="二 减少资源的数量和大小"></a>二 减少资源的数量和大小</h2><p>APK的大小对app的加载速度以及内存的使用和电量消耗都有影响。一种减小APK大小的最简单方法就是减少APK的资源文件数量和大小。也可以移除那些app不再使用的资源,或者使用可扩展的 <code>Drawable</code> 对象替代图片文件。这部分讨论了这些方法,以及其它几种减小app资源以便最终达到减小APK总体大小的其它方法。</p>
<h3 id="移除无用资源"><a href="#移除无用资源" class="headerlink" title="移除无用资源"></a>移除无用资源</h3><p>使用 <a href="https://developer.android.com/studio/write/lint.html"><code>lint</code></a> 工具,AndroidStudio中的一个静态的代码分析工具。可以检测<code>res/</code> 目录下那些没有被引用的资源. 当 <code>lint</code>工具发现了项目中潜在的无用资源,就会打印类似如下的信息:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">res/layout/preferences.xml: Warning: The resource R.layout.preferences appears</div><div class="line"> to be unused [UnusedResources]</div></pre></td></tr></table></figure>
<p><strong>注意:</strong> <code>lint</code> 工具不能够扫描<code>assets/</code> 目录, assets 资源是通过反射的方式引用的,或者app中引用的其它library 文件。但lint并不会移除资源,它只会提示它们的存在。</p>
<p>你引入的Libraries有可能引入了无用的资源。Gradle可以通过在 <code>build.gradle</code> 文件中开启<a href="https://developer.android.com/studio/build/shrink-code.html"><code>shrinkResources</code></a> 来帮你自动的移除这些资源:</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="性能优化" scheme="http://www.lightskystreet.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Android,性能优化" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>Android性能优化-内存优化</title>
<link href="http://www.lightskystreet.com/2016/10/16/android-optimize-memory/"/>
<id>http://www.lightskystreet.com/2016/10/16/android-optimize-memory/</id>
<published>2016-10-15T16:00:00.000Z</published>
<updated>2016-10-16T02:38:15.000Z</updated>
<content type="html"><![CDATA[<p>原文链接 <a href="https://developer.android.com/topic/performance/memory.html#remove" target="_blank" rel="external">Manage Your App’s Memory</a> </p>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a><strong>前言</strong></h3><p>在任何软件开发环境中,RAM都是比较珍贵的资源。在移动操作系统上更是这样,因为它们的物理内存通常受限。尽管在ART和Dalvik虚拟机都会进行垃圾回收的巡航,但这并不意味着你可以忽略何时,何地分配和释放内存。你应该避免内存泄露,通常此后又一些静态成员变量导致,也应该在恰当的时间(定义的一些生命周期回调的方法里)释放所有<code>Reference</code> 对象。</p>
<p>这里见识了你如何减少app内存的使用。因为android是基于java的,所以对于内存的管理,可以参考java相关的书籍,下面章节中也会有所讲解。关于如何分析app运行中的内存使用,可以参考 <a href="https://developer.android.com/topic/performance/memory.html#AnalyzeRam" target="_blank" rel="external">Tools for analyzing RAM usage</a>。关于ART和Dalvik虚拟机管理内存的更多细节,可以参考 <a href="https://developer.android.com/training/articles/memory-overview.html" target="_blank" rel="external">Overview of Android Memory Management</a>.</p>
<h2 id="一-监测可用内存和内存使用"><a href="#一-监测可用内存和内存使用" class="headerlink" title="一 监测可用内存和内存使用"></a><strong>一 监测可用内存和内存使用</strong></h2><p>Android 框架,AndrStudio和Android SDK都提供了分析app内存使用的途径。Android框架暴露了几个API,允许你的app动态的减少内存使用、AndroidStudio和Android SDK提供了几种工具帮你分析app的内存使用情况。</p>
<h3 id="分析RAM使用的工具"><a href="#分析RAM使用的工具" class="headerlink" title="分析RAM使用的工具"></a>分析RAM使用的工具</h3><ol>
<li><p>Device Monitor 拥有一个 Dalvik Debug Monitor Server (DDMS) 工具,可以帮助你检测app进程中内存的分配。你可以通过该信息去分析app的总体内存使用情况。比如,先执行垃圾回收事件然后再去看那些仍然保留在内存中的对象。通过这种方式去定位app中所进行的内存分配或者遗留在内存中的对象。</p>
<p>更多关于DDMS的使用请参考 <a href="https://developer.android.com/studio/profile/ddms.html" target="_blank" rel="external">Using DDMS</a></p>
</li>
<li><p>Android Studio中的Memory Monitor 可以向你展示某一个过程中的内存分配情况。该工具以图形化的方式展示了某一时段可用的和已经分配的java内存,以及发送的垃圾回收事件。也可以触发垃圾回收事件并获取app运行期间java堆内存的快照。Memory Monitor tool 的输出信息也可以帮你定位到app密集发生垃圾回收事件的点,这些点会降低了app速度。</p>
<p>关于如何使用Memory Monitor tool的更多信息可以参考 <a href="https://developer.android.com/tools/debugging/debugging-memory.html#ViewHeap" target="_blank" rel="external">Viewing Heap Updates</a>.</p>
</li>
<li><p>垃圾回收事件也会展示在 Traceview viewer中。 Traceview 允许你以时间线的方式查看trace log文件,并可以分析一个事件段内都发生了什么。你可以使用该工具确定在你的代码在垃圾回收事件发生时都做了什么操作。</p>
<p>更多信息关于如何使用Traceview viewer, 可以参考 <a href="https://developer.android.com/studio/profile/traceview.html" target="_blank" rel="external">Profiling with Traceview and dmtracedump</a>.</p>
<p></p>
</li>
<li><p>Android Studio中的Allocation Tracker tool可以帮助你分析app是如何分配内存的。Allocation Tracker 记录了app内存的分配并在快照中列出了所有的分配对象。可以使用该工具追踪哪些地方分配了过多的对象。</p>
<p>更多关于如何使用Allocation Tracker tool,可以参考 <a href="https://developer.android.com/topic/performance/%7BdocRoot%7Dstudio/profile/allocation-tracker-walkthru.html" target="_blank" rel="external">Allocation Tracker Walkthrough</a>.</p>
</li>
</ol>
<h3 id="依据事件释放内存"><a href="#依据事件释放内存" class="headerlink" title="依据事件释放内存"></a>依据事件释放内存</h3><p>根据RAM的物理内存和设备的操作行为,Android设备可以在变化的可用内存中运行。在内存压力的情况下,系统的广播信号会提示,app可以监听这些信号然后对内存的使用做恰当的处理。</p>
<p>可以使用 <a href="https://developer.android.com/reference/android/content/ComponentCallbacks2.html" target="_blank" rel="external">ComponentCallbacks2</a> 来监听内存信号响应app生命周期或者设备的事件。<code>onTrimMemory()</code> 方法可以帮助你监听app在前台或者后台时内存相关的事件。</p>
<p>在Activity中实现<code>onTrimMemory()</code> 回调,如下:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div></pre></td><td class="code"><pre><div class="line">import android.content.ComponentCallbacks2;</div><div class="line">// Other import statements ...</div><div class="line"></div><div class="line">public class MainActivity extends AppCompatActivity</div><div class="line"> implements ComponentCallbacks2 {</div><div class="line"></div><div class="line"> // Other activity code ...</div><div class="line"></div><div class="line"> /**</div><div class="line"> * Release memory when the UI becomes hidden or when system resources become low.</div><div class="line"> * @param level the memory-related event that was raised.</div><div class="line"> */</div><div class="line"> public void onTrimMemory(int level) {</div><div class="line"></div><div class="line"> // Determine which lifecycle or system event was raised.</div><div class="line"> switch (level) {</div><div class="line"></div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:</div><div class="line"></div><div class="line"> /*</div><div class="line"> Release any UI objects that currently hold memory.</div><div class="line"></div><div class="line"> The user interface has moved to the background.</div><div class="line"> */</div><div class="line"></div><div class="line"> break;</div><div class="line"></div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:</div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:</div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:</div><div class="line"></div><div class="line"> /*</div><div class="line"> Release any memory that your app doesn't need to run.</div><div class="line"></div><div class="line"> The device is running low on memory while the app is running.</div><div class="line"> The event raised indicates the severity of the memory-related event.</div><div class="line"> If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will</div><div class="line"> begin killing background processes.</div><div class="line"> */</div><div class="line"></div><div class="line"> break;</div><div class="line"></div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:</div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_MODERATE:</div><div class="line"> case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:</div><div class="line"></div><div class="line"> /*</div><div class="line"> Release as much memory as the process can.</div><div class="line"></div><div class="line"> The app is on the LRU list and the system is running low on memory.</div><div class="line"> The event raised indicates where the app sits within the LRU list.</div><div class="line"> If the event is TRIM_MEMORY_COMPLETE, the process will be one of</div><div class="line"> the first to be terminated.</div><div class="line"> */</div><div class="line"></div><div class="line"> break;</div><div class="line"></div><div class="line"> default:</div><div class="line"> /*</div><div class="line"> Release any non-critical data structures.</div><div class="line"></div><div class="line"> The app received an unrecognized memory level value</div><div class="line"> from the system. Treat this as a generic low-memory message.</div><div class="line"> */</div><div class="line"> break;</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p><a href="https://developer.android.com/reference/android/content/ComponentCallbacks2.html#onTrimMemory" target="_blank" rel="external"> onTrimMemory()</a>方法是在Android 4.0(API 14)中加入的,你可以使用在其它的低版本中使用 <code>onLowMemory()</code>回调,相当于<a href="https://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_COMPLETE" target="_blank" rel="external"> TRIM_MEMORY_COMPLETE</a>事件。</p>
<h3 id="确定你应该使用多少内存"><a href="#确定你应该使用多少内存" class="headerlink" title="确定你应该使用多少内存"></a>确定你应该使用多少内存</h3><p>为了允许多进程,Android对每个app在堆内存的大小上设置了严格的限制。由于不同的设备的总的可用RAM内存不同,准确的堆内存的大小限制也不同。如果你的app达到了堆的内存限制,并且尝试分配更多内存,系统就会抛出OOM。</p>
<p>为了避免OOM,可以通过调用<code>getMemoryInfo()</code>方法去查询当前设备的可用内存堆内存空间。该方法会返回一个 <code>ActivityManager.MemoryInfo</code> 对象,该对象提供了设备的内存状态信息,包括可用内存,总内存和内存阀值(当内存低于该值得时候,系统将会杀死进程)。<code>ActivityManager.MemoryInfo</code> 类也暴露了一些简单的boolean字段, <code>lowMemory</code> 就直接告诉你你的设备是否运行在低内存环境。下面的代码片段展示了如何使用在你的应用中使用 <code>getMemoryInfo()</code>方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line">public void doSomethingMemoryIntensive() {</div><div class="line"></div><div class="line"> // Before doing something that requires a lot of memory,</div><div class="line"> // check to see whether the device is in a low memory state.</div><div class="line"> ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();</div><div class="line"></div><div class="line"> if (!memoryInfo.lowMemory) {</div><div class="line"> // Do memory intensive work ...</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">// Get a MemoryInfo object for the device's current memory status.</div><div class="line">private ActivityManager.MemoryInfo getAvailableMemory() {</div><div class="line"> ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);</div><div class="line"> ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();</div><div class="line"> activityManager.getMemoryInfo(memoryInfo);</div><div class="line"> return memoryInfo;</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="二-从代码角度优化内存"><a href="#二-从代码角度优化内存" class="headerlink" title="二 从代码角度优化内存"></a>二 从代码角度优化内存</h2><p>一些android的特性,java classes ,以及代码结构有时候会占用更多内存,我们应该通过选择更高效的方案来减少内存的使用。</p>
<h3 id="节省的使用Service"><a href="#节省的使用Service" class="headerlink" title="节省的使用Service"></a>节省的使用Service</h3><p>在后台保留一个不需要的service,是最糟糕的内存管理方式之一。如果你需要通过service在后台去执行任务,除非当它将要去执行一个任务的时候,否则不应该一直在后台驻留。在完成任务后记得将service停掉,否则可能在不经意间导致了内存泄露(这里应该是指service占用了无用的资源)。</p>
<p>当你启动一个service的时候,系统会更好的保留service所运行的进程。这样会导致service 进程非常的消耗资源,因为被一个service占用的RAM部分,其它Servie就不可用了。这样会减少系统在LRU cache中缓存的进程数量,降低了app的切换效率。当内存紧张时,甚至会导致内存抖动,并且系统无法维护足够的进程来托管当前运行的所有服务。。</p>
<p>尽量避免使用持久化的service,因为它持有占有了可用内存。建议使用其它替代方案,比如 <code>JobScheduler</code>。关于<code>JobScheduler</code>如何调度后台进程,可以参考<a href="https://developer.android.com/topic/performance/background-optimization.html" target="_blank" rel="external">Background Optimizations</a>。</p>
<p>如果必须使用service,最好是使用 <code>IntentService</code>来限制service的生命周期,一旦处理完启动它的Intent,该IntentService就会将自己停掉。更多信息可以参考<a href="https://developer.android.com/training/run-background-service/index.html" target="_blank" rel="external">Running in a Background Service</a>.</p>
<h3 id="使用内存更高效的代码结构"><a href="#使用内存更高效的代码结构" class="headerlink" title="使用内存更高效的代码结构"></a>使用内存更高效的代码结构</h3><p>一些编程语言中的类没有针对移动设备进行优化。比如,通用HashMap实现可能是相当的内存低效,因为它需要为每个映射关系创建单独的对象。</p>
<p>Android框架中提供了几种优化过的数据结构,比如<a href="https://developer.android.com/reference/android/util/SparseArray.html" target="_blank" rel="external">SparseArray</a>, <a href="https://developer.android.com/reference/android/util/SparseBooleanArray.html" target="_blank" rel="external">SparseBooleanArray</a> 和 <a href="https://developer.android.com/reference/android/support/v4/util/LongSparseArray.html" target="_blank" rel="external">LongSparseArray</a>,比如<code>SparseArray</code> 避免了对key的自动装箱(导致每个实体会多创建对象),所以更高效。</p>
<h3 id="小心的使用代码抽象"><a href="#小心的使用代码抽象" class="headerlink" title="小心的使用代码抽象"></a><strong>小心的使用代码抽象</strong></h3><p>开发人员经常简单地使用抽象作为一个好的编程实践,因为抽象可以提高代码的灵活性和维护性。但是抽象会带来严重的开销:通常它们需要额外执行相当多的更多代码,需要更多的时间和RAM将代码映射到内存中。因此,如果你的抽象不能带来比较大的好处,那么请避免使用抽象。</p>
<p>比如,枚举相对于静态通常需要两倍甚至更多的内存。你应该严格避免在android中使用枚举。</p>
<h3 id="使用nano-protobufs进行序列化"><a href="#使用nano-protobufs进行序列化" class="headerlink" title="使用nano protobufs进行序列化"></a><strong>使用nano protobufs进行序列化</strong></h3><p><a href="https://developers.google.com/protocol-buffers/docs/overview" target="_blank" rel="external">Protocol buffers</a>是由Google研发,跨语言、跨平台,扩展性非常好的序列化数据结构,类似XML,但是更小更快,更简单。如果你决定使用protobufs进行序列化,你应该在客户端代码中使用nano protobufs。因为一般的protobufs会生成冗余的代码,导致各种问题,比如增加RAM的使用,APK大小,比较低的执行效率。</p>
<p>更多信息可以参考 <a href="https://android.googlesource.com/platform/external/protobuf/+/master/java/README.txt" target="_blank" rel="external">protobuf readme</a> “Nano version”部分</p>
<h3 id="避免内存抖动"><a href="#避免内存抖动" class="headerlink" title="避免内存抖动"></a><strong>避免内存抖动</strong></h3><p>上面提到,通常垃圾回收事件不会影响到你的app性能。但是,在短时间内突然发生很多的垃圾回收事件就会占用了帧的时间。系统花费在垃圾回收上的时间越多,那么用于渲染或者音频的时间就越少。</p>
<p>通常,内存抖动会导致大量的垃圾收集事件发生。在实践中,内存抖动是指在在一个特定时间内分配了很多临时对象。</p>
<p>比如,你可能在for 循环中分配了多个临时对象。或者在onDraw中创建了新的Paint或者Bitmap对象。这两种情况下,app都会快速创建大量的对象。这样会快速消耗掉young generation中的可用内存,强制垃圾回收器触发回收事件。</p>
<p>通过 <a href="https://developer.android.com/topic/performance/memory.html#AnalyzeRam" target="_blank" rel="external">Analyze your RAM usage</a> 可以帮你找到代码中哪些地方导致了内存抖动。</p>
<p>一旦定位到了问题,就应该试着在性能问题严重的地方减少对象的分配。考虑将他们移到内部循环外面,可能的话,也可以通过 <a href="https://en.wikipedia.org/wiki/Factory_method_pattern" target="_blank" rel="external">Factory模式</a>来实现。</p>
<h2 id="三-移除内存敏感的资源和库"><a href="#三-移除内存敏感的资源和库" class="headerlink" title="三 移除内存敏感的资源和库"></a>三 移除内存敏感的资源和库</h2><p>一些资源和库会在你不知道的情况下占用掉很多内存。总览下apk的大小,包含了哪些可能导致内存浪费的第三方的库和内嵌资源。通过移除冗余的。不必要的资源和库来提高内存的使用。</p>
<h3 id="减小总体的APK大小"><a href="#减小总体的APK大小" class="headerlink" title="减小总体的APK大小"></a><strong>减小总体的APK大小</strong></h3><p>您可以通过减少应用程序的整体大小来显着降低应用程序的内存使用量。Bitmap 大小、资源、动画帧数,和第三方库都可能增大了APK的大小。Android Studio 和 Android SDK 提供了一些工具可以帮你减少资源和外部依赖。</p>
<p>如何如何减少APK的大小,可以参考<a href="https://developer.android.com/topic/performance/reduce-apk-size.html" target="_blank" rel="external">Reduce APK Size</a>.</p>
<h3 id="小心的使用注解框架"><a href="#小心的使用注解框架" class="headerlink" title="小心的使用注解框架"></a><strong>小心的使用注解框架</strong></h3><p>比如Guice或者RoboGuice的依赖注解框架虽然简化了你的代码书写,并为测试或者其它的可能变化配置信息提供了适配。但是这些依赖框架并没有针对移动设备做优化。</p>
<p>比如,这些框架通常会通过扫描你的代码或者注解来进行初始化。系统会将这些映射页面分配到内存中,以便Android可以删除它们; 但这些映射页面所占用的内存会在很长一段时间之后才会被删除。</p>
<p>如果你需要使用注解框架,考虑使用<a href="http://google.github.io/dagger/" target="_blank" rel="external">Dagger</a>。比如,Dagger不会使用反射扫描代码。Dagger的严格实现意味着它可以用于Android应用中而不会增加不必要的内存使用。</p>
<h3 id="小心的使用外部库"><a href="#小心的使用外部库" class="headerlink" title="小心的使用外部库"></a><strong>小心的使用外部库</strong></h3><p>外部库的代码通常不会为移动环境而写,执行在移动设备上也更低效。当你决定使用一个外部库的时候,可能需要为移动设备做优化。在你决定使用之前,先考虑代码量的大小和RAM占用空间。</p>
<p>有时一些针对移动设备优化的库由于不同的实现也会产生问题。比如,某个库可能使用了nano版本的protobufs,然而另一个使用了micro protobufs,就会导致两个版本的实现。这样就会导致两份</p>
<p>logging,analytics,Image loading框架、缓存以及其它一些不期望的问题。</p>
<p>尽管 <a href="https://developer.android.com/tools/help/proguard.html" target="_blank" rel="external">ProGuard</a> 会帮助你移除API和资源,但是不会移除一个库的内部依赖。如果你使用了依赖库中的Activity子类(会导致比较宽泛的依赖关系,和你现有的Activity就会有很大冲突),问题将尤为严重。又或者,如果这个库中使用了反射等技术,你还要花大量时间去处理混淆。所以在你决定使用一个库的时候,需要慎重的考虑它是否非常匹配你的需求,否则你应该考虑自己实现一套。</p>
]]></content>
<summary type="html">
<p>原文链接 <a href="https://developer.android.com/topic/performance/memory.html#remove">Manage Your App’s Memory</a> </p>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a><strong>前言</strong></h3><p>在任何软件开发环境中,RAM都是比较珍贵的资源。在移动操作系统上更是这样,因为它们的物理内存通常受限。尽管在ART和Dalvik虚拟机都会进行垃圾回收的巡航,但这并不意味着你可以忽略何时,何地分配和释放内存。你应该避免内存泄露,通常此后又一些静态成员变量导致,也应该在恰当的时间(定义的一些生命周期回调的方法里)释放所有<code>Reference</code> 对象。</p>
<p>这里见识了你如何减少app内存的使用。因为android是基于java的,所以对于内存的管理,可以参考java相关的书籍,下面章节中也会有所讲解。关于如何分析app运行中的内存使用,可以参考 <a href="https://developer.android.com/topic/performance/memory.html#AnalyzeRam">Tools for analyzing RAM usage</a>。关于ART和Dalvik虚拟机管理内存的更多细节,可以参考 <a href="https://developer.android.com/training/articles/memory-overview.html">Overview of Android Memory Management</a>.</p>
<h2 id="一-监测可用内存和内存使用"><a href="#一-监测可用内存和内存使用" class="headerlink" title="一 监测可用内存和内存使用"></a><strong>一 监测可用内存和内存使用</strong></h2><p>Android 框架,AndrStudio和Android SDK都提供了分析app内存使用的途径。Android框架暴露了几个API,允许你的app动态的减少内存使用、AndroidStudio和Android SDK提供了几种工具帮你分析app的内存使用情况。</p>
<h3 id="分析RAM使用的工具"><a href="#分析RAM使用的工具" class="headerlink" title="分析RAM使用的工具"></a>分析RAM使用的工具</h3><ol>
<li><p>Device Monitor 拥有一个 Dalvik Debug Monitor Server (DDMS) 工具,可以帮助你检测app进程中内存的分配。你可以通过该信息去分析app的总体内存使用情况。比如,先执行垃圾回收事件然后再去看那些仍然保留在内存中的对象。通过这种方式去定位app中所进行的内存分配或者遗留在内存中的对象。</p>
<p>更多关于DDMS的使用请参考 <a href="https://developer.android.com/studio/profile/ddms.html">Using DDMS</a></p>
</li>
<li><p>Android Studio中的Memory Monitor 可以向你展示某一个过程中的内存分配情况。该工具以图形化的方式展示了某一时段可用的和已经分配的java内存,以及发送的垃圾回收事件。也可以触发垃圾回收事件并获取app运行期间java堆内存的快照。Memory Monitor tool 的输出信息也可以帮你定位到app密集发生垃圾回收事件的点,这些点会降低了app速度。</p>
<p>关于如何使用Memory Monitor tool的更多信息可以参考 <a href="https://developer.android.com/tools/debugging/debugging-memory.html#ViewHeap">Viewing Heap Updates</a>.</p>
</li>
<li><p>垃圾回收事件也会展示在 Traceview viewer中。 Traceview 允许你以时间线的方式查看trace log文件,并可以分析一个事件段内都发生了什么。你可以使用该工具确定在你的代码在垃圾回收事件发生时都做了什么操作。</p>
<p>更多信息关于如何使用Traceview viewer, 可以参考 <a href="https://developer.android.com/studio/profile/traceview.html">Profiling with Traceview and dmtracedump</a>.</p>
<p></p>
</li>
<li><p>Android Studio中的Allocation Tracker tool可以帮助你分析app是如何分配内存的。Allocation Tracker 记录了app内存的分配并在快照中列出了所有的分配对象。可以使用该工具追踪哪些地方分配了过多的对象。</p>
<p>更多关于如何使用Allocation Tracker tool,可以参考 <a href="https://developer.android.com/topic/performance/%7BdocRoot%7Dstudio/profile/allocation-tracker-walkthru.html">Allocation Tracker Walkthrough</a>.</p>
</li>
</ol>
<h3 id="依据事件释放内存"><a href="#依据事件释放内存" class="headerlink" title="依据事件释放内存"></a>依据事件释放内存</h3><p>根据RAM的物理内存和设备的操作行为,Android设备可以在变化的可用内存中运行。在内存压力的情况下,系统的广播信号会提示,app可以监听这些信号然后对内存的使用做恰当的处理。</p>
<p>可以使用 <a href="https://developer.android.com/reference/android/content/ComponentCallbacks2.html">ComponentCallbacks2</a> 来监听内存信号响应app生命周期或者设备的事件。<code>onTrimMemory()</code> 方法可以帮助你监听app在前台或者后台时内存相关的事件。</p>
<p>在Activity中实现<code>onTrimMemory()</code> 回调,如下:</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="性能优化" scheme="http://www.lightskystreet.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Android,性能优化" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>Android性能优化-App后台优化</title>
<link href="http://www.lightskystreet.com/2016/10/16/android-optimize-background/"/>
<id>http://www.lightskystreet.com/2016/10/16/android-optimize-background/</id>
<published>2016-10-15T16:00:00.000Z</published>
<updated>2016-10-16T09:43:50.000Z</updated>
<content type="html"><![CDATA[<p>原文链接 <a href="https://developer.android.com/topic/performance/background-optimization.html" target="_blank" rel="external">Background Optimizations</a></p>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>后台进程是内存和电池敏感的。一个隐式的broadcast可能会启动很多监听它的后台进程,即使这些进程可能做得工作不多。这可能丢设备性能和用户体验都有比较大的影响。</p>
<p>为了缓解这种问题,7.0(API 24)做了以下限制:</p>
<ul>
<li>Target为 Android 7.0 (API level 24)的App,将不会再收到在mainfest中注册的 <code>CONNECTIVITY_ACTION</code>广播。运行中的App仍然可以在Main Thread中通过<code>Context.registerReceiver()</code>注册 <code>CONNECTIVITY_CHANGE</code> 广播来监听</li>
</ul>
<ul>
<li>App 将不能够发送或者接收 <code>ACTION_NEW_PICTURE</code> or <code>ACTION_NEW_VIDEO</code>。这种优化会影响到所有的app,不仅是target为Android7.0的设备。`</li>
</ul>
<p>因此如果你使用了这些intennt,应该尽快的移除对它们的依赖,以便你的app可以在Target为Android 7.0的设备上正常运行。Android框架提供了几种解决方案去减小对这些隐式广播的依赖。比如,<code>JobScheduler</code> and <code>GcmNetworkManager</code>提供了强健的机制去调度特定情况下的网络操作。比如,你也可以使用<a href="https://developer.android.com/reference/android/app/job/JobScheduler.html" target="_blank" rel="external">JobScheduler</a>去响应content provider的变化。<code>JobInfo</code>对象封装了<code>JobScheduler</code>用于调度job的参数。当满足指定的条件的时候,系统会通<a href="https://developer.android.com/reference/android/app/job/JobService.html" target="_blank" rel="external">JobService</a>过执行该job。</p>
<p>这篇文章将会告诉你如何使用替代的方法,比如JobScheduler去为你的app做这些限制的适配。</p>
<h2 id="一-CONNECTIVITY-ACTION的限制"><a href="#一-CONNECTIVITY-ACTION的限制" class="headerlink" title="一 CONNECTIVITY_ACTION的限制"></a>一 CONNECTIVITY_ACTION的限制</h2><p>上面提到,Android 7.0 (API level 24) 将不再能够收到mainfest中注册的 <code>CONNECTIVITY_ACTION</code> 广播。Android框架中已经提供了几种替代方案,如何选择依赖于你的具体实现。</p>
<p><strong>注意:</strong>一个通过 <code>Context.registerReceiver()</code>注册的<a href="https://developer.android.com/reference/android/content/BroadcastReceiver.html" target="_blank" rel="external">BroadcastReceiver</a> 在app运行期间是可以继续收到广播的。</p>
<a id="more"></a>
<h3 id="在不可预测的网络的情况下调度Network-Jobs"><a href="#在不可预测的网络的情况下调度Network-Jobs" class="headerlink" title="在不可预测的网络的情况下调度Network Jobs"></a>在不可预测的网络的情况下调度Network Jobs</h3><p>当使用 <code>JobInfo.Builder</code> 类构建 <code>JobInfo</code>对象的时候, 通过 <code>setRequiredNetworkType()</code> 方法并传递<code>JobInfo.NETWORK_TYPE_UNMETERED</code>参数。下面的示例代码演示了当设备连接到一个未知的网络并且是在充电的时候,去调度一个service去执行的情景:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">public static final int MY_BACKGROUND_JOB = 0;</div><div class="line">...</div><div class="line">public static void scheduleJob(Context context) {</div><div class="line"> JobScheduler js =</div><div class="line"> (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);</div><div class="line"> JobInfo job = new JobInfo.Builder(</div><div class="line"> MY_BACKGROUND_JOB,</div><div class="line"> new ComponentName(context, MyJobService.class))</div><div class="line"> .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)</div><div class="line"> .setRequiresCharging(true)</div><div class="line"> .build();</div><div class="line"> js.schedule(job);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>当以上条件满足的时候,app就会收到一个回调去执行指定的<code>JobService.class</code>中的<code>onStartJob()</code>方法,更多 <code>JobScheduler</code>实例可参考 <a href="https://developer.android.com/samples/JobScheduler/index.html" target="_blank" rel="external">JobScheduler sample app</a>.</p>
<p>使用GMSCore service的应用,并且target是5.0或者以下的,可以使用 <a href="https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager" target="_blank" rel="external"><code>GcmNetworkManager</code></a> 并指定 <code>Task.NETWORK_STATE_UNMETERED。</code></p>
<h3 id="在APP运行期间监测网络连接"><a href="#在APP运行期间监测网络连接" class="headerlink" title="在APP运行期间监测网络连接"></a>在APP运行期间监测网络连接</h3><p>运行期间的App仍然可以监听<code>CONNECTIVITY_CHANGE</code> ,但是, <code>ConnectivityManager</code> 提供了更多强大的方法在特定网络条件满足的时候去触发一个回调。</p>
<p><a href="https://developer.android.com/reference/android/net/NetworkRequest.html" target="_blank" rel="external"><code>NetworkRequest</code></a>对象定义了<a href="https://developer.android.com/reference/android/net/NetworkRequest.html" target="_blank" rel="external"><code>NetworkCapabilities</code></a>相关网络回调的参数,你可以通过<code>NetworkRequest.Builder</code>类构建<code>NetworkRequest对象,</code>registerNetworkCallback()<code>,然后将NetworkRequest</code>传递对象到系统中去。当网络条件满足的时候,app就会受到一个回调去执行定义在 <code>ConnectivityManager.NetworkCallback</code>中的 <code>onAvailable()</code>方法。</p>
<p>App会一直接收注册的回调,除非app退出或者调用<code>unregisterNetworkCallback()</code>方法。</p>
<h2 id="二-NEW-PICTURE-和-NEW-VIDEO-的限制"><a href="#二-NEW-PICTURE-和-NEW-VIDEO-的限制" class="headerlink" title="二 NEW_PICTURE 和 NEW_VIDEO 的限制"></a>二 NEW_PICTURE 和 NEW_VIDEO 的限制</h2><p>Android 7.0 (API level 24),中,app将不能够发送和接收这两个广播。当几个不同的app必须唤醒设备去处理一个新的Image或者video的时候,这样的限制可以改善性能和用户体验的影响。Android 7.0 (API level 24) 扩展了 <code>JobInfo</code> 和 <code>JobParameters</code>来提供一种替代方案。</p>
<h3 id="新的JobInfo方法"><a href="#新的JobInfo方法" class="headerlink" title="新的JobInfo方法"></a>新的JobInfo方法</h3><p>为了让content URI的变化去触发job,Android 7.0 (API level 24)扩展了<a href="https://developer.android.com/reference/android/app/job/JobInfo.html" target="_blank" rel="external"> <code>JobInfo</code> </a>的以下方法:</p>
<ul>
<li><code>JobInfo.TriggerContentUri()</code></li>
</ul>
<p> 封装了contentn URL变化需要的参数</p>
<ul>
<li><p><code>JobInfo.Builder.addTriggerContentUri()</code></p>
<p> 传递一个 <code>TriggerContentUri</code> 对象给<code>JobInfo</code>。一个<code>ContentObserver</code> 监测器封装的content URI。如果这里有多个<code>TriggerContentUri</code> 对象关联到某个job上,只要其中某个URI变化,系统都会触发回调事件。</p>
</li>
</ul>
<p><code>TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS</code> 标志在任何给定URI的子集有变化的时候,都会触发job。该标志对应于传递给<code>registerContentObserver()</code>的<code>notifyForDescendants</code>参数。</p>
<p><strong>注意:</strong> <code>TriggerContentUri()</code> cannot be used in combination with <code>setPeriodic()</code> or <code>setPersisted()</code>. To continually monitor for content changes, schedule a new <code>JobInfo</code> before the app’s <code>JobService</code> finishes handling the most recent callback.</p>
<p><strong>注意</strong>:<code>TriggerContentUri()</code> 不能够和 <code>setPeriodic()</code> 或者 <code>setPersisted()</code>一起使用。为了持续地监测content 的变化,可以在<code>JobService</code> 处理完最近的回调之前去调度一个新的JobInfo。</p>
<p>下面的代码演示了当系统上报一个MEDIA_URI contentURI的时候,调度一个job的场景:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">public static final int MY_BACKGROUND_JOB = 0;</div><div class="line">...</div><div class="line">public static void scheduleJob(Context context) {</div><div class="line"> JobScheduler js =</div><div class="line"> (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);</div><div class="line"> JobInfo.Builder builder = new JobInfo.Builder(</div><div class="line"> MY_BACKGROUND_JOB,</div><div class="line"> new ComponentName(context, MediaContentJob.class));</div><div class="line"> builder.addTriggerContentUri(</div><div class="line"> new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,</div><div class="line"> JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));</div><div class="line"> js.schedule(builder.build());</div><div class="line">}</div></pre></td></tr></table></figure>
<p>当系统上报指定的conent URI(s),你的app会收到一个回调和一个传递给<code>onStartJob()</code>方法( 在<code>MediaContentJob.class</code>中)的 <code>JobParameters</code> 对象。</p>
<h3 id="新的JobParameter方法"><a href="#新的JobParameter方法" class="headerlink" title="新的JobParameter方法"></a><strong>新的JobParameter方法</strong></h3><p>Android 7.0 (API level 24)也扩展了<code>JobParameters</code>允许app接收有用的信息,该信息包含了具体是哪些content authorities 和 URIs 触发了job。</p>
<ul>
<li><code>Uri[] getTriggeredContentUris()</code></li>
</ul>
<p>返回一个触发了该Job的URIs数组。如果没有URIs触发job,或者URIs的数量大于50,那么该数组将为null(有可能job是由于其它原因触发,比如一个deadline)。</p>
<ul>
<li><code>String[] getTriggeredContentAuthorities()</code></li>
</ul>
<p>Returns a string array of content authorities that have triggered the job. If the returned array is not <code>null</code>, use <code>getTriggeredContentUris()</code> to retrieve the details of which URIs have changed.</p>
<p>The following sample code overrides the <code>JobService.onStartJob()</code> method and records the content authorities and URIs that have triggered the job:</p>
<p>返回一个触发了该Job的content authorities数组。如果返回的数组不为null,可以使用 <code>getTriggeredContentUris()</code>方法获取URIs变化的具体信息。</p>
<p>下面的代码复写了 <code>JobService.onStartJob()</code> 方法,并且记录了触发job的 content authorities 和URIs :</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line">@Override</div><div class="line">publicboolean onStartJob(JobParametersparams){</div><div class="line"> StringBuilder sb =newStringBuilder();</div><div class="line"> sb.append("Media content has changed:\n");</div><div class="line"> if(params.getTriggeredContentAuthorities()!=null){</div><div class="line"> sb.append("Authorities: ");</div><div class="line"> boolean first =true;</div><div class="line"> for(String auth :</div><div class="line"> params.getTriggeredContentAuthorities()){</div><div class="line"> if(first){</div><div class="line"> first =false;</div><div class="line"> }else{</div><div class="line"> sb.append(", ");</div><div class="line"> }</div><div class="line"> sb.append(auth);</div><div class="line"> }</div><div class="line"> if(params.getTriggeredContentUris()!=null){</div><div class="line"> for(Uri uri :params.getTriggeredContentUris()){</div><div class="line"> sb.append("\n");</div><div class="line"> sb.append(uri);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }else{</div><div class="line"> sb.append("(No content)");</div><div class="line"> }</div><div class="line"> Log.i(TAG, sb.toString());</div><div class="line"> returntrue;</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="三-Further-Optimizing-Your-App"><a href="#三-Further-Optimizing-Your-App" class="headerlink" title="三 Further Optimizing Your App"></a>三 Further Optimizing Your App</h2><p>为低内存设备或者在低内存条件做优化,可以提升系能和用户体验。移除对后台的service的依赖和静态方式注册的隐式广播,可以帮助你的app在这样的设备上运行的更好。尽管7.0上采取了一些措施减少了这些问题,但还是建议去优化app,即使在完全没有使用后台进程的情况也可以正常运行。</p>
<p>Android 7.0 (API level 24)引入了一些 <a href="https://developer.android.com/tools/help/adb.html" target="_blank" rel="external">Android Debug Bridge (ADB)</a> 命令,可以帮助你测试app在禁止后台进程的情况下app的行为:</p>
<ul>
<li><p>模拟隐式广播和后台service不可用的情况,可以使用下面的命令</p>
<p><code>$ adb shell cmd appops set RUN_IN_BACKGROUND ignore</code></p>
</li>
<li><p>重新开启隐式广播和后台service</p>
<p><code>$ adb shell cmd appops set RUN_IN_BACKGROUND allow</code></p>
</li>
</ul>
]]></content>
<summary type="html">
<p>原文链接 <a href="https://developer.android.com/topic/performance/background-optimization.html">Background Optimizations</a></p>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>后台进程是内存和电池敏感的。一个隐式的broadcast可能会启动很多监听它的后台进程,即使这些进程可能做得工作不多。这可能丢设备性能和用户体验都有比较大的影响。</p>
<p>为了缓解这种问题,7.0(API 24)做了以下限制:</p>
<ul>
<li>Target为 Android 7.0 (API level 24)的App,将不会再收到在mainfest中注册的 <code>CONNECTIVITY_ACTION</code>广播。运行中的App仍然可以在Main Thread中通过<code>Context.registerReceiver()</code>注册 <code>CONNECTIVITY_CHANGE</code> 广播来监听</li>
</ul>
<ul>
<li>App 将不能够发送或者接收 <code>ACTION_NEW_PICTURE</code> or <code>ACTION_NEW_VIDEO</code>。这种优化会影响到所有的app,不仅是target为Android7.0的设备。`</li>
</ul>
<p>因此如果你使用了这些intennt,应该尽快的移除对它们的依赖,以便你的app可以在Target为Android 7.0的设备上正常运行。Android框架提供了几种解决方案去减小对这些隐式广播的依赖。比如,<code>JobScheduler</code> and <code>GcmNetworkManager</code>提供了强健的机制去调度特定情况下的网络操作。比如,你也可以使用<a href="https://developer.android.com/reference/android/app/job/JobScheduler.html">JobScheduler</a>去响应content provider的变化。<code>JobInfo</code>对象封装了<code>JobScheduler</code>用于调度job的参数。当满足指定的条件的时候,系统会通<a href="https://developer.android.com/reference/android/app/job/JobService.html">JobService</a>过执行该job。</p>
<p>这篇文章将会告诉你如何使用替代的方法,比如JobScheduler去为你的app做这些限制的适配。</p>
<h2 id="一-CONNECTIVITY-ACTION的限制"><a href="#一-CONNECTIVITY-ACTION的限制" class="headerlink" title="一 CONNECTIVITY_ACTION的限制"></a>一 CONNECTIVITY_ACTION的限制</h2><p>上面提到,Android 7.0 (API level 24) 将不再能够收到mainfest中注册的 <code>CONNECTIVITY_ACTION</code> 广播。Android框架中已经提供了几种替代方案,如何选择依赖于你的具体实现。</p>
<p><strong>注意:</strong>一个通过 <code>Context.registerReceiver()</code>注册的<a href="https://developer.android.com/reference/android/content/BroadcastReceiver.html">BroadcastReceiver</a> 在app运行期间是可以继续收到广播的。</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="性能优化" scheme="http://www.lightskystreet.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Android,性能优化" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>Android性能优化-App启动优化</title>
<link href="http://www.lightskystreet.com/2016/10/15/android-optimize-start/"/>
<id>http://www.lightskystreet.com/2016/10/15/android-optimize-start/</id>
<published>2016-10-14T16:00:00.000Z</published>
<updated>2016-10-16T09:42:06.000Z</updated>
<content type="html"><![CDATA[<p>原文地址:<a href="https://developer.android.com/topic/performance/launch-time.html#common" target="_blank" rel="external">https://developer.android.com/topic/performance/launch-time.html#common</a> </p>
<p>通常用户期望app响应和加载速度越快越好。一个启动速度慢的app很可能会给用户留下不好的印象,除了导致用户在应用市场上的打分低之外,很有可能导致致用户直接卸载。</p>
<p>这篇文章提供了优化app启动时间的方法。先解释了app进程启动的内部流程。然后讨论如何优化启动的性能。最后列出几个常见的启动问题和解决方案。</p>
<h2 id="一-启动内幕"><a href="#一-启动内幕" class="headerlink" title="一 启动内幕"></a><strong>一 启动内幕</strong></h2><p>App启动可能发生在以下三种状态 之一,每一种都会影响到展现给用户的时间:冷启动、热启动和温启动(翻译的有点怪,介于冷和热之间吧)。</p>
<p>冷启动下,app所做的事情不较多,其它两种情况,系统只需要将app从后台切到前台。建议你在冷启动的基础上做优化,这样也会提升热启动和温启动的性能。</p>
<p>为了更好地优化app的启动,了解系统和app层做了什么以及如何相互影响很有必要。</p>
<h3 id="1-1-冷启动"><a href="#1-1-冷启动" class="headerlink" title="1.1 冷启动"></a>1.1 冷启动</h3><p>冷启动指:在app启动之前,系统的进程还没有,直到app启动创建app的进程。冷启动会发生在device重启或者app被杀死的情况下。这种启动在优化启动时间上,有更大的挑战,因为相比其它两种启动方式,系统和app有更多的工作需要处理。</p>
<p>冷启动之前,系统会执行以下三个task:</p>
<p>1、加载并启动app</p>
<p>2、在app启动后,立即展示空白的window</p>
<p>3、创建app进程</p>
<p>一旦系统创建了app进程,那么app进程就会执行以下步骤</p>
<p>1、创建app对象</p>
<p>2、启动main thread</p>
<p>3、创建MainActivity</p>
<p>4、Inflate view</p>
<p>5、布置屏幕</p>
<p>6、进行首次绘制</p>
<a id="more"></a>
<p>一旦app进程完成了第一次绘制,系统进程就会用main activity替换已经展示的background window。之后用户才可以使用app。</p>
<p>下图展示了系统和app进程互相如何工作的,展示了app启动时期的几个重要部分,在创建app和main activity之间,我们可以提升性能问题</p>
<p><img src="/img/android-optimize-app-start/app-launch-flow.png"> </p>
<h4 id="Application的创建"><a href="#Application的创建" class="headerlink" title="Application的创建"></a>Application的创建</h4><p>当应用启动的时候,空白的window在app第一次完成绘制之前都会存在。在那之后,系统进程才会替换启动窗口,允许用户开始和app交互。</p>
<p>如果你复写了 <code>Application.oncreate()</code> 方法,app启动的时候,会调用该方法。之后,app会孵化主线程(UI线程),并通过它来创建main activity。</p>
<p>从这之后,系统和app级别的进程将会按照<a href="https://developer.android.com/topic/performance/%7BdocRoot%7Dguide/topics/processes/process-lifecycle.html" target="_blank" rel="external">app lifecycle stages</a> 执行。</p>
<h4 id="Activity的创建"><a href="#Activity的创建" class="headerlink" title="Activity的创建"></a>Activity的创建</h4><p>在app进程创建了Activity之后,Activity将会执行以下操作</p>
<p>1、初始化值</p>
<p>2、调用构造函数</p>
<p>3、调用毁掉方法,比如Activity.onCreate()。</p>
<p>通常,onCreate方法会对加载时间有比较大的影响。因为它将执行繁重的工作:加载和填充view,并初始化Activity运行期间需要用的对象。</p>
<h3 id="1-2-热启动"><a href="#1-2-热启动" class="headerlink" title="1.2 热启动"></a>1.2 热启动</h3><p>相对于冷启动,热启动会简单的多。如果app的所有Activities还存在内存中,那么系统需要做的就是将activity切换到前台。这样app会避免进行的对象初始化,布局填充和渲染。</p>
<p>但是,如果一些内存在触发内存回调方法的时候被回收了,比如onTrimMemory(),那么这些对象就需要重新创建。</p>
<p>热启动会和冷启动有相同的行为。系统也会展示一个空白的window,知道app完成Activity的渲染。</p>
<h3 id="1-3-温启动"><a href="#1-3-温启动" class="headerlink" title="1.3 温启动"></a>1.3 温启动</h3><p>温启动做的工作介于冷热启动之间。这里列举几种可能被认为是温启动的状态:</p>
<p>1、用户离开了app,然后重新启动它。这时进程还在继续运行,但是Activity被回收了,app需要重新创建activity。</p>
<p>2、系统将你的app回收了,然后用户重新启动app。进程和Activity都需要重新启动,但它们可以从onCreate方法保存的bundle中恢复。</p>
<h2 id="二-优化启动性能"><a href="#二-优化启动性能" class="headerlink" title="二 优化启动性能"></a>二 <strong>优化启动性能</strong></h2><p>为了确定启动时间的性能问题,我们需要先确定app启动花费了多少时间。</p>
<h3 id="2-1-Time-to-initial-display"><a href="#2-1-Time-to-initial-display" class="headerlink" title="2.1 Time to initial display"></a>2.1 Time to initial display</h3><p>从4.4(API 19)开始,logcat会输出带有Displayed的log。该值代表从app启动进程到完成Activity第一次绘制的时间。该时间内完成了一下流程:</p>
<ul>
<li>启动进程</li>
</ul>
<ul>
<li>初始化对象</li>
</ul>
<ul>
<li>创建和初始化Activity</li>
</ul>
<ul>
<li>填充布局</li>
</ul>
<ul>
<li>第一次绘制app</li>
</ul>
<p>打出的log如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms</div></pre></td></tr></table></figure>
<p>如果你从命令行或者终端跟踪log的话,可以比较直接的定位到该log。如果在AndroidStudio中,别忘记关闭filter。</p>
<p><img src="/img/android-optimize-app-start/time-display.png"> </p>
<p>Displayed值并没有捕获所有资源都被加载和展示的总时间。那些不在layout文件中或者创建app初始化所需要对象的时间不包含在内。因为这些资源是在一个内部进程中加载的,并且不会阻塞app的初始化展示。</p>
<h3 id="2-2-Time-to-full-display"><a href="#2-2-Time-to-full-display" class="headerlink" title="2.2 Time to full display"></a>2.2 Time to full display</h3><p>你可以调用<a href="https://developer.android.com/reference/android/app/Activity.html#reportFullyDrawn(" target="_blank" rel="external">reportFullyDrawn()</a>)方法去测量从应用启动到所有资源和view层级都被绘制出来的时间。这对于app执行懒加载的情况很有用。在懒加载中,app不会阻塞window的初始化绘制,但同步进行资源加载和view的更新会阻塞。</p>
<p>由于懒加载,app的初始化展示不会包含所有的资源。你可以考虑完全加载并展示所有资源和view的时候作为一个考量。比如,UI可能完全加载了,包括一些text的绘制,但是由于图片需要从网络获取,这时还没有展示。</p>
<p>为了处理这种情况,你可以手动地调用reportFullyDrawn方法让系统知道你的activity已经通过懒加载完成了。但你是用该方法的时候,logcat展示的时间就包含从应用被创建到reportFullyDrawn方法被调用的时间。</p>
<h4 id="定位瓶颈"><a href="#定位瓶颈" class="headerlink" title="定位瓶颈"></a><strong>定位瓶颈</strong></h4><p>两种方式可以帮助你定位问题:AndroidStudio中的Method Tracer和内嵌tracing代码的方式。更多可以参考<a href="https://developer.android.com/topic/performance/%7BdocRoot%7Dstudio/profile/am-methodtrace.html" target="_blank" rel="external">documentation</a>.</p>
<p>如果无法使用Method Tracer Tool ,或者觉得trace的时机不够准确,那么你可以通过在app和Activity的onCreate方法中嵌入代码进行追踪,比如写下追踪代码。更多信息,可以参考<a href="https://developer.android.com/reference/android/os/Trace.html" target="_blank" rel="external">Trace</a> 、<a href="https://developer.android.com/topic/performance/%7BdocRoot%7Dstudio/profile/systrace-commandline.html" target="_blank" rel="external">Systrace</a></p>
<h2 id="三-常见的问题"><a href="#三-常见的问题" class="headerlink" title="三 常见的问题"></a>三 <strong>常见的问题</strong></h2><h3 id="3-1-繁重的App初始化"><a href="#3-1-繁重的App初始化" class="headerlink" title="3.1 繁重的App初始化"></a>3.1 繁重的App初始化</h3><p>当你继承了Application对象,又在Application对象进行初始化的时候执行繁重的工作或者复杂的逻辑,那么就可能导致启动的性能问题。在启动的时候花一些时间去初始化一些子类可能完全没必要。</p>
<p>在app初始化的时候,其它的挑战包括垃圾回收事件,繁重的操作,比如I/O,都有可能会阻塞进程的初始化。对于Dalvik运行环境来说,垃圾回收是一个需要特别考虑的点,Art运行环境会并发的执行垃圾回收,以便最小化垃圾回收产生的影响。</p>
<h4 id="3-1-1-定位为题"><a href="#3-1-1-定位为题" class="headerlink" title="3.1.1 定位为题"></a>3.1.1 <strong>定位为题</strong></h4><p>使用method tracing或者内嵌代码来定位这个问题</p>
<h5 id="Method-tracing"><a href="#Method-tracing" class="headerlink" title="Method tracing"></a>Method tracing</h5><p>Running the Method Tracer tool reveals that the <code>callApplicationOnCreate()</code> method eventually calls your<code>com.example.customApplication.onCreate</code> method. If the tool shows that these methods are taking a long time to finish executing, you should explore further to see what work is occurring there.</p>
<h5 id="内嵌代码的方式"><a href="#内嵌代码的方式" class="headerlink" title="内嵌代码的方式"></a><strong>内嵌代码的方式</strong></h5><p>可以对以下代码进行追踪</p>
<p>1、App的onCreate方法</p>
<p>2、onCreate中初始化的所有全局单例对象</p>
<p>3、所有I/O,反序列化,或者可能导致性能问题的循环</p>
<h4 id="3-1-2-解决方案"><a href="#3-1-2-解决方案" class="headerlink" title="3.1.2 解决方案"></a><strong>3.1.2 解决方案</strong></h4><p>如果是由于不必要的初始化或者硬盘I/O操作导致的问题,解决方案就是懒初始化对象:只初始化立即需要的。而不是在一开始就创建全局的静态对象,可以将它们的初始化放在一个单例中,当app首次访问它们的时候再初始化对象。</p>
<h2 id="3-2-繁重的Activity初始化"><a href="#3-2-繁重的Activity初始化" class="headerlink" title="3.2 繁重的Activity初始化"></a>3.2 <strong>繁重的Activity初始化</strong></h2><p>Activity的创建有时会承担大量的复杂操作。通常这里存在可以优化的点。常见的问题有:</p>
<p>1、填充大量复杂的布局</p>
<p>2、硬盘操作或者网络操作阻塞了绘制</p>
<p>3、加载或者编码bitmap</p>
<p>4、栅栏化VectorDrawable对象</p>
<p>5、Activity中其它子系统的初始化</p>
<h3 id="3-2-1-定位问题"><a href="#3-2-1-定位问题" class="headerlink" title="3.2.1 定位问题"></a>3.2.1 <strong>定位问题</strong></h3><p>和定位App启动问题类似,也是通过method tracing或者嵌入代码来定位。</p>
<h5 id="Method-tracing-1"><a href="#Method-tracing-1" class="headerlink" title="Method tracing"></a>Method tracing</h5><p>当执行Method Tracer tool的时候,你应该关注继承于Application的子类的构造函数和onCreate方法。</p>
<p>如果该工具表明代码中花了很长时间去执行,那么你就应该进一步查看这里的具体操作。</p>
<p><strong>嵌入代码的方式</strong></p>
<p>追踪的部分可能是以下代码块(和App初始化一样)</p>
<p>1、App的onCreate方法</p>
<p>2、启动时初始化的所有全局单例对象</p>
<p>3、所有I/O,反序列化,或者可能导致性能问题的循环</p>
<h3 id="3-2-2-解决方案"><a href="#3-2-2-解决方案" class="headerlink" title="3.2.2 解决方案"></a><strong>3.2.2 解决方案</strong></h3><p>上面可能有很多潜在的问题,这列举两种通用的问题和解决方案:</p>
<ul>
<li><p>view的层级越庞大,app就会花越多的时间去填充它</p>
<p> 减少多余的或者嵌套的布局</p>
<p> 不填充哪些不需要在启动时就需要展示的view。可通过ViewStub来实现,在需要的时候再填充</p>
</li>
<li><p>在main thread中做资源的初始化也会减慢启动速度。可以通过下面来解决</p>
<p> 延迟所有的资源初始化或者放在其它线程中去做</p>
<p> 允许app先加载和展示view,那些依赖于bitmap或者其它资源之后再去更新</p>
</li>
</ul>
<h2 id="三-主题化的启动屏幕"><a href="#三-主题化的启动屏幕" class="headerlink" title="三 主题化的启动屏幕"></a><strong>三 主题化的启动屏幕</strong></h2><p>我们可以通过主题化app的启动屏幕来改善启动体验。这样整个app的启动和接下来的操作会显得更加连贯。但这样只是将Activity的慢启动问题隐藏了。</p>
<p>一种常用的方式实现主题启动屏幕的方式是使用 <code>windowDisablePreview</code>主题属性关闭系统进程在app启动时绘制的初始化空白屏幕。但是,这种方式会导致更长时间。同样的,这样也会迫使用户等到Activity启动后才会得到反馈,会让用户产生app本身是否有问题的困惑。</p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>建议你不应该禁止预览窗口,你应该遵循 <a href="http://www.google.com/design/spec/patterns/launch-screens.html#" target="_blank" rel="external">Material Design</a> 标准。使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。</p>
<p><strong>布局文件</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"></div><div class="line"> <!-- The background color, preferably the same as your normal theme --></div><div class="line"> <item android:drawable="@android:color/white"/></div><div class="line"> <!-- Your product logo - 144dp color version of your app icon --></div><div class="line"> <item></div><div class="line"> <bitmap</div><div class="line"> android:src="@drawable/product_logo_144dp"</div><div class="line"> android:gravity="center"/></div><div class="line"> </item></div><div class="line"></layer-list></div></pre></td></tr></table></figure>
<p><strong>Manifest file:</strong> </p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><activity ...</div><div class="line">android:theme="@style/AppTheme.Launcher" /></div></pre></td></tr></table></figure>
<ul>
<li></li>
</ul>
<p>然后在代码中将主题切换回app的主题,最简单的方式是在<code>super.onCreate()</code> 和<code>setContentView()方法之前</code>调用 <code>setTheme(R.style.AppTheme)</code> </p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">public class MyMainActivity extends AppCompatActivity {</div><div class="line"> @Override</div><div class="line"> protected void onCreate(Bundle savedInstanceState) {</div><div class="line"> // Make sure this is before calling super.onCreate</div><div class="line"> setTheme(R.style.Theme_MyApp);</div><div class="line"> super.onCreate(savedInstanceState);</div><div class="line"> // ...</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>原文地址:<a href="https://developer.android.com/topic/performance/launch-time.html#common">https://developer.android.com/topic/performance/launch-time.html#common</a> </p>
<p>通常用户期望app响应和加载速度越快越好。一个启动速度慢的app很可能会给用户留下不好的印象,除了导致用户在应用市场上的打分低之外,很有可能导致致用户直接卸载。</p>
<p>这篇文章提供了优化app启动时间的方法。先解释了app进程启动的内部流程。然后讨论如何优化启动的性能。最后列出几个常见的启动问题和解决方案。</p>
<h2 id="一-启动内幕"><a href="#一-启动内幕" class="headerlink" title="一 启动内幕"></a><strong>一 启动内幕</strong></h2><p>App启动可能发生在以下三种状态 之一,每一种都会影响到展现给用户的时间:冷启动、热启动和温启动(翻译的有点怪,介于冷和热之间吧)。</p>
<p>冷启动下,app所做的事情不较多,其它两种情况,系统只需要将app从后台切到前台。建议你在冷启动的基础上做优化,这样也会提升热启动和温启动的性能。</p>
<p>为了更好地优化app的启动,了解系统和app层做了什么以及如何相互影响很有必要。</p>
<h3 id="1-1-冷启动"><a href="#1-1-冷启动" class="headerlink" title="1.1 冷启动"></a>1.1 冷启动</h3><p>冷启动指:在app启动之前,系统的进程还没有,直到app启动创建app的进程。冷启动会发生在device重启或者app被杀死的情况下。这种启动在优化启动时间上,有更大的挑战,因为相比其它两种启动方式,系统和app有更多的工作需要处理。</p>
<p>冷启动之前,系统会执行以下三个task:</p>
<p>1、加载并启动app</p>
<p>2、在app启动后,立即展示空白的window</p>
<p>3、创建app进程</p>
<p>一旦系统创建了app进程,那么app进程就会执行以下步骤</p>
<p>1、创建app对象</p>
<p>2、启动main thread</p>
<p>3、创建MainActivity</p>
<p>4、Inflate view</p>
<p>5、布置屏幕</p>
<p>6、进行首次绘制</p>
</summary>
<category term="Android" scheme="http://www.lightskystreet.com/categories/Android/"/>
<category term="性能优化" scheme="http://www.lightskystreet.com/categories/Android/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<category term="Android,性能优化" scheme="http://www.lightskystreet.com/tags/Android%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>JS学习-字符串和数组</title>
<link href="http://www.lightskystreet.com/2016/06/16/js-str-arr/"/>
<id>http://www.lightskystreet.com/2016/06/16/js-str-arr/</id>
<published>2016-06-15T16:00:00.000Z</published>
<updated>2016-06-20T23:31:42.000Z</updated>
<content type="html"><![CDATA[<h1 id="一-字符串"><a href="#一-字符串" class="headerlink" title="一 字符串"></a>一 字符串</h1><p>字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">'abc'</div><div class="line">"abc"</div></pre></td></tr></table></figure>
<h2 id="1-1-单引号与双引号"><a href="#1-1-单引号与双引号" class="headerlink" title="1.1 单引号与双引号"></a>1.1 单引号与双引号</h2><p>单引号字符串的内部,可以使用双引号。</p>
<p>双引号字符串的内部,可以使用单引号。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">'key = "value"'</div><div class="line">"It's a long journey"</div></pre></td></tr></table></figure>
<p>单引号内部使用单引号,双引号内部使用双引号必须在前面加上反斜杠,用来转义:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">'Did she say \'Hello\'?'</div><div class="line">// "Did she say 'Hello'?"</div><div class="line"></div><div class="line">"Did she say \"Hello\"?"</div><div class="line">// "Did she say "Hello"?"</div></pre></td></tr></table></figure>
<p>由于HTML语言的属性值使用双引号,所以很多项目约定JavaScript语言的字符串只使用单引号。</p>
<p>当然,只使用双引号也完全可以。重要的是,坚持使用一种风格,不要两种风格混合。</p>
<a id="more"></a>
<h2 id="1-2-字符串的多行书写方式"><a href="#1-2-字符串的多行书写方式" class="headerlink" title="1.2 字符串的多行书写方式"></a>1.2 字符串的多行书写方式</h2><p>字符串默认只能写在一行内,分成多行将会报错。</p>
<p>如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。但输出时还是单行,效果与写在同一行完全一样。</p>
<p><strong>注意:</strong>反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">var longString = "Long \</div><div class="line">long \</div><div class="line">long \</div><div class="line">string";</div><div class="line"></div><div class="line">longString</div><div class="line">// "Long long long string"</div></pre></td></tr></table></figure>
<p>连接运算符(<code>+</code>)可以连接多个单行字符串,将长字符串拆成多行书写</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">var longString = 'Long '</div><div class="line"> + 'long '</div><div class="line"> + 'long '</div><div class="line"> + 'string';</div></pre></td></tr></table></figure>
<h2 id="1-3-多行输出"><a href="#1-3-多行输出" class="headerlink" title="1.3 多行输出"></a>1.3 多行输出</h2><p>如果想输出多行字符串,有一种利用多行注释的变通方法。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">(function () { /*</div><div class="line">line 1</div><div class="line">line 2</div><div class="line">line 3</div><div class="line">*/}).toString().split('\n').slice(1, -1).join('\n')</div><div class="line">// "line 1</div><div class="line">// line 2</div><div class="line">// line 3"</div></pre></td></tr></table></figure>
<h2 id="1-4-字符串与数组"><a href="#1-4-字符串与数组" class="headerlink" title="1.4 字符串与数组"></a>1.4 字符串与数组</h2><h3 id="1-4-1-字符串可以使用数组的运算符"><a href="#1-4-1-字符串可以使用数组的运算符" class="headerlink" title="1.4.1 字符串可以使用数组的运算符"></a>1.4.1 字符串可以使用数组的运算符</h3><p>字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">var s = 'hello';</div><div class="line">s[0] // "h"</div><div class="line">s[1] // "e"</div><div class="line">s[4] // "o"</div><div class="line"></div><div class="line">// 直接对字符串使用方括号运算符</div><div class="line">'hello'[1] // "e"</div></pre></td></tr></table></figure>
<p>如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回<code>undefined</code>。</p>
<h3 id="1-4-2-字符串只是类似数组"><a href="#1-4-2-字符串只是类似数组" class="headerlink" title="1.4.2 字符串只是类似数组"></a>1.4.2 字符串只是类似数组</h3><p>字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">var s = 'hello';</div><div class="line"></div><div class="line">delete s[0];</div><div class="line">s // "hello"</div><div class="line"></div><div class="line">s[1] = 'a';</div><div class="line">s // "hello"</div><div class="line"></div><div class="line">s[5] = '!';</div><div class="line">s // "hello"</div></pre></td></tr></table></figure>
<p>上面代码表示,字符串内部的单个字符无法改变和增删,这些操作会默默地失败。</p>
<p>字符串之所以类似于字符数组,实际是由于对字符串进行方括号运算时,字符串会自动转换为一个字符串对象。</p>
<h1 id="二-数组"><a href="#二-数组" class="headerlink" title="二 数组"></a>二 数组</h1><h2 id="2-1-数组的定义"><a href="#2-1-数组的定义" class="headerlink" title="2.1 数组的定义"></a>2.1 数组的定义</h2><p>本质上,数组属于一种特殊的对象。<code>typeof</code>运算符会返回数组的类型是<code>object</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">typeof [1, 2, 3] // "object"</div></pre></td></tr></table></figure>
<p>上面代码表明,<code>typeof</code>运算符认为数组的类型就是对象。</p>
<p>任何类型的数据,都可以放入数组</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">var arr = [</div><div class="line"> {a: 1},</div><div class="line"> [1, 2, 3],</div><div class="line"> function() {return true;}</div><div class="line">];</div><div class="line"></div><div class="line">arr[0] // Object {a: 1}</div><div class="line">arr[1] // [1, 2, 3]</div><div class="line">arr[2] // function (){return true;}</div></pre></td></tr></table></figure>
<p>数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。</p>
<p><code>Object.keys</code>方法返回数组的所有键名。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">var arr = ['a', 'b', 'c'];</div><div class="line"></div><div class="line">Object.keys(arr)</div><div class="line">// ["0", "1", "2"]</div></pre></td></tr></table></figure>
<h3 id="数组的键名"><a href="#数组的键名" class="headerlink" title="数组的键名"></a>数组的键名</h3><p>JavaScript语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">var arr = ['a', 'b', 'c'];</div><div class="line"></div><div class="line">arr['0'] // 'a'</div><div class="line">arr[0] // 'a'</div></pre></td></tr></table></figure>
<p>需要注意的是,这一条在赋值时也成立。如果一个值可以被转换为整数,则以该值为键名,等于以对应的整数为键名。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">var a = [];</div><div class="line"></div><div class="line">a['1000'] = 'abc';</div><div class="line">a[1000] // 'abc'</div><div class="line"></div><div class="line">a[1.00] = 6;</div><div class="line">a[1] // 6</div></pre></td></tr></table></figure>
<p>上面代码表明,由于字符串“1000”和浮点数1.00都可以转换为整数,所以视同为整数键赋值。</p>
<p>数组成员只能用方括号<code>arr[0]</code>表示(方括号是运算符,可以接受数值)。</p>
<h2 id="2-2-length属性"><a href="#2-2-length属性" class="headerlink" title="2.2 length属性"></a>2.2 length属性</h2><p>数组的<code>length</code>属性与对象的<code>length</code>属性有区别,只要是数组,就一定有<code>length</code>属性,而对象不一定有。</p>