-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
836 lines (694 loc) · 85.6 KB
/
atom.xml
File metadata and controls
836 lines (694 loc) · 85.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Jared的Android之路]]></title>
<link href="http://jaredlam.github.io/atom.xml" rel="self"/>
<link href="http://jaredlam.github.io/"/>
<updated>2015-12-27T19:18:25+08:00</updated>
<id>http://jaredlam.github.io/</id>
<author>
<name><![CDATA[Jared Luo]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[Android Fragment Transactions & Activity State Loss(译)]]></title>
<link href="http://jaredlam.github.io/blog/2015/12/23/android-fragment-transactions-and-activity-state-loss-yi/"/>
<updated>2015-12-23T14:56:15+08:00</updated>
<id>http://jaredlam.github.io/blog/2015/12/23/android-fragment-transactions-and-activity-state-loss-yi</id>
<content type="html"><![CDATA[<p>本文翻译自<a href="http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html">Fragment Transactions & Activity State Loss</a>,原作者为<a href="https://plus.google.com/+AlexLockwood">Alex Lockwood</a>, 译者为<a href="https://github.com/jaredlam">Jared Luo</a></p>
<p>自从Honeycomb版本发布以后,下面这一块异常消息就成了StackOverflow上问题的常客:</p>
<pre><code>java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
</code></pre>
<p>这篇文章就来给大家解释一下为什么这个异常会被抛出,并且会总结出一些建议,让您的APP再也不受其导致崩溃的困扰。</p>
<h3>异常产生的原因?</h3>
<p>这个异常会被抛出的原因是:<strong>在Activity的状态被保存之后,你才尝试去commit一个FragmentTransaction,这样就会导致Activity的状态丢失</strong>。在深入了解这句话真正的意思之前,让我们先来看看当onSaveInstanceState()被调用时,其内部发生了什么。正如我上一篇文章<a href="http://www.androiddesignpatterns.com/2013/08/binders-death-recipients.html">Binders & Death Recipients</a>所说,Android应用程序在Android运行时当中对其自身的命运,几乎没有什么话语权。Android系统有权利随时杀掉任何一个进程来解放内存空间,这样就导致后台运行的Activity们随时都有可能,在没有一点警告的情况下被杀掉。为了保证这种不可预测的动作不被用户所感受到,系统框架给了Activity一个机会,在可能被杀掉之前,通过调用onSaveInstanceState()方法来保存状态。这样当保存的状态被恢复的时候,无论Activity是否被杀掉,用户都会感觉到好像前后台的Activity进行的是无缝的切换。</p>
<p>当框架调用onSaveInstanceState()的时候,它会向这个方法传递一个Bundle参数,Activity可以用这个参数来保存页面、对话框、Fragments和视图的状态。当onSaveInstanceState返回时,会将一个Bundle对象序列化之后通过Binder接口传递给系统服务进程,并安全的保存起来。当系统晚一点想要重启Activity的时候, 它会把之前的Bundle对象传递回应用,并用来恢复Activity之前的状态。</p>
<p>所以为什么会抛出之前的异常呢?问题的根源在于Bundle这个对象仅仅是Activity在onSaveInstanceState()方法被调用那一刻的快照。这就意味着当你如果在onSaveInstanceState()之后再调用FragmentTransaction.commit()的话,由于这次Transaction没有被作为Activity状态的一部分来保存,自然也就丢失掉了。从用户的角度来说,Transaction的遗失就导致意外的UI状态丢失。那么为了维护良好的用户体验,Android系统会不惜一切代价的避免页面状态的丢失,所以在这种情况发生的时候就直接抛出了IllegalStateException。</p>
<h3>异常产生的时机?</h3>
<p>如果你曾经遇见过这个异常,你可能会注意到在各个版本的Android平台上,这个异常抛出的时机略有不同。举个栗子,你可能会发现相对老的设备上这个异常可能会抛出的没有那么频繁,另外如果你的应用使用了support library的话,可能会比使用官方框架更容易崩溃一点。这个不同的现象可能会让我们假设support library是不是有bug,不能让我们信任呢?然而,并不是这样。</p>
<p>这种各个版本之间的现象不一致源于,在Honeycomb这个Android版本中,Activity的生命周期做了相当的调整。在Honeycomb之前的版本中,Activity在pause以前都是不能被杀掉的,所以onSaveInstanceState() 就会在onPause()之前被立刻调用。但是从Honeycomb开始,Activity变成了在stop之前不能被杀掉。所以自然onSaveInstanceState()就会变成在onStop()之前被立刻调用。下面的表格总结了这个区别:</p>
<p>上面的针对Activity生命周期的改变就导致了,有时候support library需要根据系统版本的不同, 来调整自己的行为。举个栗子,在Honeycomb和其以上版本的设备上,只要在onSaveInstanceState()以后调用commit(), 就会抛出异常来提示开发者页面状态丢失了。但是在Honeycomb之前的版本,因为onSaveInstanceState()被调用的时机提前了很多,导致状态的丢失更容易出现,所以每次都抛出异常就显得有点太严格了。Android开发团队不得不被迫做了一个妥协:为了更好的配合老版本的系统,Honeycomb之前的版本就必须忍受onPause()和onStop()之间可能会发生的状态丢失(而不会抛出异常,译者注)。
如下表格总结了Support library在两类版本的表现:</p>
<h3>怎样避免异常出现?</h3>
<p>一旦您了解了真正的原理,避免Activity状态丢失就会变得容易许多。如果您已经读到了这里,我希望您对support library对这类问题的处理方式,以及避免页面状态丢失的重要性,有了更深入的了解。如果您是来查找怎样能够快速修复这个问题,下面有一些关于使用FragmentTransactions的建议:</p>
<ul>
<li><strong>谨慎的在Activity的生命周期方法中调用transaction的commit方法</strong>。大多数应用只会在第一次调用onCreate()或者响应用户输入的时候去commit transaction, 这样不会有什么问题。但是,
当您把transaction放到其他Activity的生命周期方法中时,比如onActivityResult(), onStart() 和onResume(),事情就会变得有点复杂。举个栗子,您不应该在FragmentActivity#onResume()中去commit transaction,因为在某些情况下,这个方法可能会在Activity状态恢复之前被调用(<a href="http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume(">看这里</a>))。如果您的应用需要在onCreate()之外的其他生命周期方法中去commit transaction,那就请在 FragmentActivity#onResumeFragments()或者Activity#onPostResume()中去commit。这两个方法保证会在Activity恢复状态之后才会被调用,所以就能完全避免状态丢失的可能性。(作为例子,可以查看我在StackOverflow上,关于怎样在Activity#onActivityResult()中commit FragmentTransactions 的回答)。</li>
<li><p>避免在异步回调中去处理transaction。包括使用AsyncTask#onPostExecute()和 LoaderManager.LoaderCallbacks#onLoadFinished()方法等。这样做的问题在于,您不会知道这些异步方法在调用的时候,当前的Activity究竟处于生命周期的哪一个状态。比如说,下面这一系列事件:</p></li>
<li><p>Activity执行一个AsyncTask。</p></li>
<li>当用户按下Home键,导致Activity的onSaveInstanceState()和onStop()执行。</li>
<li>AsyncTask执行完成并且onPostExecute()被调用,其不知道Activity这个时候已经被stopped。</li>
<li>FragmentTransaction在onPostExecute()中被调用,导致异常被抛出。</li>
</ul>
<p>总体来说,在这种情况下,最好避免异常的方法是不要在异步回调中去commit transaction。Google的工程师们看起来也同意这个观点。根据Android开发者团队邮件组的这篇<a href="https://groups.google.com/forum/#!msg/android-developers/dXZZjhRjkMk/QybqCW5ukDwJ">文章</a>,Android团队认为在异步方法中去调用commit transaction而产生的UI切换,会导致不好的用户体验。 如果您的应用确实需要在异步方法中处理transaction,并且没有简单的方法可以保证回调在onSaveInstanceState()之前被调用的话,你可能只有使用commitAllowingStateLoss(),并且自己处理可能发生的状态丢失了(请参考StackOverflow,<a href="http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused">这里</a>,<a href="http://stackoverflow.com/questions/7992496/how-to-handle-asynctask-onpostexecute-when-paused-to-avoid-illegalstateexception">还有这里</a>)。</p>
<ul>
<li>只在不得已的情况下,才使用commitAllowingStateLoss()。commit()和commitAllowingStateLoss()唯一的不同是,后者当状态丢失时,不会抛出异常。通常由于存在状态丢失的可能性,您不会希望使用这个方法。更好的解决方法是,在您的应用中使用commit(),并保证在Activity的状态保存之前调用它,来获取更好的用户体验。除非状态丢失的可能性不能被避免,否则您不应该使用commitAllowingStateLoss()。</li>
</ul>
<p>希望这些提示能够帮您解决这个异常带来的相关问题。如果您仍然有没有解决的疑问,请到<a href="http://stackoverflow.com/">StackOverflow</a>上发帖子,并且把地址发布到下面的评论中,我可以帮您看看:)</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Android中FragmentPagerAdapter对Fragment的缓存(二)]]></title>
<link href="http://jaredlam.github.io/blog/2015/10/10/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun-er/"/>
<updated>2015-10-10T11:57:04+08:00</updated>
<id>http://jaredlam.github.io/blog/2015/10/10/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun-er</id>
<content type="html"><![CDATA[<blockquote><p>上一篇我们谈到了,当应用程序恢复时,由于FragmentPagerAdapter对Fragment进行了缓存的读取,导致其并未使用在Activity中新创建的Fragment实例。今天我们来看如何解决这种情况。</p></blockquote>
<p> 根据<a href="http://jaredlam.github.io/blog/2015/10/08/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun/">上篇Blog</a>的描述,我们不难发现,目前需要解决的问题有以下两个:</p>
<p> 1. 缓存Fragment内部成员变量缺失的问题。</p>
<p> 2. 新Fragment的创建和缓存Fragment使用之间的矛盾。</p>
<p> 下面先来解决第一个问题,缓存Fragment内部成员变量缺失。<a href="http://jaredlam.github.io/blog/2015/10/08/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun/">上篇Blog</a>中,Fragment当中,有一个成员变量mText,是通过setter的方式在创建Fragment之初设置进去的。但是在经历了一系列的存储和恢复操作过后,其值在最终却为空,导致了程序展示的异常。那么能不能让mText也在Fragment中同步缓存和恢复呢?</p>
<p> 最先能想到的方法,就是通过Fragment的onSaveInstanceState方法在进程被杀掉时存储,当恢复时通过onCreateView的savedInstanceState参数取出;代码如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="n">View</span> <span class="nf">onCreateView</span><span class="o">(</span><span class="n">LayoutInflater</span> <span class="n">inflater</span><span class="o">,</span> <span class="n">ViewGroup</span> <span class="n">container</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="o">...</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">savedInstanceState</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="n">mText</span> <span class="o">=</span> <span class="n">savedInstanceState</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="n">SAVED_KEY_TEXT</span><span class="o">);</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'> <span class="o">...</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="nd">@Override</span>
</span><span class='line'><span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSaveInstanceState</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">outState</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="kd">super</span><span class="o">.</span><span class="na">onSaveInstanceState</span><span class="o">(</span><span class="n">outState</span><span class="o">);</span>
</span><span class='line'> <span class="n">outState</span><span class="o">.</span><span class="na">putString</span><span class="o">(</span><span class="n">SAVED_KEY_TEXT</span><span class="o">,</span> <span class="n">mText</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 这种Activity和Fragment通用的方法,无疑是应用被杀掉时我们存储数据比较好的选择。不过还有其他方式吗?</p>
<p> 目前,mText是通过setter向Fragment设置的,这样做从实现来讲没有问题,不过其实并不是Android官方文档推荐的最佳实践; 官方文档上不推荐使用setter或者重写默认构造器的方式来传递参数:</p>
<blockquote><p>It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().</p></blockquote>
<p> 原因是,当Fragment重新被恢复时,不会去重新调用这些setter/有参构造方法; 而是会调用onCreateView,我们却可以在其中重新调用getArguments去获取这些参数。这就保证了在恢复过后,我们需要传入的参数可以重新被设置。一番改造之后如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="n">TestFragment</span> <span class="n">fragmentOne</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'> <span class="n">Bundle</span> <span class="n">bundleOne</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">Bundle</span><span class="o">();</span>
</span><span class='line'> <span class="n">bundleOne</span><span class="o">.</span><span class="na">putString</span><span class="o">(</span><span class="n">TestFragment</span><span class="o">.</span><span class="na">PARAM_KEY_TEXT</span><span class="o">,</span> <span class="s">"One"</span><span class="o">);</span>
</span><span class='line'> <span class="n">fragmentOne</span><span class="o">.</span><span class="na">setArguments</span><span class="o">(</span><span class="n">bundleOne</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'> <span class="n">TestFragment</span> <span class="n">fragmentTwo</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'> <span class="n">Bundle</span> <span class="n">bundleTwo</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">Bundle</span><span class="o">();</span>
</span><span class='line'> <span class="n">bundleTwo</span><span class="o">.</span><span class="na">putString</span><span class="o">(</span><span class="n">TestFragment</span><span class="o">.</span><span class="na">PARAM_KEY_TEXT</span><span class="o">,</span> <span class="s">"Two"</span><span class="o">);</span>
</span><span class='line'> <span class="n">fragmentTwo</span><span class="o">.</span><span class="na">setArguments</span><span class="o">(</span><span class="n">bundleTwo</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'> <span class="n">TestFragment</span> <span class="n">fragmentThree</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'> <span class="n">Bundle</span> <span class="n">bundleThree</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">Bundle</span><span class="o">();</span>
</span><span class='line'> <span class="n">bundleThree</span><span class="o">.</span><span class="na">putString</span><span class="o">(</span><span class="n">TestFragment</span><span class="o">.</span><span class="na">PARAM_KEY_TEXT</span><span class="o">,</span> <span class="s">"Three"</span><span class="o">);</span>
</span><span class='line'> <span class="n">fragmentThree</span><span class="o">.</span><span class="na">setArguments</span><span class="o">(</span><span class="n">bundleThree</span><span class="o">);</span>
</span></code></pre></td></tr></table></div></figure>
<p> 这样传入的参数,就不需要在onSaveInstanceState里面去手动保存了。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">public</span> <span class="n">View</span> <span class="nf">onCreateView</span><span class="o">(</span><span class="n">LayoutInflater</span> <span class="n">inflater</span><span class="o">,</span> <span class="n">ViewGroup</span> <span class="n">container</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="n">View</span> <span class="n">view</span> <span class="o">=</span> <span class="n">inflater</span><span class="o">.</span><span class="na">inflate</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">fragment_test</span><span class="o">,</span> <span class="n">container</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
</span><span class='line'> <span class="n">TextView</span> <span class="n">textView</span> <span class="o">=</span> <span class="o">(</span><span class="n">TextView</span><span class="o">)</span> <span class="n">view</span><span class="o">.</span><span class="na">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">center_text_view</span><span class="o">);</span>
</span><span class='line'> <span class="n">mText</span> <span class="o">=</span> <span class="o">(</span><span class="n">getArguments</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="n">getArguments</span><span class="o">().</span><span class="na">getString</span><span class="o">(</span><span class="n">PARAM_KEY_TEXT</span><span class="o">)</span> <span class="o">:</span> <span class="s">""</span><span class="o">;</span>
</span><span class='line'> <span class="n">textView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="n">mText</span><span class="o">);</span>
</span><span class='line'> <span class="k">return</span> <span class="n">view</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 第一个问题到这里就处理好了,接下来看看第二个问题:怎样解决onCreate中新实例化的Fragment,与Adapter中FragmentManager中取出的Fragment不一致的冲突。</p>
<p> 虽然mText找回来了,但是如果我们需要对Activity中实例化的Fragment做一些进一步的操作,比如传入一些Listener之类的事情,就会遇到一些麻烦,因为毕竟我们处理的这些Fragment,实际上并不是当前展示在屏幕上的Fragment。</p>
<p> <a href="http://jaredlam.github.io/blog/2015/10/08/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun/">上篇Blog</a>中讲到,FragmentPagerAdapter使用container.getId()与getItemId拼接的字符串作为FragmentManager中缓存的Key,FragmentPagerAdapter代码如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">makeFragmentName</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">itemId</span><span class="o">);</span>
</span><span class='line'> <span class="n">Fragment</span> <span class="n">fragment</span> <span class="o">=</span> <span class="n">mFragmentManager</span><span class="o">.</span><span class="na">findFragmentByTag</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'> <span class="o">...</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">private</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">makeFragmentName</span><span class="o">(</span><span class="kt">int</span> <span class="n">viewId</span><span class="o">,</span> <span class="kt">long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="s">"android:switcher:"</span> <span class="o">+</span> <span class="n">viewId</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 从上面的代码来看,其实要避免缓存和新创建的Fragment不一致,最简单的方式是,通过重写getItemId()方法,让每次打开应用返回不同的值(比如随机数之内的),让FragmentPagerAdapter找不到之前的缓存,就会使用我们新传入的实例了。</p>
<p> 不过这样做,看起来既不优雅,也不靠谱。毕竟Android官方给我们提供了这样一种缓存机制,那我们还是应该考虑怎样利用才好。</p>
<p> 1. 既然有缓存,那我们不必在Activity中每次都去新创建Fragment实例了。从源码中可以看出,每次如果FragmentPagerAdapter需要新实例化Fragment的话,都回去调用getItem方法,所以,可以考虑把Fragment的实例化工作放到getItem当中去。</p>
<p> 2. 考虑到后面我们会使用到这些Fragment实例,可以考虑在instantiateItem当中去获取并存放在数组当中。这里选择到instantiateItem,而不是getItem方法中去取的原因是:如果一旦出现有缓存的情况,FragmentPagerAdapter并不会调用getItem方法,如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">makeFragmentName</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">itemId</span><span class="o">);</span>
</span><span class='line'> <span class="n">Fragment</span> <span class="n">fragment</span> <span class="o">=</span> <span class="n">mFragmentManager</span><span class="o">.</span><span class="na">findFragmentByTag</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">fragment</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">DEBUG</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Attaching item #"</span> <span class="o">+</span> <span class="n">itemId</span> <span class="o">+</span> <span class="s">": f="</span> <span class="o">+</span> <span class="n">fragment</span><span class="o">);</span>
</span><span class='line'> <span class="n">mCurTransaction</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">fragment</span><span class="o">);</span>
</span><span class='line'> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
</span><span class='line'> <span class="n">fragment</span> <span class="o">=</span> <span class="n">getItem</span><span class="o">(</span><span class="n">position</span><span class="o">);</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">DEBUG</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Adding item #"</span> <span class="o">+</span> <span class="n">itemId</span> <span class="o">+</span> <span class="s">": f="</span> <span class="o">+</span> <span class="n">fragment</span><span class="o">);</span>
</span><span class='line'> <span class="n">mCurTransaction</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">fragment</span><span class="o">,</span>
</span><span class='line'> <span class="n">makeFragmentName</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">itemId</span><span class="o">));</span>
</span><span class='line'> <span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 按照上面两点想法,经过改造的Adapter的代码如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomPagerAdapter</span> <span class="kd">extends</span> <span class="n">FragmentPagerAdapter</span> <span class="o">{</span>
</span><span class='line'> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">COUNT</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">private</span> <span class="n">Fragment</span><span class="o">[]</span> <span class="n">mFragments</span><span class="o">;</span>
</span><span class='line'> <span class="kd">private</span> <span class="n">Context</span> <span class="n">mContext</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">public</span> <span class="nf">CustomPagerAdapter</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">FragmentManager</span> <span class="n">fm</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="kd">super</span><span class="o">(</span><span class="n">fm</span><span class="o">);</span>
</span><span class='line'> <span class="k">this</span><span class="o">.</span><span class="na">mContext</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
</span><span class='line'> <span class="k">this</span><span class="o">.</span><span class="na">mFragments</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Fragment</span><span class="o">[</span><span class="n">COUNT</span><span class="o">];</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="n">Fragment</span> <span class="nf">getItem</span><span class="o">(</span><span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="n">String</span> <span class="n">text</span><span class="o">;</span>
</span><span class='line'> <span class="k">switch</span> <span class="o">(</span><span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">case</span> <span class="mi">0</span><span class="o">:</span>
</span><span class='line'> <span class="n">text</span> <span class="o">=</span> <span class="s">"One"</span><span class="o">;</span>
</span><span class='line'> <span class="k">break</span><span class="o">;</span>
</span><span class='line'> <span class="k">case</span> <span class="mi">1</span><span class="o">:</span>
</span><span class='line'> <span class="n">text</span> <span class="o">=</span> <span class="s">"Two"</span><span class="o">;</span>
</span><span class='line'> <span class="k">break</span><span class="o">;</span>
</span><span class='line'> <span class="k">case</span> <span class="mi">2</span><span class="o">:</span>
</span><span class='line'> <span class="n">text</span> <span class="o">=</span> <span class="s">"Three"</span><span class="o">;</span>
</span><span class='line'> <span class="k">break</span><span class="o">;</span>
</span><span class='line'> <span class="k">default</span><span class="o">:</span>
</span><span class='line'> <span class="n">text</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'> <span class="n">Bundle</span> <span class="n">bundle</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">Bundle</span><span class="o">();</span>
</span><span class='line'> <span class="n">bundle</span><span class="o">.</span><span class="na">putString</span><span class="o">(</span><span class="n">TestFragment</span><span class="o">.</span><span class="na">PARAM_KEY_TEXT</span><span class="o">,</span> <span class="n">text</span><span class="o">);</span>
</span><span class='line'> <span class="k">return</span> <span class="n">Fragment</span><span class="o">.</span><span class="na">instantiate</span><span class="o">(</span><span class="n">mContext</span><span class="o">,</span> <span class="n">TestFragment</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">bundle</span><span class="o">);</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getCount</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="n">COUNT</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">long</span> <span class="nf">getItemId</span><span class="o">(</span><span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="n">position</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="n">Object</span> <span class="nf">instantiateItem</span><span class="o">(</span><span class="n">ViewGroup</span> <span class="n">container</span><span class="o">,</span> <span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="n">Fragment</span> <span class="n">fragment</span> <span class="o">=</span> <span class="o">(</span><span class="n">Fragment</span><span class="o">)</span> <span class="kd">super</span><span class="o">.</span><span class="na">instantiateItem</span><span class="o">(</span><span class="n">container</span><span class="o">,</span> <span class="n">position</span><span class="o">);</span>
</span><span class='line'> <span class="n">mFragments</span><span class="o">[</span><span class="n">position</span><span class="o">]</span> <span class="o">=</span> <span class="n">fragment</span><span class="o">;</span>
</span><span class='line'> <span class="k">return</span> <span class="n">fragment</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">public</span> <span class="n">Fragment</span><span class="o">[]</span> <span class="nf">getFragments</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="n">mFragments</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p>有一点需要注意的是,mFragment数组需要在每个页面都实例化好了之后才会填充完成,需要注意调用的时机。</p>
<p>FragmentPagerAdapter对Fragment缓存的分析就是这么多了,欢迎指正。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Android中FragmentPagerAdapter对Fragment的缓存(一)]]></title>
<link href="http://jaredlam.github.io/blog/2015/10/08/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun/"/>
<updated>2015-10-08T21:02:43+08:00</updated>
<id>http://jaredlam.github.io/blog/2015/10/08/androidzhong-fragmentpageradapterdui-fragmentde-huan-cun</id>
<content type="html"><![CDATA[<blockquote><p>ViewPager + FragmentPagerAdapter,时我们经常使用的一对搭档,其实际应用的代码也非常简单,但是也有一些容易被忽略的地方,这次我们就来讨论下FragmentPagerAdapter对Fragment的缓存应用。</p></blockquote>
<p> 我们可以先看看最简单的实现,自定义Adapter如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomPagerAdapter</span> <span class="kd">extends</span> <span class="n">FragmentPagerAdapter</span><span class="o">{</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">private</span> <span class="n">List</span><span class="o"><</span><span class="n">Fragment</span><span class="o">></span> <span class="n">mFragments</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">public</span> <span class="nf">CustomPagerAdapter</span><span class="o">(</span><span class="n">FragmentManager</span> <span class="n">fm</span><span class="o">,</span> <span class="n">List</span><span class="o"><</span><span class="n">Fragment</span><span class="o">></span> <span class="n">fragments</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="kd">super</span><span class="o">(</span><span class="n">fm</span><span class="o">);</span>
</span><span class='line'> <span class="k">this</span><span class="o">.</span><span class="na">mFragments</span> <span class="o">=</span> <span class="n">fragments</span><span class="o">;</span>
</span><span class='line'> <span class="n">fm</span><span class="o">.</span><span class="na">beginTransaction</span><span class="o">().</span><span class="na">commitAllowingStateLoss</span><span class="o">();</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="n">Fragment</span> <span class="nf">getItem</span><span class="o">(</span><span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">mFragments</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">position</span><span class="o">);</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getCount</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">mFragments</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">long</span> <span class="nf">getItemId</span><span class="o">(</span><span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="n">position</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 代码比较简单,就不解释了,接着在Activity中使用这个Adapter:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="n">ViewPager</span> <span class="n">pager</span> <span class="o">=</span> <span class="o">(</span><span class="n">ViewPager</span><span class="o">)</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">view_pager</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">List</span><span class="o"><</span><span class="n">Fragment</span><span class="o">></span> <span class="n">fragmentList</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>()</span>
</span><span class='line'><span class="n">TestFragment</span> <span class="n">fragmentOne</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'><span class="n">fragmentOne</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"One"</span><span class="o">);</span>
</span><span class='line'><span class="n">TestFragment</span> <span class="n">fragmentTwo</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'><span class="n">fragmentTwo</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"Two"</span><span class="o">);</span>
</span><span class='line'><span class="n">TestFragment</span> <span class="n">fragmentThree</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'><span class="n">fragmentThree</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"Three"</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">fragmentList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fragmentOne</span><span class="o">);</span>
</span><span class='line'><span class="n">fragmentList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fragmentTwo</span><span class="o">);</span>
</span><span class='line'><span class="n">fragmentList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fragmentThree</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">CustomPagerAdapter</span> <span class="n">adapter</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">CustomPagerAdapter</span><span class="o">(</span><span class="n">getSupportFragmentManager</span><span class="o">(),</span> <span class="n">fragmentList</span><span class="o">);</span>
</span><span class='line'><span class="n">pager</span><span class="o">.</span><span class="na">setAdapter</span><span class="o">(</span><span class="n">adapter</span><span class="o">);</span>
</span></code></pre></td></tr></table></div></figure>
<p> 这样就完成了一个FragmentPagerAdapter最基本的应用。现在,看上去一切都如我们所愿,但是真的没有任何问题了吗?</p>
<p><img src="http://jaredlam.github.io/images/20151008_02.png"></p>
<p> 接下来,我们来模拟一下程序运行在后台时,Android系统由于内存紧张,杀掉我们程序进程的情况:</p>
<blockquote><ol>
<li>首先运行程序至前台</li>
<li>接下来,点击Home键,返回桌面,同时我们的程序退回至后台运行。</li>
<li>进入Android Studio中,点击Android Monitor这个tab,并选择当前Device,并选择我们程序的进程名。</li>
<li>点击Terminal Application这个小红叉按钮,如下图:<img src="http://jaredlam.github.io/images/20151008_01.png"></li>
<li>这个时候后台进程已经被杀掉了,但是应用程序历史里我们的应用还在,所以长按Home键,并选择我们的程序,让其恢复到前台。</li>
<li>这时会看到,程序的确恢复到之前的页面。但奇怪的是,页面上却只有Hello,我们之前传入的Two到哪里去了?</li>
</ol>
</blockquote>
<p><img src="http://jaredlam.github.io/images/20151008_03.png"></p>
<p> Fragment代码也比较简单,通过日志,我们发现恢复时,mText字段为空。所以页面上对应的TextView无法显示。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestFragment</span> <span class="kd">extends</span> <span class="n">Fragment</span> <span class="o">{</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">private</span> <span class="n">String</span> <span class="n">mText</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Nullable</span>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="n">View</span> <span class="nf">onCreateView</span><span class="o">(</span><span class="n">LayoutInflater</span> <span class="n">inflater</span><span class="o">,</span> <span class="n">ViewGroup</span> <span class="n">container</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="n">View</span> <span class="n">view</span> <span class="o">=</span> <span class="n">inflater</span><span class="o">.</span><span class="na">inflate</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">fragment_test</span><span class="o">,</span> <span class="n">container</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
</span><span class='line'> <span class="n">TextView</span> <span class="n">textView</span> <span class="o">=</span> <span class="o">(</span><span class="n">TextView</span><span class="o">)</span> <span class="n">view</span><span class="o">.</span><span class="na">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">center_text_view</span><span class="o">);</span>
</span><span class='line'> <span class="n">textView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="n">mText</span><span class="o">);</span>
</span><span class='line'> <span class="k">return</span> <span class="n">view</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setText</span><span class="o">(</span><span class="n">String</span> <span class="n">text</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">this</span><span class="o">.</span><span class="na">mText</span> <span class="o">=</span> <span class="n">text</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 我们知道,以上是模拟Android系统内存紧张时,杀掉后台应用的流程。另外,当用户安装了类似360安全管家等应用,选择清理内存时,也会触发以上情况。</p>
<p> 那么当上面的流程发生时,Activity的onSaveInstanceState会被调用,以便我们可以保存当前的用户数据/页面状态等。当恢复时,在onCreate时,我们通过savedInstanceState参数,可以取到之前存储的数据,然后重新绑定到View上。</p>
<p> 这个过程都可以理解,可是回到我们的Activity代码当中:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="n">TestFragment</span> <span class="n">fragmentOne</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'><span class="n">fragmentOne</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"One"</span><span class="o">);</span>
</span><span class='line'><span class="n">TestFragment</span> <span class="n">fragmentTwo</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'><span class="n">fragmentTwo</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"Two"</span><span class="o">);</span>
</span><span class='line'><span class="n">TestFragment</span> <span class="n">fragmentThree</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'><span class="n">fragmentThree</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"Three"</span><span class="o">);</span>
</span><span class='line'><span class="n">fragmentList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fragmentOne</span><span class="o">);</span>
</span><span class='line'><span class="n">fragmentList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fragmentTwo</span><span class="o">);</span>
</span><span class='line'><span class="n">fragmentList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">fragmentThree</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'><span class="n">CustomPagerAdapter</span> <span class="n">adapter</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">CustomPagerAdapter</span><span class="o">(</span><span class="n">getSupportFragmentManager</span><span class="o">(),</span> <span class="n">fragmentList</span><span class="o">);</span>
</span><span class='line'> <span class="n">pager</span><span class="o">.</span><span class="na">setAdapter</span><span class="o">(</span><span class="n">adapter</span><span class="o">);</span>
</span></code></pre></td></tr></table></div></figure>
<p> 这段代码,是在onCreate方法中调用的。应用从后台恢复的时候,这段代码是被完整的执行过的。既然这样,三个Fragment都被重新创建过,并设置过对应的Text值,那么为什么Fragment中mText字段仍然为空呢?</p>
<p> 难道说,呈现在屏幕上的Fragment,和我们在onCreate中实例化的Fragment,已然不是同一个实例?</p>
<p> 为了验证这个想法,在OnCreate中加入下面的日志:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="n">TestFragment</span> <span class="n">fragmentOne</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">TestFragment</span><span class="o">();</span>
</span><span class='line'> <span class="n">fragmentOne</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"One"</span><span class="o">);</span>
</span><span class='line'> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span> <span class="s">"++++fragmentOne++++:"</span> <span class="o">+</span> <span class="n">fragmentOne</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
</span></code></pre></td></tr></table></div></figure>
<p> 同时在TestFragment的onCreateView方法中也记下日志:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span> <span class="s">"++++current fragment++++:"</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
</span></code></pre></td></tr></table></div></figure>
<p> 第一次运行,恩,没有问题。创建和运行的都是同一个实例 <strong>534ed278</strong></p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="n">I</span><span class="o">/</span><span class="nl">test:</span> <span class="o">++++</span><span class="n">fragmentOne</span><span class="o">++++:</span><span class="n">TestFragment</span><span class="o">{</span><span class="mi">534</span><span class="n">ed278</span><span class="o">}</span>
</span><span class='line'><span class="n">I</span><span class="o">/</span><span class="nl">test:</span> <span class="o">++++</span><span class="n">current</span> <span class="n">fragment</span><span class="o">++++:</span><span class="n">TestFragment</span><span class="o">{</span><span class="mi">534</span><span class="n">ed278</span> <span class="err">#</span><span class="mi">0</span> <span class="n">id</span><span class="o">=</span><span class="mh">0x7f0c0066</span> <span class="nl">android:switcher:</span><span class="mi">2131492966</span><span class="o">:</span><span class="mi">0</span><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 接下来,我们再次进行杀进程并恢复的过程。日志输出为:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="n">I</span><span class="o">/</span><span class="nl">test:</span> <span class="o">++++</span><span class="n">fragmentOne</span><span class="o">++++:</span><span class="n">TestFragment</span><span class="o">{</span><span class="mi">534</span><span class="n">c5c30</span><span class="o">}</span>
</span><span class='line'><span class="n">I</span><span class="o">/</span><span class="nl">test:</span> <span class="o">++++</span><span class="n">current</span> <span class="n">fragment</span><span class="o">++++:</span><span class="n">TestFragment</span><span class="o">{</span><span class="mi">534</span><span class="n">d10d4</span> <span class="err">#</span><span class="mi">0</span> <span class="n">id</span><span class="o">=</span><span class="mh">0x7f0c0066</span> <span class="nl">android:switcher:</span><span class="mi">2131492966</span><span class="o">:</span><span class="mi">0</span><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 额。。果然,这次我们创建的Fragment,和实际经过onCreateView的Fragment。并不是同一个(<strong>534c5c30/534d10d4</strong>)。</p>
<p> 看来,还是要从源码中寻求真相,打开FragmentPagerAdapter的源码,在instantiateItem方法中发现了下面这一段:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="c1">// Do we already have this fragment?</span>
</span><span class='line'><span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">makeFragmentName</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">itemId</span><span class="o">);</span>
</span><span class='line'><span class="n">Fragment</span> <span class="n">fragment</span> <span class="o">=</span> <span class="n">mFragmentManager</span><span class="o">.</span><span class="na">findFragmentByTag</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="o">(</span><span class="n">fragment</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">DEBUG</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Attaching item #"</span> <span class="o">+</span> <span class="n">itemId</span> <span class="o">+</span> <span class="s">": f="</span> <span class="o">+</span> <span class="n">fragment</span><span class="o">);</span>
</span><span class='line'> <span class="n">mCurTransaction</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">fragment</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
</span><span class='line'> <span class="n">fragment</span> <span class="o">=</span> <span class="n">getItem</span><span class="o">(</span><span class="n">position</span><span class="o">);</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">DEBUG</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Adding item #"</span> <span class="o">+</span> <span class="n">itemId</span> <span class="o">+</span> <span class="s">": f="</span> <span class="o">+</span> <span class="n">fragment</span><span class="o">);</span>
</span><span class='line'> <span class="n">mCurTransaction</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">fragment</span><span class="o">,</span>
</span><span class='line'> <span class="n">makeFragmentName</span><span class="o">(</span><span class="n">container</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">itemId</span><span class="o">));</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> makeFragmentName方法如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">private</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">makeFragmentName</span><span class="o">(</span><span class="kt">int</span> <span class="n">viewId</span><span class="o">,</span> <span class="kt">long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="s">"android:switcher:"</span> <span class="o">+</span> <span class="n">viewId</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 原来,在实例化Fragment的时候,FragmentPagerAdapter会先通过makeFragmentName返回的tag,到FragmentManager当中进行查找是否有当前Fragment的缓存,如果有的话,就直接将之前的Fragment恢复回来并使用;反之,才会使用我们传入的新实例。</p>
<p> 而makeFragmentName产生的tag,只受我们重写的getItemId()方法返回值,和当前容器View的Id,container.getId()的影响。</p>
<p> 到这里,问题就清楚了,由于FragmentPagerAdapter会主动的去取缓存当中的Fragment,所以导致恢复回来之后,Fragment的实例不一样的问题。</p>
<p> 至于为什么mText字段为空,以及怎样解决这个情况,我们下一篇再来讨论^_^。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Android ListView中使用多种Type时,getItemViewType返回值不能为负数,特别是-2]]></title>
<link href="http://jaredlam.github.io/blog/2015/10/02/android-listviewzhong-shi-yong-duo-chong-typeshi-,getitemviewtypefan-hui-zhi-bu-neng-wei-fu-shu-,te-bie-shi-2/"/>
<updated>2015-10-02T16:03:39+08:00</updated>
<id>http://jaredlam.github.io/blog/2015/10/02/android-listviewzhong-shi-yong-duo-chong-typeshi-,getitemviewtypefan-hui-zhi-bu-neng-wei-fu-shu-,te-bie-shi-2</id>
<content type="html"><![CDATA[<h2>背景</h2>
<blockquote><p>大家在Android开发中使用,常常由于业务的需求,会重写ListView的getItemViewType方法,来展示不同种类的Items;比如微博客户端,就有很多类型的Item(比如,普通微博,转发,推荐…)。 在公司项目的开发中,我们也采用了这种手段。但是没想到却意外的遇到了问题……</p></blockquote>
<p>  其实遇到的问题也很简单, 假设我们有6种类型的item需要展示在ListView当中,我们会在Adapter中这样去写</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">VIEW_TYPE_NEWS_SPORT</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
</span><span class='line'> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">VIEW_TYPE_NEWS_SOCIAL</span> <span class="o">=</span> <span class="mi">2</span><span class="o">;</span>
</span><span class='line'> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">VIEW_TYPE_NEWS_POLITICS</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span>
</span><span class='line'> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">VIEW_TYPE_NEWS_SIGNIFICANT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
</span><span class='line'> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">=</span> <span class="o">-</span><span class="mi">2</span><span class="o">;</span>
</span><span class='line'> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">VIEW_TYPE_NEWS_SUGGESTION</span> <span class="o">=</span> <span class="o">-</span><span class="mi">3</span><span class="o">;</span>
</span></code></pre></td></tr></table></div></figure>
<p>  上面我们在Adapter中定义好了6个常量,分别对应我们要展示的6种type。前三种type是我们和服务端约定好的,后面三种是客户端自己加入的,为了良好的扩展性,我们将后三种定义为负数,以免和以后服务端增加的type有所冲突。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getItemViewType</span><span class="o">(</span><span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="nf">getCurrentTypeByPosition</span><span class="o">([</span><span class="n">position</span><span class="o">]);</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getViewTypeCount</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="mi">6</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p>  然后就是重写这两个方法,根据数据结构的特征,返回不同的type。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="nd">@Override</span>
</span><span class='line'> <span class="n">getView</span><span class="o">(</span><span class="n">View</span> <span class="n">convertView</span><span class="o">){</span>
</span><span class='line'> <span class="k">switch</span><span class="o">(</span><span class="n">type</span><span class="o">){</span>
</span><span class='line'> <span class="k">case</span><span class="o">:</span>
</span><span class='line'> <span class="o">.....</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'> <span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p>  接下来,当然是在getView中根据不同的类型,返回不同的View。然后代码基本就是这样,APP运行起来也符合我们的预期。目前看起来一切ok。</p>
<p>  不过,当ListView数据量大一点过后,我们发现了一个现象:每当ListView滚动到VIEW_TYPE_NEWS_POLITICS这种type的Item的时候,会有一点卡顿掉帧的现象。另外,我们的图片异步加载出来后,有一个渐变的动画,每当这种Item呈现出来时,只要手指放在屏幕上,轻轻移动,这个Item中的图片,就会频繁闪动。</p>
<p>  起初遇到这个Bug时,我的心情还是很淡定的。猜想是因为这个item的getView被频繁调用了多次呗,于是在getView方法中每种Type里都记下了日志,一运行,输出是这样的:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_SPORT</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_POLITICS</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">====</span>
</span><span class='line'><span class="o">....</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_LATEST</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_SIGNIFICANT</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_SOCIAL</span> <span class="o">====</span>
</span><span class='line'><span class="o">====</span> <span class="n">VIEW_TYPE_NEWS_SUGGESTION</span> <span class="o">====</span>
</span></code></pre></td></tr></table></div></figure>
<p> 果然!当Item为VIEW_TYPE_NEWS_LATEST这种type时,手指一放上屏幕,getView就会被调用了很多次!</p>
<p> getView这种异常频繁的调用在之前也遇到过,不过通常是因为ListView的android:layout_height被设置成了wrap_content, 导致其需要在Layout时调用getView去计算自身高度导致的。改成match_parent或者固定值就好了。具体可以参考<a href="http://stackoverflow.com/questions/2618272/custom-listview-adapter-getview-method-being-called-multiple-times-and-in-no-co">StackOverFlow上这个问题</a>。</p>
<p>  不过,这次似乎有点不同,我检查了ListView的android:layout_height,确实是match_parent。并且这次只有这一个Item类型出现这种状况,另外5种却完全正常。</p>
<p>  接下来,我分别尝试了改换VIEW_TYPE_NEWS_LATEST的布局文件,更换这种Item在列表中的位置。可这个问题却和这种Type如影随行,不管怎么改动,只要当前Item的Type为VIEW_TYPE_NEWS_LATEST,getView便会被调用个不停。</p>
<p>  此刻,我的心情是崩溃的T<sup>T</sup></p>
<p>  冷静下来想了想,这个现象,既然和位置,布局都没有关系,唯一的关系大概也就只剩下VIEW_TYPE_NEWS_LATEST这个Type的值了吧?不管了,试一下,把换VIEW_TYPE_NEWS_LATEST改成了4。</p>
<p>  运行…</p>
<p>  …</p>
<p>  …</p>
<p>  居然好了!!!当手指放到这个type上的时候,一大堆getView的日志居然消失了!!!</p>
<p>  于是马上想到,这个特别的值 <strong>-2</strong>,一定在源码中有所应用吧。</p>
<p> 于是,马上打开ListView源码,搜索使用getItemViewType的地方,果然发现了如下代码:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="n">p</span><span class="o">.</span><span class="na">viewType</span> <span class="o">=</span> <span class="n">mAdapter</span><span class="o">.</span><span class="na">getItemViewType</span><span class="o">(</span><span class="n">position</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="o">((</span><span class="n">recycled</span> <span class="o">&&</span> <span class="o">!</span><span class="n">p</span><span class="o">.</span><span class="na">forceAdd</span><span class="o">)</span> <span class="o">||</span> <span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">recycledHeaderFooter</span>
</span><span class='line'> <span class="o">&&</span> <span class="n">p</span><span class="o">.</span><span class="na">viewType</span> <span class="o">==</span> <span class="n">AdapterView</span><span class="o">.</span><span class="na">ITEM_VIEW_TYPE_HEADER_OR_FOOTER</span><span class="o">))</span> <span class="o">{</span>
</span><span class='line'> <span class="n">attachViewToParent</span><span class="o">(</span><span class="n">child</span><span class="o">,</span> <span class="n">flowDown</span> <span class="o">?</span> <span class="o">-</span><span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="o">,</span> <span class="n">p</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
</span><span class='line'> <span class="n">p</span><span class="o">.</span><span class="na">forceAdd</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
</span><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">viewType</span> <span class="o">==</span> <span class="n">AdapterView</span><span class="o">.</span><span class="na">ITEM_VIEW_TYPE_HEADER_OR_FOOTER</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="n">p</span><span class="o">.</span><span class="na">recycledHeaderFooter</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'> <span class="n">addViewInLayout</span><span class="o">(</span><span class="n">child</span><span class="o">,</span> <span class="n">flowDown</span> <span class="o">?</span> <span class="o">-</span><span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="o">,</span> <span class="n">p</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 注意,AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER 的值为-2,很简单,当item type为-2时,就把当前这种type当成了Header或者Footer。</p>
<p> 另外,看看ListView是怎么处理这种情况的,当其为Header或者Footer时,会调用attachViewToParent方法;而当前View为普通Item时,则调用addViewInLayout方法,并传入最后一个参数preventRequestLayout为true:</p>
<pre><code>@param preventRequestLayout if true, calling this method will not trigger a layout request on child
</code></pre>
<p> 这里对Header/Footer与普通View处理方式的不同造成了这种情况。</p>
<p> 另外,除了-2之外,使用其他负数貌似也会使View无法成功的被缓存,源码如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'> <span class="k">if</span> <span class="o">(</span><span class="n">recycleOnMeasure</span><span class="o">()</span> <span class="o">&&</span> <span class="n">mRecycler</span><span class="o">.</span><span class="na">shouldRecycleViewType</span><span class="o">(</span>
</span><span class='line'> <span class="o">((</span><span class="n">LayoutParams</span><span class="o">)</span> <span class="n">child</span><span class="o">.</span><span class="na">getLayoutParams</span><span class="o">()).</span><span class="na">viewType</span><span class="o">))</span> <span class="o">{</span>
</span><span class='line'> <span class="n">mRecycler</span><span class="o">.</span><span class="na">addScrapView</span><span class="o">(</span><span class="n">child</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
</span><span class='line'> <span class="o">}</span>
</span><span class='line'>
</span><span class='line'> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">shouldRecycleViewType</span><span class="o">(</span><span class="kt">int</span> <span class="n">viewType</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'> <span class="k">return</span> <span class="n">viewType</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span>
</span><span class='line'> <span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p> 最后,去看看文档上对getItemViewType的return写的什么:</p>
<pre><code>Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
</code></pre>
<p> 话说,自己对文档和源码不熟悉,才导致了这次的问题;编写代码的时候还是要注意细节,要不很容易造成隐含的问题。不过话说回来,Android这API设计的,也太…</p>
]]></content>
</entry>
</feed>