-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
1210 lines (581 loc) · 470 KB
/
local-search.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"?>
<search>
<entry>
<title>我的MoneyKeeper之旅</title>
<link href="/2020/08/31/%E6%88%91%E7%9A%84MoneyKeeper%E4%B9%8B%E6%97%85/"/>
<url>/2020/08/31/%E6%88%91%E7%9A%84MoneyKeeper%E4%B9%8B%E6%97%85/</url>
<content type="html"><![CDATA[<ul><li><input disabled="" type="checkbox"> LeakCanary 性能优化工具,检测内存泄漏</li><li><input disabled="" type="checkbox"> [AndResGuard][<a href="https://github.com/shwenzhang/AndResGuard]" target="_blank" rel="noopener">https://github.com/shwenzhang/AndResGuard]</a> 微信提供的 Android 资源混淆打包工具</li><li><input disabled="" type="checkbox"> [BRVAH][<a href="https://github.com/CymChad/BaseRecyclerViewAdapterHelper]" target="_blank" rel="noopener">https://github.com/CymChad/BaseRecyclerViewAdapterHelper]</a> RecyclerView Adapter</li><li><input disabled="" type="checkbox"> [easypermissions][<a href="https://github.com/googlesamples/easypermissions]" target="_blank" rel="noopener">https://github.com/googlesamples/easypermissions]</a> 权限申请框架</li><li><input disabled="" type="checkbox"> [prettytime][<a href="https://github.com/ocpsoft/prettytime]" target="_blank" rel="noopener">https://github.com/ocpsoft/prettytime]</a> 时间转化,几分钟前、几小时前</li><li><input disabled="" type="checkbox"> </li></ul>]]></content>
<categories>
<category>安卓</category>
<category>项目</category>
</categories>
<tags>
<tag>开源项目学习</tag>
</tags>
</entry>
<entry>
<title>《Android开发艺术探索》</title>
<link href="/2020/08/09/%E3%80%8AAndroid%E5%BC%80%E5%8F%91%E8%89%BA%E6%9C%AF%E6%8E%A2%E7%B4%A2%E3%80%8B/"/>
<url>/2020/08/09/%E3%80%8AAndroid%E5%BC%80%E5%8F%91%E8%89%BA%E6%9C%AF%E6%8E%A2%E7%B4%A2%E3%80%8B/</url>
<content type="html"><![CDATA[]]></content>
<categories>
<category>安卓</category>
<category>书籍</category>
</categories>
</entry>
<entry>
<title>《Android进阶之光》</title>
<link href="/2020/08/05/%E3%80%8AAndroid%E8%BF%9B%E9%98%B6%E4%B9%8B%E5%85%89%E3%80%8B/"/>
<url>/2020/08/05/%E3%80%8AAndroid%E8%BF%9B%E9%98%B6%E4%B9%8B%E5%85%89%E3%80%8B/</url>
<content type="html"><![CDATA[<h3 id="Palette"><a href="#Palette" class="headerlink" title="Palette"></a>Palette</h3><ul><li>Vibrant (充满活力的)</li><li>Vibrant dark (充满活力的黑)</li><li>Vibrant light (充满活力的亮)</li><li>Muted (柔和的)</li><li>Muted dark (柔和的黑)</li><li>Muted light (柔和的亮)</li></ul><div class="hljs"><pre><code class="hljs java">implementation <span class="hljs-string">'androidx.palette:palette:1.0.0'</span></code></pre></div><div class="hljs"><pre><code class="hljs java">Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.color_size);Palette.from(bitmap).generate(palette -> { <span class="hljs-keyword">assert</span> palette != <span class="hljs-keyword">null</span>; Palette.Swatch swatch = palette.getVibrantSwatch(); <span class="hljs-keyword">assert</span> swatch != <span class="hljs-keyword">null</span>; <span class="hljs-keyword">int</span> color = swatch.getRgb();});</code></pre></div><h3 id="禁用多窗口模式"><a href="#禁用多窗口模式" class="headerlink" title="禁用多窗口模式"></a>禁用多窗口模式</h3><ul><li><p>当 targetSdkVersion 设置的值不小于 24 时:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">application</span>></span> ... android:resizeableActivity="false" ...<span class="hljs-tag"></<span class="hljs-name">application</span>></span></code></pre></div></li><li><p>当 targetSdkVersion 设置的值小于 24 时,<code>android:resizeableActivity</code> 属性不会生效,此时设置应用不支持横竖屏切换 <code>android:screenOrientation="portrait"</code>(在清单文件的 <code><activity></code> 元素中设置) 即可。</p></li></ul><h3 id="权限"><a href="#权限" class="headerlink" title="权限"></a>权限</h3><p><img src="/img/%E6%8F%92%E5%9B%BE/normal_permissions.jpg" srcset="/img/loading.gif" alt="权限"><br><img src="/img/%E6%8F%92%E5%9B%BE/dangerous_permissions.jpg" srcset="/img/loading.gif" alt="权限"></p><h2 id="Material-Design-控件及布局"><a href="#Material-Design-控件及布局" class="headerlink" title="Material Design 控件及布局"></a>Material Design 控件及布局</h2><h3 id="TextInputLayout"><a href="#TextInputLayout" class="headerlink" title="TextInputLayout"></a>TextInputLayout</h3><p>其子元素只能为 EditText</p><ul><li><code>setErrorEnabled(true)</code></li><li><code>setError(String message)</code></li></ul><h3 id="TabLayout"><a href="#TabLayout" class="headerlink" title="TabLayout"></a>TabLayout</h3><p>AppBarLayout 布局内嵌元素 Toolbar 和 TabLayout</p><p>TabLayout 属性</p><ul><li><code>app:tabMode="scrollable"</code> 设置 Tab 的模式为可滑动的</li><li><code>app:tabMode="fixed"</code> 设置 Tab 固定不可滑动</li><li><code>tabIndicatorHeight</code> 设置底部指示器的高度</li><li><code>tabIndicatorColor</code> 设置底部指示器的颜色</li></ul><h3 id="NavigationView"><a href="#NavigationView" class="headerlink" title="NavigationView"></a>NavigationView</h3><ul><li><code>app:headerLayout=""</code> 引入头部文件</li><li><code>app:menu=""</code> 引入菜单布局</li></ul><h3 id="CoordinatorLayout-实现-Toolbar-隐藏效果"><a href="#CoordinatorLayout-实现-Toolbar-隐藏效果" class="headerlink" title="CoordinatorLayout 实现 Toolbar 隐藏效果"></a>CoordinatorLayout 实现 Toolbar 隐藏效果</h3><p>Toolbar 中 <code>app:layout_scrollFlags="scroll|enterAlways"</code> 属性设置滚动事件,属性里必须至少启用 scroll 这个 flag,这个 View才会滚出屏幕,否则它将一直固定在顶部。</p><h3 id="CollapsingToolbarLayout"><a href="#CollapsingToolbarLayout" class="headerlink" title="CollapsingToolbarLayout"></a>CollapsingToolbarLayout</h3><ul><li><code>app:contentScrim=""</code> 设置 CollapsingToolbarLayout 收缩后最顶部的颜色</li><li><code>app:enpandedTitleGravity="left|bottom"</code> 将 CollapsingToolbarLayout完全展开后,title 所处的位置,默认为 <code>left|bottom</code></li><li><code>app:collapsedTitleGravity="left"</code> 当头部的衬图 ImageView 消失后,此 title 将回归到 Toolbar 的位置,默认为 <code>left</code></li><li><code>app:layout_scrollFlags=""</code> 设置滚动事件,属性里面必须至少启用 <code>scroll</code>,这样当前 View 才会滚动出屏幕<ul><li><code>scroll|exitUntilCollapsed</code> 折叠</li><li><code>scroll|enterAlways</code> 隐藏</li></ul></li></ul><p>特殊字符串资源 <code>@string/appbar_scrolling_view_behavior</code> 和 <code>AppBarLayout.ScrollingViewBehavior</code> 相匹配,用来通知 AppBarLayout 何时发生了滚动事件,此 Behavior 需要设置在触发事件的 View 上,比如 <code>RecyclerView</code>、<code>NestedScrollView</code>。当然,AppBarLayout 中的子 View 需要设置 <code>app:layout_scrollFlags</code> 属性。</p><h2 id="第3章-View-和自定义-View"><a href="#第3章-View-和自定义-View" class="headerlink" title="第3章 View 和自定义 View"></a>第3章 View 和自定义 View</h2><h3 id="坐标系"><a href="#坐标系" class="headerlink" title="坐标系"></a>坐标系</h3><p><img src="/img/%E6%8F%92%E5%9B%BE/view_position.jpg" srcset="/img/loading.gif" alt="View位置方法"></p><h3 id="View-的滑动"><a href="#View-的滑动" class="headerlink" title="View 的滑动"></a>View 的滑动</h3><ul><li><code>layout()</code></li><li><code>offsetLeftAndRight()</code> 和 <code>offsetTopAndBottom()</code></li><li><code>LayoutParams(改变布局参数)</code></li><li><code>scrollTo()</code> 和 <code>scrollBy()</code></li><li><code>Scroller</code><br>Scroller 本身不能实现 View 的滑动,需要与 View 的 <code>computeScroll()</code> 方法配合实现弹性滑动效果</li></ul><h3 id="属性动画"><a href="#属性动画" class="headerlink" title="属性动画"></a>属性动画</h3><p>四种动画:Alpha、Rotate、Translate、Scale</p><ol><li>ObjectAnimator<ul><li>translationX 和 translationY:沿着 X 轴或 Y 轴移动</li><li>rotatioin 和 rotationX 和 rotationY:围绕 View 的支点进行旋转<ul><li>PrivotX 和 ProvitY:控制 View 对象的支点位置,默认该支点位置就是 View 对象的中心</li></ul></li><li>alpha:</li><li>透明度,默认是 1(不透明),0 代表完全透明</li><li>x 和 y:描述 View 对象在其容器中的最终位置</li></ul></li><li>ValueAnimator</li><li>AnimationSet</li></ol><h3 id="View-的事件分发机制"><a href="#View-的事件分发机制" class="headerlink" title="View 的事件分发机制"></a>View 的事件分发机制</h3><ul><li><code>dispatchTouchEvent(MotionEvent ev)</code> 用来进行事件的分发</li><li><code>onInterceptTouchEvent(MotionEvent ev)</code> 用来进行事件的拦截,在 <code>dispatchTouchEvent()</code> 中调用</li><li><code>onTouchEvent(MotionEvent ev)</code> 用来处理点击事件,在 <code>dispatchTouchEvent()</code> 中调用</li></ul><p>当点击事件产生后会由 Activity 来处理,传递给 PhoneWindow,再传递给 DecorView,最后传递给顶层的 ViewGroup。一般在事件传递中只考虑 ViewGroup 的 <code>onInterceptTouchEvent()</code> 方法。对于根 ViewGroup,点击事件首先传递给它的 <code>dispatchTouchEvent()</code> 方法,如果 ViewGroup 的 <code>onInterceptTouchEvent()</code> 方法返回 <strong>true</strong>,则表示它要拦截这个事件,这个事件就会交给它的 <code>onTouchEvent()</code> 方法处理,如果 <code>onInterceptTouchEvent()</code> 方法返回 false,则表示它不拦截这个事件,则这个事件会交给它的子元素的 <code>dispatchTouchEvent()</code> 来处理,如此反复。如果传递给底层的 View,View 是没有子 View 的,就会调用 View 的 <code>dispatchTouchEvent()</code> 方法,一般情况下最终会调用 View 的 <code>onTouchEvent()</code> 方法。</p><p>当点击事件传给底层的 View 时,如果其 <code>onTouchEvent()</code> 方法返回 <strong>true</strong>,则事件由底层的 View 消耗并处理;如果返回 false 则表示该 View 不做处理,则传递给父 View的 <code>onTouchEvent()</code> 处理;如果父 View 的 <code>onTouchEvent()</code> 仍旧返回 false,则继续传递给该父 View 的 父 View 处理,如此反复下去。</p><h3 id="View-的工作流程"><a href="#View-的工作流程" class="headerlink" title="View 的工作流程"></a>View 的工作流程</h3><ul><li>measure 测量 View 的宽和高</li><li>layout 确定 View 的位置</li><li>draw 绘制 View</li></ul><h2 id="第4章-多线程编程"><a href="#第4章-多线程编程" class="headerlink" title="第4章 多线程编程"></a>第4章 多线程编程</h2><h3 id="线程基础"><a href="#线程基础" class="headerlink" title="线程基础"></a>线程基础</h3><ul><li>实现 <strong>Callable</strong> 接口,重写 <strong><code>call()</code></strong> 方法</li><li><code>Thread.interrupt()</code> 请求中断线程</li><li><code>Thread.interrupted()</code> 对中断标识位进行复位</li><li><code>Thread.currentThread.isInterrupted()</code> 判断线程是否被置位</li><li>不要在底层代码里捕获 <code>InterruptedException</code> 异常后不做处理,最好不使用 try 语句捕获这样的异常</li></ul><h3 id="同步"><a href="#同步" class="headerlink" title="同步"></a>同步</h3><ul><li><code>synchronized</code></li><li>重入锁 <code>ReentrantLock</code></li><li>每一个对象有一个内部锁,并且该锁有一个内部条件。</li><li>并发编程中的 3 个特性:原子性、可见性、有序性</li></ul><h3 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h3><div class="hljs"><pre><code class="hljs java"><span class="hljs-comment">/**</span><span class="hljs-comment"> * ThreadPoolExecutor 类一共有 4 个构造方法,这是参数最多的一个</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> corePoolSize 核心线程数</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> maximumPoolSize 线程池允许创建的最大线程数</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> keepAliveTime 非核心线程闲置的超时时间。如果设置 allowCoreThreadTimeOut 属性为 true, 这个参数也会应用到核心线程上</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> unit keepAliveTime 参数的时间单位</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> workQueue 任务队列</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> threadFactory 线程工厂。可以用线程工厂为每个创建出来的线程设置名字。一般情况下无需设置</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> handler 饱和策略</span><span class="hljs-comment"> */</span><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ThreadPoolExecutor</span><span class="hljs-params">(<span class="hljs-keyword">int</span> corePoolSize, <span class="hljs-keyword">int</span> maximumPoolSize, <span class="hljs-keyword">long</span> keepAliveTime,</span></span><span class="hljs-function"><span class="hljs-params"> TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,</span></span><span class="hljs-function"><span class="hljs-params"> RejectedExecutionHandler handler)</span> </span>{ ...}</code></pre></div><p>饱和策略默认为 <code>AbordPolicy</code>,表示无法处理新任务,并抛出 <code>RejectedExecutionException</code> 异常,此外还有 3 种策略:</p><ol><li><code>CallerRunsPolicy</code><br>用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。</li><li><code>DiscardPolicy</code><br>不能执行的任务,并将该任务删除。</li><li><code>DiscardOldestPolicy</code><br>丢弃队列最近的任务,并执行当前的任务。</li></ol><h4 id="线程池的处理流程和原理"><a href="#线程池的处理流程和原理" class="headerlink" title="线程池的处理流程和原理"></a>线程池的处理流程和原理</h4><div class="hljs"><pre><code class="hljs mermaid">graph LRA[提交任务] --> B{线程数是否<br/>达到 corePoolSize} B --否--> C[创建核心<br/>线程执行任务] B --是--> D{任务队列<br/>是否已满} D --否--> E[将任务加在<br/>任务队列中] D --是--> F{线程数是否<br/>达到最大线程数} F --是--> I[执行饱和策略]</code></pre></div><h4 id="线程池的种类"><a href="#线程池的种类" class="headerlink" title="线程池的种类"></a>线程池的种类</h4><ul><li><code>FixedThreadPool</code><br>可重用固定线程数的线程池,只有数量固定的核心线程。keepAliveTime 设置为 0L,多余的线程会被立即终止。</li><li><code>CachedThreadPool</code><br>根据需要创建线程的线程池,没有核心线程。keepAliveTime 设置为 60L。</li><li><code>SingleThreadExecutor</code><br>使用单个工作线程的线程池,只有一个核心线程。</li><li><code>ScheduledThreadPool</code><br>实现定时和周期性任务的线程池。</li></ul>]]></content>
<categories>
<category>安卓</category>
<category>书籍</category>
</categories>
</entry>
<entry>
<title>Notification</title>
<link href="/2020/08/04/Notification/"/>
<url>/2020/08/04/Notification/</url>
<content type="html"><![CDATA[<p><a href="https://developer.android.google.cn/guide/topics/ui/notifiers/notifications?hl=zh" target="_blank" rel="noopener">通知</a>是指 Android 在应用的界面之外显示的消息,旨在向用户提供提醒、来自他人的通信信息或应用中的其他实时信息。</p><a id="more"></a><h2 id="通知刨析"><a href="#通知刨析" class="headerlink" title="通知刨析"></a>通知刨析<a id="通知刨析"></a></h2><p><img src="/img/%E6%8F%92%E5%9B%BE/notification-callouts_2x.png" srcset="/img/loading.gif" alt="通知"></p><ol><li><code>setSmallIcon()</code></li><li>应用名称</li><li>时间戳:由系统提供,但可以通过 <code>setWhen()</code> 将其替换掉或者通过 <code>setShowWhen(false)</code> 将其隐藏。</li><li><code>setLargeIcon()</code></li><li><code>setContentTitle()</code></li><li><code>setContentText()</code></li></ol><h2 id="创建通知"><a href="#创建通知" class="headerlink" title="创建通知"></a>创建通知</h2><h3 id="添加支持库"><a href="#添加支持库" class="headerlink" title="添加支持库"></a>添加支持库</h3><div class="hljs"><pre><code class="hljs java">implementation <span class="hljs-string">"com.android.support:support-compat:28.0.0"</span></code></pre></div><h3 id="创建基本通知"><a href="#创建基本通知" class="headerlink" title="创建基本通知"></a>创建基本通知</h3><h4 id="设置通知内容"><a href="#设置通知内容" class="headerlink" title="设置通知内容"></a>设置通知内容</h4><ul><li>见上<a href="#通知刨析">通知刨析</a></li><li><code>setPriority()</code> 设置通知优先级。优先级确定通知在 Android 7.1 和更低版本上的干扰程度。</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">var</span> builder = NotificationCompat.Builder(<span class="hljs-keyword">this</span>, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle(textTitle) .setContentText(textContent) .setPriority(NotificationCompat.PRIORITY_DEFAULT)</code></pre></div><h4 id="创建渠道并设置重要性"><a href="#创建渠道并设置重要性" class="headerlink" title="创建渠道并设置重要性"></a>创建渠道并设置重要性</h4><p>向 <code>createNotificationChannel()</code> 传递 <code>NotificationChannel</code> 的实例在系统中注册应用的通知渠道。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">createNotificationChannel</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">// Create the NotificationChannel, but only on API 26+ because</span> <span class="hljs-comment">// the NotificationChannel class is new and not in the support library</span> <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { <span class="hljs-keyword">val</span> name = getString(R.string.channel_name) <span class="hljs-keyword">val</span> descriptionText = getString(R.string.channel_description) <span class="hljs-keyword">val</span> importance = NotificationManager.IMPORTANCE_DEFAULT <span class="hljs-keyword">val</span> channel = NotificationChannel(CHANNEL_ID, name, importance).apply { description = descriptionText } <span class="hljs-comment">// Register the channel with the system</span> <span class="hljs-keyword">val</span> notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) <span class="hljs-keyword">as</span> NotificationManager notificationManager.createNotificationChannel(channel) }}</code></pre></div><h4 id="设置通知的点按操作"><a href="#设置通知的点按操作" class="headerlink" title="设置通知的点按操作"></a>设置通知的点按操作</h4><p>指定通过 <code>PendingIntent</code> 对象定义的内容 Intent,并将其传递给 <code>setContentIntent()</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">// Create an explicit intent for an Activity in your app</span><span class="hljs-keyword">val</span> intent = Intent(<span class="hljs-keyword">this</span>, AlertDetails::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>).<span class="hljs-title">apply</span> </span>{ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK}<span class="hljs-keyword">val</span> pendingIntent: PendingIntent = PendingIntent.getActivity(<span class="hljs-keyword">this</span>, <span class="hljs-number">0</span>, intent, <span class="hljs-number">0</span>)<span class="hljs-keyword">val</span> builder = NotificationCompat.Builder(<span class="hljs-keyword">this</span>, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle(<span class="hljs-string">"My notification"</span>) .setContentText(<span class="hljs-string">"Hello World!"</span>) .setPriority(NotificationCompat.PRIORITY_DEFAULT) <span class="hljs-comment">// Set the intent that will fire when the user taps the notification</span> .setContentIntent(pendingIntent) .setAutoCancel(<span class="hljs-literal">true</span>) <span class="hljs-comment">// 点按通知后自动移除通知</span></code></pre></div><p><code>setFlags()</code> 方法可帮助保留用户在通过通知打开应用后的预期导航体验。</p><ul><li>专用于响应通知的 Activity。启动一个新任务,而不是添加到应用的现有任务和返回堆栈。</li><li>应用的常规应用流程中存在的 Activity。启动 Activity 时应创建返回堆栈,以便保留用户对返回和向上按钮的预期。</li></ul><h4 id="显示通知"><a href="#显示通知" class="headerlink" title="显示通知"></a>显示通知</h4><p><code>NotificationManagerCompat.notify()</code> 方法参数为:通知 ID 和 <code>NotificationCompat.Builder.build()</code>。</p><h3 id="创建折叠式通知"><a href="#创建折叠式通知" class="headerlink" title="创建折叠式通知"></a>创建折叠式通知</h3><ol><li>创建视图布局</li><li><code>RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_name);</code></li><li><code>notification.bigContentView = remoteViews;</code> <strong>or</strong> <code>notification.contentView = remoteViews;</code></li></ol><h3 id="添加操作按钮"><a href="#添加操作按钮" class="headerlink" title="添加操作按钮"></a>添加操作按钮</h3><p>将 <code>addAction()</code> 传递给 <code>PendingIntent</code> 方法。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> snoozeIntent = Intent(<span class="hljs-keyword">this</span>, MyBroadcastReceiver::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>).<span class="hljs-title">apply</span> </span>{ action = ACTION_SNOOZE putExtra(EXTRA_NOTIFICATION_ID, <span class="hljs-number">0</span>)}<span class="hljs-keyword">val</span> snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(<span class="hljs-keyword">this</span>, <span class="hljs-number">0</span>, snoozeIntent, <span class="hljs-number">0</span>)<span class="hljs-keyword">val</span> builder = NotificationCompat.Builder(<span class="hljs-keyword">this</span>, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle(<span class="hljs-string">"My notification"</span>) .setContentText(<span class="hljs-string">"Hello World!"</span>) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) .addAction(R.drawable.ic_snooze, getString(R.string.snooze), snoozePendingIntent)</code></pre></div><h3 id="添加直接回复操作"><a href="#添加直接回复操作" class="headerlink" title="添加直接回复操作"></a><a href="https://developer.android.google.cn/training/notify-user/build-notification?hl=zh#reply-action" target="_blank" rel="noopener">添加直接回复操作</a></h3><h3 id="添加进度条"><a href="#添加进度条" class="headerlink" title="添加进度条"></a>添加进度条</h3><p>如果可以估算操作在任何时间点的完成进度,可以通过调用 <code>setProgress(mamx, progress, false)</code>。移除进度条 <code>setProgress(0, 0, false)</code>。</p><p>如果需要显示不确定性进度条,可以调用 <code>setProgress(0, 0, true)</code>。移除进度条同样使用 <code>setProgress(0, 0, false)</code>。</p><h3 id="设置系统范围的类别"><a href="#设置系统范围的类别" class="headerlink" title="设置系统范围的类别"></a>设置系统范围的类别</h3><ul><li><code>setCategory()</code><br><code>CATEGORY_ALARM</code>、<code>CATEGORY_REMINDER</code>、<code>CATEGORY_EVENT</code>、<code>CATEGORY_CALL</code></li></ul><h3 id="显示紧急信息"><a href="#显示紧急信息" class="headerlink" title="显示紧急信息"></a>显示紧急信息</h3><p>如果应用的目标平台是 Android 10(API29)或更高版本,必须在应用清单文件中请求 <code>USE_FULL_SCREEN_INTENT</code> 权限,以便系统启动与时效性通知关联的全屏 Activity。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> fullScreenIntent = Intent(<span class="hljs-keyword">this</span>, ImportantActivity::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>)</span><span class="hljs-keyword">val</span> fullScreenPendingIntent = PendingIntent.getActivity(<span class="hljs-keyword">this</span>, <span class="hljs-number">0</span>, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)<span class="hljs-keyword">var</span> builder = NotificationCompat.Builder(<span class="hljs-keyword">this</span>, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle(<span class="hljs-string">"My notification"</span>) .setContentText(<span class="hljs-string">"Hello World!"</span>) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setFullScreenIntent(fullScreenPendingIntent, <span class="hljs-literal">true</span>)</code></pre></div><h3 id="设置锁定屏幕的公开范围"><a href="#设置锁定屏幕的公开范围" class="headerlink" title="设置锁定屏幕的公开范围"></a>设置锁定屏幕的公开范围</h3><p><code>setVisibility()</code></p><ul><li><code>VISIBILITY_PUBLIC</code> 显示通知的完整内容。</li><li><code>VISIBILITY_SECRET</code> 不在锁定屏幕上显示该通知的任何部分。</li><li><code>VISIBILITY_PRIVATE</code> 显示基本信息,但隐藏通知的完整内容。当设置为此级别时,可以通过 <code>setPublicVersion()</code> 方法提供通知内容的备用版本。</li></ul><h3 id="更新和移除通知"><a href="#更新和移除通知" class="headerlink" title="更新和移除通知"></a>更新和移除通知</h3><ul><li><code>NotificationManagerCompat.notify()</code> 更新通知,传入参数为之前使用的具有同一 ID 的通知,如果之前的通知已被关闭,系统会创建一个新通知。调用 <code>setOnlyAlertOnce()</code> 方法会使得通知只在首次出现时打断用户。</li><li><code>cancel(ID)</code>、<code>cancelAll()</code>、<code>setTimeoutAfter()</code></li></ul><h2 id="创建展开式通知"><a href="#创建展开式通知" class="headerlink" title="创建展开式通知"></a>创建展开式通知</h2><ul><li><p><code>NotificationCompat.BigPictureStyle</code> 添加大图片,如需使该图片仅在通知收起时显示为缩略图,调用 <code>setLargeIcon()</code> 并向其传递图片,同时调用 <code>BigPictureStyle.bigLargeIcon(null)</code>,这样大图标就会在通知展开时消失。</p></li><li><p><code>NotificationCompat.BigTextStyle</code> 添加一大段文本</p></li><li><p><code>NotificationCOmpat.InboxStyle</code> 创建收件箱样式的通知。如需添加新行,最多可调用 <code>addLine()</code> 6 次,若超过 6 次,仅显示前 6 行。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">var</span> notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_mail) .setContentTitle(<span class="hljs-string">"5 New mails from "</span> + sender.toString()) .setContentText(subject) .setLargeIcon(aBitmap) .setStyle(NotificationCompat.InboxStyle() .addLine(messageSnippet1) .addLine(messageSnippet2)) .build()</code></pre></div></li><li><p><a href="https://developer.android.google.cn/training/notify-user/expanded?hl=zh#message-style" target="_blank" rel="noopener">在通知中显示对话</a></p></li><li><p><a href="https://developer.android.google.cn/training/notify-user/expanded?hl=zh#media-style" target="_blank" rel="noopener">使用媒体控件创建通知</a></p></li></ul><h2 id="从通知启动-Activity"><a href="#从通知启动-Activity" class="headerlink" title="从通知启动 Activity"></a>从通知启动 Activity</h2><h3 id="设置常规-Activity-PendingIntent"><a href="#设置常规-Activity-PendingIntent" class="headerlink" title="设置常规 Activity PendingIntent"></a>设置常规 Activity PendingIntent</h3><p>使用 <code>TaskStackBuilder</code> 设置 <code>PendingIntent</code>。</p><ol><li><p><strong>定义应用的 Activity 层次结构</strong><br>通过应用清单文件为每个 <code><activity></code> 元素添加 <code>android:parentActivityName</code> 属性来定义 Activity 的自然层次结构。</p></li><li><p><strong>构建包含返回堆栈的 PendingIntent</strong><br>创建 <code>TaskStackBuilder</code> 的实例并调用 <code>addNextIntentWithParentStack()</code>,向其传递需要启动的 Activity 的 <code>Intent</code>。只要为每个 Activity 定义了父 Activity,就可以调用 <code>getPendingIntent()</code> 来接收包含整个返回堆栈的 <code>PendingIntent</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">// Create an Intent for the activity you want to start</span><span class="hljs-keyword">val</span> resultIntent = Intent(<span class="hljs-keyword">this</span>, ResultActivity::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>)</span><span class="hljs-comment">// Create the TaskStackBuilder</span><span class="hljs-keyword">val</span> resultPendingIntent: PendingIntent? = TaskStackBuilder.create(<span class="hljs-keyword">this</span>).run { <span class="hljs-comment">// Add the intent, which inflates the back stack</span> addNextIntentWithParentStack(resultIntent) <span class="hljs-comment">// Get the PendingIntent containing the entire back stack</span> getPendingIntent(<span class="hljs-number">0</span>, PendingIntent.FLAG_UPDATE_CURRENT)}</code></pre></div></li></ol><h3 id="设置特殊-Activity-PendingIntent"><a href="#设置特殊-Activity-PendingIntent" class="headerlink" title="设置特殊 Activity PendingIntent"></a>设置特殊 Activity PendingIntent</h3><ol><li><p>在清单中将以下属性添加到 <code><activity></code> 元素。</p><ul><li><code>android:taskAffinity=""</code><br>结合 <code>FLAG_ACTIVITY_NEW_TASK</code> 标记使用,将此属性设置为空,可确保这类 Activity 不会进入应用的默认任务。</li><li><code>android:excludeFromRecents="true"</code><br>用于从“最近”中排除新任务,以免用户意外返回它。</li></ul></li><li><p>构建并发出通知<br>a. 创建可启动 <code>Activity</code> 的 <code>Intent</code>。<br>b. 通过使用 <code>FLAG_ACTIVITY_NEW_TASK</code> 和 <code>FLAG_ACTIVITY_CLEAR_TASK</code> 标记调用 <code>setFlags()</code>,将 <code>Activity</code> 设置为在新的空任务中启动。<br>c. 通过调用 <code>getActivity()</code> 创建 <code>PendingIntent</code></p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> notifyIntent = Intent(<span class="hljs-keyword">this</span>, ResultActivity::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>).<span class="hljs-title">apply</span> </span>{ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK}<span class="hljs-keyword">val</span> notifyPendingIntent = PendingIntent.getActivity( <span class="hljs-keyword">this</span>, <span class="hljs-number">0</span>, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT)</code></pre></div></li><li><p>将 <code>PendingIntent</code> 传递到通知中。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> builder = NotificationCompat.Builder(<span class="hljs-keyword">this</span>, CHANNEL_ID).apply { setContentIntent(notifyPendingIntent) ...}with(NotificationManagerCompat.from(<span class="hljs-keyword">this</span>)) { notify(NOTIFICATION_ID, builder.build())}</code></pre></div></li></ol><h2 id="创建一组通知"><a href="#创建一组通知" class="headerlink" title="创建一组通知"></a>创建一组通知</h2><ul><li>调用 <code>setGroup()</code> 方法,传入参数为一个唯一标识符字符串。</li><li><code>setSortKey()</code> 更改通知顺序,默认根据发布时间排序</li><li><code>setGroupAlertBehavior()</code> 通知组的提醒应由其他通知处理。如果只希望通知组摘要发出提醒,那么通知组中的所有子级都应具有通知组提醒行为 <code>GROUP_ALERT_SUMMARY</code>。其他选项包括 <code>GROUP_ALERT_ALL</code> 和 <code>GROUP_ALERT_CHILDREN</code>。</li><li><code>setGroupSummary(true)</code> 设置通知组摘要。</li></ul><h2 id="创建和管理通知渠道"><a href="#创建和管理通知渠道" class="headerlink" title="创建和管理通知渠道"></a>创建和管理通知渠道</h2><p>从 Android 8.0(API 级别 26)开始,所有通知都必须分配到相应的渠道。</p><h3 id="创建通知渠道"><a href="#创建通知渠道" class="headerlink" title="创建通知渠道"></a>创建通知渠道</h3><ol><li>构建一个具有唯一渠道 ID、用户可见名称和重要性级别的 <code>NotificationChannel</code> 对象。</li><li>(可选)使用 <code>setDescription()</code> 指定用户在系统设置中看到的说明。</li><li>注册通知渠道,方法是将该渠道传递给 <code>createNotificationChannel()</code>。</li></ol><h4 id="设置重要性级别"><a href="#设置重要性级别" class="headerlink" title="设置重要性级别"></a>设置重要性级别</h4><p>渠道重要性会影响在渠道中发布的所有通知的干扰级别,包括从 <code>IMPORTANCE_NONE(0)</code> 到 <code>IMPORTANCE_HIGH(4)</code> 5 个重要性级别。</p><h3 id="读取通知渠道设置"><a href="#读取通知渠道设置" class="headerlink" title="读取通知渠道设置"></a>读取通知渠道设置</h3><ol><li>通过调用 <code>getNotificationChannel()</code> 或 <code>getNotificationChannels()</code> 来获取 <code>NotificationChannel</code> 对象。</li><li>查询特定的渠道设置,例如 <code>getVibrationPattern()</code>、<code>getSound()</code> 和 <code>getImportance()</code>。</li></ol><h3 id="打开通知渠道设置"><a href="#打开通知渠道设置" class="headerlink" title="打开通知渠道设置"></a>打开通知渠道设置</h3><p>创建通知渠道后,无法以编程方式更改通知渠道的视觉和听觉行为,只有用户可以通过系统设置更改渠道行为。为了让用户轻松访问这些通知设置,应在应用的设置界面中添加一个用于打开这些系统设置的项。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { putExtra(Settings.EXTRA_APP_PACKAGE, packageName) putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId())}startActivity(intent)</code></pre></div><h3 id="删除通知渠道"><a href="#删除通知渠道" class="headerlink" title="删除通知渠道"></a>删除通知渠道</h3><p>可以通过调用 <code>deleteNotificationChannel()</code> 删除通知渠道。</p><h3 id="创建通知渠道分组"><a href="#创建通知渠道分组" class="headerlink" title="创建通知渠道分组"></a>创建通知渠道分组</h3><p>创建新渠道分组</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">// The id of the group.</span><span class="hljs-keyword">val</span> groupId = <span class="hljs-string">"my_group_01"</span><span class="hljs-comment">// The user-visible name of the group.</span><span class="hljs-keyword">val</span> groupName = getString(R.string.group_name)<span class="hljs-keyword">val</span> notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) <span class="hljs-keyword">as</span> NotificationManagernotificationManager.createNotificationChannelGroup(NotificationChannelGroup(groupId, groupName))</code></pre></div><p>创建分组后,可以调用 <code>setGroup()</code> 将 <code>NotificationChannel</code> 对象与该分组相关联。</p><h2 id="修改通知标志"><a href="#修改通知标志" class="headerlink" title="修改通知标志"></a><a href="https://developer.android.google.cn/training/notify-user/badges?hl=zh" target="_blank" rel="noopener">修改通知标志</a></h2><h2 id="创建自定义通知"><a href="#创建自定义通知" class="headerlink" title="创建自定义通知"></a><a href="https://developer.android.google.cn/training/notify-user/custom-notification?hl=zh" target="_blank" rel="noopener">创建自定义通知</a></h2>]]></content>
<categories>
<category>安卓</category>
<category>组件</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>Android中的单位</title>
<link href="/2020/08/02/Android%E4%B8%AD%E7%9A%84%E5%8D%95%E4%BD%8D/"/>
<url>/2020/08/02/Android%E4%B8%AD%E7%9A%84%E5%8D%95%E4%BD%8D/</url>
<content type="html"><![CDATA[<p>在开发过程中,一般使用 xml 进行界面布局的绘制,在设置界面布局宽高方面,Android 提供了多种计量单位(px、dp/dip、sp、dpi等)。</p><a id="more"></a><h3 id="单位详解"><a href="#单位详解" class="headerlink" title="单位详解"></a>单位详解</h3><ul><li><p>px<br>像素(Pixels),构成图像的最小单位。</p></li><li><p>dp/dip<br>Density Independent Pixels,密度无关像素。</p></li><li><p>sp<br>Scale-Independent Pixels,可以根据文字大小首选项进行缩放,常用于设置字体大小。</p></li><li><p>dpi<br>Dots Per Inch,屏幕像素密度,即每英寸上的像素点数。</p></li></ul><h3 id="dp、px-转换"><a href="#dp、px-转换" class="headerlink" title="dp、px 转换"></a>dp、px 转换</h3><h4 id="Java-版"><a href="#Java-版" class="headerlink" title="Java 版"></a>Java 版</h4><div class="hljs"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">dip2px</span><span class="hljs-params">(Context context, <span class="hljs-keyword">float</span> dipValue)</span></span>{ <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> scale = context.getResources().getDisplayMetrics().density; <span class="hljs-keyword">return</span> (<span class="hljs-keyword">int</span>)(dipValue * scale + <span class="hljs-number">0.5f</span>);}<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">px2dip</span><span class="hljs-params">(Context context, <span class="hljs-keyword">float</span> pxValue)</span></span>{ <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> scale = context.getResource().getDisplayMetrics().density; <span class="hljs-keyword">return</span> (<span class="hljs-keyword">int</span>)(pxValue / scale + <span class="hljs-number">0.5f</span>);}</code></pre></div><h4 id="Kotlin-版"><a href="#Kotlin-版" class="headerlink" title="Kotlin 版"></a>Kotlin 版</h4><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">dp2px</span><span class="hljs-params">(dp: <span class="hljs-type">Float</span>)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">val</span> scale = GifFun.getContext().resources.displayMetrics.density <span class="hljs-keyword">return</span> (dp * scale + <span class="hljs-number">0.5f</span>).toInt()}<span class="hljs-comment">/**</span><span class="hljs-comment"> * 根据手机的分辨率将px转成dp</span><span class="hljs-comment"> */</span><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">px2dp</span><span class="hljs-params">(px: <span class="hljs-type">Float</span>)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">val</span> scale = GifFun.getContext().resources.displayMetrics.density <span class="hljs-keyword">return</span> (px / scale + <span class="hljs-number">0.5f</span>).toInt()}</code></pre></div>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
</entry>
<entry>
<title>Plan</title>
<link href="/2020/08/02/Aplan/"/>
<url>/2020/08/02/Aplan/</url>
<content type="html"><![CDATA[<h3 id="以前"><a href="#以前" class="headerlink" title="以前"></a>以前</h3><ul><li><input disabled="" type="checkbox"> <a href="https://developer.android.com/guide/topics/resources/runtime-changes#HandlingTheChange" target="_blank" rel="noopener">自行处理配置变更</a><br>如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免 Activity 重启,则可声明 Activity 自行处理配置变更,从而阻止系统重启 Activity。</li><li><input disabled="" type="checkbox"> <a href="https://developer.android.com/guide/topics/resources/internationalization" target="_blank" rel="noopener">Unicode 和国际化支持</a></li><li><input disabled="" type="checkbox"> <a href="https://developer.android.com/training/testing" target="_blank" rel="noopener">在 Android 平台上测试应用</a></li><li><input disabled="" type="checkbox"> <a href="https://developer.android.com/reference/androidx/preference/PreferenceFragmentCompat" target="_blank" rel="noopener">PreferenceFragmentCompat</a> 偏好显示</li><li><input disabled="" type="checkbox"> <a href="https://developer.android.com/topic/libraries/architecture" target="_blank" rel="noopener">Android 架构组件</a> (稍稍再看)</li><li><input disabled="" type="checkbox"> <a href="https://developer.android.com/guide/topics/ui/custom-components" target="_blank" rel="noopener">自定义视图组件</a> (稍稍后)</li></ul><h3 id="08-02"><a href="#08-02" class="headerlink" title="08-02"></a>08-02</h3><ul><li><input checked="" disabled="" type="checkbox"> <a href="https://developer.android.google.cn/guide/components/processes-and-threads?hl=zh" target="_blank" rel="noopener">进程和线程</a> 08/03</li><li><input checked="" disabled="" type="checkbox"> <a href="https://developer.android.google.cn/guide/topics/ui/notifiers/notifications?hl=zh" target="_blank" rel="noopener">通知</a> 08/04</li><li><input checked="" disabled="" type="checkbox"> 事务 08/05<br> 事务是应用程序中一系列严密的操作,事务具有原子性,一个事务中的一系列操作要么全部成功,要么一个不做。事务的四大特征(ACID)原子性、一致性、隔离性、持久性。<ul><li><code>beginTransition()</code></li><li><code>endTransition()</code></li></ul></li><li><input disabled="" type="checkbox"> inBitmap</li><li><input disabled="" type="checkbox"> JobSchedule</li><li><input disabled="" type="checkbox"> JobIntentService</li></ul><h3 id="08-04"><a href="#08-04" class="headerlink" title="08-04"></a>08-04</h3><ul><li><input checked="" disabled="" type="checkbox"> <a href="https://developer.android.google.cn/training/appbar?hl=zh" target="_blank" rel="noopener">添加应用栏</a> 08/05</li><li><input disabled="" type="checkbox"> <a href="https://developer.android.google.cn/guide/topics/media?hl=zh" target="_blank" rel="noopener">音频和视频</a></li></ul><h3 id="08-05"><a href="#08-05" class="headerlink" title="08-05"></a>08-05</h3><ul><li><input disabled="" type="checkbox"> <a href="https://www.runoob.com/sqlite/sqlite-tutorial.html" target="_blank" rel="noopener">SQLite</a></li><li><input disabled="" type="checkbox"> ContentProvider</li><li><input disabled="" type="checkbox"> <a href="http://airbnb.io/lottie/" target="_blank" rel="noopener">Lottie</a> 动画</li><li><input disabled="" type="checkbox"> Permission<ul><li><input checked="" disabled="" type="checkbox"> <a href="https://github.com/guolindev/PermissionX" target="_blank" rel="noopener">PermissionX</a> 08/05 文档未写(也就几个方法而已)</li><li><input disabled="" type="checkbox"> <a href="https://github.com/permissions-dispatcher/PermissionsDispatcher" target="_blank" rel="noopener">PermissionsDispatcher</a></li></ul></li><li><input disabled="" type="checkbox"> Android 进阶之光</li></ul><h3 id="08-06"><a href="#08-06" class="headerlink" title="08-06"></a>08-06</h3><ul><li><input disabled="" type="checkbox"> 黏性广播</li></ul><h3 id="08-07"><a href="#08-07" class="headerlink" title="08-07"></a>08-07</h3><ul><li><input disabled="" type="checkbox"> 跳表</li><li><input disabled="" type="checkbox"> 红黑树</li><li><input disabled="" type="checkbox"> AVL</li><li><input disabled="" type="checkbox"> Hook 框架:xposed、Substrate、Cydia、frida</li><li><input disabled="" type="checkbox"> <a href="https://github.com/airbnb/epoxy" target="_blank" rel="noopener">epoxy</a></li></ul>]]></content>
<categories>
<category>其他</category>
</categories>
</entry>
<entry>
<title>过时API</title>
<link href="/2020/08/02/%E8%BF%87%E6%97%B6API/"/>
<url>/2020/08/02/%E8%BF%87%E6%97%B6API/</url>
<content type="html"><![CDATA[<ul><li><p>getResources().getColor()方法在Android 6.0即API 23中 已经过时,替代方法为:</p><div class="hljs"><pre><code class="hljs kotlin">ContextCompat.getColor(context, R.color.name)</code></pre></div></li></ul>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
</entry>
<entry>
<title>RecyclerView相关</title>
<link href="/2020/08/02/RecyclerView%E7%9B%B8%E5%85%B3/"/>
<url>/2020/08/02/RecyclerView%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<h2 id="为-RecyclerView-设置点击事件和长按事件"><a href="#为-RecyclerView-设置点击事件和长按事件" class="headerlink" title="为 RecyclerView 设置点击事件和长按事件"></a>为 RecyclerView 设置点击事件和长按事件</h2><a id="more"></a><ol><li><p>新建接口</p><div class="hljs"><pre><code class="hljs java"> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">OnItemClickListener</span> </span>{ <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">onItemClick</span><span class="hljs-params">(View view, <span class="hljs-keyword">int</span> position)</span></span>; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">onItemLongClick</span><span class="hljs-params">(View view, <span class="hljs-keyword">int</span> position)</span></span>;}</code></pre></div></li><li><p>在 Adapter 类的 <code>onBindViewHolder()</code> 方法中,使用当前 item 的点击和长按事件触发接口的对应方法。在 Adapter 类中添加 <code>setOnItemClickListener(OnItemClickListener onItemClickListener)</code> 方法。</p></li><li><p>在 Activity 中调用 Adapter 的 <code>setOnItemClickListener()</code> 方法。</p></li></ol>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
<tags>
<tag>控件</tag>
</tags>
</entry>
<entry>
<title>文件相关</title>
<link href="/2020/08/02/%E6%96%87%E4%BB%B6%E7%9B%B8%E5%85%B3/"/>
<url>/2020/08/02/%E6%96%87%E4%BB%B6%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<h2 id="文件外-内部存储获取各种存储目录路径"><a href="#文件外-内部存储获取各种存储目录路径" class="headerlink" title="文件外/内部存储获取各种存储目录路径"></a>文件外/内部存储获取各种存储目录路径</h2><a id="more"></a><p>摘录自:[博文][1]</p><h3 id="访问内部存储API"><a href="#访问内部存储API" class="headerlink" title="访问内部存储API"></a>访问内部存储API</h3><ol><li><code>Environment.getDataDirectory()</code> /data</li><li><code>getFilesDir().getAbsolutePath()</code> /data/user/0/packagename/files</li><li><code>getCacheDir().getAbsolutePath()</code> /data/user/0/packagename/cache</li></ol><h3 id="访问外部存储API"><a href="#访问外部存储API" class="headerlink" title="访问外部存储API"></a>访问外部存储API</h3><p><code>getExternalFilesDirs(Environment.MEDIA_MOUNTED)</code></p><ol><li><code>Environment.getExternalStorageDirectory().getAbsolutePath()</code> /storage/emulated/0(已过时,百度替代方法为 <code>Context.getExternalFilsDir()</code>, 但此方法返回路径与过时方法不一致,反倒是与第三条方法相同,好像与29文件相关权限有关,但我尚未对此有过了解)</li><li><code>Environment.getExternalStoragePublicDirectory("").getAbsolutePath()</code> /storage/emulated/0(已过时)</li><li><code>getExternalFilesDir("").getAbsolutePath()</code> /storage/emulated/0/Android/data/packgename/files</li><li><code>getExternalCacheDir().getAbsolutePath()</code> /storage/emulated/0/Android/data/packgename/cache</li></ol><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p><code>Environment.getDownloadCacheDirectory()</code> /data/cache<br><code>Environment.getRootDirectory()</code> /system</p><p>/data目录下的文件物理上存放在我们通常所说的内部存储里面。<br>/storage目录下的文件物理上存放在我们通常所说的外部存储里面。<br>/system用于存放系统文件,/cache用于存放一些缓存文件,物理上它们也是存放在内部存储里面的。</p>]]></content>
<categories>
<category>安卓</category>
<category>文件</category>
</categories>
<tags>
<tag>文件</tag>
</tags>
</entry>
<entry>
<title>Android使用Git时的一些小事</title>
<link href="/2020/08/02/Android%E4%BD%BF%E7%94%A8Git%E6%97%B6%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B0%8F%E4%BA%8B/"/>
<url>/2020/08/02/Android%E4%BD%BF%E7%94%A8Git%E6%97%B6%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B0%8F%E4%BA%8B/</url>
<content type="html"><![CDATA[<h2 id="Q:Git-版本控制时,哪些文件不需要-add"><a href="#Q:Git-版本控制时,哪些文件不需要-add" class="headerlink" title="Q:Git 版本控制时,哪些文件不需要 add"></a>Q:Git 版本控制时,哪些文件不需要 <code>add</code></h2><a id="more"></a><p>A:根目录 .gitignore 文件</p><div class="hljs"><pre><code class="hljs java">*.iml.gradle.idea/local.properties.DS_Store/build/captures.externalNativeBuild.cxx</code></pre></div><p>app目录下 .gitignore文件</p><div class="hljs"><pre><code class="hljs java">/build</code></pre></div><ul><li>项目根目录中需要忽略的:<ul><li>.gradle(目录)</li><li>.idea(目录)</li><li>.build(目录)</li><li>local.properties</li><li>*.iml</li></ul></li><li>模块目录中需要忽略的:<ul><li>build(目录)</li></ul></li></ul><h2 id="Q:如何将项目同时推送到-github-和-gitee"><a href="#Q:如何将项目同时推送到-github-和-gitee" class="headerlink" title="Q:如何将项目同时推送到 github 和 gitee"></a>Q:如何将项目同时推送到 github 和 gitee</h2><p>A:假设项目已推送至 github,在 gitee 新建一个仓库,新建时导入已有项目。通过命令将码云项目地址添加到本地已有的 remote 下,<code>git remote set-url --add origin <gitee 项目地址></code>,也可以修改本地项目的配置文件。推送时,执行 <code>git push</code> 即可。</p><h2 id="Q:安卓目录下哪些文件可以删除"><a href="#Q:安卓目录下哪些文件可以删除" class="headerlink" title="Q:安卓目录下哪些文件可以删除"></a>Q:安卓目录下哪些文件可以删除</h2><p>A:git 版本控制中,不用 <code>add</code> 的文件即是。</p>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>WorkManager</title>
<link href="/2020/08/02/WorkManager/"/>
<url>/2020/08/02/WorkManager/</url>
<content type="html"><![CDATA[<p>使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。</p><a id="more"></a><p><a href="https://developer.android.google.cn/topic/libraries/architecture/workmanager?hl=zh" target="_blank" rel="noopener">文档</a></p><h2 id="将-WorkManager-添加到-Android-项目中"><a href="#将-WorkManager-添加到-Android-项目中" class="headerlink" title="将 WorkManager 添加到 Android 项目中"></a>将 WorkManager 添加到 Android 项目中</h2><p>要将 WorkManager 库导入到 Android 项目中,请将以下依赖项添加到应用的 build.gradle 文件:</p><div class="hljs"><pre><code class="hljs java">dependencies { def work_version = <span class="hljs-string">"2.3.4"</span> <span class="hljs-comment">// (Java only)</span> implementation <span class="hljs-string">"androidx.work:work-runtime:$work_version"</span> <span class="hljs-comment">// Kotlin + coroutines</span> implementation <span class="hljs-string">"androidx.work:work-runtime-ktx:$work_version"</span> <span class="hljs-comment">// optional - RxJava2 support</span> implementation <span class="hljs-string">"androidx.work:work-rxjava2:$work_version"</span> <span class="hljs-comment">// optional - GCMNetworkManager support</span> implementation <span class="hljs-string">"androidx.work:work-gcm:$work_version"</span> <span class="hljs-comment">// optional - Test helpers</span> androidTestImplementation <span class="hljs-string">"androidx.work:work-testing:$work_version"</span> }</code></pre></div><h2 id="创建后台任务"><a href="#创建后台任务" class="headerlink" title="创建后台任务"></a>创建后台任务</h2><p>任务是使用 <code>Worker</code> 类定义的。<code>doWork()</code> 方法在 WorkManager 提供的后台线程上同步运行。</p><p>从 <code>doWork()</code> 返回的 <code>Result</code> 会通知 WorkManager 任务:</p><ul><li>已成功完成:<code>Result.success()</code></li><li>已失败:<code>Result.failure()</code></li><li>需要稍后重试:<code>Result.retry()</code></li></ul><h3 id="配置运行任务的方式和时间"><a href="#配置运行任务的方式和时间" class="headerlink" title="配置运行任务的方式和时间"></a>配置运行任务的方式和时间</h3><p><code>Worker</code> 定义工作单元,<code>WorkRequest</code> 则定义工作的运行方式和时间。对于一次性 <code>WorkRequest</code>,可以使用 <code>OneTimeWorkRequest</code>,对于周期性工作,可以使用 <code>PeriodicWorkRequest</code>。</p><h3 id="工作约束"><a href="#工作约束" class="headerlink" title="工作约束"></a>工作约束</h3><p><code>Constraints</code> 指明工作何时可以运行。具体约束详见<a href="https://developer.android.google.cn/reference/androidx/work/Constraints.Builder?hl=zh" target="_blank" rel="noopener">约束的完整列表</a>。</p><h3 id="初始延迟"><a href="#初始延迟" class="headerlink" title="初始延迟"></a>初始延迟</h3><p><code>setInitialDelay()</code> 初始延迟,加入队列后至少经过多长时间再运行。</p><h3 id="重试和退避政策"><a href="#重试和退避政策" class="headerlink" title="重试和退避政策"></a>重试和退避政策</h3><p>如果想要让 WorkManager 重新尝试执行任务,可以从工作器返回 <code>Result.retry()</code>。</p><p>退避延迟时间指定重试工作前的最短等待时间。退避政策定义了再后续重试过程中,退避延迟时间随时间以怎样的方式增长,默认情况下按 <code>BackoffPolicy.EXPONENTIAL</code> 延长。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>() .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build()</code></pre></div><h4 id="退避政策"><a href="#退避政策" class="headerlink" title="退避政策"></a>退避政策</h4><ul><li><code>BackoffPolicy.EXPONENTIAL</code> 指数方式增长</li><li><code>BackoffPolicy.LINEAR</code> 线性增长</li></ul><h3 id="定义任务的输入-输出"><a href="#定义任务的输入-输出" class="headerlink" title="定义任务的输入/输出"></a>定义任务的输入/输出</h3><p>输入和输出值以键值对的形式存储在 <code>Data</code> 对象中。</p><h4 id="输入"><a href="#输入" class="headerlink" title="输入"></a>输入</h4><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">// 设置输入数据</span><span class="hljs-keyword">val</span> imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)<span class="hljs-comment">// 访问输入参数</span><span class="hljs-keyword">val</span> imageUriInput = getInputData().getString(Constants.KEY_IMAGE_URI)</code></pre></div><h4 id="输出"><a href="#输出" class="headerlink" title="输出"></a>输出</h4><p><code>Data</code> 也可用于输出返回值,将其添加到 <code>Result.success()</code> 或 <code>Result.failure()</code> 时的 <code>Result()</code> 中。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadWorker</span></span>(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">doWork</span><span class="hljs-params">()</span></span>: Result { <span class="hljs-comment">// Get the input</span> <span class="hljs-keyword">val</span> imageUriInput = getInputData().getString(Constants.KEY_IMAGE_URI) <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> validate inputs.</span> <span class="hljs-comment">// Do the work</span> <span class="hljs-keyword">val</span> response = uploadFile(imageUriInput) <span class="hljs-comment">// Create the output of the work</span> <span class="hljs-keyword">val</span> outputData = workDataOf(Constants.KEY_IMAGE_URL to response.imageUrl) <span class="hljs-comment">// Return the output</span> <span class="hljs-keyword">return</span> Result.success(outputData) }}</code></pre></div><h3 id="标记工作"><a href="#标记工作" class="headerlink" title="标记工作"></a>标记工作</h3><p><code>WorkRequest.Builder.addTag(String)</code> 为任意 <code>WorkRequest</code> 对象分配标识字符串,按逻辑对任务进行分组,就可以对使用特定标记的所有任务执行操作。</p><h2 id="工作状态和观察工作"><a href="#工作状态和观察工作" class="headerlink" title="工作状态和观察工作"></a>工作状态和观察工作</h2><h3 id="工作状态"><a href="#工作状态" class="headerlink" title="工作状态"></a>工作状态</h3><ul><li><code>BLOCKED</code> 有尚未完成的前提性工作</li><li><code>ENQUEUED</code> 工作能够在满足 <code>Constraints</code> 和时机条件后立即执行</li><li><code>RUNNING</code> 工作器在活跃运行</li><li>终止 <code>State</code>,只有 <code>OneTimeWorkRequest</code> 可以进入这种 <code>State</code><ul><li><code>SUCCEEDED</code> 工作器返回 <code>Result.success()</code>。</li><li><code>FAILED</code> 工作器返回 <code>Result.failure()</code>。<strong>所有依赖工作也会被标记为 <code>FAILED</code>,并且不会运行</strong>。</li></ul></li><li><code>CANCELLED</code> 明确取消尚未终止的 <code>WorkRequest</code> 时。<strong>所有依赖工作也会被标记为 <code>CANCELLED</code>,并且不会运行。</strong></li></ul><h3 id="观察工作"><a href="#观察工作" class="headerlink" title="观察工作"></a>观察工作<a id="观察工作"></a></h3><p>将工作加入队列后,可以通过 WorkManager 检查其状态,相关信息在 <code>WorkInfo</code> 对象中提供,包括工作的 <code>id</code>、标签、当前 <code>State</code> 和任何输出数据。</p><ul><li><code>WorkManager.getWorkInfoById(UUID)</code> or <code>WorkManager.getWorkInfoByIdLiveData(UUID)</code></li><li><code>WorkManager.getWorkInfosByTag(String)</code> or <code>WorkManager.getWorkInfosByTagLiveData(String)</code></li><li><code>WorkManager.getWorkInfosForUniqueWork(String)</code> or <code>WorkManager.getWorkInfosForUniqueWorkLiveData(String)</code></li></ul><p>利用每个方法的 <code>LiveData</code> 变量,可以通过注册监听器观察 <code>WorkInfo</code> 的变化。</p><div class="hljs"><pre><code class="hljs kotlin">WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(uploadWorkRequest.id) .observe(lifecycleOwner, Observer { workInfo -> <span class="hljs-keyword">if</span> (workInfo != <span class="hljs-literal">null</span> && workInfo.state == WorkInfo.State.SUCCEEDED) { displayMessage(<span class="hljs-string">"Work finished!"</span>) } })</code></pre></div><h2 id="观察工作器的中间进度"><a href="#观察工作器的中间进度" class="headerlink" title="观察工作器的中间进度"></a>观察工作器的中间进度</h2><p><code>ListenableWorker</code> 支持 <code>setProgressAsync()</code> API,此类 API 可以保留中间进度。</p><p><strong>只有在运行时才能观察到和更新进度信息。</strong></p><h3 id="更新进度"><a href="#更新进度" class="headerlink" title="更新进度"></a>更新进度</h3><p>对于 <code>ListenableWorker</code> 或 <code>Worker</code>,<code>setProgressAsync()</code> 会返回 <code>ListenableFuture<Void></code>;更新进度是异步过程,因为更新过程包括将进度信息存储在数据库中。在 Kotlin 中,可以使用 <code>CoroutineWorker</code> 对象的 <code>setProgress()</code> 扩展函数来更新进度信息。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProgressWorker</span></span>(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) { <span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> { <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> Progress = <span class="hljs-string">"Progress"</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> delayDuration = <span class="hljs-number">1L</span> } <span class="hljs-keyword">override</span> <span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">doWork</span><span class="hljs-params">()</span></span>: Result { <span class="hljs-keyword">val</span> firstUpdate = workDataOf(Progress to <span class="hljs-number">0</span>) <span class="hljs-keyword">val</span> lastUpdate = workDataOf(Progress to <span class="hljs-number">100</span>) setProgress(firstUpdate) delay(delayDuration) setProgress(lastUpdate) <span class="hljs-keyword">return</span> Result.success() }}</code></pre></div><h3 id="观察进度"><a href="#观察进度" class="headerlink" title="观察进度"></a>观察进度</h3><p>使用<a href="#观察工作">观察工作</a>中的 <code>getWorkInfoBy...()</code> 或 <code>getWorkInfoBy...LiveData()</code> 方法,并引用 <code>WorkInfo</code>。</p><div class="hljs"><pre><code class="hljs kotlin">WorkManager.getInstance(applicationContext) <span class="hljs-comment">// requestId is the WorkRequest id</span> .getWorkInfoByIdLiveData(requestId) .observe(observer, Observer { workInfo: WorkInfo? -> <span class="hljs-keyword">if</span> (workInfo != <span class="hljs-literal">null</span>) { <span class="hljs-keyword">val</span> progress = workInfo.progress <span class="hljs-keyword">val</span> value = progress.getInt(Progress, <span class="hljs-number">0</span>) <span class="hljs-comment">// Do something with progress information</span> } })</code></pre></div><h2 id="链接工作"><a href="#链接工作" class="headerlink" title="链接工作"></a>链接工作</h2><ol><li><code>WorkManager.beginWith(OneTimeWorkRequest/List<OneTimeWorkRequest>)</code> 返回 <code>WorkContinuation</code> 实例。</li><li><code>WorkContinuation.then(OneTimeWorkRequest/List<OneTimeWorkRequest>)</code>,每次调用 <code>then()</code> 都会返回一个新的 <code>WorkContinuation</code> 实例。</li><li><code>WorkContinuation.enqueue()</code> 为 <code>WorkContinuation</code> 链排队。</li></ol><p><strong>如果添加了 <code>OneTimeWorkRequest</code> 的 <code>List</code>,这些请求可能会并行运行。</strong></p><div class="hljs"><pre><code class="hljs kotlin">WorkManager.getInstance(myContext) <span class="hljs-comment">// Candidates to run in parallel</span> .beginWith(listOf(filter1, filter2, filter3)) <span class="hljs-comment">// Dependent work (only runs after all previous work in chain)</span> .then(compress) .then(upload) <span class="hljs-comment">// Don't forget to enqueue()</span> .enqueue()</code></pre></div><h3 id="Input-Merger"><a href="#Input-Merger" class="headerlink" title="Input Merger"></a>Input Merger</h3><p>为了管理来自多个父级 <code>OneTimeWorkRequest</code> 的输入,<code>WorkManager</code> 使用 <code>OneTimeWorkRequestBuilder.setInputMerger()</code>。</p><ul><li><code>OverwritingInputMerger</code> 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。</li><li><code>ArrayCreatingInputMerger</code> 会尝试合并输入,并在必要时创建数组。</li></ul><h2 id="取消和停止工作"><a href="#取消和停止工作" class="headerlink" title="取消和停止工作"></a><a href="https://developer.android.google.cn/topic/libraries/architecture/workmanager/how-to/cancel-stop-work?hl=zh" target="_blank" rel="noopener">取消和停止工作</a></h2><p><code>WorkManager.cancelWorkById(UUID)</code></p><h2 id="重复性工作"><a href="#重复性工作" class="headerlink" title="重复性工作"></a>重复性工作</h2><p><code>PeriodicWorkRequest</code> 无法链接。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> constraints = Constraints.Builder() .setRequiresCharging(<span class="hljs-literal">true</span>) .build()<span class="hljs-keyword">val</span> saveRequest =PeriodicWorkRequestBuilder<SaveImageToFileWorker>(<span class="hljs-number">1</span>, TimeUnit.HOURS) .setConstraints(constraints) .build()WorkManager.getInstance(myContext) .enqueue(saveRequest)</code></pre></div><h2 id="唯一工作"><a href="#唯一工作" class="headerlink" title="唯一工作"></a>唯一工作</h2><p>调用 <code>WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)</code> 或 <code>WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest)</code> 创建唯一工作序列。</p><p>第二个参数为冲突解决策略,指定如果已存在具有该唯一名称的未完成工作链,应如何处理。</p><ul><li><code>REPLACE</code> 取消现有工作链,并将其替换为新工作链。</li><li><code>KEEP</code>,保持现有序列并忽略您的新请求。</li><li><code>APPEND</code> 在现有序列的最后一个任务完成后运行新序列的第一个任务。</li></ul><p>创建唯一工作链 <code>WorkManager.beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)</code>。</p>]]></content>
<categories>
<category>安卓</category>
<category>后台任务</category>
</categories>
<tags>
<tag>进阶知识</tag>
</tags>
</entry>
<entry>
<title>后台处理指南</title>
<link href="/2020/08/01/%E5%90%8E%E5%8F%B0%E5%A4%84%E7%90%86%E6%8C%87%E5%8D%97/"/>
<url>/2020/08/01/%E5%90%8E%E5%8F%B0%E5%A4%84%E7%90%86%E6%8C%87%E5%8D%97/</url>
<content type="html"><![CDATA[<p>每个 Android 应用都有一个主线程,负责处理界面(包括测量和绘制视图)、协调用户互动以及接收生命周期事件。如果有太多工作在主线程中进行,则应用可能会挂起或运行速度变慢。任何长时间运行的计算和操作(例如解码位图、访问磁盘或执行网络请求)都应在单独的后台线程上完成。</p><a id="more"></a><h2 id="为工作选择合适的解决方案"><a href="#为工作选择合适的解决方案" class="headerlink" title="为工作选择合适的解决方案"></a>为工作选择合适的解决方案</h2><ul><li><strong>工作可以延迟,还是需要立即执行?</strong></li><li><strong>工作是否依赖系统条件?</strong></li><li><strong>作业是否需要在确切的时间运行?</strong></li></ul><h2 id="WorkManager"><a href="#WorkManager" class="headerlink" title="WorkManager"></a>WorkManager</h2><p>对于可延迟的工作以及预计即使设备或应用重启也会运行的工作,可以使用 <a href="https://developer.android.google.cn/topic/libraries/architecture/workmanager?hl=zh" target="_blank" rel="noopener"><code>WorkManager</code></a>。WorkManager 是一个 Android 库,可在满足工作的条件(例如网络可用性和电源)时妥善运行可延迟的后台工作。</p><h2 id="前台任务"><a href="#前台任务" class="headerlink" title="前台任务"></a>前台任务</h2><p>对于需要立即运行并且必须执行完毕的由用户发起的工作,可以使用前台服务。使用前台服务可告知系统应用正在执行重要任务,不应被终止。前台服务通过通知栏中的不可关闭通知向用户显示。</p><h2 id="AlarmManager"><a href="#AlarmManager" class="headerlink" title="AlarmManager"></a>AlarmManager</h2><p>如果需要在确切的时间运行某项工作,可以使用 <a href="https://developer.android.google.cn/reference/android/app/AlarmManager?hl=zh" target="_blank" rel="noopener"><code>AlarmManager</code></a>。<code>AlarmManager</code> 会在指定的时间启动应用(如有必要),以便运行该作业。如果作业不需要在确切的时间运行,<code>WorkManager</code> 是更好的选择。</p><h2 id="DownloadManager"><a href="#DownloadManager" class="headerlink" title="DownloadManager"></a>DownloadManager</h2><p>如果应用执行长时间的 HTTP 下载,可以使用 <a href="https://developer.android.google.cn/reference/android/app/DownloadManager?hl=zh" target="_blank" rel="noopener">DownloadManager</a>。内容下载管理器会在后台执行下载操作,它负责处理 HTTP 互动,在下载失败或连接发生更改以及系统重新启动后重新尝试下载。</p>]]></content>
<categories>
<category>安卓</category>
<category>后台任务</category>
</categories>
<tags>
<tag>进阶知识</tag>
</tags>
</entry>
<entry>
<title>BroadcastReceiver</title>
<link href="/2020/08/01/BroadcastReceiver/"/>
<url>/2020/08/01/BroadcastReceiver/</url>
<content type="html"><![CDATA[<p>系统会在发生各种系统事件时自动发送广播,例如当系统进入和退出飞行模式时。系统广播会被发送给所有同意接收相关事件的应用。广播消息本身会被封装在一个 <code>Intent</code> 对象中,该对象的操作字符串会标识所发生的事件(例如 <code>android.intent.action.AIRPLANE_MODE</code>)。该 Intent 可能还包含绑定到其 extra 字段中的附加信息。例如,飞行模式 intent 包含布尔值 extra 来指示是否已开启飞行模式。</p><a id="more"></a><h2 id="接收广播"><a href="#接收广播" class="headerlink" title="接收广播"></a>接收广播</h2><p>应用可以通过两种方式接收广播:清单声明的接收器和上下文注册的接收器。</p><h3 id="清单声明的接收器"><a href="#清单声明的接收器" class="headerlink" title="清单声明的接收器"></a>清单声明的接收器</h3><p>如果应用以 API 级别 26 或更高级别的平台版本为目标,则不能使用清单为隐式广播声明接收器。大多数情况下,可以使用<a href="https://developer.android.google.cn/guide/background?hl=zh" target="_blank" rel="noopener">调度作业</a>来代替。</p><ol><li><p>在应用清单中指定 <code><receiver></code> 元素。</p><div class="hljs"><pre><code class="hljs kotlin"><receiver android:name=<span class="hljs-string">".MyBroadcastReceiver"</span> android:exported=<span class="hljs-string">"true"</span>> <intent-filter> <action android:name=<span class="hljs-string">"android.intent.action.BOOT_COMPLETED"</span>/> <action android:name=<span class="hljs-string">"android.intent.action.INPUT_METHOD_CHANGED"</span> /> </intent-filter></receiver></code></pre></div><p>Intent 过滤器指定接收器所订阅的广播操作。</p></li><li><p>创建 <code>BroadcastReceiver</code> 子类并实现 <code>onReceive(Context, Intent)</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> TAG = <span class="hljs-string">"MyBroadcastReceiver"</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyBroadcastReceiver</span> : <span class="hljs-type">BroadcastReceiver</span></span>() { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onReceive</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, intent: <span class="hljs-type">Intent</span>)</span></span> { StringBuilder().apply { append(<span class="hljs-string">"Action: <span class="hljs-subst">${intent.action}</span>\n"</span>) append(<span class="hljs-string">"URI: <span class="hljs-subst">${intent.toUri(Intent.URI_INTENT_SCHEME)}</span>\n"</span>) toString().also { log -> Log.d(TAG, log) Toast.makeText(context, log, Toast.LENGTH_LONG).show() } } }}</code></pre></div></li></ol><p>如果应用当前未运行,系统可以启动应用并发送广播。</p><p>系统会创建新的 <code>BroadcastReceiver</code> 组件对象来处理它接收到的每个广播。此对象仅在 <code>onReceive(Context, Intent)</code> 期间有效。一旦从此方法返回代码,系统便会认为该组件<strong>不再活跃</strong>。</p><h3 id="上下文注册的接收器"><a href="#上下文注册的接收器" class="headerlink" title="上下文注册的接收器"></a>上下文注册的接收器</h3><ol><li>创建 <code>BroadcastReceiver</code> 的实例。</li><li>创建 <code>IntentFilter</code> 并调用 <code>registerReceiver(BroadcastReceiver, IntentFilter)</code> 来注册接收器。</li><li>要停止广播,可以调用 <code>unregisterReceiver(android.content.BroadcastReceiver)</code>。</li></ol><p><strong>注意注册和注销接收器的位置。如果使用 Activity Context 在 <code>onCreate()</code> 中注册接收器,则需要在 <code>onDestory()</code> 中注销,以防止接收器从 Activity Context 中泄露出去。如果在 <code>onResume()</code> 中注册接收器,应在 <code>onPause()</code> 中注销,以防止多次注册接收器(此举可以减少不必要的系统开销,在暂停时停止接收广播)。</strong></p><h3 id="对进程状态的影响"><a href="#对进程状态的影响" class="headerlink" title="对进程状态的影响"></a>对进程状态的影响</h3><p><code>BroadcastReceiver</code> 的状态会影响其所在进程的状态,因此不应从广播接收器启动长时间运行的后太线程。<code>onReceive()</code> 完成后,系统可以随时终止进程来回收内存,在此过程中,也会终止进程中运行的派生线程。要避免这种情况,可以调用 <a href="https://developer.android.google.cn/reference/android/content/BroadcastReceiver?hl=zh#goAsync()" target="_blank" rel="noopener"><code>goAsync()</code></a> 或使用 <code>JobScheduler</code> 从接收器调度 <code>JobService</code>。</p><h2 id="发送广播"><a href="#发送广播" class="headerlink" title="发送广播"></a>发送广播</h2><ul><li><code>sendOrderedBroadcast(Intent, String)</code><br>一次向一个接收器发送广播。当接收器逐个顺序执行时,接收器可以向下传递效果,也可以中止广播。</li><li><code>sendBroadcast(Intent)</code><br>常规广播,会按随机的顺序向所有接收器发送广播。</li><li><code>LocalBroadcastManager.sendBroadcast</code><br>将广播发送给与发送器位于统一应用中的接收器。</li></ul><h2 id="通过权限限制广播"><a href="#通过权限限制广播" class="headerlink" title="通过权限限制广播"></a>通过权限限制广播</h2><h3 id="带权限的发送"><a href="#带权限的发送" class="headerlink" title="带权限的发送"></a>带权限的发送</h3><p>调用方法发送广播时,可以指定权限参数。接收器若要接受此广播,需要通过清单中的标记请求该权限。</p><h3 id="带权限的接收"><a href="#带权限的接收" class="headerlink" title="带权限的接收"></a>带权限的接收</h3><p>如果在注册广播接收器时制定了权限参数,广播方需要在清单中请求该权限。</p><h2 id="安全注意事项和最佳做法"><a href="#安全注意事项和最佳做法" class="headerlink" title="安全注意事项和最佳做法"></a><a href="https://developer.android.google.cn/guide/components/broadcasts?hl=zh#security-and-best-practices" target="_blank" rel="noopener">安全注意事项和最佳做法</a></h2><ul><li>如果不需要向应用以外的组件发送广播,可以使用 <code>LocalBroadcastManager</code>.</li><li>如果有许多应用在清单中注册接收相同的广播,可能会导致系统启动大量应用,为避免此种情况的发生,可以优先使用上下文注册方法。</li></ul>]]></content>
<categories>
<category>安卓</category>
<category>组件</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>AIDL</title>
<link href="/2020/07/31/AIDL/"/>
<url>/2020/07/31/AIDL/</url>
<content type="html"><![CDATA[]]></content>
<categories>
<category>安卓</category>
<category>进程</category>
</categories>
<tags>
<tag>进阶知识</tag>
</tags>
</entry>
<entry>
<title>Service</title>
<link href="/2020/07/31/Service/"/>
<url>/2020/07/31/Service/</url>
<content type="html"><![CDATA[<p><a href="https://developer.android.google.cn/guide/components/services?hl=zh#top_of_page" target="_blank" rel="noopener"><code>Service</code></a> 是一种可在后台执行长时间运行操作而不提供界面的组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过<strong>绑定</strong>到服务与之进行交互,甚至是执行<strong>进程间通信(IPC)</strong>。例如,服务可在后台处理网络事务、播放音乐、执行文件 I/O 或与内容提供程序进行交互。</p><a id="more"></a><ul><li><a href="#创建启动服务">创建启动服务</a><ul><li><a href="#启动服务">启动服务</a></li><li><a href="#停止服务">停止服务</a></li></ul></li><li><a href="#在前台运行服务">在前台运行服务</a></li><li><a href="#创建后台服务">创建后台服务(IntentService)</a></li><li><a href="#绑定服务">绑定服务</a></li></ul><h2 id="三种不同的服务类型"><a href="#三种不同的服务类型" class="headerlink" title="三种不同的服务类型"></a>三种不同的服务类型</h2><h3 id="前台"><a href="#前台" class="headerlink" title="前台"></a>前台</h3><p>前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。</p><h3 id="后台"><a href="#后台" class="headerlink" title="后台"></a>后台</h3><p>后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。</p><p><strong>注意:如果应用面向 API 级别 26 或更高版本,当应用本身未在前台运行时,系统会对<a href="https://developer.android.google.cn/about/versions/oreo/background?hl=zh" target="_blank" rel="noopener">运行后台服务施加限制</a>。在诸如此类的大多数情况下,应用应改为使用<a href="https://developer.android.google.cn/guide/background?hl=zh" target="_blank" rel="noopener">计划作业</a>。</strong></p><h3 id="绑定"><a href="#绑定" class="headerlink" title="绑定"></a>绑定</h3><p>当应用组件通过调用 <code>bindService()</code> 绑定到服务时,服务即处于<em>绑定</em>状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。</p><p><strong>注意:服务在其托管进程的主线程中运行,它</strong>既不<strong>创建自己的线程,</strong>也不<strong>在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。</strong></p><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><p>如果要创建<a href="https://developer.android.google.cn/reference/android/app/Service?hl=zh" target="_blank" rel="noopener">服务</a>,需要先创建 <code>Service</code> 的子类(或使用它的一个现有子类)。以下是需要重写的最重要的回调方法:</p><ul><li><code>onStartCommand()</code><br>当另一个组件请求启动服务时,系统会通过调用 <code>startService()</code> 来调用此方法。执行此方法时,服务即会启动并可在后台<strong>无限期运行</strong>。因此在服务工作完成后,需要通过调用 <code>stopSelf()</code> 或 <code>stopService()</code> 来停止服务。(如果只想提供绑定,则无需实现此方法。)</li><li><code>onBind()</code><br>当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 <code>bindService()</code> 来调用此方法。在此方法的实现中,必须通过返回 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a> 提供一个接口,以供客户端用来与服务进行通信。但是,如果并不希望允许绑定,则应返回 null。</li><li><code>onCreate()</code><br>首次创建服务时,系统会(在调用 <code>onStartCommand()</code> 或 <code>onBind()</code> 之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。</li><li><code>onDestory()</code><br>当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。</li></ul><h3 id="使用清单文件声明服务"><a href="#使用清单文件声明服务" class="headerlink" title="使用清单文件声明服务"></a>使用清单文件声明服务</h3><p>添加 <code><service></code> 元素作为 <code><application></code> 元素的子元素。<code>android:name</code> 为其唯一必须的属性,用于指定服务的类名。可通过添加 <code>android:exported</code> 属性并将其设置为 <code>fasle</code>,确保服务仅本应用可适用。<strong>为了避免用户意外停止服务,需添加 <code>android:description</code> 属性,属性值为解释服务的作用及其提供的好处的短句。</strong></p><h2 id="创建启动服务"><a href="#创建启动服务" class="headerlink" title="创建启动服务"></a>创建启动服务<a id="创建启动服务"></a></h2><ul><li><code>Service</code><br>这是适用于所有服务的基类。扩展此类时,必须创建用于执行所有服务工作的新线程,因为服务默认使用应用的主线程,这会降低应用正在运行的任何 Activity 的性能。</li><li><code>IntentService</code><br><code>Service</code> 的子类,使用工作线程逐一处理所有启动请求。如果<strong>不要求</strong>服务同时处理<strong>多个</strong>请求,此类为最佳选择,实现 <code>onHandleIntent()</code>,该方法会接收每个启动请求的 Intent,以便执行后台工作。</li></ul><h3 id="扩展-IntentService-类"><a href="#扩展-IntentService-类" class="headerlink" title="扩展 IntentService 类"></a>扩展 IntentService 类</h3><p>实现 <code>onHandleIntent()</code> 方法即可,若要重写其他回调函数,确保调用超类实现。除 <code>onHandleIntent()</code> 方法外无需调用超类实现的唯一方法就是 <code>onBind()</code>。</p><h3 id="扩展-Service-类"><a href="#扩展-Service-类" class="headerlink" title="扩展 Service 类"></a>扩展 Service 类</h3><p><code>onStartCommand()</code> 方法的返回值必须是以下常量之一:</p><ul><li><code>START_NOT_STICKY</code><br>如果系统在 <code>onStartCommand()</code> 返回后终止服务,则除非有待传递的挂起 Intent,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。</li><li><code>START_STICKY</code><br>如果系统在 <code>onStartCommand()</code> 返回后终止服务,则其会重建服务并调用 <code>onStartCommand()</code>,但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会调用包含空 Intent 的 <code>onStartCommand()</code>。在此情况下,系统会传递这些 Intent。此常量适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。</li><li><code>START_REDELIVER_INTENT</code><br>如果系统在 <code>onStartCommand()</code> 返回后终止服务,则其会重建服务,并通过传递给服务的最后一个 Intent 调用 <code>onStartCommand()</code>。所有挂起 Intent 均依次传递。此常量适用于主动执行应立即恢复的作业(例如下载文件)的服务。</li></ul><h3 id="启动服务"><a href="#启动服务" class="headerlink" title="启动服务"></a>启动服务<a id="启动服务"></a></h3><p>将 <code>Intent</code> 传递给 <code>startService()</code> 或 <code>startForegroundService()</code>,从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 <code>onStartCommand()</code> 方法,如果服务尚未运行,则系统首先会调用 <code>onCreate()</code>,然后调用 <code>onStartCommand()</code>。</p><p><strong>如果应用面向 API 级别 26 或更高版本,除非应用本身在前台运行,否则系统不会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则需要调用 <code>startForegroundService()</code>,此方法会创建后台服务,但它会向系统发出信号,表明服务会自行提升至前台。创建服务后,该服务必须在五秒钟内调用自己的 <code>startForeground()</code> 方法。</strong></p><p>如果服务未提供绑定,则应用组件与服务间的唯一通信模式便是使用 <code>startService()</code> 传递的 Intent。如果希望服务返回结果,可以为广播创建一个 <code>PendingIntent</code>,将其传递给启动服务的 <code>Intent</code> 中的服务。然后,服务就可以使用广播传递结果。</p><h3 id="停止服务"><a href="#停止服务" class="headerlink" title="停止服务"></a>停止服务<a id="停止服务"></a></h3><p>除非必须回收内存资源,否则系统不会停止或销毁服务。服务必须通过调用 <code>stopSelf()</code> 自行停止运行,或由另一个组件通过调用 <code>stopService()</code> 来停止。</p><p>为了避免在第一个请求结束时停止服务会终止第二个请求的问题,可以使用 <code>stopSelf(int)</code>,传入参数为启动请求的 ID,调用 <code>stopSelf(int)</code> 停止服务时,若 ID 不匹配(已有新的 <code>onStartCommand()</code> 调用),则服务不会停止。</p><h2 id="在前台运行服务"><a href="#在前台运行服务" class="headerlink" title="在前台运行服务"></a>在前台运行服务<a id="在前台运行服务"></a></h2><p>前台服务必须为状态栏提供通知。</p><p>如果应用面向 Android 9(API28)或更高版本并使用前台服务,需要请求 <code>FOREGROUND_SERVICE</code> 权限(普通权限)。</p><p>如要请求让服务在前台运行,需要调用 <a href="https://developer.android.google.cn/reference/android/app/Service?hl=zh#startForeground(int,%20android.app.Notification)" target="_blank" rel="noopener"><code>startForeground()</code></a>。此方法有两个参数:唯一标识通知的整型数和用于状态栏的 Notifition。此通知必须具有 <code>PRIORITY_LOW</code> 或更高的优先级。</p><p>如要从前台移除服务,需要调用 <a href="https://developer.android.google.cn/reference/android/app/Service?hl=zh#stopForeground(boolean)" target="_blank" rel="noopener"><code>stopForeground()</code></a>。此方法参数为布尔值,指示是否需要同时移除状态栏通知,此方法<strong>不会停止服务</strong>。</p><h2 id="创建后台服务-IntentService"><a href="#创建后台服务-IntentService" class="headerlink" title="创建后台服务(IntentService)"></a>创建后台服务(IntentService)<a id="创建后台服务"></a></h2><ul><li>创建 <code>IntentService</code> 子类</li><li>创建所需的回调方法 <code>onHandleIntent()</code></li><li>在清单文件中定义 <code>IntentService</code>。</li></ul><p><code>IntentService</code> 有一些限制:</p><ul><li>无法直接与界面互动。要在界面中显示结果,需要将结果发送到 <code>Activity</code></li><li>工作请求依序执行</li><li>在 <code>IntentService</code> 上运行的操作无法中断</li></ul><h3 id="将工作请求发送到后台服务-JobIntentService"><a href="#将工作请求发送到后台服务-JobIntentService" class="headerlink" title="将工作请求发送到后台服务(JobIntentService)"></a>将工作请求发送到后台服务(JobIntentService)</h3><ul><li><input disabled="" type="checkbox"> JobIntentService</li></ul><h2 id="绑定服务"><a href="#绑定服务" class="headerlink" title="绑定服务"></a>绑定服务<a id="绑定服务"></a></h2><p>实现 <code>onBind()</code> 回调方法返回 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a>,收到 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a> 后,客户端就可以通过该接口与服务进行交互。完成与服务的交互后,客户端可以通过调用 <code>unbindService()</code> 取消绑定。如果没有绑定到服务的客户端,系统会销毁该服务。</p><p>客户端通过调用 <code>bindService()</code> 绑定服务。调用时,必须提供 <code>ServiceConnection</code>的实现,后者会监控与服务的连接。当创建客户端与服务之间的连接时,Android 系统会调用 <code>ServiceConnection</code> 上的 <code>onServiceConnected()</code>。<code>onServiceConnected()</code> 方法包含 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a> 参数,客户端随后使用该参数与绑定服务进行通信。</p><p>只有在第一个客户端绑定服务时,系统才会调用服务的 <code>onBind()</code> 方法来生成 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a>。然后,系统会将同一 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a> 传递至绑定到相同服务的所有其他客户端,无需再次调用 <code>onBind()</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">var</span> mService: LocalService<span class="hljs-keyword">val</span> mConnection = <span class="hljs-keyword">object</span> : ServiceConnection { <span class="hljs-comment">// Called when the connection with the service is established</span> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onServiceConnected</span><span class="hljs-params">(className: <span class="hljs-type">ComponentName</span>, service: <span class="hljs-type">IBinder</span>)</span></span> { <span class="hljs-comment">// Because we have bound to an explicit</span> <span class="hljs-comment">// service that is running in our own process, we can</span> <span class="hljs-comment">// cast its IBinder to a concrete class and directly access it.</span> <span class="hljs-keyword">val</span> binder = service <span class="hljs-keyword">as</span> LocalService.LocalBinder mService = binder.getService() mBound = <span class="hljs-literal">true</span> } <span class="hljs-comment">// Called when the connection with the service disconnects unexpectedly</span> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onServiceDisconnected</span><span class="hljs-params">(className: <span class="hljs-type">ComponentName</span>)</span></span> { Log.e(TAG, <span class="hljs-string">"onServiceDisconnected"</span>) mBound = <span class="hljs-literal">false</span> }}</code></pre></div><div class="hljs"><pre><code class="hljs kotlin">Intent(<span class="hljs-keyword">this</span>, LocalService::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>).<span class="hljs-title">also</span> </span>{ intent -> bindService(intent, connection, Context.BIND_AUTO_CREATE)}</code></pre></div><h3 id="创建绑定服务"><a href="#创建绑定服务" class="headerlink" title="创建绑定服务"></a>创建绑定服务</h3><h4 id="扩展-Binder-类"><a href="#扩展-Binder-类" class="headerlink" title="扩展 Binder 类"></a>扩展 Binder 类</h4><p>如果服务只是自有应用的后台工作线程,且无需跨进程工作,优先采用此方法。</p><ol><li>在服务中,创建可执行以下某种操作的 Binder 实例:<ul><li>包含客户端可调用的公共方法。</li><li>返回当前的 <code>Service</code> 实例,该实例中包含客户端可调用的公共方法。</li><li>返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。</li></ul></li><li>在 <code>onBind()</code> 回调方法返回此 <code>Binder</code> 实例。</li><li>在客户端中,从 <code>onServiceConnected()</code> 回调方法接收 <code>Binder</code>,并使用提供的方法调用绑定服务。</li></ol><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocalService</span> : <span class="hljs-type">Service</span></span>() { <span class="hljs-comment">// Binder given to clients</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> binder = LocalBinder() <span class="hljs-comment">// Random number generator</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> mGenerator = Random() <span class="hljs-comment">/** method for clients */</span> <span class="hljs-keyword">val</span> randomNumber: <span class="hljs-built_in">Int</span> <span class="hljs-keyword">get</span>() = mGenerator.nextInt(<span class="hljs-number">100</span>) <span class="hljs-comment">/**</span><span class="hljs-comment"> * Class used for the client Binder. Because we know this service always</span><span class="hljs-comment"> * runs in the same process as its clients, we don't need to deal with IPC.</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">inner</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocalBinder</span> : <span class="hljs-type">Binder</span></span>() { <span class="hljs-comment">// Return this instance of LocalService so clients can call public methods</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getService</span><span class="hljs-params">()</span></span>: LocalService = <span class="hljs-keyword">this</span><span class="hljs-symbol">@LocalService</span> } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBind</span><span class="hljs-params">(intent: <span class="hljs-type">Intent</span>)</span></span>: IBinder { <span class="hljs-keyword">return</span> binder }}</code></pre></div><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BindingActivity</span> : <span class="hljs-type">Activity</span></span>() { <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> mService: LocalService <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> mBound: <span class="hljs-built_in">Boolean</span> = <span class="hljs-literal">false</span> <span class="hljs-comment">/** Defines callbacks for service binding, passed to bindService() */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> connection = <span class="hljs-keyword">object</span> : ServiceConnection { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onServiceConnected</span><span class="hljs-params">(className: <span class="hljs-type">ComponentName</span>, service: <span class="hljs-type">IBinder</span>)</span></span> { <span class="hljs-comment">// We've bound to LocalService, cast the IBinder and get LocalService instance</span> <span class="hljs-keyword">val</span> binder = service <span class="hljs-keyword">as</span> LocalService.LocalBinder mService = binder.getService() mBound = <span class="hljs-literal">true</span> } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onServiceDisconnected</span><span class="hljs-params">(arg0: <span class="hljs-type">ComponentName</span>)</span></span> { mBound = <span class="hljs-literal">false</span> } } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> { <span class="hljs-keyword">super</span>.onCreate(savedInstanceState) setContentView(R.layout.main) } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onStart</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">super</span>.onStart() <span class="hljs-comment">// Bind to LocalService</span> Intent(<span class="hljs-keyword">this</span>, LocalService::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>).<span class="hljs-title">also</span> </span>{ intent -> bindService(intent, connection, Context.BIND_AUTO_CREATE) } } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onStop</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">super</span>.onStop() unbindService(connection) mBound = <span class="hljs-literal">false</span> } <span class="hljs-comment">/** Called when a button is clicked (the button in the layout file attaches to</span><span class="hljs-comment"> * this method with the android:onClick attribute) */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onButtonClick</span><span class="hljs-params">(v: <span class="hljs-type">View</span>)</span></span> { <span class="hljs-keyword">if</span> (mBound) { <span class="hljs-comment">// Call a method from the LocalService.</span> <span class="hljs-comment">// However, if this call were something that might hang, then this request should</span> <span class="hljs-comment">// occur in a separate thread to avoid slowing down the activity performance.</span> <span class="hljs-keyword">val</span> num: <span class="hljs-built_in">Int</span> = mService.randomNumber Toast.makeText(<span class="hljs-keyword">this</span>, <span class="hljs-string">"number: <span class="hljs-variable">$num</span>"</span>, Toast.LENGTH_SHORT).show() } }}</code></pre></div><h4 id="使用-Messenger"><a href="#使用-Messenger" class="headerlink" title="使用 Messenger"></a>使用 Messenger</h4><p>如需让接口跨不同进程工作(一次接收一个请求),可以使用 <code>Messenger</code> 为服务创建接口。</p><ol><li>服务实现一个 <code>Handler</code>,由该类为每个客户端调用接收回调。</li><li>服务使用 <code>Handler</code> 来创建 <code>Messenger</code> 对象(对 <code>Handler</code> 的引用)。</li><li><code>Messenger</code> 创建一个 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a>,服务通过 <code>onBind()</code> 使其返回客户端。</li><li>客户端使用 <a href="https://developer.android.google.cn/reference/android/os/IBinder?hl=zh" target="_blank" rel="noopener"><code>IBinder</code></a> 将 <code>Messenger</code>(其引用服务的 <code>Handler</code>)实例化,然后使用后者将 <code>Message</code> 对象发送给服务。</li><li>服务在其 <code>Handler</code> 中(具体地讲,是在 <code>handleMessage()</code> 方法中)接收每个 <code>Message</code>。</li></ol><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/** Command to the service to display a message */</span><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> MSG_SAY_HELLO = <span class="hljs-number">1</span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessengerService</span> : <span class="hljs-type">Service</span></span>() { <span class="hljs-comment">/**</span><span class="hljs-comment"> * Target we publish for clients to send messages to IncomingHandler.</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> mMessenger: Messenger <span class="hljs-comment">/**</span><span class="hljs-comment"> * Handler of incoming messages from clients.</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">internal</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IncomingHandler</span></span>( context: Context, <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> applicationContext: Context = context.applicationContext ) : Handler() { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleMessage</span><span class="hljs-params">(msg: <span class="hljs-type">Message</span>)</span></span> { <span class="hljs-keyword">when</span> (msg.what) { MSG_SAY_HELLO -> Toast.makeText(applicationContext, <span class="hljs-string">"hello!"</span>, Toast.LENGTH_SHORT).show() <span class="hljs-keyword">else</span> -> <span class="hljs-keyword">super</span>.handleMessage(msg) } } } <span class="hljs-comment">/**</span><span class="hljs-comment"> * When binding to the service, we return an interface to our messenger</span><span class="hljs-comment"> * for sending messages to the service.</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBind</span><span class="hljs-params">(intent: <span class="hljs-type">Intent</span>)</span></span>: IBinder? { Toast.makeText(applicationContext, <span class="hljs-string">"binding"</span>, Toast.LENGTH_SHORT).show() mMessenger = Messenger(IncomingHandler(<span class="hljs-keyword">this</span>)) <span class="hljs-keyword">return</span> mMessenger.binder }}</code></pre></div><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ActivityMessenger</span> : <span class="hljs-type">Activity</span></span>() { <span class="hljs-comment">/** Messenger for communicating with the service. */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> mService: Messenger? = <span class="hljs-literal">null</span> <span class="hljs-comment">/** Flag indicating whether we have called bind on the service. */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> bound: <span class="hljs-built_in">Boolean</span> = <span class="hljs-literal">false</span> <span class="hljs-comment">/**</span><span class="hljs-comment"> * Class for interacting with the main interface of the service.</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> mConnection = <span class="hljs-keyword">object</span> : ServiceConnection { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onServiceConnected</span><span class="hljs-params">(className: <span class="hljs-type">ComponentName</span>, service: <span class="hljs-type">IBinder</span>)</span></span> { <span class="hljs-comment">// This is called when the connection with the service has been</span> <span class="hljs-comment">// established, giving us the object we can use to</span> <span class="hljs-comment">// interact with the service. We are communicating with the</span> <span class="hljs-comment">// service using a Messenger, so here we get a client-side</span> <span class="hljs-comment">// representation of that from the raw IBinder object.</span> mService = Messenger(service) bound = <span class="hljs-literal">true</span> } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onServiceDisconnected</span><span class="hljs-params">(className: <span class="hljs-type">ComponentName</span>)</span></span> { <span class="hljs-comment">// This is called when the connection with the service has been</span> <span class="hljs-comment">// unexpectedly disconnected -- that is, its process crashed.</span> mService = <span class="hljs-literal">null</span> bound = <span class="hljs-literal">false</span> } } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">sayHello</span><span class="hljs-params">(v: <span class="hljs-type">View</span>)</span></span> { <span class="hljs-keyword">if</span> (!bound) <span class="hljs-keyword">return</span> <span class="hljs-comment">// Create and send a message to the service, using a supported 'what' value</span> <span class="hljs-keyword">val</span> msg: Message = Message.obtain(<span class="hljs-literal">null</span>, MSG_SAY_HELLO, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>) <span class="hljs-keyword">try</span> { mService?.send(msg) } <span class="hljs-keyword">catch</span> (e: RemoteException) { e.printStackTrace() } } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> { <span class="hljs-keyword">super</span>.onCreate(savedInstanceState) setContentView(R.layout.main) } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onStart</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">super</span>.onStart() <span class="hljs-comment">// Bind to the service</span> Intent(<span class="hljs-keyword">this</span>, MessengerService::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>).<span class="hljs-title">also</span> </span>{ intent -> bindService(intent, mConnection, Context.BIND_AUTO_CREATE) } } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onStop</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">super</span>.onStop() <span class="hljs-comment">// Unbind from the service</span> <span class="hljs-keyword">if</span> (bound) { unbindService(mConnection) bound = <span class="hljs-literal">false</span> } }}</code></pre></div><h4 id="使用-AIDL"><a href="#使用-AIDL" class="headerlink" title="使用 AIDL"></a>使用 AIDL</h4><p>如果想让服务同时处理多个请求,可以使用 AIDL。</p><h3 id="管理绑定服务的生命周期"><a href="#管理绑定服务的生命周期" class="headerlink" title="管理绑定服务的生命周期"></a>管理绑定服务的生命周期</h3><p><img src="/img/%E6%8F%92%E5%9B%BE/service_binding_tree_lifecycle.png" srcset="/img/loading.gif" alt="绑定服务的生命周期"></p><h2 id="管理服务的生命周期"><a href="#管理服务的生命周期" class="headerlink" title="管理服务的生命周期"></a>管理服务的生命周期</h2><ul><li><code>startService()</code> -> <code>stopSelf()</code> or <code>stopService()</code></li><li><code>bindService()</code> -> <code>unbindService()</code><br><img src="/img/%E6%8F%92%E5%9B%BE/service_lifecycle.png" srcset="/img/loading.gif" alt="服务生命周期"></li></ul><h2 id="报告工作状态"><a href="#报告工作状态" class="headerlink" title="报告工作状态"></a>报告工作状态</h2><ul><li><input disabled="" type="checkbox"> <code>LocalBroadcastManager</code></li><li><input disabled="" type="checkbox"> <code>BroadCastReceiver</code></li></ul>]]></content>
<categories>
<category>安卓</category>
<category>组件</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>GitBook使用入门(简)</title>
<link href="/2020/07/31/GitBook%E4%BD%BF%E7%94%A8%E5%85%A5%E9%97%A8(%E7%AE%80)/"/>
<url>/2020/07/31/GitBook%E4%BD%BF%E7%94%A8%E5%85%A5%E9%97%A8(%E7%AE%80)/</url>
<content type="html"><![CDATA[<p>Git 遇上 Book</p><a id="more"></a><ul><li>安装GitBook <code>$ npm install gitbook-cli -g</code></li><li>创建一本书 <code>$ gitbook init</code></li><li>用现有的目录创建一本书 <code>$ gitbook init ./directory</code></li><li>预览创建的图书 <code>$ gitbook serve</code></li><li>构建静态网站 <code>$ gitbook build</code></li><li>列出可用于安装的远程版本 <code>$ gitbook ls-remote</code></li><li>下载和安装不同版本的GitBook <code>$ gitbook fetch version</code></li><li>调试获取详细的错误信息 <code>gitbook build ./ --log=debug --debug</code> or <code>gitbook serve ./ --log=debug --debug</code></li><li>生成电子书 <code>gitbook pdf ././book.pdf</code></li></ul>]]></content>
<categories>
<category>一些新东西</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>Android Studio 插件</title>
<link href="/2020/07/30/Android-Studio-%E6%8F%92%E4%BB%B6/"/>
<url>/2020/07/30/Android-Studio-%E6%8F%92%E4%BB%B6/</url>
<content type="html"><![CDATA[<p>使用方式:File -> Settings -> Plugins</p><a id="more"></a><ul><li>GsonFormat 将 Json 字符串快速转成 JavaBean 对象。快捷键 alt + s (windows) / option + s (mac)</li><li>Android Methods Count 显示依赖库中的方法数</li></ul>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
</entry>
<entry>
<title>Settings-Preference</title>
<link href="/2020/07/30/Settings-Preference/"/>
<url>/2020/07/30/Settings-Preference/</url>
<content type="html"><![CDATA[<p>设置使用户能够改变应用的功能和行为。设置可以影响后台行为,例如应用与云同步数据的频率;设置的影响还可能更为深远,例如改变用户界面的内容和呈现方式。</p><a id="more"></a><ul><li><a href="#创建层次结构">创建层次结构(XML & 代码)</a></li><li><a href="#查找偏好设置">查找偏好设置</a></li><li><a href="#读取偏好设置值">读取偏好设置值</a></li><li><a href="#偏好设置操作">偏好设置操作(Intent & onPreferenceClickListener)</a></li><li><a href="#监听偏好设置值的更改">监听偏好设置值的更改</a></li><li><a href="#偏好设置数据存储">偏好设置数据存储</a><ul><li><a href="#使用自定义数据存储">自定义数据存储</a></li></ul></li><li><a href="#偏好设置组件">偏好设置组件</a></li></ul><h2 id="设置使用入门(概览)"><a href="#设置使用入门(概览)" class="headerlink" title="设置使用入门(概览)"></a>设置使用入门(概览)</h2><div class="hljs"><pre><code class="hljs java">implementation <span class="hljs-string">'androidx.preference:preference:1.1.1'</span></code></pre></div><h3 id="创建层次结构"><a href="#创建层次结构" class="headerlink" title="创建层次结构"></a>创建层次结构<a id="创建层次结构"></a></h3><h4 id="将层次结构定义为-XML-资源"><a href="#将层次结构定义为-XML-资源" class="headerlink" title="将层次结构定义为 XML 资源"></a>将层次结构定义为 XML 资源</h4><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">PreferenceScreen</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:app</span>=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>></span> <span class="hljs-tag"><<span class="hljs-name">SwitchPreferenceCompat</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"notifications"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Enable message notifications"</span>/></span> <span class="hljs-tag"><<span class="hljs-name">Preference</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"feedback"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Send feedback"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:summary</span>=<span class="hljs-string">"Report technical issues or suggest new features"</span>/></span><span class="hljs-tag"></<span class="hljs-name">PreferenceScreen</span>></span></code></pre></div><p>注意:</p><ul><li>根标签必须为 <code><PreferenceScreen></code>,XML 资源必须放置于 res/xml/ 目录。</li><li>构建层次结构时,每个 <code>Preference</code> 都应配有唯一的密钥 <code>app:key</code>。</li></ul><h4 id="通过代码构建层次结构"><a href="#通过代码构建层次结构" class="headerlink" title="通过代码构建层次结构"></a><a href="#在代码中构建层次结构">通过代码构建层次结构</a></h4><h3 id="扩充层次结构"><a href="#扩充层次结构" class="headerlink" title="扩充层次结构"></a>扩充层次结构</h3><p>若要从 XML 属性中扩充层次结构,则需要先创建一个 <code>PreferenceFragmentCompat</code>,替换 <code>onCreatePreferences()</code>,然后提供 XML 资源进行扩充,如以下示例所示:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SettingsFragment</span> : <span class="hljs-type">PreferenceFragmentCompat</span></span>() { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreatePreferences</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?, rootKey: <span class="hljs-type">String</span>?)</span></span> { setPreferencesFromResource(R.xml.preferences, rootKey) }}</code></pre></div><p>然后将此 <code>Fragment</code> 添加到 <code>Activity</code>:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> { <span class="hljs-keyword">super</span>.onCreate(savedInstanceState) supportFragmentManager .beginTransaction() .replace(R.id.settings_container, SettingsFragment()) .commit()}</code></pre></div><h2 id="整理设置"><a href="#整理设置" class="headerlink" title="整理设置"></a>整理设置</h2><h3 id="偏好设置类别"><a href="#偏好设置类别" class="headerlink" title="偏好设置类别"></a>偏好设置类别</h3><p>如果单个屏幕上有多个相关的 <code>Preferences</code>,可以使用 <code>PreferenceCategory</code> 进行分组。</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">PreferenceScreen</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:app</span>=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>></span> <span class="hljs-tag"><<span class="hljs-name">PreferenceCategory</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"notifications_category"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Notifications"</span>></span> <span class="hljs-tag"><<span class="hljs-name">SwitchPreferenceCompat</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"notifications"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Enable message notifications"</span>/></span> <span class="hljs-tag"></<span class="hljs-name">PreferenceCategory</span>></span> <span class="hljs-tag"><<span class="hljs-name">PreferenceCategory</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"help_category"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Help"</span>></span> <span class="hljs-tag"><<span class="hljs-name">Preference</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"feedback"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:summary</span>=<span class="hljs-string">"Report technical issues or suggest new features"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Send feedback"</span>/></span> <span class="hljs-tag"></<span class="hljs-name">PreferenceCategory</span>></span><span class="hljs-tag"></<span class="hljs-name">PreferenceScreen</span>></span></code></pre></div><h3 id="将层次结构拆分为多个屏幕"><a href="#将层次结构拆分为多个屏幕" class="headerlink" title="将层次结构拆分为多个屏幕"></a>将层次结构拆分为多个屏幕</h3><p>如果有大量的 <code>Preferences</code> 或者不同的类别,可以分不同的屏幕显示。<br>每个屏幕都应该是一个 <code>PreferenceFragmentCompat</code>,拥有自己单独的层次结构。然后,初始屏幕上的 <code>Preferences</code> 可关联至含有相关 <code>Preferences</code> 的子屏幕。</p><p>要关联带有 <code>Preference</code> 的屏幕,可以在 XML 中声明 <code>app:fragment</code>,或使用 <code>Preference.setFragment()</code>。</p><p>当用户点按带有关联 <code>Fragment</code> 的 <code>Preference</code> 时,系统会调用接口 <code>PreferenceFragmentCompat.OnPreferenceStartFragmentCallback.onPreferenceStartFragment()</code>。此方法应该用于显示新屏幕,且应在托管 <code>Activity</code> 中实现。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { ... <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPreferenceStartFragment</span><span class="hljs-params">(caller: <span class="hljs-type">PreferenceFragmentCompat</span>, pref: <span class="hljs-type">Preference</span>)</span></span>: <span class="hljs-built_in">Boolean</span> { <span class="hljs-comment">// Instantiate the new Fragment</span> <span class="hljs-keyword">val</span> args = pref.extras <span class="hljs-keyword">val</span> fragment = supportFragmentManager.fragmentFactory.instantiate( classLoader, pref.fragment) fragment.arguments = args fragment.setTargetFragment(caller, <span class="hljs-number">0</span>) <span class="hljs-comment">// Replace the existing Fragment with the new Fragment</span> supportFragmentManager.beginTransaction() .replace(R.id.settings_container, fragment) .addToBackStack(<span class="hljs-literal">null</span>) .commit() <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span> }}</code></pre></div><h4 id="使用单独的-Activity"><a href="#使用单独的-Activity" class="headerlink" title="使用单独的 Activity"></a>使用单独的 Activity</h4><h2 id="自定义设置"><a href="#自定义设置" class="headerlink" title="自定义设置"></a>自定义设置</h2><h3 id="查找偏好设置"><a href="#查找偏好设置" class="headerlink" title="查找偏好设置"></a>查找偏好设置<a id="查找偏好设置"></a></h3><p>要访问单独的 <code>Preference</code>,可以使用 <a href="https://developer.android.google.cn/reference/androidx/preference/PreferenceFragmentCompat?hl=zh#findPreference(java.lang.CharSequence)" target="_blank" rel="noopener"><code>PreferenceFragmentCompat.findPreference()</code></a>。此方法会在整个层次结构中搜索具有指定键的 <code>Preference</code>。</p><h3 id="控制偏好设置可见性"><a href="#控制偏好设置可见性" class="headerlink" title="控制偏好设置可见性"></a>控制偏好设置可见性</h3><ol><li><p>要仅在符合某项条件时显示 <code>Preference</code>,首先要在 XML 中将 可见性设置为 false。</p></li><li><p>当符合相应条件时,在 <code>onCreatePreferences()</code> 中将 <code>Preference</code> 可见性(isVisible)设置为 true。</p></li></ol><h3 id="动态更新摘要"><a href="#动态更新摘要" class="headerlink" title="动态更新摘要"></a>动态更新摘要</h3><h4 id="使用-SimpleSummaryProvider"><a href="#使用-SimpleSummaryProvider" class="headerlink" title="使用 SimpleSummaryProvider"></a>使用 SimpleSummaryProvider</h4><ul><li><code>app:useSimpleSummaryProvider="true"</code></li><li><code>Preference.SimpleSummaryProvider.getInstance()</code></li></ul><h4 id="使用自定义-SummaryProvider"><a href="#使用自定义-SummaryProvider" class="headerlink" title="使用自定义 SummaryProvider"></a>使用自定义 SummaryProvider</h4><p>在 <code>onCreatePreferences()</code> 中,新建 <code>SummaryProvider</code>,然后替换 <code>provideSummary()</code> 以返回要显示的摘要:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">EditTextPreference</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">"counting"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Counting preference"</span>/></span></code></pre></div><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> countingPreference: EditTextPreference? = findPreference(<span class="hljs-string">"counting"</span>)countingPreference?.summaryProvider = SummaryProvider<EditTextPreference> { preference -> <span class="hljs-keyword">val</span> text = preference.text <span class="hljs-keyword">if</span> (TextUtils.isEmpty(text)) { <span class="hljs-string">"Not set"</span> } <span class="hljs-keyword">else</span> { <span class="hljs-string">"Length of saved value: "</span> + text.length }}</code></pre></div><p><code>Preference</code> 摘要现在显示已保存值的长度,如果不存在已保存值,则显示“Not set”。</p><h3 id="自定义-EditTextPreference-对话框"><a href="#自定义-EditTextPreference-对话框" class="headerlink" title="自定义 EditTextPreference 对话框"></a>自定义 EditTextPreference 对话框</h3><p>在 <code>onCreatePreferences()</code> 中新建 <code>OnBindEditTextListener</code>,然后替换 <code>onBindEditText()</code> 以自定义系统向用户显示的 <code>EditText</code>。</p><h3 id="偏好设置操作"><a href="#偏好设置操作" class="headerlink" title="偏好设置操作"></a>偏好设置操作<a id="偏好设置操作"></a></h3><p>在点按时,<code>Preference</code> 可产生特定操作。若要为 <code>Preference</code> 添加操作,可以直接在 <code>Preference</code> 上设置 <code>Intent</code>,或为更具体地逻辑设置 <a href="https://developer.android.google.cn/reference/androidx/preference/Preference.OnPreferenceClickListener?hl=zh" target="_blank" rel="noopener"><code>OnPreferenceClickListener</code></a>。</p><h4 id="设置-Intent"><a href="#设置-Intent" class="headerlink" title="设置 Intent"></a>设置 Intent</h4><ul><li><p>在 XML 中嵌套 <code><intent></code>,<code><intent></code> 中可设置子元素 <code><extra></code></p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">Preference</span></span><span class="hljs-tag"> <span class="hljs-attr">app:key</span>=<span class="hljs-string">”activity”</span></span><span class="hljs-tag"> <span class="hljs-attr">app:title</span>=<span class="hljs-string">"Launch activity"</span>></span> <span class="hljs-tag"><<span class="hljs-name">intent</span></span><span class="hljs-tag"> <span class="hljs-attr">android:targetPackage</span>=<span class="hljs-string">"com.example"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:targetClass</span>=<span class="hljs-string">"com.example.ExampleActivity"</span>/></span><span class="hljs-tag"></<span class="hljs-name">Preference</span>></span></code></pre></div></li><li><p>在 <code>Preference</code> 上直接使用 <code>setIntent()</code></p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> intent = Intent(context, ExampleActivity::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>)</span>activityPreference.setIntent(intent)</code></pre></div></li></ul><h4 id="onPreferenceClickListener"><a href="#onPreferenceClickListener" class="headerlink" title="onPreferenceClickListener"></a>onPreferenceClickListener</h4><h2 id="使用已保存的值"><a href="#使用已保存的值" class="headerlink" title="使用已保存的值"></a>使用已保存的值</h2><p>如何存储和使用由 Preference 库保存的 <code>Preference</code> 值。</p><h3 id="偏好设置数据存储"><a href="#偏好设置数据存储" class="headerlink" title="偏好设置数据存储"></a>偏好设置数据存储<a id="偏好设置数据存储"></a></h3><h4 id="SharedPreferences"><a href="#SharedPreferences" class="headerlink" title="SharedPreferences"></a>SharedPreferences</h4><p>默认情况下,<code>Preference</code> 使用 <code>SharedPreferences</code> 保存值。<br>对于在应用会话运行期间保存的文件,可通过 <code>SharedPreferences</code> API 读取和写入简单的键值对。<br>Preference 库使用不公开的 <code>SharedPreferences</code> 实例。</p><p><strong>使用 <code>SharedPreferences</code> 保存 <code>Preference</code> 的值时,所用键与为 <code>Preference</code> 设置的键相同。</strong></p><h4 id="PreferenceDataStore"><a href="#PreferenceDataStore" class="headerlink" title="PreferenceDataStore"></a><a href="#使用自定义数据存储">PreferenceDataStore</a></h4><p>如果应用具有特定于设备的配置选项,设备上的每个用户都会有单独的设置,此时 <code>SharedPreferences</code> 就不是太理想的解决方案。</p><h3 id="读取偏好设置值"><a href="#读取偏好设置值" class="headerlink" title="读取偏好设置值"></a>读取偏好设置值<a id="读取偏好设置值"></a></h3><p>要检索正在使用的 <code>SharedPreferences</code> 对象,可以调用 <a href="https://developer.android.google.cn/reference/androidx/preference/PreferenceManager?hl=zh#getDefaultSharedPreferences(android.content.Context)" target="_blank" rel="noopener"><code>PreferenceManager.getDefaultSharedPreferences()</code></a>。</p><h3 id="监听偏好设置值的更改"><a href="#监听偏好设置值的更改" class="headerlink" title="监听偏好设置值的更改"></a>监听偏好设置值的更改<a id="监听偏好设置值的更改"></a></h3><p>要监听 <code>Preference</code> 值得更改,可以在以下两个接口中选择一个:</p><ul><li><a href="https://developer.android.google.cn/reference/androidx/preference/Preference.OnPreferenceChangeListener?hl=zh" target="_blank" rel="noopener">Preference.OnPreferenceChangeListener</a></li><li><a href="https://developer.android.google.cn/reference/android/content/SharedPreferences.OnSharedPreferenceChangeListener?hl=zh" target="_blank" rel="noopener">SharedPreferences.OnSharedPreferenceChangeListener</a></li></ul><p>下表说明了两个接口的差别:</p><table><thead><tr><th><strong>OnPreferenceChangeListener</strong></th><th><strong>OnSharedPreferenceChangeListener</strong></th></tr></thead><tbody><tr><td>针对每个偏好设置进行设置</td><td>应用于所有偏好设置</td></tr><tr><td>当某个偏好设置即将要更改其已保存值时进行调用。这包括待处理值与当前已保存值<strong>相同</strong>的情况。</td><td>仅在偏好设置的已保存值发生更改时调用。</td></tr><tr><td>仅通过 Preference 库调用。应用的其他部分可能会更改已保存值。</td><td>每当已保存值发生更改时调用,即使是来自应用的其他部分。</td></tr><tr><td>在待处理值保存之前调用。</td><td>在值已保存之后调用。</td></tr><tr><td>在使用 SharedPreferences 或 PreferenceDataStore 时调用。</td><td>仅在使用 SharedPreferences 时调用。</td></tr><tr><td></td><td></td></tr></tbody></table><h4 id="OnSharedPreferenceChangeListener"><a href="#OnSharedPreferenceChangeListener" class="headerlink" title="OnSharedPreferenceChangeListener"></a><a href="https://developer.android.google.cn/guide/topics/ui/settings/use-saved-values?hl=zh#onsharedpreferencechangelistener" target="_blank" rel="noopener">OnSharedPreferenceChangeListener</a></h4><p>使用此接口时,需要通过 <code>registerOnSharedPreferenceChangedListener()</code> 注册监听器。</p><p>为正确管理 <code>Activity</code> 或 <code>Fragment</code> 的生命周期,需要在 <code>onResume()</code> 和 <code>onPause()</code> 回调中注册和取消注册此监听器。</p><h3 id="使用自定义数据存储"><a href="#使用自定义数据存储" class="headerlink" title="使用自定义数据存储"></a>使用自定义数据存储<a id="使用自定义数据存储"></a></h3><h4 id="实现数据存储"><a href="#实现数据存储" class="headerlink" title="实现数据存储"></a>实现数据存储</h4><p>创建一个扩展 <a href="https://developer.android.google.cn/reference/androidx/preference/PreferenceDataStore?hl=zh" target="_blank" rel="noopener"><code>PreferenceDataStore</code></a> 的类,以下示例创建了一个处理 <code>String</code> 值得数据存储:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataStore</span> : <span class="hljs-type">PreferenceDataStore</span></span>() { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">putString</span><span class="hljs-params">(key: <span class="hljs-type">String</span>, value: <span class="hljs-type">String</span>?)</span></span> { <span class="hljs-comment">// Save the value somewhere</span> } <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getString</span><span class="hljs-params">(key: <span class="hljs-type">String</span>, defValue: <span class="hljs-type">String</span>?)</span></span>: String? { <span class="hljs-comment">// Retrieve the value</span> }}</code></pre></div><p><strong>由于包含数据存储的 <code>Fragment</code> 或 <code>Activity</code> 可能会在保留值的过程中被销毁,因此应该序列化数据,以免丢失用户更改的任何值。</strong></p><h4 id="启用数据存储"><a href="#启用数据存储" class="headerlink" title="启用数据存储"></a>启用数据存储</h4><p>实现数据存储后,必须在 <code>onCreatePreferences()</code> 中设置新的数据存储,以便 <code>Preference</code> 对象使用数据存储来保留值,而不是使用默认的 <code>SharedPreferences</code>。可以为每个 <code>Preference</code> 或整个层次结构启用数据存储。</p><ul><li><p>为特定 <code>Preference</code> 启用自定义数据存储,需要对特定 <code>Preference</code> 调用 <code>setPreferenceDataStore()</code>:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> preference: Preference? = findPreference(<span class="hljs-string">"key"</span>)preference?.preferenceDataStore = dataStore</code></pre></div></li><li><p>为整个层次结构启用自定义数据存储,需要对 <code>PreferenceManager</code> 调用 <code>setPreferenceDataStore()</code>:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> preferenceManager = preferenceManagerpreferenceManager.preferenceDataStore = dataStore</code></pre></div></li></ul><p>为特定 <code>Preference</code> 设置的数据存储会替换针对相应层次结构设置的任何数据存储。在大多数情况下,应为<strong>整个层次结构</strong>设置数据存储。</p><p><strong>注意:如果在某个 Preference 附加到层次结构之后为该 Preference 设置数据存储,则 Preference 的初始值将</strong>不再<strong>传播。</strong></p><h2 id="在代码中构建层次结构"><a href="#在代码中构建层次结构" class="headerlink" title="在代码中构建层次结构"></a>在代码中构建层次结构<a id="在代码中构建层次结构"></a></h2><p>可以在 <code>onCreatePreferences()</code> 中以编程方式创建层次结构:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreatePreferences</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?, rootKey: <span class="hljs-type">String</span>?)</span></span> { <span class="hljs-keyword">val</span> context = preferenceManager.context <span class="hljs-keyword">val</span> screen = preferenceManager.createPreferenceScreen(context) <span class="hljs-keyword">val</span> notificationPreference = SwitchPreferenceCompat(context) notificationPreference.key = <span class="hljs-string">"notifications"</span> notificationPreference.title = <span class="hljs-string">"Enable message notifications"</span> <span class="hljs-keyword">val</span> feedbackPreference = Preference(context) feedbackPreference.key = <span class="hljs-string">"feedback"</span> feedbackPreference.title = <span class="hljs-string">"Send feedback"</span> feedbackPreference.summary = <span class="hljs-string">"Report technical issues or suggest new features"</span> screen.addPreference(notificationPreference) screen.addPreference(feedbackPreference) preferenceScreen = screen}</code></pre></div><p>添加 <code>PreferenceCategory</code> 的方法与此类似。切记要将子级添加到 <code>PreferenceCategory</code>,而不是根 <code>PreferenceScreen</code>:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreatePreferences</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?, rootKey: <span class="hljs-type">String</span>?)</span></span> { <span class="hljs-keyword">val</span> context = preferenceManager.context <span class="hljs-keyword">val</span> screen = preferenceManager.createPreferenceScreen(context) <span class="hljs-keyword">val</span> notificationPreference = SwitchPreferenceCompat(context) notificationPreference.key = <span class="hljs-string">"notifications"</span> notificationPreference.title = <span class="hljs-string">"Enable message notifications"</span> <span class="hljs-keyword">val</span> notificationCategory = PreferenceCategory(context) notificationCategory.key = <span class="hljs-string">"notifications_category"</span> notificationCategory.title = <span class="hljs-string">"Notifications"</span> screen.addPreference(notificationCategory) notificationCategory.addPreference(notificationPreference) <span class="hljs-keyword">val</span> feedbackPreference = Preference(context) feedbackPreference.key = <span class="hljs-string">"feedback"</span> feedbackPreference.title = <span class="hljs-string">"Send feedback"</span> feedbackPreference.summary = <span class="hljs-string">"Report technical issues or suggest new features"</span> <span class="hljs-keyword">val</span> helpCategory = PreferenceCategory(context) helpCategory.key = <span class="hljs-string">"help"</span> helpCategory.title = <span class="hljs-string">"Help"</span> screen.addPreference(helpCategory) helpCategory.addPreference(feedbackPreference) preferenceScreen = screen}</code></pre></div><h2 id="偏好设置组件和属性"><a href="#偏好设置组件和属性" class="headerlink" title="偏好设置组件和属性"></a><a href="https://developer.android.google.cn/guide/topics/ui/settings/components-and-attributes?hl=zh" target="_blank" rel="noopener">偏好设置组件和属性</a></h2><p>常用 <code>Preference</code> 组件和属性。</p><h3 id="偏好设置组件"><a href="#偏好设置组件" class="headerlink" title="偏好设置组件"></a>偏好设置组件<a id="偏好设置组件"></a></h3><h4 id="偏好设置基础框架"><a href="#偏好设置基础框架" class="headerlink" title="偏好设置基础框架"></a>偏好设置基础框架</h4><p><code>PreferenceFragmentCompat</code> - 负责显示 <code>Preference</code> 对象的互动层次结构的 <code>Fragment</code>。</p><h4 id="偏好设置容器"><a href="#偏好设置容器" class="headerlink" title="偏好设置容器"></a>偏好设置容器</h4><ul><li><code>PreferenceScreen</code> - 设置屏幕的顶层容器,<code>Preference</code> 层次结构的根组件。</li><li><code>PreferenceCategory</code> - 对类似的 <code>Preference</code> 进行分组的容器。<code>PreferenceCategory</code> 显示类别标题,并直观划分 <code>Preferences</code> 的组别。</li></ul><h4 id="单独偏好设置"><a href="#单独偏好设置" class="headerlink" title="单独偏好设置"></a>单独偏好设置</h4><ul><li><code>Preference</code> - 代表单独设置的基本构建块。如果 <code>Preference</code> 设置为保留,则其拥有相应的键值对来保存用户对设置的选择,让用户在应用的其他位置也可以访问此设置。</li><li><code>EditTextPreference</code> - 保留 <code>String</code> 值的 <code>Preference</code>。用户可点按 <code>Preference</code> 启动包含文本字段的对话框,然后通过文本字段更改保留值。</li><li><code>ListPreference</code> - 保留 <code>String</code> 值的 <code>Preference</code>。用户可在一个对话框中更改此值,该对话框包含一列带有对应标签的单选按钮。</li><li><code>MultiSelectListPreference</code> - 保留一组 <code>String</code> 的 <code>Preference</code>。用户可在一个对话框中更改这些值,该对话框包含一列带有对应标签的复选框。</li><li><code>SeekBarPreference</code> - 保留整数值的 <code>Preference</code>。用户可通过拖动 <code>Preference</code> 布局中显示的对应拖动条更改此值。</li><li><code>SwitchPreferenceCompat</code> - 保留布尔值的 <code>Preference</code>。用户可通过与对应的开关微件互动或点按 <code>Preference</code> 布局更改此值。</li><li><code>CheckBoxPreference</code> - 保留布尔值的 <code>Preference</code>。用户可通过与对应的复选框互动或点按 <code>Preference</code> 布局更改此值。</li></ul><h3 id="偏好设置属性"><a href="#偏好设置属性" class="headerlink" title="偏好设置属性"></a>偏好设置属性</h3><h4 id="通用属性"><a href="#通用属性" class="headerlink" title="通用属性"></a>通用属性</h4><ul><li><code>title</code> - 表示 <code>Preference</code> 标题的 <code>String</code> 值。</li><li><code>summary</code> - 表示 <code>Preference</code> 摘要的 <code>String</code> 值。</li><li><code>icon</code> - 表示 <code>Preference</code> 图标的 <code>Drawable</code>。</li><li><code>key</code> - <code>String</code> 值,表示用于保留关联 <code>Preference</code> 值的键。键使得能够在运行时进一步自定义 <code>Preference</code>。应为层次结构中的每个 <code>Preference</code> 设置键。</li><li><code>enabled</code> - 指示用户能否与 <code>Preference</code> 互动的布尔值。</li><li><code>isPreferenceVisible</code> 指示 <code>Preference</code> 类别是否可见的布尔值。</li><li><code>defaultValue</code> - <code>Preference</code> 的默认值。</li><li><code>dependency</code> - 表示 <code>SwitchPreferenceCompat</code> 的键,它会控制此 <code>Preference</code> 的状态。当对应开关关闭时,此 <code>Preference</code> 停用,且无法修改。</li></ul><h4 id="PreferenceCategory-属性"><a href="#PreferenceCategory-属性" class="headerlink" title="PreferenceCategory 属性"></a>PreferenceCategory 属性</h4><p><code>initialExpandedChildrenCount</code> - 启用展开式 <code>Preference</code> 行为的整数值。此值表示 <code>PreferenceGroup</code> 中所显示子级的最大数量。系统会收起所有多余子级,用户可以点按展开按钮查看。默认情况下,此值为 <code>Integer.MAX_VALUE</code>,且系统会显示所有子级。</p><h4 id="ListPreference-MultiSelectListPreference-属性"><a href="#ListPreference-MultiSelectListPreference-属性" class="headerlink" title="ListPreference / MultiSelectListPreference 属性"></a>ListPreference / MultiSelectListPreference 属性</h4><ul><li><code>entries</code> - 对应要向用户显示的列表条目的 <code>String</code> 数组。这些值中的每一个都通过索引与内部保留的值数组对应。例如,当用户选择第一个列表条目时,系统将保留对应值数组中的第一个元素。</li><li><code>entryValues</code> - 要保留的条目数组。这些值中的每一个都通过索引与显示给用户的列表条目数组对应。</li></ul>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
<tags>
<tag>进阶知识</tag>
</tags>
</entry>
<entry>
<title>Handler</title>
<link href="/2020/07/29/Handler%E4%BD%BF%E7%94%A8/"/>
<url>/2020/07/29/Handler%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<p>Handler 主要用于消息通信。</p><a id="more"></a><p>摘录自<a href="https://www.jianshu.com/p/592fb6bb69fa" target="_blank" rel="noopener">博文</a></p><h2 id="1-Handler-简单使用"><a href="#1-Handler-简单使用" class="headerlink" title="1. Handler 简单使用"></a>1. Handler 简单使用</h2><ul><li>创建一个 Handler 对象,重写 <code>handleMessage()</code> 方法</li><li>在需要消息通信的地方,通过 Handler 的 <code>sendMessage()</code> 方法发送信息。</li></ul><p><strong>注意:在启动 App 时,系统使用 <code>Looper.prepare()</code> 为主线程创建了 Handler 的对象,因此在子线程中使用 Handler 时,需要在创建 Handler 对象前手动调用 <code>Looper.prepare()</code>,并在创建 Handler 对象后调用 <code>Looper.loop()</code>。</strong></p><h2 id="2-Handler-机制原理"><a href="#2-Handler-机制原理" class="headerlink" title="2. Handler 机制原理"></a>2. Handler 机制原理</h2><p>Handler 机制,主要涉及如下四个类:</p><ol><li>Message</li><li>Handler</li><li>Looper</li><li>MessageQueue</li></ol>]]></content>
<categories>
<category>安卓</category>
<category>进程</category>
</categories>
<tags>
<tag>进阶知识</tag>
</tags>
</entry>
<entry>
<title>安卓无法分类的知识点</title>
<link href="/2020/07/18/%E5%AE%89%E5%8D%93%E6%97%A0%E6%B3%95%E5%88%86%E7%B1%BB%E7%9F%A5%E8%AF%86%E7%82%B9/"/>
<url>/2020/07/18/%E5%AE%89%E5%8D%93%E6%97%A0%E6%B3%95%E5%88%86%E7%B1%BB%E7%9F%A5%E8%AF%86%E7%82%B9/</url>
<content type="html"><![CDATA[<h2 id="finish-、onDestory-、System-exit"><a href="#finish-、onDestory-、System-exit" class="headerlink" title="finish()、onDestory()、System.exit()"></a>finish()、onDestory()、System.exit()</h2><p>摘录自:<a href="https://blog.csdn.net/imzoer/article/details/9380807" target="_blank" rel="noopener">博文</a></p><ul><li><code>finish()</code> 在 Activity 动作完成的时候或需要 Activity 关闭时,调用此方法。此方法只是将最上面的 Activity 移出了栈,并没有及时 <code>onDestory()</code>,资源也未及时释放。</li><li><code>onDestory()</code> 系统销毁了 Activity 的实例在内存中占据的空间。</li><li><code>System.exit(0)</code> 针对 Application,直接将进程结束。</li></ul><h2 id="Q:如何设置类头注释"><a href="#Q:如何设置类头注释" class="headerlink" title="Q:如何设置类头注释"></a>Q:如何设置类头注释</h2><p>A:打开 Settings 找到 Editor 下的 File and Code Templates,在 Includes 下新建一个 <code>Class Header</code>,在其内输入想要添加的类头注释模板,然后在 <code>Files</code> 下对应的文件引用 <code>Class Header.java</code>。</p><div class="hljs"><pre><code class="hljs java">${PACKAGE_NAME} 创建新文件在其中的包的名称。${USER} 当前用户系统登录名。${DATE} 当前系统日期${TIME} 当前系统时间${YEAR} 当前年份${MONTH} 当前月份${MONTH_NAME_SHORT} 当前月份名称的前<span class="hljs-number">3</span>个字母。示例:Jan, Feb等。(英文下简称)${MONTH_NAME_FULL} 当前月份的全名。示例:January, February等。(英文下全程)${DAY} 当月的当前日期${DAY_NAME_SHORT} 当前日期名称的前<span class="hljs-number">3</span>个字母。例如:Mon, Tue等。(英文下简称)${DAY_NAME_FULL} 当前日期的全名。例如:Monday, Tuesday等。(英文下全称)${HOUR} 当前小时${MINUTE} 当前分钟${PROJECT_NAME} 当前项目的名称</code></pre></div>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
</entry>
<entry>
<title>安卓一些问题及解决方法</title>
<link href="/2020/07/18/%E5%AE%89%E5%8D%93%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/"/>
<url>/2020/07/18/%E5%AE%89%E5%8D%93%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<p>Keywords: <code>Received close_notify during handshake</code>、文件真实存在但找不到、AS Build Output栏内汉字乱码、</p><a id="more"></a><h4 id="1-打包apk时遇到”Received-close-notify-during-handshake”"><a href="#1-打包apk时遇到”Received-close-notify-during-handshake”" class="headerlink" title="1.打包apk时遇到”Received close_notify during handshake”"></a>1.打包apk时遇到”Received close_notify during handshake”</h4><p>1.1(此方法仅解决我的一次问题,是否可靠仍待测试)可能是由build.grade文件下</p><div class="hljs"><pre><code class="hljs gradle"><span class="hljs-keyword">buildscript</span> { <span class="hljs-keyword">repositories</span> { jcenter() google() }}</code></pre></div><p>jcenter()相关文件不能下载引起的,切换为阿里的源之后即可,代码如下</p><div class="hljs"><pre><code class="hljs gradle"><span class="hljs-keyword">buildscript</span> { <span class="hljs-keyword">repositories</span> { maven { url <span class="hljs-string">'http://maven.aliyun.com/nexus/content/groups/public/'</span> } maven { url <span class="hljs-string">'http://maven.aliyun.com/nexus/content/repositories/jcenter'</span> } google() }}</code></pre></div><h4 id="2-AndroidStudio-3-6-中-R-layout-找不到对应的xml文件(学习Fragment时遇到的)"><a href="#2-AndroidStudio-3-6-中-R-layout-找不到对应的xml文件(学习Fragment时遇到的)" class="headerlink" title="2.AndroidStudio 3.6 中 R.layout 找不到对应的xml文件(学习Fragment时遇到的)"></a>2.AndroidStudio 3.6 中 R.layout 找不到对应的xml文件(学习Fragment时遇到的)</h4><p>原因:当前目录中的文件与实际文件系统中不同步,也就是创建完后需要刷新一下当前的项目目录。</p><p>解决办法:File–>Sync with File System</p><h4 id="3-在生成apk文件后,准备再次在AVD运行程序时遇到-Entry-name-39-META-INF-androidx-legacy-legacy-support-core-utils-version-39-collided"><a href="#3-在生成apk文件后,准备再次在AVD运行程序时遇到-Entry-name-39-META-INF-androidx-legacy-legacy-support-core-utils-version-39-collided" class="headerlink" title="3.在生成apk文件后,准备再次在AVD运行程序时遇到 Entry name 'META-INF/androidx.legacy_legacy-support-core-utils.version' collided"></a>3.在生成apk文件后,准备再次在AVD运行程序时遇到 <code>Entry name 'META-INF/androidx.legacy_legacy-support-core-utils.version' collided</code></h4><p>原因暂时未知。</p><p>解决方法如下:</p><ul><li>果断删除生成的apk/debug目录下文件(apk以及json文件),然后就OK了。</li></ul><h4 id="4-Android-Studio-Build-Output-栏内汉字出现乱码"><a href="#4-Android-Studio-Build-Output-栏内汉字出现乱码" class="headerlink" title="4.Android Studio Build Output 栏内汉字出现乱码"></a>4.Android Studio Build Output 栏内汉字出现乱码</h4><p>原因暂时未知。</p><p>解决方法如下:</p><ol><li><p>点击右上角搜索符号</p></li><li><p>输入 Edit Custom VM Options,会出现两个选项,但似乎两者并无二样,任意点击一个即可。</p></li><li><p>在上一步后会打开一个文件,在其内输入 <code>-Dfile.encoding=UTF-8</code>,编译(据<a href="https://blog.csdn.net/zhang5690800/article/details/104502632" target="_blank" rel="noopener">博文</a>作者解释,修改后必须进行编译过程),然后重启。</p></li></ol><h4 id="5-More-than-one-file-was-found-with-OS-independent-path-META-INF-androidx-core-core-version"><a href="#5-More-than-one-file-was-found-with-OS-independent-path-META-INF-androidx-core-core-version" class="headerlink" title="5.More than one file was found with OS independent path META-INF/androidx.core_core.version"></a>5.More than one file was found with OS independent path META-INF/androidx.core_core.version</h4><p>解决方案:在 Module 级别的 build.gradle 文件下 android 下 添加</p><div class="hljs"><pre><code class="hljs java">packagingOptions { exclude <span class="hljs-string">'META-INF/androidx.core_core.version'</span>}</code></pre></div>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
<tags>
<tag>bug</tag>
</tags>
</entry>
<entry>
<title>蓝牙使用</title>
<link href="/2020/07/13/%E8%93%9D%E7%89%99%E4%BD%BF%E7%94%A8/"/>
<url>/2020/07/13/%E8%93%9D%E7%89%99%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<h2 id="关于蓝牙的使用"><a href="#关于蓝牙的使用" class="headerlink" title="关于蓝牙的使用"></a>关于蓝牙的使用</h2><a id="more"></a><ul><li><a href="https://github.com/Klien-m/BluetoothChat" target="_blank" rel="noopener">Github</a></li><li><a href="https://gitee.com/Klien-m/BluetoothChat" target="_blank" rel="noopener">Gitee</a></li></ul><h3 id="蓝牙权限"><a href="#蓝牙权限" class="headerlink" title="蓝牙权限"></a>蓝牙权限</h3><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">manifest</span> <span class="hljs-attr">...</span> ></span> <span class="hljs-tag"><<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH"</span> /></span> <span class="hljs-tag"><<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH_ADMIN"</span> /></span> <span class="hljs-comment"><!-- If your app targets Android 9 or lower, you can declare</span><span class="hljs-comment"> ACCESS_COARSE_LOCATION instead. --></span> <span class="hljs-tag"><<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_FINE_LOCATION"</span> /></span> ...<span class="hljs-tag"></<span class="hljs-name">manifest</span>></span></code></pre></div><h3 id="设置蓝牙"><a href="#设置蓝牙" class="headerlink" title="设置蓝牙"></a>设置蓝牙</h3><h4 id="1-获取-BluetoothAdapter"><a href="#1-获取-BluetoothAdapter" class="headerlink" title="1. 获取 BluetoothAdapter"></a>1. 获取 <code>BluetoothAdapter</code></h4><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()<span class="hljs-keyword">if</span> (bluetoothAdapter == <span class="hljs-literal">null</span>) { <span class="hljs-comment">// Device doesn't support Bluetooth</span>}</code></pre></div><h4 id="2-启用蓝牙"><a href="#2-启用蓝牙" class="headerlink" title="2. 启用蓝牙"></a>2. 启用蓝牙</h4><p>使用 <code>isEnabled()</code> 检查是否已启用蓝牙,若未启用,可使用以下方法启用蓝牙:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">if</span> (bluetoothAdapter?.isEnabled == <span class="hljs-literal">false</span>) { <span class="hljs-keyword">val</span> enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) <span class="hljs-comment">// REQUEST_ENABLE_BT为自定义常量</span> startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)}</code></pre></div><blockquote><p>启用可检测性即可自动启用蓝牙。如果计划在执行蓝牙 Activity 之前一直启用设备的可检测性,则可以跳过上述步骤 2。</p></blockquote><h4 id="侦听蓝牙状态更改广播"><a href="#侦听蓝牙状态更改广播" class="headerlink" title="侦听蓝牙状态更改广播"></a>侦听蓝牙状态更改广播</h4><p>每当蓝牙状态发生变化时,系统会发出 <code>ACTION_STATE_CHANGED</code> 广播Intent。此广播包含额外字段 <code>EXTRA_STATE</code> 和 <code>EXTRA_PREVIOUS_STATE</code>,二者分别包含新的和旧的蓝牙状态。这些额外字段可能为以下值:<code>STATE_TURNING_ON</code>、<code>STATE_ON</code>、<code>STATE_TURNING_OFF</code>和 <code>STATE_OFF</code>。</p><h3 id="查找设备"><a href="#查找设备" class="headerlink" title="查找设备"></a>查找设备</h3><h4 id="查找已配对设备"><a href="#查找已配对设备" class="headerlink" title="查找已配对设备"></a>查找已配对设备</h4><p><strong><code>getBondedDevices()</code></strong></p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevicespairedDevices?.forEach { device -> <span class="hljs-keyword">val</span> deviceName = device.name <span class="hljs-keyword">val</span> deviceHardwareAddress = device.address <span class="hljs-comment">// MAC address</span>}</code></pre></div><h4 id="发现设备"><a href="#发现设备" class="headerlink" title="发现设备"></a>发现设备</h4><p><strong><code>startDiscovery()</code></strong><br>为了发现设备,必须针对<code>ACTION_FOUND</code> Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。系统会为每台设备广播此 Intent。Intent 包含额外字段 <code>EXTRA_DEVICE</code> 和 <code>EXTRA_CLASS</code>,二者又分别包含 <code>BluetoothDevice</code> 和 <code>BluetoothClass</code>。以下代码段展示如何在发现设备时通过注册来处理广播:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> { ... <span class="hljs-comment">// Register for broadcasts when a device is discovered.</span> <span class="hljs-keyword">val</span> filter = IntentFilter(BluetoothDevice.ACTION_FOUND) registerReceiver(receiver, filter)}<span class="hljs-comment">// Create a BroadcastReceiver for ACTION_FOUND.</span><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> receiver = <span class="hljs-keyword">object</span> : BroadcastReceiver() { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onReceive</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, intent: <span class="hljs-type">Intent</span>)</span></span> { <span class="hljs-keyword">val</span> action: String = intent.action <span class="hljs-keyword">when</span>(action) { BluetoothDevice.ACTION_FOUND -> { <span class="hljs-comment">// Discovery has found a device. Get the BluetoothDevice</span> <span class="hljs-comment">// object and its info from the Intent.</span> <span class="hljs-keyword">val</span> device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) <span class="hljs-keyword">val</span> deviceName = device.name <span class="hljs-keyword">val</span> deviceHardwareAddress = device.address <span class="hljs-comment">// MAC address</span> } } }}<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onDestroy</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">super</span>.onDestroy() ... <span class="hljs-comment">// Don't forget to unregister the ACTION_FOUND receiver.</span> unregisterReceiver(receiver)}</code></pre></div><h4 id="启用可检测性"><a href="#启用可检测性" class="headerlink" title="启用可检测性"></a>启用可检测性</h4><p>使用 <code>ACTION_REQUEST_DISCOVERABLE</code> Intent 调用 <code>startActivityForResult(Intent, int)</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> discoverableIntent: Intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply { putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, <span class="hljs-number">300</span>)}startActivity(discoverableIntent)</code></pre></div><p>此后设备将在分配的时间内以静默方式保持可检测到模式。可以为 <code>ACTION_SCAN_MODE_CHANGED</code> Intent 注册 BroadcastReceiver。此 Intent 将包含额外字段 <code>EXTRA_SCAN_MODE</code> 和 <code>EXTRA_PREVIOUS_SCAN_MODE</code>,二者分别提供新的和旧的扫描模式。每个 Extra 属性可能拥有以下值:</p><ul><li><code>SCAN_MODE_CONNECTABLE_DISCOVERABLE</code> 设备处于可检测到模式。</li><li><code>SCAN_MODE_CONNECTABLE</code> 设备未处于可检测到模式,但仍能收到连接。</li><li><code>SCAN_MODE_NONE</code> 设备未处于可检测到模式,且无法收到连接。</li></ul><blockquote><p>发起对远程设备的连接无需启用设备可检测性,只有当需要应用对接受传入连接的服务器套接字进行托管时,才有必要启用可检测性。</p></blockquote><h3 id="连接设备"><a href="#连接设备" class="headerlink" title="连接设备"></a>连接设备</h3><h4 id="作为服务器连接"><a href="#作为服务器连接" class="headerlink" title="作为服务器连接"></a>作为服务器连接</h4><ol><li>通过调用 <code>listenUsingRfcommWithServiceRecord()</code> 获取 <code>BluetoothServerSocket</code>。</li><li>通过调用 <code>accept()</code> 开始侦听连接请求。</li><li>使用完成后,调用 <code>close()</code>。</li></ol><h4 id="作为客户端连接"><a href="#作为客户端连接" class="headerlink" title="作为客户端连接"></a>作为客户端连接</h4><ol><li>使用 <code>BluetoothDevice</code>,通过调用 <code>createRfcommSocketToServiceRecord(UUID)</code> 获取 <code>BluetoothSocket</code>。</li><li>通过调用 <code>connect()</code> 发起连接。</li></ol><h4 id="管理连接"><a href="#管理连接" class="headerlink" title="管理连接"></a>管理连接</h4><ol><li>使用 <code>getInputStream()</code> 和 <code>getOutputStream()</code>,分别获取通过套接字处理数据传输的 <code>InputStream</code> 和 <code>OutputStream</code>。</li><li>使用 <code>read(byte[])</code> 和 <code>write(byte[])</code> 读取数据以及将其写入数据流。</li></ol>]]></content>
<categories>
<category>安卓</category>
<category>通讯</category>
</categories>
<tags>
<tag>进阶知识</tag>
<tag>开源项目学习</tag>
</tags>
</entry>
<entry>
<title>Android Studio 快捷键</title>
<link href="/2020/07/08/Android-Studio-%E5%BF%AB%E6%8D%B7%E9%94%AE/"/>
<url>/2020/07/08/Android-Studio-%E5%BF%AB%E6%8D%B7%E9%94%AE/</url>
<content type="html"><![CDATA[<p>一些Android Stuido 快捷键,其中较为主要的为:2、7、11、12、13、14</p><a id="more"></a><ol><li><code>Ctrl+G</code><br>同时按下Ctrl+G快捷键弹出快速定位框,在框中输入行数点击OK即可快速切换到对应的行数。</li><li><code>Ctrl+E</code><br>同时按下Ctrl+E快捷键,弹出最近打开文件列表,可以快速选择最近曾经打开的文件。</li><li><code>Ctrl+/</code><br>选中某一行,同时按下Ctrl+/快捷键可以注释这一行。</li><li><code>Ctrl+F</code><br>同时按下Ctrl+F快捷键,将在编辑页的顶部弹出类内快速搜索栏,可以快速定位类内的某个单词,支持联想查找。输入prote,将会高亮显示protected,同时注意到搜索栏中有三个复选框,选中第一个Match Case复选框将会对大小写敏感。</li><li><code>Ctrl+R</code><br>Ctrl+F快捷键常和Ctrl+R快捷键使用,用来快速查找并全部替换。</li><li><code>Ctrl+J</code><br>同时按下Ctrl和J快捷键,弹出快捷代码框。</li><li><code>Ctrl+F12</code><br>在类中方法比较多的情况下,同时按下Ctrl和F12键可以快速查看类中所有的方法,弹出这个框的同时可以直接输入想要搜索的方法,进行快速匹配。</li><li><code>Ctrl+Alt+T</code><br>选中一块代码,同时按下Ctrl、Alt和T键,弹出“包裹”弹出框,选择需要包裹的类型即可包裹选中的代码。</li><li><code>Ctrl+Alt+L</code><br>对当前类的所有代码进行格式化。</li><li><code>Ctrl+Alt+V</code><br>此快捷键可以快速声明一个变量,本地变量赋值。</li><li><code>Ctrl+Alt+H</code><br>点中某一个方法按下这个快捷键,在左边栏上弹出此方法的调用关系,此快捷键在开发中十分常用。</li><li><code>Ctrl+Alt+O</code><br>这个快捷键可以自动导包或删除无用的包,这时候按下快捷键即可自动删除这些无用的包。</li><li><code>Alt+Insert</code><br>同时按下Alt和Insert键,弹出快速代码生成框,有构造方法、getter/setter方法、toString方法等。</li><li><code>Ctrl+鼠标左键</code><br>此快捷键可以查看鼠标选中的类或方法。</li></ol>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
</entry>
<entry>
<title>Litepal的使用</title>
<link href="/2020/07/04/Litepal%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
<url>/2020/07/04/Litepal%E7%9A%84%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<p>Litepal,一个关于安卓数据库操作的一个第三方开源库,由《第一行代码》作者郭霖开源并维护。</p><a id="more"></a><p><a href="https://blog.csdn.net/sinyu890807/category_9262963.html" target="_blank" rel="noopener">来源于</a></p><h3 id="1-Include-library"><a href="#1-Include-library" class="headerlink" title="1. Include library"></a>1. Include library</h3><div class="hljs"><pre><code class="hljs gradle"><span class="hljs-keyword">dependencies</span> { implementation <span class="hljs-string">'org.litepal.guolindev:core:3.1.1'</span>}</code></pre></div><h3 id="2-配置litepal-xml"><a href="#2-配置litepal-xml" class="headerlink" title="2. 配置litepal.xml"></a>2. 配置litepal.xml</h3><p>在项目的 assets 目录下新建一个 litepal.xml 文件,把下面的代码 copy 过去:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">litepal</span>></span> <span class="hljs-comment"><!--</span><span class="hljs-comment"> Define the database name of your application. </span><span class="hljs-comment"> By default each database name should be end with .db. </span><span class="hljs-comment"> If you didn't name your database end with .db, </span><span class="hljs-comment"> LitePal would plus the suffix automatically for you.</span><span class="hljs-comment"> For example: </span><span class="hljs-comment"> <dbname value="demo" /></span><span class="hljs-comment"> --></span> <span class="hljs-tag"><<span class="hljs-name">dbname</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"demo"</span> /></span> <span class="hljs-comment"><!--</span><span class="hljs-comment"> Define the version of your database. Each time you want </span><span class="hljs-comment"> to upgrade your database, the version tag would helps.</span><span class="hljs-comment"> Modify the models you defined in the mapping tag, and just </span><span class="hljs-comment"> make the version value plus one, the upgrade of database</span><span class="hljs-comment"> will be processed automatically without concern.</span><span class="hljs-comment">For example: </span><span class="hljs-comment"> <version value="1" /></span><span class="hljs-comment"> --></span> <span class="hljs-tag"><<span class="hljs-name">version</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"1"</span> /></span> <span class="hljs-comment"><!--</span><span class="hljs-comment"> Define your models in the list with mapping tag, LitePal will</span><span class="hljs-comment"> create tables for each mapping class. The supported fields</span><span class="hljs-comment"> defined in models will be mapped into columns.</span><span class="hljs-comment"> For example: </span><span class="hljs-comment"> <list></span><span class="hljs-comment"> <mapping class="com.test.model.Reader" /></span><span class="hljs-comment"> <mapping class="com.test.model.Magazine" /></span><span class="hljs-comment"> </list></span><span class="hljs-comment"> --></span> <span class="hljs-tag"><<span class="hljs-name">list</span>></span> <span class="hljs-tag"></<span class="hljs-name">list</span>></span> <span class="hljs-comment"><!--</span><span class="hljs-comment"> Define where the .db file should be. "internal" means the .db file</span><span class="hljs-comment"> will be stored in the database folder of internal storage which no</span><span class="hljs-comment"> one can access. "external" means the .db file will be stored in the</span><span class="hljs-comment"> path to the directory on the primary external storage device where</span><span class="hljs-comment"> the application can place persistent files it owns which everyone</span><span class="hljs-comment"> can access. "internal" will act as default.</span><span class="hljs-comment"> For example:</span><span class="hljs-comment"> <storage value="external" /></span><span class="hljs-comment"> --></span><span class="hljs-tag"></<span class="hljs-name">litepal</span>></span></code></pre></div><h3 id="3-配置-LitePalApplication"><a href="#3-配置-LitePalApplication" class="headerlink" title="3. 配置 LitePalApplication"></a>3. 配置 LitePalApplication</h3><p>由于操作数据库时需要用到 Context,而我们显然不希望在每个接口中都去传一遍这个参数,那样操作数据库就显得太繁琐了。因此,LitePal使用了一个方法来简化掉 Context 这个参数,只需要在 AndroidManifest.xml 中配置一下LitePalApplication,所有的数据库操作就都不用再传Context了,如下所示:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">manifest</span>></span> <span class="hljs-tag"><<span class="hljs-name">application</span></span><span class="hljs-tag"> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"org.litepal.LitePalApplication"</span></span><span class="hljs-tag"> <span class="hljs-attr">...</span></span><span class="hljs-tag"> ></span> ... <span class="hljs-tag"></<span class="hljs-name">application</span>></span><span class="hljs-tag"></<span class="hljs-name">manifest</span>></span></code></pre></div><p>当然,有些程序可能会有自己的Application,并在这里配置过了。比如说有一个MyApplication,如下所示:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">manifest</span>></span> <span class="hljs-tag"><<span class="hljs-name">application</span></span><span class="hljs-tag"> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"com.example.MyApplication"</span></span><span class="hljs-tag"> <span class="hljs-attr">...</span></span><span class="hljs-tag"> ></span> ... <span class="hljs-tag"></<span class="hljs-name">application</span>></span><span class="hljs-tag"></<span class="hljs-name">manifest</span>></span></code></pre></div><p>没有关系,这时只需要在自定义的 Application 中添加一句 <code>Litepal.initialize(context)</code>:</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApplication</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Application</span> </span>{ <span class="hljs-meta">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">super</span>.onCreate(); LitePal.initialize(<span class="hljs-keyword">this</span>); }}</code></pre></div>]]></content>
<categories>
<category>安卓</category>
<category>开源库</category>
</categories>
<tags>
<tag>开源库</tag>
</tags>
</entry>
<entry>
<title>我的 GifFun 之旅</title>
<link href="/2020/07/04/%E6%88%91%E7%9A%84-GifFun-%E4%B9%8B%E6%97%85/"/>
<url>/2020/07/04/%E6%88%91%E7%9A%84-GifFun-%E4%B9%8B%E6%97%85/</url>
<content type="html"><![CDATA[<p>GifFun 是一个由《第一行代码》作者郭霖开源的一个项目。用户可以登录分享 Gif 图,查看别人分享的 Gif 图等。</p><a id="more"></a><h2 id="关于在学习-GifFun-项目中的一些收获"><a href="#关于在学习-GifFun-项目中的一些收获" class="headerlink" title="关于在学习 GifFun 项目中的一些收获"></a>关于在学习 GifFun 项目中的一些收获</h2><ul><li><a href="https://github.com/Klien-m/giffun" target="_blank" rel="noopener">Github</a></li><li><a href="https://gitee.com/Klien-m/giffun" target="_blank" rel="noopener">Gitee</a></li></ul><ol><li><p>在自定义 Application 中进行全局的初始化操作。</p></li><li><p>封装 SharedPreferences 的操作。</p></li><li><p>定义常量标识符。</p></li><li><p>导入 module <code>api project('module name')</code></p></li><li><p><a href="https://databank.umeng.com/sdc/datasources/create?type=app" target="_blank" rel="noopener">umeng</a>(数据统计)、 Litepal、 gson、 <a href="https://github.com/greenrobot/EventBus" target="_blank" rel="noopener">eventbus</a>(事件发布订阅,观察者?)、 okhttp、Glide图片加载、<a href="https://github.com/wasabeef/glide-transformations" target="_blank" rel="noopener"><code>implementation 'jp.wasabeef:glide-transformations:4.1.0'</code></a>、<a href="https://github.com/hdodenhof/CircleImageView" target="_blank" rel="noopener"><code>'de.hdodenhof:circleimageview:2.1.0'</code></a>、七牛云、<a href="https://github.com/ArthurHub/Android-Image-Cropper" target="_blank" rel="noopener">Android-Image-Cropper</a>、filebrowser、<a href="https://github.com/chrisbanes/PhotoView" target="_blank" rel="noopener">PhotoView</a></p></li><li><p>接口指定方法,继承接口则有此方法</p></li><li><p>设置 activity 的基类,就像《第二行代码》里讲的</p></li><li><p>日志操作的扩展工具类,就像《第二行代码》里讲的</p></li><li><p>AndroidVersion.kt</p></li><li><p>looper</p></li><li><p>集成 Toast 方法的 Context 使用 Application 的 Context,当前所在代码类有 Context 则使用当前的。</p></li><li><p>Nickname(昵称)</p></li><li><p>isNotEmpty(str) 等价于 str != null && str.length > 0<br>isNotBlank(str) 等价于 str != null && str.length > 0 && str.trim().length() > 0</p></li><li><p><code>@JvmStatic</code> 指定如果它是函数,则需要从此元素生成额外的静态方法。如果此元素是属性,则应生成额外的静态 getter / setter 方法。</p></li><li><p>api 和 implementation</p></li><li><p>implementation “androidx.palette:palette:1.0.0” 拾色器</p></li><li><p>actionStart()</p></li><li><p>setupViews() 初始化布局控件 UserInfo modify</p></li><li><p>CountDownTimer 计时器</p></li></ol><h2 id="com-quxianggif-core"><a href="#com-quxianggif-core" class="headerlink" title="com.quxianggif.core"></a>com.quxianggif.core</h2><h3 id="单位转换工具类,会根据手机的分辨率来进行单位转换"><a href="#单位转换工具类,会根据手机的分辨率来进行单位转换" class="headerlink" title="单位转换工具类,会根据手机的分辨率来进行单位转换"></a>单位转换工具类,会根据手机的分辨率来进行单位转换</h3><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 根据手机的分辨率将dp转成为px</span><span class="hljs-comment"> */</span><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">dp2px</span><span class="hljs-params">(dp: <span class="hljs-type">Float</span>)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">val</span> scale = GifFun.getContext().resources.displayMetrics.density <span class="hljs-keyword">return</span> (dp * scale + <span class="hljs-number">0.5f</span>).toInt()}<span class="hljs-comment">/**</span><span class="hljs-comment"> * 根据手机的分辨率将px转成dp</span><span class="hljs-comment"> */</span><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">px2dp</span><span class="hljs-params">(px: <span class="hljs-type">Float</span>)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">val</span> scale = GifFun.getContext().resources.displayMetrics.density <span class="hljs-keyword">return</span> (px / scale + <span class="hljs-number">0.5f</span>).toInt()}</code></pre></div><h3 id="Toast"><a href="#Toast" class="headerlink" title="Toast"></a>Toast</h3><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> toast: Toast? = <span class="hljs-literal">null</span> <span class="hljs-comment">/**</span><span class="hljs-comment"> * 弹出Toast信息。如果不是在主线程中调用此方法,Toast信息将会不显示。</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> content</span><span class="hljs-comment"> * Toast中显示的内容</span><span class="hljs-comment"> */</span> <span class="hljs-meta">@SuppressLint(<span class="hljs-meta-string">"ShowToast"</span>)</span> <span class="hljs-meta">@JvmOverloads</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">showToast</span><span class="hljs-params">(content: <span class="hljs-type">String</span>, duration: <span class="hljs-type">Int</span> = Toast.LENGTH_SHORT)</span></span> { <span class="hljs-keyword">if</span> (Looper.myLooper() == Looper.getMainLooper()) { <span class="hljs-keyword">if</span> (toast == <span class="hljs-literal">null</span>) { toast = Toast.makeText(GifFun.getContext(), content, duration) } <span class="hljs-keyword">else</span> { toast?.setText(content) } toast?.show() } } <span class="hljs-comment">/**</span><span class="hljs-comment"> * 切换到主线程后弹出Toast信息。此方法不管是在子线程还是主线程中,都可以成功弹出Toast信息。</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> content</span><span class="hljs-comment"> * Toast中显示的内容</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> duration</span><span class="hljs-comment"> * Toast显示的时长</span><span class="hljs-comment"> */</span> <span class="hljs-meta">@SuppressLint(<span class="hljs-meta-string">"ShowToast"</span>)</span> <span class="hljs-meta">@JvmOverloads</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">showToastOnUiThread</span><span class="hljs-params">(content: <span class="hljs-type">String</span>, duration: <span class="hljs-type">Int</span> = Toast.LENGTH_SHORT)</span></span> { GifFun.getHandler().post { <span class="hljs-keyword">if</span> (toast == <span class="hljs-literal">null</span>) { toast = Toast.makeText(GifFun.getContext(), content, duration) } <span class="hljs-keyword">else</span> { toast?.setText(content) } toast?.show() } }</code></pre></div><h2 id="com-quxianggif-main"><a href="#com-quxianggif-main" class="headerlink" title="com.quxianggif.main"></a>com.quxianggif.main</h2><h3 id="comments"><a href="#comments" class="headerlink" title="comments"></a>comments</h3><ul><li><p>取消数据改变时的动画,防止闪烁</p><div class="hljs"><pre><code class="hljs kotlin">(recyclerView.itemAnimator <span class="hljs-keyword">as</span> SimpleItemAnimator).supportsChangeAnimations = <span class="hljs-literal">false</span></code></pre></div></li><li><p>当知道 Adapter 内 Item 的改变不会影响 RecyclerView 宽高的时候,可以设置为 true 让 RecyclerView 避免重新计算大小,并通过 Adapter 的增删改查方法刷新 RecyclerView。<strong>在需要改变宽高的时候就用 <code>notifyDataSetChanged()</code> 刷新。</strong></p><div class="hljs"><pre><code class="hljs kotlin">recyclerView.setHasFixedSize(<span class="hljs-literal">true</span>)</code></pre></div></li><li><p>自定义 RecyclerView,根据 item 高度改变 RecyclerView 高度</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TopCommentsRecyclerView</span> : <span class="hljs-type">RecyclerView {</span></span> <span class="hljs-keyword">constructor</span>(context: Context) : <span class="hljs-keyword">super</span>(context) <span class="hljs-keyword">constructor</span>(context: Context, attrs: AttributeSet) : <span class="hljs-keyword">super</span>(context, attrs) <span class="hljs-keyword">constructor</span>(context: Context, attrs: AttributeSet, defStyleAttr: <span class="hljs-built_in">Int</span>) : <span class="hljs-keyword">super</span>( context, attrs, defStyleAttr ) <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onMeasure</span><span class="hljs-params">(widthMeasureSpec: <span class="hljs-type">Int</span>, heightMeasureSpec: <span class="hljs-type">Int</span>)</span></span> { <span class="hljs-comment">// AT_MOST参数表示控件可以自由调整大小,最大不超过Integer.MAX_VALUE/4</span> <span class="hljs-keyword">val</span> height = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE shr <span class="hljs-number">2</span>, MeasureSpec.AT_MOST) <span class="hljs-keyword">super</span>.onMeasure(widthMeasureSpec, height) }}</code></pre></div></li></ul><h3 id="common"><a href="#common" class="headerlink" title="common"></a>common</h3><ul><li><p>将状态栏设置成透明</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 将状态栏设置成透明。只适配Android 5.0以上系统的手机。</span><span class="hljs-comment"> */</span><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">transparentStatusBar</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">if</span> (AndroidVersion.hasLollipop()) { <span class="hljs-keyword">val</span> decorView = window.decorView decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE window.statusBarColor = Color.TRANSPARENT }}</code></pre></div></li><li><p>隐藏软键盘</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 隐藏软键盘。</span><span class="hljs-comment"> */</span><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">hideSoftKeyboard</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">try</span> { <span class="hljs-keyword">val</span> view = currentFocus <span class="hljs-keyword">if</span> (view != <span class="hljs-literal">null</span>) { <span class="hljs-keyword">val</span> binder = view.windowToken <span class="hljs-keyword">val</span> manager = getSystemService(Context.INPUT_METHOD_SERVICE) <span class="hljs-keyword">as</span> InputMethodManager manager.hideSoftInputFromWindow(binder, InputMethodManager.HIDE_NOT_ALWAYS) } } <span class="hljs-keyword">catch</span> (e: Exception) { logWarn(TAG, e.message, e) }}</code></pre></div></li><li><p>显示软键盘</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 显示软键盘。</span><span class="hljs-comment"> */</span><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">showSoftKeyboard</span><span class="hljs-params">(editText: <span class="hljs-type">EditText</span>?)</span></span> { <span class="hljs-keyword">try</span> { <span class="hljs-keyword">if</span> (editText != <span class="hljs-literal">null</span>) { editText.requestFocus() <span class="hljs-keyword">val</span> manager = getSystemService(Context.INPUT_METHOD_SERVICE) <span class="hljs-keyword">as</span> InputMethodManager manager.showSoftInput(editText, <span class="hljs-number">0</span>) } } <span class="hljs-keyword">catch</span> (e: Exception) { logWarn(TAG, e.message, e) }}</code></pre></div></li><li><p>跳转到应用设置界面</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)<span class="hljs-keyword">val</span> uri = Uri.fromParts(<span class="hljs-string">"package"</span>, GlobalUtil.appPackage, <span class="hljs-literal">null</span>)intent.<span class="hljs-keyword">data</span> = uri</code></pre></div></li></ul><h3 id="feeds"><a href="#feeds" class="headerlink" title="feeds"></a>feeds</h3><ul><li><p>ImageView、TextView 等控件具有 <code>android:layout_gravity</code> 属性</p></li><li><p>发布文章时间显示</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getDraftTime</span><span class="hljs-params">(draftMillis: <span class="hljs-type">Long</span>)</span></span>: String { <span class="hljs-keyword">val</span> currentMillis = System.currentTimeMillis() <span class="hljs-keyword">val</span> calendar = Calendar.getInstance() calendar.timeInMillis = currentMillis <span class="hljs-keyword">val</span> currentYear = calendar.<span class="hljs-keyword">get</span>(Calendar.YEAR) <span class="hljs-keyword">val</span> currentMonth = calendar.<span class="hljs-keyword">get</span>(Calendar.MONTH) <span class="hljs-keyword">val</span> currentDay = calendar.<span class="hljs-keyword">get</span>(Calendar.DAY_OF_MONTH) calendar.timeInMillis = draftMillis <span class="hljs-keyword">val</span> draftYear = calendar.<span class="hljs-keyword">get</span>(Calendar.YEAR) <span class="hljs-keyword">val</span> draftMonth = calendar.<span class="hljs-keyword">get</span>(Calendar.MONTH) <span class="hljs-keyword">val</span> draftDay = calendar.<span class="hljs-keyword">get</span>(Calendar.DAY_OF_MONTH) <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (currentYear == draftYear && currentMonth == draftMonth && currentDay == draftDay) { <span class="hljs-comment">// 当天的草稿只显示时间</span> SimpleDateFormat(<span class="hljs-string">"HH:mm"</span>, Locale.getDefault()).format(Date(draftMillis)) } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">if</span> (currentYear == draftYear) { <span class="hljs-comment">// 当年的草稿只显示月日</span> SimpleDateFormat(<span class="hljs-string">"MM-dd"</span>, Locale.getDefault()).format(Date(draftMillis)) } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 隔年的草稿显示完整年月日</span> SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd"</span>, Locale.getDefault()).format(Date(draftMillis)) } }}</code></pre></div></li><li><p>ContextMenu</p></li><li><p>SparseArray</p></li></ul><h3 id="init-ui"><a href="#init-ui" class="headerlink" title="init.ui"></a>init.ui</h3><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 跳转到下一个Activity。如果在闪屏界面停留的时间还不足规定最短停留时间,则会在这里等待一会,保证闪屏界面不至于一闪而过。</span><span class="hljs-comment"> */</span><span class="hljs-meta">@Synchronized</span><span class="hljs-keyword">open</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">forwardToNextActivity</span><span class="hljs-params">(hasNewVersion: <span class="hljs-type">Boolean</span>, version: <span class="hljs-type">Version</span>?)</span></span> { <span class="hljs-keyword">if</span> (!isForwarding) { <span class="hljs-comment">// 如果正在跳转或已经跳转到下一个界面,则不再重复执行跳转</span> isForwarding = <span class="hljs-literal">true</span> <span class="hljs-keyword">val</span> currentTime = System.currentTimeMillis() <span class="hljs-keyword">val</span> timeSpent = currentTime - enterTime <span class="hljs-keyword">if</span> (timeSpent < MIN_WAIT_TIME) { GlobalUtil.sleep(MIN_WAIT_TIME - timeSpent) } runOnUiThread { <span class="hljs-keyword">if</span> (GifFun.isLogin()) { MainActivity.actionStart(<span class="hljs-keyword">this</span>) finish() } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">if</span> (isActive) { LoginActivity.actionStartWithTransition(<span class="hljs-keyword">this</span>, logoView, hasNewVersion, version) } <span class="hljs-keyword">else</span> { LoginActivity.actionStart(<span class="hljs-keyword">this</span>, hasNewVersion, version) finish() } } } }}</code></pre></div><h3 id="util"><a href="#util" class="headerlink" title="util"></a>util</h3><ul><li><p>ActivityCollector.kt</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 应用中所有Activity的管理器,可用于一键杀死所有Activity。</span><span class="hljs-comment"> */</span><span class="hljs-keyword">object</span> ActivityCollector { <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> TAG = <span class="hljs-string">"ActivityCollector"</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> activityList = ArrayList<WeakReference<Activity>?>() <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">size</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">return</span> activityList.size } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">add</span><span class="hljs-params">(weakRefActivity: <span class="hljs-type">WeakReference</span><<span class="hljs-type">Activity</span>>?)</span></span> { activityList.add(weakRefActivity) } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">remove</span><span class="hljs-params">(weakRefActivity: <span class="hljs-type">WeakReference</span><<span class="hljs-type">Activity</span>>?)</span></span> { <span class="hljs-keyword">val</span> result = activityList.remove(weakRefActivity) logDebug(TAG, <span class="hljs-string">"remove activity reference <span class="hljs-variable">$result</span>"</span>) } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">finishAll</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">if</span> (activityList.isNotEmpty()) { <span class="hljs-keyword">for</span> (activityWeakReference <span class="hljs-keyword">in</span> activityList) { <span class="hljs-keyword">val</span> activity = activityWeakReference?.<span class="hljs-keyword">get</span>() <span class="hljs-keyword">if</span> (activity != <span class="hljs-literal">null</span> && !activity.isFinishing) { activity.finish() } } activityList.clear() } }}</code></pre></div></li><li><p>ColorUtils.kt</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * Utility methods for working with colors.</span><span class="hljs-comment"> */</span><span class="hljs-keyword">object</span> ColorUtils { <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> IS_LIGHT = <span class="hljs-number">0</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> IS_DARK = <span class="hljs-number">1</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> LIGHTNESS_UNKNOWN = <span class="hljs-number">2</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> TAG = <span class="hljs-string">"ColorUtils"</span> <span class="hljs-comment">/**</span><span class="hljs-comment"> * Set the alpha component of `color` to be `alpha`.</span><span class="hljs-comment"> */</span> <span class="hljs-meta">@CheckResult</span> <span class="hljs-meta">@ColorInt</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">modifyAlpha</span><span class="hljs-params">(<span class="hljs-meta">@ColorInt</span> color: <span class="hljs-type">Int</span>,</span></span><span class="hljs-function"><span class="hljs-params"> <span class="hljs-meta">@IntRange(from = 0, to = 255)</span> alpha: <span class="hljs-type">Int</span>)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">return</span> color and <span class="hljs-number">0x00ffffff</span> or (alpha shl <span class="hljs-number">24</span>) } <span class="hljs-comment">/**</span><span class="hljs-comment"> * Set the alpha component of `color` to be `alpha`.</span><span class="hljs-comment"> */</span> <span class="hljs-meta">@CheckResult</span> <span class="hljs-meta">@ColorInt</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">modifyAlpha</span><span class="hljs-params">(<span class="hljs-meta">@ColorInt</span> color: <span class="hljs-type">Int</span>,</span></span><span class="hljs-function"><span class="hljs-params"> <span class="hljs-meta">@FloatRange(from = 0.0, to = 1.0)</span> alpha: <span class="hljs-type">Float</span>)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">return</span> modifyAlpha(color, (<span class="hljs-number">255f</span> * alpha).toInt()) } <span class="hljs-comment">/**</span><span class="hljs-comment"> * 判断传入的图片的颜色属于深色还是浅色。</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> bitmap</span><span class="hljs-comment"> * 图片的Bitmap对象。</span><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 返回true表示图片属于深色,返回false表示图片属于浅色。</span><span class="hljs-comment"> */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isBitmapDark</span><span class="hljs-params">(palette: <span class="hljs-type">Palette</span>?, bitmap: <span class="hljs-type">Bitmap</span>)</span></span>: <span class="hljs-built_in">Boolean</span> { <span class="hljs-keyword">val</span> isDark: <span class="hljs-built_in">Boolean</span> <span class="hljs-keyword">val</span> lightness = ColorUtils.isDark(palette) <span class="hljs-keyword">if</span> (lightness == ColorUtils.LIGHTNESS_UNKNOWN) { isDark = ColorUtils.isDark(bitmap, bitmap.width / <span class="hljs-number">2</span>, <span class="hljs-number">0</span>) } <span class="hljs-keyword">else</span> { isDark = lightness == ColorUtils.IS_DARK } <span class="hljs-keyword">return</span> isDark } <span class="hljs-comment">/**</span><span class="hljs-comment"> * Checks if the most populous color in the given palette is dark</span><span class="hljs-comment"> *</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * Annoyingly we have to return this Lightness 'enum' rather than a boolean as palette isn't</span><span class="hljs-comment"> * guaranteed to find the most populous color.</span><span class="hljs-comment"> */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isDark</span><span class="hljs-params">(palette: <span class="hljs-type">Palette</span>?)</span></span>: <span class="hljs-built_in">Int</span> { <span class="hljs-keyword">val</span> mostPopulous = getMostPopulousSwatch(palette) ?: <span class="hljs-keyword">return</span> LIGHTNESS_UNKNOWN <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (isDark(mostPopulous.hsl)) IS_DARK <span class="hljs-keyword">else</span> IS_LIGHT } <span class="hljs-comment">/**</span><span class="hljs-comment"> * Determines if a given bitmap is dark. This extracts a palette inline so should not be called</span><span class="hljs-comment"> * with a large image!! If palette fails then check the color of the specified pixel</span><span class="hljs-comment"> */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isDark</span><span class="hljs-params">(bitmap: <span class="hljs-type">Bitmap</span>, backupPixelX: <span class="hljs-type">Int</span>, backupPixelY: <span class="hljs-type">Int</span>)</span></span>: <span class="hljs-built_in">Boolean</span> { <span class="hljs-comment">// first try palette with a small color quant size</span> <span class="hljs-keyword">val</span> palette = Palette.from(bitmap).maximumColorCount(<span class="hljs-number">3</span>).generate() <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (palette.swatches.size > <span class="hljs-number">0</span>) { isDark(palette) == IS_DARK } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// if palette failed, then check the color of the specified pixel</span> isDark(bitmap.getPixel(backupPixelX, backupPixelY)) } } <span class="hljs-comment">/**</span><span class="hljs-comment"> * Convert to HSL & check that the lightness value</span><span class="hljs-comment"> */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isDark</span><span class="hljs-params">(<span class="hljs-meta">@ColorInt</span> color: <span class="hljs-type">Int</span>)</span></span>: <span class="hljs-built_in">Boolean</span> { <span class="hljs-keyword">val</span> hsl = FloatArray(<span class="hljs-number">3</span>) android.support.v4.graphics.ColorUtils.colorToHSL(color, hsl) <span class="hljs-keyword">return</span> isDark(hsl) } <span class="hljs-comment">/**</span><span class="hljs-comment"> * Check that the lightness value (0–1)</span><span class="hljs-comment"> */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isDark</span><span class="hljs-params">(hsl: <span class="hljs-type">FloatArray</span>)</span></span>: <span class="hljs-built_in">Boolean</span> { <span class="hljs-comment">// @Size(3)</span> logDebug(TAG, <span class="hljs-string">"hsl[2] is "</span> + hsl[<span class="hljs-number">2</span>]) <span class="hljs-keyword">return</span> hsl[<span class="hljs-number">2</span>] < <span class="hljs-number">0.8f</span> } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getMostPopulousSwatch</span><span class="hljs-params">(palette: <span class="hljs-type">Palette</span>?)</span></span>: Palette.Swatch? { <span class="hljs-keyword">var</span> mostPopulous: Palette.Swatch? = <span class="hljs-literal">null</span> <span class="hljs-keyword">if</span> (palette != <span class="hljs-literal">null</span>) { <span class="hljs-keyword">for</span> (swatch <span class="hljs-keyword">in</span> palette.swatches) { <span class="hljs-keyword">if</span> (mostPopulous == <span class="hljs-literal">null</span> || swatch.population > mostPopulous.population) { mostPopulous = swatch } } } <span class="hljs-keyword">return</span> mostPopulous }}</code></pre></div></li><li><p>DateUtil.kt</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 时间和日期工具类。</span><span class="hljs-comment"> */</span><span class="hljs-keyword">object</span> DateUtil { <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> MINUTE = (<span class="hljs-number">60</span> * <span class="hljs-number">1000</span>).toLong() <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> HOUR = <span class="hljs-number">60</span> * MINUTE <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> DAY = <span class="hljs-number">24</span> * HOUR <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> WEEK = <span class="hljs-number">7</span> * DAY <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> MONTH = <span class="hljs-number">4</span> * WEEK <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> YEAR = <span class="hljs-number">365</span> * DAY <span class="hljs-comment">/**</span><span class="hljs-comment"> * 根据传入的Unix时间戳,获取转换过后更加易读的时间格式。</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> dateMillis</span><span class="hljs-comment"> * Unix时间戳</span><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 转换过后的时间格式,如2分钟前,1小时前。</span><span class="hljs-comment"> */</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getConvertedDate</span><span class="hljs-params">(dateMillis: <span class="hljs-type">Long</span>)</span></span>: String { <span class="hljs-keyword">val</span> currentMillis = System.currentTimeMillis() <span class="hljs-keyword">val</span> timePast = currentMillis - dateMillis <span class="hljs-keyword">if</span> (timePast > -MINUTE) { <span class="hljs-comment">// 采用误差一分钟以内的算法,防止客户端和服务器时间不同步导致的显示问题</span> <span class="hljs-keyword">when</span> { timePast < HOUR -> { <span class="hljs-keyword">var</span> pastMinutes = timePast / MINUTE <span class="hljs-keyword">if</span> (pastMinutes <= <span class="hljs-number">0</span>) { pastMinutes = <span class="hljs-number">1</span> } <span class="hljs-keyword">return</span> pastMinutes.toString() + GlobalUtil.getString(R.string.minutes_ago) } timePast < DAY -> { <span class="hljs-keyword">var</span> pastHours = timePast / HOUR <span class="hljs-keyword">if</span> (pastHours <= <span class="hljs-number">0</span>) { pastHours = <span class="hljs-number">1</span> } <span class="hljs-keyword">return</span> pastHours.toString() + GlobalUtil.getString(R.string.hours_ago) } timePast < WEEK -> { <span class="hljs-keyword">var</span> pastDays = timePast / DAY <span class="hljs-keyword">if</span> (pastDays <= <span class="hljs-number">0</span>) { pastDays = <span class="hljs-number">1</span> } <span class="hljs-keyword">return</span> pastDays.toString() + GlobalUtil.getString(R.string.days_ago) } timePast < MONTH -> { <span class="hljs-keyword">var</span> pastDays = timePast / WEEK <span class="hljs-keyword">if</span> (pastDays <= <span class="hljs-number">0</span>) { pastDays = <span class="hljs-number">1</span> } <span class="hljs-keyword">return</span> pastDays.toString() + GlobalUtil.getString(R.string.weeks_ago) } <span class="hljs-keyword">else</span> -> <span class="hljs-keyword">return</span> getDate(dateMillis) } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> getDateAndTime(dateMillis) } } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getTimeLeftTip</span><span class="hljs-params">(timeLeft: <span class="hljs-type">Long</span>)</span></span> = <span class="hljs-keyword">when</span> { timeLeft > YEAR -> { <span class="hljs-keyword">val</span> year = (timeLeft / YEAR) + <span class="hljs-number">1</span> year.toString() + GlobalUtil.getString(R.string.year) } timeLeft > MONTH -> { <span class="hljs-keyword">val</span> month = (timeLeft / MONTH) + <span class="hljs-number">1</span> month.toString() + GlobalUtil.getString(R.string.month) } timeLeft > DAY -> { <span class="hljs-keyword">val</span> day = (timeLeft / DAY) + <span class="hljs-number">1</span> day.toString() + GlobalUtil.getString(R.string.day) } timeLeft > HOUR -> { <span class="hljs-keyword">val</span> hour = (timeLeft / HOUR) + <span class="hljs-number">1</span> hour.toString() + GlobalUtil.getString(R.string.hour) } timeLeft > MINUTE -> { <span class="hljs-keyword">val</span> minute = (timeLeft / MINUTE) + <span class="hljs-number">1</span> minute.toString() + GlobalUtil.getString(R.string.minute) } <span class="hljs-keyword">else</span> -> { <span class="hljs-string">"1"</span> + GlobalUtil.getString(R.string.minute) } } <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isBlockedForever</span><span class="hljs-params">(timeLeft: <span class="hljs-type">Long</span>)</span></span> = timeLeft > <span class="hljs-number">5</span> * YEAR <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getDate</span><span class="hljs-params">(dateMillis: <span class="hljs-type">Long</span>)</span></span>: String { <span class="hljs-keyword">val</span> sdf = SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd"</span>, Locale.getDefault()) <span class="hljs-keyword">return</span> sdf.format(Date(dateMillis)) } <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getDateAndTime</span><span class="hljs-params">(dateMillis: <span class="hljs-type">Long</span>)</span></span>: String { <span class="hljs-keyword">val</span> sdf = SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd HH:mm"</span>, Locale.getDefault()) <span class="hljs-keyword">return</span> sdf.format(Date(dateMillis)) }}</code></pre></div></li><li><p>DeviceInfo.kt</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * 提供所有与设备相关的信息。</span><span class="hljs-comment"> */</span><span class="hljs-keyword">object</span> DeviceInfo { <span class="hljs-comment">/**</span><span class="hljs-comment"> * 获取当前设备屏幕的宽度,以像素为单位。</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 当前设备屏幕的宽度。</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">val</span> screenWidth: <span class="hljs-built_in">Int</span> <span class="hljs-keyword">get</span>() { <span class="hljs-keyword">val</span> windowManager = GifFun.getContext().getSystemService(Context.WINDOW_SERVICE) <span class="hljs-keyword">as</span> WindowManager <span class="hljs-keyword">val</span> metrics = DisplayMetrics() <span class="hljs-keyword">if</span> (AndroidVersion.hasJellyBeanMR1()) { windowManager.defaultDisplay.getRealMetrics(metrics) } <span class="hljs-keyword">else</span> { windowManager.defaultDisplay.getMetrics(metrics) } <span class="hljs-keyword">return</span> metrics.widthPixels } <span class="hljs-comment">/**</span><span class="hljs-comment"> * 获取当前设备屏幕的高度,以像素为单位。</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 当前设备屏幕的高度。</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">val</span> screenHeight: <span class="hljs-built_in">Int</span> <span class="hljs-keyword">get</span>() { <span class="hljs-keyword">val</span> windowManager = GifFun.getContext().getSystemService(Context.WINDOW_SERVICE) <span class="hljs-keyword">as</span> WindowManager <span class="hljs-keyword">val</span> metrics = DisplayMetrics() <span class="hljs-keyword">if</span> (AndroidVersion.hasJellyBeanMR1()) { windowManager.defaultDisplay.getRealMetrics(metrics) } <span class="hljs-keyword">else</span> { windowManager.defaultDisplay.getMetrics(metrics) } <span class="hljs-keyword">return</span> metrics.heightPixels }}</code></pre></div></li></ul><h2 id="com-quxianggif-network"><a href="#com-quxianggif-network" class="headerlink" title="com.quxianggif.network"></a>com.quxianggif.network</h2><ul><li><p>MD5.kt</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><span class="hljs-comment"> * MD5加密辅助工具类。</span><span class="hljs-comment"> */</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MD5</span> </span>{ <span class="hljs-keyword">private</span> static <span class="hljs-keyword">final</span> char[] DIGITS_UPPER = {<span class="hljs-string">'0'</span>, <span class="hljs-string">'1'</span>, <span class="hljs-string">'2'</span>, <span class="hljs-string">'3'</span>, <span class="hljs-string">'4'</span>, <span class="hljs-string">'5'</span>, <span class="hljs-string">'6'</span>, <span class="hljs-string">'7'</span>, <span class="hljs-string">'8'</span>, <span class="hljs-string">'9'</span>, <span class="hljs-string">'A'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'C'</span>, <span class="hljs-string">'D'</span>, <span class="hljs-string">'E'</span>, <span class="hljs-string">'F'</span>}; <span class="hljs-comment">/**</span><span class="hljs-comment"> * 对传入的字符串进行MD5加密。</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> origin 原始字符串。</span><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 经过MD5加密后的字符串。</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">public</span> static String encrypt(String origin) { <span class="hljs-keyword">try</span> { MessageDigest digest = MessageDigest.getInstance(<span class="hljs-string">"MD5"</span>); digest.update(origin.getBytes(Charset.defaultCharset())); <span class="hljs-keyword">return</span> new String(toHex(digest.digest())); } <span class="hljs-keyword">catch</span> (NoSuchAlgorithmException e) { e.printStackTrace(); } <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>; } <span class="hljs-comment">/**</span><span class="hljs-comment"> * 获取文件的MD5值。</span><span class="hljs-comment"> *</span><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> path 文件的路径</span><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 文件的MD5值。</span><span class="hljs-comment"> */</span> <span class="hljs-keyword">public</span> static String getFileMD5(String path) { <span class="hljs-keyword">try</span> { FileInputStream fis = new FileInputStream(path); MessageDigest md = MessageDigest.getInstance(<span class="hljs-string">"MD5"</span>); byte[] buffer = new byte[<span class="hljs-number">1024</span>]; int length; <span class="hljs-keyword">while</span> ((length = fis.read(buffer, <span class="hljs-number">0</span>, <span class="hljs-number">1024</span>)) != -<span class="hljs-number">1</span>) { md.update(buffer, <span class="hljs-number">0</span>, length); } BigInteger bigInt = new BigInteger(<span class="hljs-number">1</span>, md.digest()); <span class="hljs-keyword">return</span> bigInt.toString(<span class="hljs-number">16</span>).toUpperCase(); } <span class="hljs-keyword">catch</span> (Exception e) { e.printStackTrace(); } <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>; } <span class="hljs-keyword">private</span> static char[] toHex(byte[] <span class="hljs-keyword">data</span>) { char[] toDigits = DIGITS_UPPER; int l = <span class="hljs-keyword">data</span>.length; char[] <span class="hljs-keyword">out</span> = new char[l << <span class="hljs-number">1</span>]; <span class="hljs-comment">// two characters form the hex value.</span> <span class="hljs-keyword">for</span> (int i = <span class="hljs-number">0</span>, j = <span class="hljs-number">0</span>; i < l; i++) { <span class="hljs-keyword">out</span>[j++] = toDigits[(<span class="hljs-number">0xF0</span> & <span class="hljs-keyword">data</span>[i]) >>> <span class="hljs-number">4</span>]; <span class="hljs-keyword">out</span>[j++] = toDigits[<span class="hljs-number">0x0F</span> & <span class="hljs-keyword">data</span>[i]]; } <span class="hljs-keyword">return</span> <span class="hljs-keyword">out</span>; }}</code></pre></div></li></ul>]]></content>
<categories>
<category>安卓</category>
<category>项目</category>
</categories>
<tags>
<tag>开源项目学习</tag>
</tags>
</entry>
<entry>
<title>Kotlin的学习(六)——协程</title>
<link href="/2020/07/04/Kotlin%EF%BC%88%E5%85%AD%EF%BC%89/"/>
<url>/2020/07/04/Kotlin%EF%BC%88%E5%85%AD%EF%BC%89/</url>
<content type="html"><![CDATA[<p>Keywords:</p>]]></content>
<categories>
<category>LANGUAGE</category>
<category>Kotlin</category>
</categories>
<tags>
<tag>Kotlin进阶</tag>
</tags>
</entry>
<entry>
<title>Kotlin的学习(五)——集合</title>
<link href="/2020/07/03/Kotlin%EF%BC%88%E4%BA%94%EF%BC%89/"/>
<url>/2020/07/03/Kotlin%EF%BC%88%E4%BA%94%EF%BC%89/</url>
<content type="html"><![CDATA[<p>Keywords: list、set、map、构造集合、<code>iterator()</code>、<code>next()</code> List特有的 <code>previous()</code>、<code>MutableIterator()</code>、<code>rangeTo()</code>、<code>Sequence<T></code>、<code>map()</code>、<code>zip()</code>、<code>associateWith()</code>、<code>flatten()</code>、<code>joinToString()</code>、<code>joinTo()</code>、<code>filter()</code>、<code>partition()</code>、plus、minus、<code>groupBy()</code>、取集合的一部分 <code>slice()</code> … 、取单个元素、排序、聚合操作、集合写操作、List相关操作、Set相关操作、Map相关操作</p><a id="more"></a><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Kotlin 标准库提供了基本集合类型的实现:set(MutableSet)、list(MutableList)以及map(MutableMap)。前者只读,括号内的集合类型可以进行写操作。</p><h2 id="构造集合"><a href="#构造集合" class="headerlink" title="构造集合"></a>构造集合</h2><h3 id="由元素构造"><a href="#由元素构造" class="headerlink" title="由元素构造"></a>由元素构造</h3><p><code>listOf<T>()</code>、<code>setOf<T>()</code>、<code>mutableListOf<T>()</code>、<code>mutableSetOf<T>()</code>、<code>mapOf()</code>、<code>mutableMapOf()</code></p><h3 id="空集合"><a href="#空集合" class="headerlink" title="空集合"></a>空集合</h3><p><code>emptyList()</code>、<code>emptySet()</code>、<code>emptyMap()</code></p><h3 id="list-的初始化函数"><a href="#list-的初始化函数" class="headerlink" title="list 的初始化函数"></a>list 的初始化函数</h3><p>对于 List,有一个接受 List 的大小与初始化函数的构造函数,该初始化函数根据索引定义元素的值。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> doubled = List(<span class="hljs-number">3</span>, { it * <span class="hljs-number">2</span> })println(doubled)</code></pre></div><h3 id="具体类型构造函数"><a href="#具体类型构造函数" class="headerlink" title="具体类型构造函数"></a>具体类型构造函数</h3><p>要创建具体类型的集合,例如 <code>ArrayList</code> 或 <code>LinkedList</code>,可以使用这些类型的构造函数。 类似的构造函数对于 <code>Set</code> 与 <code>Map</code> 的各实现中均有提供。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> linkedList = LinkedList<String>(listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>))<span class="hljs-keyword">val</span> presizedSet = HashSet<<span class="hljs-built_in">Int</span>>(<span class="hljs-number">32</span>)</code></pre></div><h3 id="复制"><a href="#复制" class="headerlink" title="复制"></a>复制</h3><p>要创建与现有集合具有相同元素的集合,可以使用复制操作。标准库中的集合复制操作创建了具有相同元素引用的 <strong>浅</strong> 复制集合。 因此,对集合元素所做的更改会反映在其所有副本中。</p><p>在特定时刻通过集合复制函数,例如<code>toList()</code>、<code>toMutableList()</code>、<code>toSet()</code> 等等。创建了集合的快照。 结果是创建了一个具有相同元素的新集合如果在源集合中添加或删除元素,则不会影响副本。副本也可以独立于源集合进行更改。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> sourceList = mutableListOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<span class="hljs-keyword">val</span> copyList = sourceList.toMutableList()<span class="hljs-keyword">val</span> readOnlyCopyList = sourceList.toList()sourceList.add(<span class="hljs-number">4</span>)println(<span class="hljs-string">"Copy size: <span class="hljs-subst">${copyList.size}</span>"</span>)<span class="hljs-comment">//readOnlyCopyList.add(4) // 编译异常</span>println(<span class="hljs-string">"Read-only copy size: <span class="hljs-subst">${readOnlyCopyList.size}</span>"</span>)</code></pre></div><p>这些函数还可用于将集合转换为其他类型,例如根据 List 构建 Set,反之亦然。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> sourceList = mutableListOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<span class="hljs-keyword">val</span> copySet = sourceList.toMutableSet()copySet.add(<span class="hljs-number">3</span>)copySet.add(<span class="hljs-number">4</span>)println(copySet)</code></pre></div><p>或者,可以创建对同一集合实例的新引用。使用现有集合初始化集合变量时,将创建新引用。 因此,当通过引用更改集合实例时,更改将反映在其所有引用中。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> sourceList = mutableListOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<span class="hljs-keyword">val</span> referenceList = sourceListreferenceList.add(<span class="hljs-number">4</span>)println(<span class="hljs-string">"Source size: <span class="hljs-subst">${sourceList.size}</span>"</span>)</code></pre></div><p>集合的初始化可用于限制其可变性。例如,如果构建了一个 <code>MutableList</code> 的 <code>List</code> 引用,当你试图通过此引用修改集合的时候,编译器会抛出错误。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> sourceList = mutableListOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<span class="hljs-keyword">val</span> referenceList: List<<span class="hljs-built_in">Int</span>> = sourceList<span class="hljs-comment">//referenceList.add(4) // 编译错误</span>sourceList.add(<span class="hljs-number">4</span>)println(referenceList) <span class="hljs-comment">// 显示 sourceList 当前状态</span></code></pre></div><h3 id="调用其他集合的函数"><a href="#调用其他集合的函数" class="headerlink" title="调用其他集合的函数"></a>调用其他集合的函数</h3><p>可以通过其他集合各种操作的结果来创建集合。例如,过滤列表会创建与过滤器匹配的新元素列表:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>) <span class="hljs-keyword">val</span> longerThan3 = numbers.filter { it.length > <span class="hljs-number">3</span> }println(longerThan3)</code></pre></div><p>映射生成转换结果列表:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = setOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)println(numbers.map { it * <span class="hljs-number">3</span> })println(numbers.mapIndexed { idx, value -> value * idx })<span class="hljs-keyword">val</span> numbers = setOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)println(numbers.map { it * <span class="hljs-number">3</span> })println(numbers.mapIndexed { idx, value -> value * idx })</code></pre></div><p>关联生成 Map:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)println(numbers.associateWith { it.length })</code></pre></div><h2 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h2><p>在集合中使用 <code>for</code> 循环时,将隐式获取迭代器。</p><p><code>forEach()</code> 函数可自动迭代集合并为每个元素执行给定的代码。</p><h3 id="List迭代器"><a href="#List迭代器" class="headerlink" title="List迭代器"></a>List迭代器</h3><p>对于列表,有一个特殊的迭代器实现:<code>ListIterator</code> 支持列表双向迭代。反向迭代由 <code>hasPrevious()</code> 和 <code>previous()</code> 函数实现。 此外,<code>ListIterator</code> 通过 <code>nextIndex()</code> 与 <code>previousIndex()</code> 函数提供有关元素索引的信息。</p><p>具有双向迭代的能力意味着 <code>ListIterator</code> 在到达最后一个元素后仍可以使用。</p><h3 id="可变迭代器"><a href="#可变迭代器" class="headerlink" title="可变迭代器"></a>可变迭代器</h3><p><code>MutableIterator</code> 扩展 <code>Iterator</code> 使其具有 <code>remove()</code>、<code>add()</code>、<code>next()</code>。</p><h2 id="区间与数列"><a href="#区间与数列" class="headerlink" title="区间与数列"></a>区间与数列</h2><ul><li><code>rangeTo()</code> 函数操作符 <code>..</code>。</li><li>in !in</li><li><code>downTo</code>、<code>step</code>、<code>until</code></li><li>数列实现 <code>Iterable<N></code>,因此可以在各种集合函数(如 <code>map</code>、<code>filter</code>与其他)中使用它们。</li></ul><h2 id="序列(Sequence-lt-T-gt-)"><a href="#序列(Sequence-lt-T-gt-)" class="headerlink" title="序列(Sequence<T>)"></a>序列(<code>Sequence<T></code>)</h2><p>序列提供与 <code>Iterable</code> 相同的函数,但实现另一种方法来进行多步骤集合处理。</p><p>当 <code>Iterable</code> 的处理包含多个步骤时,它们会优先执行:每个处理步骤完成并返回其结果——中间集合。 在此集合上执行以下步骤。反过来,序列的多步处理在可能的情况下会延迟执行:仅当请求整个处理链的结果时才进行实际计算。</p><p>操作执行的顺序也不同:<code>Sequence</code> 对每个元素逐个执行所有处理步骤。 反过来,<code>Iterable</code> 完成整个集合的每个步骤,然后进行下一步。</p><h3 id="构造"><a href="#构造" class="headerlink" title="构造"></a>构造</h3><ul><li><code>sequenceOf()</code> 函数</li><li>如果已经有一个 <code>Iterable</code> 对象(例如 <code>List</code> 或 <code>Set</code>),可以通过调用 <code>asSequence()</code> 从而创建一个序列</li><li>创建序列的另一种方法是通过使用计算其元素的函数来构建序列。 要基于函数构建序列,请以该函数作为参数调用 <code>generateSequence()</code>。 (可选)可以将第一个元素指定为显式值或函数调用的结果。 当提供的函数返回 <code>null</code> 时,序列生成停止。</li><li>有一个函数可以逐个或按任意大小的组块生成序列元素——<code>sequence()</code> 函数。 此函数采用一个 lambda 表达式,其中包含 <code>yield()</code> 与 <code>yieldAll()</code> 函数的调用。 它们将一个元素返回给序列使用者,并暂停 <code>sequence()</code> 的执行,直到使用者请求下一个元素。 <code>yield()</code> 使用单个元素作为参数;<code>yieldAll()</code> 中可以采用 <code>Iterable</code> 对象、<code>Iterable</code> 或其他 <code>Sequence</code>。<code>yieldAll()</code> 的 <code>Sequence</code> 参数可以是无限的。 当然,这样的调用必须是最后一个:之后的所有调用都永远不会执行。</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> oddNumbers = sequence { yield(<span class="hljs-number">1</span>) yieldAll(listOf(<span class="hljs-number">3</span>, <span class="hljs-number">5</span>)) yieldAll(generateSequence(<span class="hljs-number">7</span>) { it + <span class="hljs-number">2</span> })}println(oddNumbers.take(<span class="hljs-number">5</span>).toList())<span class="hljs-comment">// 运行结果:[1, 3, 5, 7, 9]</span></code></pre></div><h2 id="操作概述"><a href="#操作概述" class="headerlink" title="操作概述"></a>操作概述</h2><h3 id="公共操作"><a href="#公共操作" class="headerlink" title="公共操作"></a>公共操作</h3><p>集合转换、集合过滤、plus和minus操作符、分组、取集合一部分、取单个元素、集合排序、集合聚合操作,<strong>上述操作将返回操作结果,而不会影响原始集合。</strong></p><p>对于某些集合操作,有一个选项可以指定目标对象。目标是一个可变集合,该函数将其结果项附加到该可变对象中。对于执行带有目标的操作,有单独的函数,其名称中带有 <code>To</code> 后缀,例如,用 <code>filterTo()</code> 代替 <code>filter()</code> 以及用 <code>associateTo()</code> 代替 <code>associate()</code>。</p><div class="hljs"><pre><code class="hljs kotlin">numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)<span class="hljs-keyword">val</span> filterResults = mutableListOf<String>() <span class="hljs-comment">// 目标对象</span>numbers.filterTo(filterResults) { it.length > <span class="hljs-number">3</span> }numbers.filterIndexedTo(filterResults) { index, _ -> index == <span class="hljs-number">0</span> }println(filterResults) <span class="hljs-comment">// 包含两个操作的结果</span><span class="hljs-comment">// 运行结果为:[three, four, one]</span></code></pre></div><p>为了方便起见,这些函数将目标集合返回了,因此可以在函数调用的相关参数中直接创建它:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">// 将数字直接过滤到新的哈希集中,</span><span class="hljs-comment">// 从而消除结果中的重复项</span><span class="hljs-keyword">val</span> result = numbers.mapTo(HashSet()) { it.length }println(<span class="hljs-string">"distinct item lengths are <span class="hljs-variable">$result</span>"</span>)</code></pre></div><h3 id="写操作"><a href="#写操作" class="headerlink" title="写操作"></a>写操作</h3><p>对于某些操作,有成对的函数可以执行相同的操作:一个函数就地应用该操作,另一个函数将结果作为单独的集合返回。 例如, <code>sort()</code> 就地对可变集合进行排序,因此其状态发生了变化; <code>sorted()</code> 创建一个新集合,该集合包含按排序顺序相同的元素。</p><h2 id="转换"><a href="#转换" class="headerlink" title="转换"></a>转换</h2><h3 id="映射"><a href="#映射" class="headerlink" title="映射"></a>映射</h3><p>基本的映射函数是 <code>map()</code>。如果需要用到元素索引,可以使用 <code>mapIndexed()</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = setOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)println(numbers.map { it * <span class="hljs-number">3</span> })println(numbers.mapIndexed { idx, value -> value * idx })</code></pre></div><p>如果转换在某些元素上产生 <code>null</code> 值,可以调用 <code>mapNotNull()</code> or <code>mapIndexedNotNull()</code> 来过滤掉 <code>null</code> 值。</p><p>映射转换时,有两个选择:转换键,使值保持不变,反之亦然。要将指定转换应用于键,使用 <code>mapKeys()</code>;反过来,<code>mapValues()</code> 转换值。这两个函数都使用将映射条目作为参数的转换,因此可以操作其键与值。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbersMap = mapOf(<span class="hljs-string">"key1"</span> to <span class="hljs-number">1</span>, <span class="hljs-string">"key2"</span> to <span class="hljs-number">2</span>, <span class="hljs-string">"key3"</span> to <span class="hljs-number">3</span>, <span class="hljs-string">"key11"</span> to <span class="hljs-number">11</span>)println(numbersMap.mapKeys { it.key.toUpperCase() })println(numbersMap.mapValues { it.value + it.key.length })</code></pre></div><h3 id="双路合并"><a href="#双路合并" class="headerlink" title="双路合并"></a>双路合并</h3><p>在一个集合(或数组)上以另一个集合(或数组)作为参数调用时,<code>zip()</code> 返回 <code>Pair</code> 对象的列表(<code>List</code>)。<code>zip()</code> 也可以使用中缀表达式。</p><p>当拥有 <code>Pair</code> 的 <code>List</code> 时,可以进行反向转换 unzipping。<br>分割键值对列表,可以调用 <code>unzip()</code>。</p><h3 id="关联"><a href="#关联" class="headerlink" title="关联"></a>关联</h3><p>关联转换允许从集合元素和与其关联的某些值构建 Map。</p><p>基本的关联函数 <code>associateWith()</code> 创建一个 <code>Map</code>,其中原始集合的元素是键,并通过给定的转换函数从中产生值。 如果两个元素相等,则仅最后一个保留在 Map 中。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)println(numbers.associateWith { it.length })<span class="hljs-comment">// 结果为:{one=3, two=3, three=5, four=4}</span></code></pre></div><p>为了使用集合元素作为值来构建 Map,有一个函数 <code>associateBy()</code>。 它需要一个函数,该函数根据元素的值返回键。如果两个元素相等,则仅最后一个保留在 Map 中。 还可以使用值转换函数来调用 <code>associateBy()</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)println(numbers.associateBy { it.first().toUpperCase() })println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length }))<span class="hljs-comment">// 结果为:{O=one, T=three, F=four} {O=3, T=5, F=4}</span></code></pre></div><p>另一种构建 Map 的方法是使用函数 <code>associate()</code>,其中 Map 键和值都是通过集合元素生成的。 它需要一个 lambda 函数,该函数返回 <code>Pair</code>:键和相应 Map 条目的值。<code>associate()</code> 会生成临时的 <code>Pair</code> 对象,这可能会影响性能。 因此,当性能不是很关键或比其他选项更可取时,应使用 <code>associate()</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> names = listOf(<span class="hljs-string">"Alice Adams"</span>, <span class="hljs-string">"Brian Brown"</span>, <span class="hljs-string">"Clara Campbell"</span>)println(names.associate { name -> parseFullName(name).let { it.lastName to it.firstName } })<span class="hljs-comment">// 结果为:{Adams=Alice, Brown=Brian, Campbell=Clara}</span></code></pre></div><h3 id="打平"><a href="#打平" class="headerlink" title="打平"></a>打平</h3><p><code>flatten()</code> 可以在一个集合的集合(例如,一个 <code>Set</code> 组成的 <code>List</code>)上调用它。该函数返回嵌套集合中的所有元素的一个 <code>List</code>。</p><h3 id="字符串表示"><a href="#字符串表示" class="headerlink" title="字符串表示"></a>字符串表示</h3><p><code>joinToString()</code> 根据提供的参数从集合元素构建单个 <code>String</code>。 <code>joinTo()</code> 执行相同的操作,但将结果附加到给定的 <code>Appendable</code> 对象。</p><p>要构建自定义字符串表示形式,可以在函数参数 <code>separator</code>、<code>prefix</code> 与 <code>postfix</code>中指定其参数。 结果字符串将以 <code>prefix</code> 开头,以 <code>postfix</code> 结尾。除最后一个元素外,<code>separator</code> 将位于每个元素之后。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)println(numbers.joinToString(separator = <span class="hljs-string">" | "</span>, prefix = <span class="hljs-string">"start: "</span>, postfix = <span class="hljs-string">": end"</span>))<span class="hljs-comment">// 结果为:start: one | two | three | four: end</span></code></pre></div><p>对于较大的集合,可能需要指定 <code>limit</code> ——将包含在结果中元素的数量。 如果集合大小超出 <code>limit</code>,所有其他元素将被 <code>truncated</code> 参数的单个值替换。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = (<span class="hljs-number">1</span>..<span class="hljs-number">100</span>).toList()println(numbers.joinToString(limit = <span class="hljs-number">10</span>, truncated = <span class="hljs-string">"<...>"</span>))<span class="hljs-comment">// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, <...></span></code></pre></div><p>要自定义元素本身的表示形式,提供 <code>transform</code> 函数。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)println(numbers.joinToString { <span class="hljs-string">"Element: <span class="hljs-subst">${it.toUpperCase()}</span>"</span>})<span class="hljs-comment">// 结果为:Element: ONE, Element: TWO, Element: THREE, Element: FOUR</span></code></pre></div><h2 id="过滤"><a href="#过滤" class="headerlink" title="过滤"></a>过滤</h2><h3 id="按谓词过滤"><a href="#按谓词过滤" class="headerlink" title="按谓词过滤"></a>按谓词过滤</h3><p>当使用一个谓词来调用时,<code>filter()</code> 返回与其匹配的集合元素。对于 <code>List</code> 和 <code>Set</code>,过滤结果都是一个 <code>List</code>,对 <code>Map</code> 来说结果还是一个 <code>Map</code>。</p><p><code>filter()</code> 中的谓词只能检查元素的值。如果想在过滤中使用元素在集合中的位置,应该使用 <code>filterIndexed()</code>。它接受一个带有两个参数的谓词:元素的索引和元素的值。</p><p>如果想使用否定条件来过滤集合,请使用 <code>filterNot()</code>。它返回一个让谓词产生 <code>false</code> 的元素列表。</p><p><code>filterIsInstance()</code> 返回给定类型的集合元素。</p><p><code>filterNotNull()</code> 返回所有的非空元素。</p><h3 id="划分"><a href="#划分" class="headerlink" title="划分"></a>划分</h3><p><code>partition()</code> – 通过一个谓词过滤集合并且将不匹配的元素存放在一个单独的列表中。因此,你得到一个 <code>List</code> 的 <code>Pair</code> 作为返回值:第一个列表包含与谓词匹配的元素并且第二个列表包含原始集合中的所有其他元素。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>)<span class="hljs-keyword">val</span> (match, rest) = numbers.partition { it.length > <span class="hljs-number">3</span> }println(match)println(rest)<span class="hljs-comment">// 结果为:[three, four] [one, two]</span></code></pre></div><h3 id="检验谓词"><a href="#检验谓词" class="headerlink" title="检验谓词"></a>检验谓词</h3><ul><li>如果至少有一个元素匹配给定谓词,那么 <code>any()</code> 返回 <code>true</code>。</li><li>如果没有元素与给定谓词匹配,那么 <code>none()</code> 返回 <code>true</code>。</li><li>如果所有元素都匹配给定谓词,那么 <code>all()</code> 返回 <code>true</code>。</li></ul><p><code>any()</code> 和 <code>none()</code> 也可以不带谓词使用:在这种情况下它们只是用来检查集合是否为空。 如果集合中有元素,<code>any()</code> 返回 <code>true</code>,否则返回 <code>false</code>;<code>none()</code> 则相反。</p><h2 id="plus-与-minus-操作符"><a href="#plus-与-minus-操作符" class="headerlink" title="plus 与 minus 操作符"></a>plus 与 minus 操作符</h2><p>plus 就是集合的并集, minus 就是集合的差集。</p><h2 id="分组"><a href="#分组" class="headerlink" title="分组"></a>分组</h2><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-string">"one"</span>, <span class="hljs-string">"two"</span>, <span class="hljs-string">"three"</span>, <span class="hljs-string">"four"</span>, <span class="hljs-string">"five"</span>)println(numbers.groupBy { it.first().toUpperCase() })println(numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.toUpperCase() }))<span class="hljs-comment">// 结果为:{O=[one], T=[two, three], F=[four, five]} {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}</span></code></pre></div><p>如果要对元素进行分组,然后一次将操作应用于所有分组,可以使用 <code>groupingBy()</code> 函数。 它返回一个 <code>Grouping</code> 类型的实例。 通过 <code>Grouping</code> 实例,可以以一种惰性的方式将操作应用于所有组:这些分组实际上是刚好在执行操作前构建的。</p><p><code>Grouping</code> 支持以下操作:</p><ul><li><code>eachCount()</code> 计算每个组中的元素。</li><li><code>fold()</code> 与 <code>reduce()</code> 对每个组分别执行 <code>fold 与 reduce</code> 操作,作为一个单独的集合并返回结果。</li><li><code>aggregate()</code> 随后将给定操作应用于每个组中的所有元素并返回结果。</li></ul><h2 id="取集合的一部分"><a href="#取集合的一部分" class="headerlink" title="取集合的一部分"></a>取集合的一部分</h2><p><a href="https://www.kotlincn.net/docs/reference/collection-parts.html#%E5%8F%96%E9%9B%86%E5%90%88%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86" target="_blank" rel="noopener">详见</a></p><ul><li><code>slice()</code> 返回具有给定索引的集合元素列表。</li><li><code>take()</code> 从头开始获取指定数量的元素。<code>takeLast()</code> 从尾开始获取指定数量的元素。<code>takeWhile()</code> 将不停获取元素直到排除与谓词匹配的首个元素。 <code>takeLastWhile()</code></li><li><code>drop()</code> 与 <code>dropLast()</code> 从头或尾去除给定数量的元素。<code>dropWhile()</code> <code>dropLastWhile()</code></li><li><code>chunked()</code> 将集合分解为给定大小的“块”。还可以立即对返回的块应用转换(在调用 <code>chunked()</code> 时将转换作为 lambda 函数提供)。</li><li><code>windowed()</code> 返回一个元素区间列表,与 <code>chunked()</code> 不同,<code>windowed()</code> 返回从每个集合元素开始的元素区间。可选参数:<code>step</code>、<code>partialWindows</code> 包含从集合末尾的元素开始的较小的窗口。函数可以立即对返回的区间应用转换,只需在调用 <code>windowed()</code> 时将转换作为 lambda 函数提供。</li><li>构建两个元素的窗口有一个单独的函数—— <code>zipWithNext()</code>。</li></ul><h2 id="取单个元素"><a href="#取单个元素" class="headerlink" title="取单个元素"></a>取单个元素</h2><ul><li><code>elementAt()</code>、<code>first()</code>、<code>last()</code>、<code>elementAtOrNull()</code>、<code>elementAtOrFalse()</code></li><li><code>first()</code>、<code>last()</code>(<code>find()</code>、<code>findLast()</code>)按照给定谓词匹配。为了避免没有元素匹配的异常,可以使用 <code>firstOrNull()</code> 和 <code>lastOrNull()</code></li><li><code>random()</code></li><li><code>contains()</code>、<code>isEmpty()</code>、<code>isNotEmpty()</code></li></ul><h2 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h2><ul><li>用于只读集合的排序函数将结果作为一个新集合返回。</li><li><code>sorted</code> 与 <code>sortedDescending</code></li><li><code>sortedBy</code> 与 <code>sortedByDescending()</code> 接受一个将集合元素映射为 <code>Comparable</code> 值得选择器,并以该值得自然顺序对集合排序。</li><li><code>reversed()</code> 返回带有元素副本的新集合。<strong><code>asReversed()</code> 返回相同集合实例的一个反向视图</strong>。如果原始列表是可变的,那么其所有更改都会反映在反向视图中,反之亦然。</li><li><code>shuffled()</code> 函数返回一个包含了以<strong>随机顺序排序</strong>的集合元素的新的 <code>List</code>。</li></ul><h2 id="聚合操作"><a href="#聚合操作" class="headerlink" title="聚合操作"></a>聚合操作</h2><div class="hljs"><pre><code class="hljs kotlin">min() max average() sum() count() maxBy() minBy() maxWith() minWith() sumBy() sumByDouble()</code></pre></div><h3 id="Fold-与-reduce"><a href="#Fold-与-reduce" class="headerlink" title="Fold 与 reduce"></a>Fold 与 reduce</h3><p><code>reduce()</code> 和 <code>fold()</code>,它们依次将所提供的操作应用于集合元素并返回累积的结果。操作有两个参数:先前的累计值和集合元素。</p><p>这两个函数的区别在于:<code>fold()</code> 接受一个初始值并将其用作第一步的累积值,而 <code>reduce()</code> 的第一步则将第一个和第二个元素作为第一步的操作参数。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-number">5</span>, <span class="hljs-number">2</span>, <span class="hljs-number">10</span>, <span class="hljs-number">4</span>)<span class="hljs-keyword">val</span> sum = numbers.reduce { sum, element -> sum + element }println(sum)<span class="hljs-keyword">val</span> sumDoubled = numbers.fold(<span class="hljs-number">0</span>) { sum, element -> sum + element * <span class="hljs-number">2</span> }println(sumDoubled)<span class="hljs-comment">//val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 } //错误:第一个元素在结果中没有加倍</span><span class="hljs-comment">//println(sumDoubledReduce)</span><span class="hljs-comment">// 结果为:21 42</span></code></pre></div><p>如需将函数以相反的顺序应用于元素,可以使用函数 <code>reduceRight()</code> 和 <code>foldRight()</code> 它们的工作方式类似于 <code>fold()</code> 和 <code>reduce()</code>,但从最后一个元素开始,然后再继续到前一个元素。</p><p>还可以使用将元素索引作为参数的操作。 为此,使用函数 <code>reduceIndexed()</code> 和 <code>foldIndexed()</code> 传递元素索引作为操作的第一个参数。</p><p>还有将这些操作从右到左应用于集合元素的函数——<code>reduceRightIndexed()</code>与 <code>foldRightIndexed()</code>。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbers = listOf(<span class="hljs-number">5</span>, <span class="hljs-number">2</span>, <span class="hljs-number">10</span>, <span class="hljs-number">4</span>)<span class="hljs-keyword">val</span> sumEven = numbers.foldIndexed(<span class="hljs-number">0</span>) { idx, sum, element -> <span class="hljs-keyword">if</span> (idx % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) sum + element <span class="hljs-keyword">else</span> sum }println(sumEven)<span class="hljs-keyword">val</span> sumEvenRight = numbers.foldRightIndexed(<span class="hljs-number">0</span>) { idx, element, sum -> <span class="hljs-keyword">if</span> (idx % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) sum + element <span class="hljs-keyword">else</span> sum }println(sumEvenRight)<span class="hljs-comment">// 结果为:15 15</span></code></pre></div><h2 id="集合写操作"><a href="#集合写操作" class="headerlink" title="集合写操作"></a>集合写操作</h2><div class="hljs"><pre><code class="hljs kotlin">add() addAll() plusAssign(+=) remove() removeAll() retainAll() clear() minusAssign(-=)</code></pre></div><p>其中 <code>retainAll()</code> 是与 <code>removeAll()</code> 相反的一个函数:移除除参数集合中的元素之外的所有元素,结合谓词一起使用时,它只留下与之匹配的元素。</p><h2 id="List-相关操作"><a href="#List-相关操作" class="headerlink" title="List 相关操作"></a>List 相关操作</h2><h3 id="按索引取元素"><a href="#按索引取元素" class="headerlink" title="按索引取元素"></a>按索引取元素</h3><p>支持按索引取元素的所有常用操作:<code>elementAt()</code>、 <code>first()</code>、 <code>last()</code>、 <code>get()</code> <code>[index]</code>、 <code>getOrElse()</code>、 <code>getOrNull()</code></p><h3 id="取列表的一部分"><a href="#取列表的一部分" class="headerlink" title="取列表的一部分"></a>取列表的一部分</h3><p><code>subList()</code> 将指定元素范围的视图作为列表返回。如果原始集合的元素发生变化,通过此方法先前创建的子列表中也会发生变化,反之亦然。</p><h3 id="查找元素位置"><a href="#查找元素位置" class="headerlink" title="查找元素位置"></a>查找元素位置</h3><h4 id="线性查找"><a href="#线性查找" class="headerlink" title="线性查找"></a>线性查找</h4><p><code>indexOf()</code> <code>lastIndexOf()</code> <code>indexOfFirst()</code> <code>indexOfLast()</code></p><h4 id="在有序列表中二分查找"><a href="#在有序列表中二分查找" class="headerlink" title="在有序列表中二分查找"></a>在有序列表中二分查找</h4><p>有序列表二分查找:<code>binarySearch()</code></p><h4 id="Comparator-二分搜索"><a href="#Comparator-二分搜索" class="headerlink" title="Comparator 二分搜索"></a>Comparator 二分搜索</h4><p>如果列表元素不是 <code>Comparator</code>,则提供一个用于二分搜索的 <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-comparator.html" target="_blank" rel="noopener"><code>Comparator</code></a> 该列表必须根据此 <code>Comparator</code> 以升序排序:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> productList = listOf( Product(<span class="hljs-string">"WebStorm"</span>, <span class="hljs-number">49.0</span>), Product(<span class="hljs-string">"AppCode"</span>, <span class="hljs-number">99.0</span>), Product(<span class="hljs-string">"DotTrace"</span>, <span class="hljs-number">129.0</span>), Product(<span class="hljs-string">"ReSharper"</span>, <span class="hljs-number">149.0</span>))println(productList.binarySearch(Product(<span class="hljs-string">"AppCode"</span>, <span class="hljs-number">99.0</span>), compareBy<Product> { it.price }.thenBy { it.name }))<span class="hljs-comment">// 结果为 1</span></code></pre></div><h4 id="比较函数二分搜索"><a href="#比较函数二分搜索" class="headerlink" title="比较函数二分搜索"></a>比较函数二分搜索</h4><p>使用比较函数的二分搜索无需提供明确的搜索值即可查找元素。 取而代之的是,它使用一个比较函数将元素映射到 <code>Int</code> 值,并搜索函数返回 0 的元素。 该列表必须根据提供的函数以升序排序;换句话说,比较的返回值必须从一个列表元素增长到下一个列表元素。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span></span>(<span class="hljs-keyword">val</span> name: String, <span class="hljs-keyword">val</span> price: <span class="hljs-built_in">Double</span>)<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">priceComparison</span><span class="hljs-params">(product: <span class="hljs-type">Product</span>, price: <span class="hljs-type">Double</span>)</span></span> = sign(product.price - price).toInt()<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { <span class="hljs-keyword">val</span> productList = listOf( Product(<span class="hljs-string">"WebStorm"</span>, <span class="hljs-number">49.0</span>), Product(<span class="hljs-string">"AppCode"</span>, <span class="hljs-number">99.0</span>), Product(<span class="hljs-string">"DotTrace"</span>, <span class="hljs-number">129.0</span>), Product(<span class="hljs-string">"ReSharper"</span>, <span class="hljs-number">149.0</span>)) println(productList.binarySearch { priceComparison(it, <span class="hljs-number">99.0</span>) })}<span class="hljs-comment">// 结果为:1</span></code></pre></div><p>Comparator 与比较函数二分搜索都可以针对列表区间执行。</p><h3 id="List-写操作"><a href="#List-写操作" class="headerlink" title="List 写操作"></a>List 写操作</h3><p><code>add()</code> <code>addAll()</code> <code>set()</code> <code>[]</code> <code>removeAt()</code></p><p><code>fill()</code> 将所有集合元素的值替换为指定值。</p><h4 id="List-排序"><a href="#List-排序" class="headerlink" title="List 排序"></a>List 排序</h4><p>就地排序函数的名称与应用于只读列表的函数的名称相似,但没有 <code>ed/d</code> 后缀:</p><ul><li><code>sort*</code> 在所有排序函数的名称中代替 <code>sorted*</code>:<code>sort()</code>、<code>sortDescending()</code>、<code>sortBy()</code> 等等。</li><li><code>shuffle()</code> 代替 <code>shuffled()</code>。</li><li><code>reverse()</code> 代替 <code>reversed()</code>。</li><li><code>asReversed()</code> 在可变列表上调用会返回另一个可变列表,该列表是原始列表的反向视图。在该视图中的更改将反映在原始列表中。</li></ul><h2 id="Set-相关操作"><a href="#Set-相关操作" class="headerlink" title="Set 相关操作"></a>Set 相关操作</h2><p>Kotlin 集合包中包含 set 常用操作的扩展函数:查找交集(<code>intersect()</code>)、并集(<code>union()</code>)或差集(<code>subtract()</code>)。</p><p>上述方法都支持中缀形式调用。<code>List</code> 也支持 Set 操作,但是对 List 进行 Set 操作的结果仍然是 <code>Set</code>,因此将删除所有重复的元素。</p><h2 id="Map-相关操作"><a href="#Map-相关操作" class="headerlink" title="Map 相关操作"></a>Map 相关操作</h2><h3 id="取键与值"><a href="#取键与值" class="headerlink" title="取键与值"></a>取键与值</h3><p>要从 Map 中检索值,必须提供其键作为 <code>get()</code> 函数的参数。 还支持简写 <code>[key]</code> 语法。 如果找不到给定的键,则返回 <code>null</code>。 还有一个函数 <code>getValue()</code>,它的行为略有不同:如果在 Map 中找不到键,则抛出异常。 此外,还有两个选项可以解决键缺失的问题:</p><ul><li><code>getOrElse()</code> 与 list 的工作方式相同:对于不存在的键,其值由给定的 lambda 表达式返回。</li><li><code>getOrDefault()</code> 如果找不到键,则返回指定的默认值。</li></ul><p>要对 map 的所有键或所有值执行操作,可以从属性 <code>keys</code> 和 <code>values</code> 中相应地检索它们。 <code>keys</code> 是 <code>Map</code> 中所有键的集合, <code>values</code> 是 <code>Map</code> 中所有值的集合。</p><h3 id="Map-过滤"><a href="#Map-过滤" class="headerlink" title="Map 过滤"></a>Map 过滤</h3><p>对 map 使用 <code>filter()</code> 函数时,<code>Pair</code> 将作为参数的谓词传递给它。 它将使用谓词同时过滤其中的键和值。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbersMap = mapOf(<span class="hljs-string">"key1"</span> to <span class="hljs-number">1</span>, <span class="hljs-string">"key2"</span> to <span class="hljs-number">2</span>, <span class="hljs-string">"key3"</span> to <span class="hljs-number">3</span>, <span class="hljs-string">"key11"</span> to <span class="hljs-number">11</span>)<span class="hljs-keyword">val</span> filteredMap = numbersMap.filter { (key, value) -> key.endsWith(<span class="hljs-string">"1"</span>) && value > <span class="hljs-number">10</span>}println(filteredMap)<span class="hljs-comment">// 结果为:{key11=11}</span></code></pre></div><p>还有两种用于过滤 map 的特定函数:按键或按值。 这两种方式,都有对应的函数:<code>filterKeys()</code> 和 <code>filterValues()</code>。 两者都将返回一个新 Map,其中包含与给定谓词相匹配的条目。<code>filterKeys()</code> 的谓词仅检查元素键,<code>filterValues()</code> 的谓词仅检查值。</p><h3 id="plus-与-minus-操作"><a href="#plus-与-minus-操作" class="headerlink" title="plus 与 minus 操作"></a>plus 与 minus 操作</h3><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbersMap = mapOf(<span class="hljs-string">"one"</span> to <span class="hljs-number">1</span>, <span class="hljs-string">"two"</span> to <span class="hljs-number">2</span>, <span class="hljs-string">"three"</span> to <span class="hljs-number">3</span>)println(numbersMap + Pair(<span class="hljs-string">"four"</span>, <span class="hljs-number">4</span>))println(numbersMap + Pair(<span class="hljs-string">"one"</span>, <span class="hljs-number">10</span>))println(numbersMap + mapOf(<span class="hljs-string">"five"</span> to <span class="hljs-number">5</span>, <span class="hljs-string">"one"</span> to <span class="hljs-number">11</span>))<span class="hljs-comment">// 结果为 {one=1, two=2, three=3, four=4}</span><span class="hljs-comment">// 结果为 {one=10, two=2, three=3}</span><span class="hljs-comment">// 结果为 {one=11, two=2, three=3, five=5}</span></code></pre></div><p><code>minus</code> 将根据左侧 <code>Map</code> 条目创建一个新 <code>Map</code>,右侧操作数带有键的条目将被剔除。 因此,右侧操作数可以是单个键或键的集合: list 、 set 等。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> numbersMap = mapOf(<span class="hljs-string">"one"</span> to <span class="hljs-number">1</span>, <span class="hljs-string">"two"</span> to <span class="hljs-number">2</span>, <span class="hljs-string">"three"</span> to <span class="hljs-number">3</span>)println(numbersMap - <span class="hljs-string">"one"</span>)println(numbersMap - listOf(<span class="hljs-string">"two"</span>, <span class="hljs-string">"four"</span>))<span class="hljs-comment">// 结果为:{two=2, three=3}</span><span class="hljs-comment">// 结果为:{one=1, three=3}</span></code></pre></div><h3 id="Map-写操作"><a href="#Map-写操作" class="headerlink" title="Map 写操作"></a>Map 写操作</h3><h4 id="添加与更新条目"><a href="#添加与更新条目" class="headerlink" title="添加与更新条目"></a>添加与更新条目</h4><ul><li><code>put()</code> 将新的键值对添加到可变 Map。</li><li>要一次添加多个条目,可以使用 <code>putAll()</code>。</li><li>plusAssign(<code>+=</code>)</li><li><code>[]</code> 操作符为 <code>put()</code> 的别名。</li></ul><h4 id="删除条目"><a href="#删除条目" class="headerlink" title="删除条目"></a>删除条目</h4><ul><li>从可变 Map 中删除条目,可以使用 <code>remove()</code> 函数。</li><li>还可以通过键或值从可变 Map 中删除条目。 在 Map 的 <code>.keys</code> 或 <code>.values</code> 中调用 <code>remove()</code> 并提供键或值来删除条目。 在 <code>.values</code> 中调用时,<code>remove()</code> 仅删除给定值匹配到的的第一个条目。</li><li>minusAssign(<code>-=</code>)操作符。</li></ul>]]></content>
<categories>
<category>LANGUAGE</category>
<category>Kotlin</category>
</categories>
<tags>
<tag>Kotlin基础</tag>
</tags>
</entry>
<entry>
<title>Kotlin的学习(四)——函数与 Lambda 表达式</title>
<link href="/2020/07/03/Kotlin%EF%BC%88%E5%9B%9B%EF%BC%89/"/>
<url>/2020/07/03/Kotlin%EF%BC%88%E5%9B%9B%EF%BC%89/</url>
<content type="html"><![CDATA[<p>Keywrods: 函数、<code>vararg</code> 可变数量参数、<code>infix</code> 中缀表达式、<code>tailrec</code> 尾递归函数、内联函数 <code>inline</code> <code>noinline</code>、非局部返回、<a href="https://www.kotlincn.net/docs/reference/inline-functions.html#%E9%9D%9E%E5%B1%80%E9%83%A8%E8%BF%94%E5%9B%9E" target="_blank" rel="noopener"><code>crossinline</code></a>、具体化的类型参数 <code>reified</code></p><a id="more"></a><h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><h3 id="默认参数"><a href="#默认参数" class="headerlink" title="默认参数"></a>默认参数</h3><p>如果⼀个默认参数在⼀个⽆默认值的参数之前,那么该默认值只能通过使⽤具名参数调⽤该函数来使⽤:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">foo</span><span class="hljs-params">(bar: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span>, baz: <span class="hljs-type">Int</span>)</span></span> { <span class="hljs-comment">/*……*/</span> }foo(baz = <span class="hljs-number">1</span>) <span class="hljs-comment">// 使⽤默认值 bar = 0</span></code></pre></div><p>如果在默认参数之后的最后⼀个参数是 lambda 表达式,那么它既可以作为具名参数在括号内传⼊,也可以在括号外传⼊:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">foo</span><span class="hljs-params">(bar: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span>, baz: <span class="hljs-type">Int</span> = <span class="hljs-number">1</span>, qux: () -> <span class="hljs-type">Unit</span>)</span></span> { <span class="hljs-comment">/*……*/</span> }foo(<span class="hljs-number">1</span>) { println(<span class="hljs-string">"hello"</span>) } <span class="hljs-comment">// 使⽤默认值 baz = 1</span>foo(qux = { println(<span class="hljs-string">"hello"</span>) }) <span class="hljs-comment">// 使⽤两个默认值 bar = 0 与 baz = 1</span>foo { println(<span class="hljs-string">"hello"</span>) } <span class="hljs-comment">// 使⽤两个默认值 bar = 0 与 baz = 1</span></code></pre></div><h3 id="具名参数"><a href="#具名参数" class="headerlink" title="具名参数"></a>具名参数</h3><p>可以通过使⽤星号操作符将可变数量参数(vararg)以具名形式传⼊:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">foo</span><span class="hljs-params">(<span class="hljs-keyword">vararg</span> strings: <span class="hljs-type">String</span>)</span></span> { <span class="hljs-comment">/*……*/</span> }foo(strings = *arrayOf(<span class="hljs-string">"a"</span>, <span class="hljs-string">"b"</span>, <span class="hljs-string">"c"</span>))</code></pre></div><h3 id="可变数量的参数(Varargs)"><a href="#可变数量的参数(Varargs)" class="headerlink" title="可变数量的参数(Varargs)"></a>可变数量的参数(Varargs)</h3><p>函数的参数(通常是最后一个)可以用 <code>vararg</code> 修饰符标记:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type"><T></span> <span class="hljs-title">asList</span><span class="hljs-params">(<span class="hljs-keyword">vararg</span> ts: <span class="hljs-type">T</span>)</span></span>: List<T> { <span class="hljs-keyword">val</span> result = ArrayList<T>() <span class="hljs-keyword">for</span> (t <span class="hljs-keyword">in</span> ts) <span class="hljs-comment">// ts is an Array</span> result.add(t) <span class="hljs-keyword">return</span> result}</code></pre></div><p>在函数内部,类型 <code>T</code> 的 <code>vararg</code> 参数的可⻅⽅式是作为 <code>T</code> 数组,即上例中的 <code>ts</code> 变量具有类型 <code>Array <out T></code> 。<br>只有⼀个参数可以标注为 <code>vararg</code> 。如果 <code>vararg</code> 参数不是列表中的最后⼀个参数,可以使⽤具名参数语法传递其后的参数的值,或者,如果参数具有函数类型,则通过在括号外部传⼀个 <code>lambda</code>。</p><h3 id="中缀表示法"><a href="#中缀表示法" class="headerlink" title="中缀表示法"></a>中缀表示法</h3><p>标有 <code>infix</code> 关键字的函数也可以使⽤中缀表⽰法(忽略该调⽤的点与圆括号)调⽤。中缀函数必须满⾜以下要求:</p><ul><li>它们必须是成员函数或扩展函数;</li><li>它们必须只有⼀个参数;</li><li>其参数不得接受可变数量的参数且不能有默认值。</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">infix</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-built_in">Int</span>.<span class="hljs-title">shl</span><span class="hljs-params">(x: <span class="hljs-type">Int</span>)</span></span>: <span class="hljs-built_in">Int</span> { …… }<span class="hljs-comment">// ⽤中缀表⽰法调⽤该函数</span><span class="hljs-number">1</span> shl <span class="hljs-number">2</span><span class="hljs-comment">// 等同于这样</span><span class="hljs-number">1</span>.shl(<span class="hljs-number">2</span>)</code></pre></div><h3 id="尾递归函数"><a href="#尾递归函数" class="headerlink" title="尾递归函数"></a>尾递归函数</h3><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> eps = <span class="hljs-number">1E-10</span> <span class="hljs-comment">// "good enough", could be 10^-15</span><span class="hljs-keyword">tailrec</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">findFixPoint</span><span class="hljs-params">(x: <span class="hljs-type">Double</span> = <span class="hljs-number">1.0</span>)</span></span>: <span class="hljs-built_in">Double</span> = <span class="hljs-keyword">if</span> (Math.abs(x - Math.cos(x)) < eps) x <span class="hljs-keyword">else</span> findFixPoint(Math.cos(x))</code></pre></div><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> eps = <span class="hljs-number">1E-10</span> <span class="hljs-comment">// "good enough", could be 10^-15</span><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">findFixPoint</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Double</span> { <span class="hljs-keyword">var</span> x = <span class="hljs-number">1.0</span> <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) { <span class="hljs-keyword">val</span> y = Math.cos(x) <span class="hljs-keyword">if</span> (Math.abs(x - y) < eps) <span class="hljs-keyword">return</span> x x = Math.cos(x) }}</code></pre></div><p>要符合 <code>tailrec</code> 修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。</p><h2 id="高阶函数与-lambda-表达式"><a href="#高阶函数与-lambda-表达式" class="headerlink" title="高阶函数与 lambda 表达式"></a>高阶函数与 lambda 表达式</h2><p><a href="https://www.kotlincn.net/docs/reference/lambdas.html#%E5%B8%A6%E6%9C%89%E6%8E%A5%E6%94%B6%E8%80%85%E7%9A%84%E5%87%BD%E6%95%B0%E5%AD%97%E9%9D%A2%E5%80%BC" target="_blank" rel="noopener">带有接收者的函数字面值</a></p><h2 id="内联函数"><a href="#内联函数" class="headerlink" title="内联函数"></a>内联函数</h2><p>内联可能导致生成的代码增加,所以要避免内联过大函数。</p><h3 id="非局部返回"><a href="#非局部返回" class="headerlink" title="非局部返回"></a>非局部返回</h3><p>位于 lambda 表达式中,但退出包含它的函数的返回称为非局部返回。</p><p>一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 <code>crossinline</code> 修饰符标记:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-keyword">crossinline</span> body: () -> <span class="hljs-type">Unit</span>)</span></span> { <span class="hljs-keyword">val</span> f = <span class="hljs-keyword">object</span>: Runnable { <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">run</span><span class="hljs-params">()</span></span> = body() } <span class="hljs-comment">// ……</span>}</code></pre></div><h3 id="具体化的类型参数"><a href="#具体化的类型参数" class="headerlink" title="具体化的类型参数"></a>具体化的类型参数</h3><p>有时候我们需要访问一个作为参数传给我们的一个类型:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type"><T></span> TreeNode.<span class="hljs-title">findParentOfType</span><span class="hljs-params">(clazz: <span class="hljs-type">Class</span><<span class="hljs-type">T</span>>)</span></span>: T? { <span class="hljs-keyword">var</span> p = parent <span class="hljs-keyword">while</span> (p != <span class="hljs-literal">null</span> && !clazz.isInstance(p)) { p = p.parent } <span class="hljs-meta">@Suppress(<span class="hljs-meta-string">"UNCHECKED_CAST"</span>)</span> <span class="hljs-keyword">return</span> p <span class="hljs-keyword">as</span> T?}</code></pre></div><p>在这里我们向上遍历一棵树并且检测每个节点是不是特定的类型。 这都没有问题,但是调用处不是很优雅:</p><div class="hljs"><pre><code class="hljs kotlin">treeNode.findParentOfType(MyTreeNode::<span class="hljs-class"><span class="hljs-keyword">class</span>.<span class="hljs-title">java</span>)</span></code></pre></div><p>我们真正想要的只是传一个类型给该函数,即像这样调用它:</p><div class="hljs"><pre><code class="hljs kotlin">treeNode.findParentOfType<MyTreeNode>()</code></pre></div><p>为能够这么做,内联函数支持具体化的类型参数,于是我们可以这样写:</p><div class="hljs"><pre><code class="hljs kotlin">.findParentOfType(): T? {<span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type"><<span class="hljs-keyword">reified</span> T></span> TreeNode.<span class="hljs-title">findParentOfType</span><span class="hljs-params">()</span></span>: T? { <span class="hljs-keyword">var</span> p = parent <span class="hljs-keyword">while</span> (p != <span class="hljs-literal">null</span> && p !<span class="hljs-keyword">is</span> T) { p = p.parent } <span class="hljs-keyword">return</span> p <span class="hljs-keyword">as</span> T?}</code></pre></div><h3 id="内联属性"><a href="#内联属性" class="headerlink" title="内联属性"></a>内联属性</h3><p><code>inline</code> 修饰符可用于没有幕后字段的属性的访问器。</p>]]></content>
<categories>
<category>LANGUAGE</category>
<category>Kotlin</category>
</categories>
<tags>
<tag>Kotlin基础</tag>
</tags>
</entry>
<entry>
<title>Kotlin的学习(三)——类与对象</title>
<link href="/2020/07/03/Kotlin%EF%BC%88%E4%B8%89%EF%BC%89/"/>
<url>/2020/07/03/Kotlin%EF%BC%88%E4%B8%89%EF%BC%89/</url>
<content type="html"><![CDATA[<p>Keywords: constructor、实例初始化顺序、<code>Any</code>、<a href="https://www.kotlincn.net/docs/reference/classes.html#%E8%A6%86%E7%9B%96%E6%96%B9%E6%B3%95" target="_blank" rel="noopener">覆盖方法</a>、<a href="https://www.kotlincn.net/docs/reference/classes.html#%E8%A6%86%E7%9B%96%E5%B1%9E%E6%80%A7" target="_blank" rel="noopener">覆盖属性</a>、初始化顺序、继承、抽象类、伴生对象、<code>lateinit</code>、幕后字段、幕后属性、可见性修饰符、扩展、<code>sealed</code>、<code>inline</code>、<a href="https://www.kotlincn.net/docs/reference/delegation.html#%E5%A7%94%E6%89%98" target="_blank" rel="noopener">委托</a></p><a id="more"></a><h2 id="类与继承"><a href="#类与继承" class="headerlink" title="类与继承"></a>类与继承</h2><h3 id="类"><a href="#类" class="headerlink" title="类"></a>类</h3><ul><li>如果主构造函数没有任何注解后者可见性修饰符,可以省略 constructor 关键字。如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面。</li><li>在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起。</li><li>如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可。</li><li>初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块。</li><li>在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。</li></ul><h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><ul><li><code>Any</code> 超类有三个方法:<code>equals()</code> 、 <code>hashCode()</code> 与 <code>toString()</code>。</li><li>默认情况下,Kotlin 类是最终(final)的:它们不能被继承。 要使一个类可继承,请用 <code>open</code> 关键字标记它。</li><li>如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化。<br>如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数。</li></ul><h4 id="覆盖方法"><a href="#覆盖方法" class="headerlink" title="覆盖方法"></a>覆盖方法</h4><ul><li><code>open</code> 、 <code>override</code></li><li>标记为 <code>override</code> 的成员本身是开放的,它可以在子类中覆盖。如果想禁止再次覆盖,使用 <code>final</code> 关键字:</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">open</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Rectangle</span></span>() : Shape() { <span class="hljs-keyword">final</span> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">draw</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">/*……*/</span> }}</code></pre></div><h4 id="覆盖属性"><a href="#覆盖属性" class="headerlink" title="覆盖属性"></a>覆盖属性</h4><p>属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 <code>override</code> 开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 <code>get</code> 方法的属性覆盖。</p><p>可以用 <code>var</code> 属性覆盖 <code>val</code> 属性,反之则不行。一个 <code>val</code> 属性本质上声明了一个 <code>get</code> 方法,将其覆盖为 <code>var</code> 只是在子类中额外声明一个 <code>set</code> 方法。</p><h4 id="派生类初始化顺序"><a href="#派生类初始化顺序" class="headerlink" title="派生类初始化顺序"></a>派生类初始化顺序</h4><p>在构造派生类的新实例的过程中,第一步完成其基类的初始化。(在之前只有对基类构造函数参数的求值)。<br>设计一个基类时,应该避免在构造函数、属性初始化器以及 <code>init</code> 块中使用 <code>open</code> 成员。</p><h4 id="覆盖规则"><a href="#覆盖规则" class="headerlink" title="覆盖规则"></a>覆盖规则</h4><p>一个类从它的直接超类继承相同成员的多个实现,它必须覆盖这个成员并提供自己的实现。为了表示采用从哪个超类型继承的实现,使用由尖括号中超类型名限定的 super 如:<code>super<Base></code> 。</p><h3 id="伴生对象"><a href="#伴生对象" class="headerlink" title="伴生对象"></a>伴生对象</h3><p>如果在类内声明了一个<a href="https://www.kotlincn.net/docs/reference/object-declarations.html#%E4%BC%B4%E7%94%9F%E5%AF%B9%E8%B1%A1" target="_blank" rel="noopener">伴生对象</a>,就可以用类名为限定符访问其成员。</p><h2 id="属性与字段"><a href="#属性与字段" class="headerlink" title="属性与字段"></a>属性与字段</h2><h3 id="延迟初始化属性与变量"><a href="#延迟初始化属性与变量" class="headerlink" title="延迟初始化属性与变量"></a>延迟初始化属性与变量</h3><p>一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检测。</p><p>为处理这种情况,你可以用 <code>lateinit</code> 修饰符标记该属性:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyTest</span> </span>{ <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> subject: TestSubject <span class="hljs-meta">@SetUp</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span></span> { subject = TestSubject() } <span class="hljs-meta">@Test</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">test</span><span class="hljs-params">()</span></span> { subject.method() <span class="hljs-comment">// 直接解引用</span> }}</code></pre></div><p>该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 <code>var</code> 属性,并且仅当该属性没有自定义 <code>getter</code> 或 <code>setter</code> 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。</p><p>在初始化前访问一个 <code>lateinit</code> 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。</p><h4 id="检测一个-lateinit-var-是否已初始化(自1-2起)"><a href="#检测一个-lateinit-var-是否已初始化(自1-2起)" class="headerlink" title="检测一个 lateinit var 是否已初始化(自1.2起)"></a>检测一个 lateinit var 是否已初始化(自1.2起)</h4><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">if</span> (foo::bar.isInitialized) { println(foo.bar)}</code></pre></div><p>此检测仅对可词法级访问的属性可用,即声明位于同一个类型内、位于其中一个外围类型中或者位于相同文件的顶层的属性。</p><h2 id="可见性修饰符"><a href="#可见性修饰符" class="headerlink" title="可见性修饰符"></a>可见性修饰符</h2><ul><li>Kotlin 中有四个可见性修饰符:<code>private</code>、 <code>protected</code>、 <code>internal</code>和 <code>public</code>。默认可见性为 <code>public</code>。</li><li>局部变量、函数和类不能有可见性修饰符。</li></ul><h2 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h2><p>扩展属性不能有初始化器。它们的行为只能由显示的提供 getters/setters 定义。</p><h2 id="密封类"><a href="#密封类" class="headerlink" title="密封类"></a>密封类</h2><p>要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。(在 Kotlin 1.1 之前, 该规则更加严格:子类必须嵌套在密封类声明的内部)。在某种意义上,密封类是枚举类的扩展。</p><h2 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h2><p><a href="https://www.kotlincn.net/docs/reference/generics.html" target="_blank" rel="noopener">不太懂,先放着</a></p><h2 id="内联类"><a href="#内联类" class="headerlink" title="内联类"></a>内联类</h2><p>有时候,业务逻辑需要围绕某种类型创建包装器。然⽽,由于额外的堆内存分配问题,它会引⼊运⾏时的性能开销。此外,如果被包装的类型是原⽣类型,性能的损失是很糟糕的,因为原⽣类型通常在运⾏时就进⾏了⼤量优化,然⽽他们<br>的包装器却没有得到任何特殊的处理。</p><p>为了解决这类问题,Kotlin 引入了 <code>内联类</code> ,它通过在类的前面定义一个 <code>inline</code> 修饰符来声明。</p><p>内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例。</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Password</span></span>(<span class="hljs-keyword">val</span> value: String)<span class="hljs-comment">// 不存在 'Password' 类的真实实例对象</span><span class="hljs-comment">// 在运行时, 'securePassword' 仅仅包含 'String'</span><span class="hljs-keyword">val</span> securePassword = Passwrod(<span class="hljs-string">"Don't try this in production"</span>)</code></pre></div><h3 id="成员"><a href="#成员" class="headerlink" title="成员"></a>成员</h3><p>内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Name</span></span>(<span class="hljs-keyword">val</span> s: String) {<span class="hljs-keyword">val</span> length: <span class="hljs-built_in">Int</span><span class="hljs-keyword">get</span>() = s.length<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">greet</span><span class="hljs-params">()</span></span> {println(<span class="hljs-string">"Hello, <span class="hljs-variable">$s</span>"</span>)}}<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {<span class="hljs-keyword">val</span> name = Name(<span class="hljs-string">"Kotlin"</span>)name.greet() <span class="hljs-comment">// `greet` ⽅法会作为⼀个静态⽅法被调⽤</span>println(name.length) <span class="hljs-comment">// 属性的 get ⽅法会作为⼀个静态⽅法被调⽤</span>}</code></pre></div><p>但内联类不能含有 init 代码块,不能含有幕后字段。</p><h3 id="内联类的继承"><a href="#内联类的继承" class="headerlink" title="内联类的继承"></a>内联类的继承</h3><p>内联类允许去继承接口。禁止内联类参与到类的继承关系结构中。这就意味着内联类不能继承其他的类而且必须是 final。</p><h2 id="委托"><a href="#委托" class="headerlink" title="委托"></a>委托</h2><p><a href="https://www.kotlincn.net/docs/reference/delegation.html#%E5%A7%94%E6%89%98" target="_blank" rel="noopener">看的不是太明白</a></p>]]></content>
<categories>
<category>LANGUAGE</category>
<category>Kotlin</category>
</categories>
<tags>
<tag>Kotlin基础</tag>
</tags>
</entry>
<entry>
<title>Kotlin的学习(二)——基础</title>
<link href="/2020/07/03/Kotlin%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<url>/2020/07/03/Kotlin%EF%BC%88%E4%BA%8C%EF%BC%89/</url>
<content type="html"><![CDATA[<p>Keywords:==、===、显示转换、<code>inv()</code> 位非、<code>arrayOf()</code>、标签 <code>@</code></p><a id="more"></a><h2 id="基本类型"><a href="#基本类型" class="headerlink" title="基本类型"></a>基本类型</h2><h3 id="数字"><a href="#数字" class="headerlink" title="数字"></a>数字</h3><ul><li>Float 6-7 位十进制数,Double 15-16 位十进制数。 </li><li>Kotlin 的数字没有隐式拓宽转换,如果需要可以使用显示转化。 </li><li>数字字面值中可以加入分隔的下划线。 </li><li>可空引用或泛型会把数字装箱。数字装箱**不一定保留同一性(===)。但保留了相等性(==)。 </li><li>显示转换:<code>toByte() toShort() toInt() toLong() toFloat() toDouble() toChar()</code> </li><li>整数除法总是返回整数,想要返回浮点类型,需要把其中一个参数显示转换为浮点类型。 </li><li>位运算:<code>shl()</code> 有符号左移,<code>shr()</code> 有符号右移,<code>ushr()</code> 无符号右移,<code>inv()</code> 非。</li></ul><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><ul><li>数组是不型变de,<code>arrayOf()</code> 库函数创建数组,<code>arrayOfNulls()</code> 创建所有元素为空的数组,或者接受数组大小以及一个函数参数得 <code>Array</code> 构造函数,用作参数得函数能够返回给定索引得每个元素初始值:</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-comment">// 创建一个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]</span><span class="hljs-keyword">val</span> asc = Array(<span class="hljs-number">5</span>) { i -> (i * i).toString() }asc.forEach { println(it) }</code></pre></div><ul><li>Kotlin 也有无装箱开销的专门的类来表示原生类型数组,这些类与 <code>Array</code> 没有继承关系,但有同样的方法属性集,也有对用的工厂方法:</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> x: IntArray = intArrayOf(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<span class="hljs-comment">// 大小为 5、值为 [0, 0, 0, 0, 0] 的整型数组</span><span class="hljs-keyword">val</span> arr = IntArray(<span class="hljs-number">5</span>)<span class="hljs-comment">// 例如:用常量初始化数组中的值</span><span class="hljs-comment">// 大小为 5、值为 [42, 42, 42, 42, 42] 的整型数组</span><span class="hljs-keyword">val</span> arr = IntArray(<span class="hljs-number">5</span>) { <span class="hljs-number">42</span> }<span class="hljs-comment">// 例如:使用 lambda 表达式初始化数组中的值</span><span class="hljs-comment">// 大小为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值)</span><span class="hljs-keyword">var</span> arr = IntArray(<span class="hljs-number">5</span>) { it * <span class="hljs-number">1</span> }</code></pre></div><h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><ul><li>字符串是不可变的,可以使用索引运算符访问:<code>s[i]</code>。</li><li>原始字符串使用三个引号(<code>"""</code>)括起来,内部没有转义并且可以包括换行以及任何其他字符。</li><li>使用 <code>trimMargin()</code> 函数去除前导空格,默认 <code>|</code> 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 <code>trimMargin(">")</code>:</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> text = <span class="hljs-string">"""</span><span class="hljs-string"> |Tell me and I forget.</span><span class="hljs-string"> |Teach me and I remember.</span><span class="hljs-string"> |Involve me and I learn.</span><span class="hljs-string"> |(Benjamin Franklin)</span><span class="hljs-string"> """</span>.trimMargin()</code></pre></div><h2 id="返回和跳转"><a href="#返回和跳转" class="headerlink" title="返回和跳转"></a>返回和跳转</h2><p>在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 <code>@</code> 符号,例如:<code>abc@</code>、<code>fooBar@</code>都是有效的标签(参见语法)。 要为一个表达式加标签,我们只要在其前加标签即可。</p>]]></content>
<categories>
<category>LANGUAGE</category>
<category>Kotlin</category>
</categories>
<tags>
<tag>Kotlin基础</tag>
</tags>
</entry>
<entry>
<title>Kotlin的学习(一)——开始</title>
<link href="/2020/07/02/Kotlin%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/2020/07/02/Kotlin%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<p>Keywords: val、var、null、when、data class、lazy、目录结构、源文件名称、源文件组织、类布局、命名规则、代码格式、until</p><a id="more"></a><p class="note note-primary">其实在前年还是什么时候,就已经把菜鸟教程里的 <a href="https://www.runoob.com/kotlin/kotlin-tutorial.html" target="_blank" rel="noopener">Kotlin</a> 语言教程给看了一遍,结果由于疏于使用,忘了个一干二净。去年寒假学习安卓的时候,又大致把 kotlin 看了一遍。为了跟敲郭霖大神的开源项目趣图,今天打算再把 <a href="https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html" target="_blank" rel="noopener">Kotlin</a> 过一遍,顺便做个记录。</p><h2 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h2><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>只读局部变量使用关键字 <code>val</code> 定义,只能为其赋值一次。<br>可重复赋值的变量使用 <code>var</code> 关键字</p><h3 id="字符串模板"><a href="#字符串模板" class="headerlink" title="字符串模板"></a>字符串模板</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span><span class="hljs-comment">// 模板中的简单名称:</span><span class="hljs-keyword">val</span> s1 = <span class="hljs-string">"a is <span class="hljs-variable">$a</span>"</span>a = <span class="hljs-number">2</span><span class="hljs-comment">// 模板中的任意表达式:</span><span class="hljs-keyword">val</span> s2 = <span class="hljs-string">"<span class="hljs-subst">${s1.replace(<span class="hljs-string">"is"</span>, <span class="hljs-string">"was"</span>)}</span>, but now is <span class="hljs-variable">$a</span>"</span></code></pre></div><h3 id="空值与-null-检测"><a href="#空值与-null-检测" class="headerlink" title="空值与 null 检测"></a>空值与 null 检测</h3><p>当某个变量的值可以为 null 的时候,必须在声明处的类型后添加 <code>?</code> 来标识该引用可为空。</p><h4 id="If-not-null-缩写"><a href="#If-not-null-缩写" class="headerlink" title="If not null 缩写"></a>If not null 缩写</h4><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> files = File(<span class="hljs-string">"Test"</span>).listFiles()println(files?.size)</code></pre></div><h4 id="If-not-null-and-else-缩写"><a href="#If-not-null-and-else-缩写" class="headerlink" title="If not null and else 缩写"></a>If not null and else 缩写</h4><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> files = File(<span class="hljs-string">"Test"</span>).listFiles()println(files?.size ?: <span class="hljs-string">"empty"</span>)</code></pre></div><h4 id="if-null-执行一个语句"><a href="#if-null-执行一个语句" class="headerlink" title="if null 执行一个语句"></a>if null 执行一个语句</h4><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> values = ……<span class="hljs-keyword">val</span> email = values[<span class="hljs-string">"email"</span>] ?: <span class="hljs-keyword">throw</span> IllegalStateException(<span class="hljs-string">"Email is missing!"</span>)</code></pre></div><h4 id="在可能会空的集合中取第一元素"><a href="#在可能会空的集合中取第一元素" class="headerlink" title="在可能会空的集合中取第一元素"></a>在可能会空的集合中取第一元素</h4><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> emails = …… <span class="hljs-comment">// 可能会是空集合</span><span class="hljs-keyword">val</span> mainEmail = emails.firstOrNull() ?: <span class="hljs-string">""</span></code></pre></div><h4 id="if-not-null-执行代码"><a href="#if-not-null-执行代码" class="headerlink" title="if not null 执行代码"></a>if not null 执行代码</h4><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> value = ……value?.let { …… <span class="hljs-comment">// 代码会执行到此处, 假如data不为null</span>}</code></pre></div><h4 id="映射可空值(如果非空的话)"><a href="#映射可空值(如果非空的话)" class="headerlink" title="映射可空值(如果非空的话)"></a>映射可空值(如果非空的话)</h4><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> value = ……<span class="hljs-keyword">val</span> mapped = value?.let { transformValue(it) } ?: defaultValue<span class="hljs-comment">// 如果该值或其转换结果为空,那么返回 defaultValue。</span></code></pre></div><h3 id="类型检测与自动类型转换"><a href="#类型检测与自动类型转换" class="headerlink" title="类型检测与自动类型转换"></a>类型检测与自动类型转换</h3><p>is 运算符检测一个表达式是否某类型的一个实例。 如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换: </p><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getStringLength</span><span class="hljs-params">(obj: <span class="hljs-type">Any</span>)</span></span>: <span class="hljs-built_in">Int</span>? { <span class="hljs-comment">// `obj` 在 `&&` 右边自动转换成 `String` 类型</span> <span class="hljs-keyword">if</span> (obj <span class="hljs-keyword">is</span> String && obj.length > <span class="hljs-number">0</span>) { <span class="hljs-keyword">return</span> obj.length } <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>}</code></pre></div><h3 id="when-表达式-java-switch"><a href="#when-表达式-java-switch" class="headerlink" title="when 表达式(java switch)"></a>when 表达式(java switch)</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">describe</span><span class="hljs-params">(obj: <span class="hljs-type">Any</span>)</span></span>: String = <span class="hljs-keyword">when</span> (obj) { <span class="hljs-number">1</span> -> <span class="hljs-string">"One"</span> <span class="hljs-string">"Hello"</span> -> <span class="hljs-string">"Greeting"</span> <span class="hljs-keyword">is</span> <span class="hljs-built_in">Long</span> -> <span class="hljs-string">"Long"</span> !<span class="hljs-keyword">is</span> String -> <span class="hljs-string">"Not a string"</span> <span class="hljs-keyword">else</span> -> <span class="hljs-string">"Unknown"</span> }</code></pre></div><h3 id="使用区间(range)"><a href="#使用区间(range)" class="headerlink" title="使用区间(range)"></a>使用区间(range)</h3><ul><li>in</li><li>!in</li><li>.. step downTo</li></ul><h3 id="集合"><a href="#集合" class="headerlink" title="集合"></a>集合</h3><p>使用 lambda 表达式来过滤(filter)与映射(map)集合:</p><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> fruits = listOf(<span class="hljs-string">"banana"</span>, <span class="hljs-string">"avocado"</span>, <span class="hljs-string">"apple"</span>, <span class="hljs-string">"kiwifruit"</span>)fruits .filter { it.startsWith(<span class="hljs-string">"a"</span>) } .sortedBy { it } .map { it.toUpperCase() } .forEach { println(it) }</code></pre></div><h2 id="习惯用法"><a href="#习惯用法" class="headerlink" title="习惯用法"></a>习惯用法</h2><h3 id="创建-DTOs(POJOs-POCOs)"><a href="#创建-DTOs(POJOs-POCOs)" class="headerlink" title="创建 DTOs(POJOs/POCOs)"></a>创建 DTOs(POJOs/POCOs)</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Customer</span></span>(<span class="hljs-keyword">val</span> name: String, <span class="hljs-keyword">val</span> email: String)</code></pre></div><p>会为 <code>Customer</code> 类提供一下功能: </p><ul><li>所有属性的 getters(对于 var 定义的还有 setters)</li><li><code>equals()</code></li><li><code>hasCode()</code></li><li><code>toString()</code></li><li><code>copy()</code></li><li>所有属性的 <code>component1()</code>、<code>component2()</code>……等等</li></ul><h3 id="过滤-list"><a href="#过滤-list" class="headerlink" title="过滤 list"></a>过滤 list</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> positives = list.filter { x -> x > <span class="hljs-number">0</span> }</code></pre></div><p>or</p><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> positives = list.filter { it > <span class="hljs-number">0</span> }</code></pre></div><h3 id="延迟属性"><a href="#延迟属性" class="headerlink" title="延迟属性"></a>延迟属性</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> p: String <span class="hljs-keyword">by</span> lazy { <span class="hljs-comment">// 计算该字符串</span>}</code></pre></div><h3 id="创建单例"><a href="#创建单例" class="headerlink" title="创建单例"></a>创建单例</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">object</span> Resource { <span class="hljs-keyword">val</span> name = <span class="hljs-string">"Name"</span>}</code></pre></div><h3 id="对一个对象实例调用多个方法-(with)"><a href="#对一个对象实例调用多个方法-(with)" class="headerlink" title="对一个对象实例调用多个方法 (with)"></a>对一个对象实例调用多个方法 (with)</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Turtle</span> </span>{ <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">penDown</span><span class="hljs-params">()</span></span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">penUp</span><span class="hljs-params">()</span></span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">turn</span><span class="hljs-params">(degrees: <span class="hljs-type">Double</span>)</span></span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">forward</span><span class="hljs-params">(pixels: <span class="hljs-type">Double</span>)</span></span>}<span class="hljs-keyword">val</span> myTurtle = Turtle()with(myTurtle) { <span class="hljs-comment">// 画一个 100 像素的正方形</span> penDown() <span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>..<span class="hljs-number">4</span>) { forward(<span class="hljs-number">100.0</span>) turn(<span class="hljs-number">90.0</span>) } penUp()}</code></pre></div><h3 id="配置对象的属性(apply)"><a href="#配置对象的属性(apply)" class="headerlink" title="配置对象的属性(apply)"></a>配置对象的属性(apply)</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">val</span> myRectangle = Rectangle().apply { length = <span class="hljs-number">4</span> breadth = <span class="hljs-number">5</span> color = <span class="hljs-number">0xFAFAFA</span>}</code></pre></div><p>这对于配置未出现在对象构造函数中的属性非常有用。</p><h3 id="交换两个变量"><a href="#交换两个变量" class="headerlink" title="交换两个变量"></a>交换两个变量</h3><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span><span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>a = b.also { b = a }</code></pre></div><h3 id="TODO-:将代码标记为不完整"><a href="#TODO-:将代码标记为不完整" class="headerlink" title="TODO():将代码标记为不完整"></a>TODO():将代码标记为不完整</h3><p>Kotlin 的标准库有一个 <code>TODO()</code> 函数,该函数总是抛出一个 <code>NotImplementedError</code>。 其返回类型为 <code>Nothing</code>,因此无论预期类型是什么都可以使用它。 还有一个接受原因参数的重载:</p><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">calcTaxes</span><span class="hljs-params">()</span></span>: BigDecimal = TODO(<span class="hljs-string">"Waiting for feedback from accounting"</span>)</code></pre></div><p>IntelliJ IDEA 的 kotlin 插件理解 TODO() 的语言,并且会自动在 TODO 工具窗口中添加代码指示。</p><h2 id="编码规范"><a href="#编码规范" class="headerlink" title="编码规范"></a>编码规范</h2><h3 id="源代码组织"><a href="#源代码组织" class="headerlink" title="源代码组织"></a>源代码组织</h3><h4 id="源文件名称"><a href="#源文件名称" class="headerlink" title="源文件名称"></a>源文件名称</h4><p>如果 Kotlin 文件包含单个类(以及可能相关的顶层声明),那么文件名应该与该类的名称相同,并追加 .kt 扩展名。如果文件包含多个类或只包含顶层声明, 那么选择一个描述该文件所包含内容的名称,并以此命名该文件。 使用首字母大写的驼峰风格(例如 <code>ProcessDeclarations.kt</code>)。<br>文件的名称应该描述文件中代码的作用。因此,应避免在文件名中使用诸如“Util”之类的无意义词语。</p><h4 id="源文件组织"><a href="#源文件组织" class="headerlink" title="源文件组织"></a>源文件组织</h4><p>鼓励多个声明(类、顶级函数或者属性)放在同一个 Kotlin 源文件中, 只要这些声明在语义上彼此紧密关联并且文件保持合理大小 (不超过几百行)。 </p><p>特别是在为类定义与类的<strong>所有客户</strong>都相关的扩展函数时, 请将它们放在与<strong>类自身定义相同的地方</strong>。而在定义仅对<strong>指定客户</strong>有意义的扩展函数时,请将它们放在<strong>紧挨该客户代码之后</strong>。不要只是为了保存 “Foo 的所有扩展函数”而创建文件。</p><h4 id="类布局"><a href="#类布局" class="headerlink" title="类布局"></a>类布局</h4><p>通常,一个类的内容按以下顺序排列:</p><ul><li>属性声明与初始化块 </li><li>次构造函数 </li><li>方法声明 </li><li>伴生对象</li></ul><p>不要按字母顺序或者可见性对方法声明排序,也不要将常规方法与扩展方法分开。而是要把相关的东西放在一起,这样从上到下阅读类的人就能够跟进所发生事情的逻辑。选择一个顺序(高级别优先,或者相反)并坚持下去。 </p><p><strong>将嵌套类放在紧挨使用这些类的代码之后。如果打算在外部使用嵌套类,而且类中并没有引用这些类,那么把它们放到末尾,在伴生对象之后。</strong> </p><h4 id="接口实现布局"><a href="#接口实现布局" class="headerlink" title="接口实现布局"></a>接口实现布局</h4><p>在实现一个接口时,实现成员的顺序应该与该接口的成员顺序相同(如果需要, 还要插入用于实现的额外的私有方法)。</p><h4 id="重载布局"><a href="#重载布局" class="headerlink" title="重载布局"></a>重载布局</h4><p>在类中总是将重载放在一起。</p><h3 id="命名规则"><a href="#命名规则" class="headerlink" title="命名规则"></a>命名规则</h3><ul><li>包的名称总是小写且不使用下划线。如不必要,不要使用连词,如果确实需要使用多个词,可以将它们连接在一起或使用驼峰风格。 </li><li>类与对象的名称使用 Pascal 风格。</li></ul><h4 id="函数名"><a href="#函数名" class="headerlink" title="函数名"></a>函数名</h4><ul><li>函数、属性与局部变量的命名使用驼峰风格。(例外:用于创建类实例的工厂函数可以与抽象返回类型具有相同的名称。)</li><li><strong>当且仅当</strong>在测试中,可以使用反引号括起来的带空格的方法名。(Android 运行时暂不支持。)测试代码中也允许方法名使用下划线。</li></ul><div class="hljs"><pre><code class="hljs Kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyTestCase</span> </span>{ <span class="hljs-meta">@Test</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> `ensure everything works`<span class="hljs-params">()</span></span> { <span class="hljs-comment">/*...*/</span> } <span class="hljs-meta">@Test</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ensureEverythingWorks_onAndroid</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">/*...*/</span> }}</code></pre></div><h4 id="属性名"><a href="#属性名" class="headerlink" title="属性名"></a>属性名</h4><ul><li>常量名称(标有 <code>const</code> 属性,或者保存不可变数据的没有自定义 <code>get</code> 函数的顶层/对象 <code>val</code> 属性)应该使用大写、下划线分隔的名称。</li><li>保存带有行为的对象或者可变数据的顶层/对象属性的名称应该使用驼峰风格名称。</li><li>保存<strong>单例对象</strong>引用的属性的名称可以使用与 <code>object</code> 声明相同的命名风格。</li><li>对于枚举常量,可以使用大写、下划线分隔的名称 <code>(enum class Color { RED, GREEN })</code>也可使用首字母大写的常规驼峰名称,具体取决于用途。</li><li><strong>幕后属性的名称</strong> 如果一个类有两个概念上相同的属性,一个是公共 API 的一部分,另一个是实现细节,那么使用下划线作为私有属性名称的前缀。</li></ul><h4 id="选择好名称"><a href="#选择好名称" class="headerlink" title="选择好名称"></a>选择好名称</h4><ul><li>类的名称通常是用来解释类是什么的名词或者名词短语:<code>List</code>、<code>PersonReader</code>。</li><li>方法的名称通常是动词或动词短语,说明该方法做什么:<code>close</code>、<code>readPersons</code>。修改对象或者返回一个新对象的名称也应遵循建议。例如<code>sort</code> 是对一个集合就地排序,而 <code>sorted</code> 是返回一个排序后的集合副本。</li><li>名称应该表明实体的目的是什么,所以最好避免在名称中使用无意义的单词(<code>Manager</code>、<code>Wrapper</code> 等)。</li><li>当使用首字母缩写作为名称的一部分时,如果缩写由两个字母组成,就将其大写(<code>IOStream</code>);而如果缩写更长一些,就只大写其首字母(<code>XmlFormatter</code>、<code>HttpInputStream</code>)。</li></ul><h3 id="格式化"><a href="#格式化" class="headerlink" title="格式化"></a>格式化</h3><ul><li><strong>使用 4 个空格缩进。不要使用tab。</strong></li><li>对于花括号,将左花括号放在结构起始处的行尾,而将右花括号放在与左括结构横向对齐的单独一行。</li></ul><h4 id="横向空白"><a href="#横向空白" class="headerlink" title="横向空白"></a>横向空白</h4><ul><li>在二元操作符左右留空格(<code>a + b</code>)。例外:不要在“range to”操作符(<code>0..i</code>)左右留空格。</li><li>不要在一元运算符左右留空格(<code>a++</code>)</li><li>在控制流关键字(<code>if</code>、 <code>when</code>、 <code>for</code> 以及 <code>while</code>)与相应的左括号之间留空格。</li><li>不要在主构造函数声明、方法声明或者方法调用的左括号之前留空格。</li><li><strong>绝不在 <code>(</code>、 <code>[</code> 之后或者 <code>]</code>、 <code>)</code> 之前留空格。</strong></li><li>绝不在 <code>.</code> 或者 <code>?.</code> 左右留空格:<code>foo.bar().filter { it > 2 }.joinToString(), foo?.bar()</code></li><li>在 <code>//</code> 之后留一个空格:<code>// 这是一条注释</code></li><li>不要在用于指定类型参数的尖括号前后留空格: <code>class Map<K, V> { …… }</code></li><li>不要在 :: 前后留空格:<code>Foo::class</code>、 <code>String::length</code></li><li>不要在用于标记可空类型的 ? 前留空格:<code>String?</code></li><li><strong>作为一般规则,避免任何类型的水平对齐。将标识符重命名为不同长度的名称不应该影响声明或者任何用法的格式。</strong></li></ul><h4 id="冒号"><a href="#冒号" class="headerlink" title="冒号"></a>冒号</h4><p>在以下场景中的 <code>:</code> 之前留一个空格:</p><ul><li>当它用于分隔类型与超类型时;</li><li>当委托给一个超类的构造函数或者同一类的另一个构造函数时;</li><li>在 <code>object</code> 关键字之后。</li></ul><p>而当分隔声明与其类型时,不要在 : 之前留空格。</p><h4 id="类头格式化"><a href="#类头格式化" class="headerlink" title="类头格式化"></a>类头格式化</h4><p>具有少数主构造函数参数的类可以写成一行:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span></span>(id: <span class="hljs-built_in">Int</span>, name: String)</code></pre></div><p>具有较长类头的类应该格式化,以使每个主构造函数参数都在带有缩进的独立的行中。 另外,右括号应该位于一个新行上。如果使用了继承,那么超类的构造函数调用或者所实现接口的列表应该与右括号位于同一行:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span></span>( id: <span class="hljs-built_in">Int</span>, name: String, surname: String) : Human(id, name) { <span class="hljs-comment">/*……*/</span> }</code></pre></div><p>对于多个接口,应该将超类构造函数调用放在首位,然后将每个接口应放在不同的行中:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span></span>( id: <span class="hljs-built_in">Int</span>, name: String, surname: String) : Human(id, name), KotlinMaker { <span class="hljs-comment">/*……*/</span> }</code></pre></div><p>对于具有很长超类型列表的类,在冒号后面换行,并横向对齐所有超类型名:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyFavouriteVeryLongClassHolder</span> :<span class="hljs-type"></span></span> MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">/*...*/</span> }}</code></pre></div><p>为了将类头与类体分隔清楚,当类头很长时,可以在类头后放一空行 (如上例所示)或者将左花括号放在独立行上:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyFavouriteVeryLongClassHolder</span> :<span class="hljs-type"></span></span> MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne{ <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">/*...*/</span> }}</code></pre></div><h4 id="修饰符"><a href="#修饰符" class="headerlink" title="修饰符"></a>修饰符</h4><p>如果一个声明有多个修饰符,按照以下顺序安放:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">public</span> / <span class="hljs-keyword">protected</span> / <span class="hljs-keyword">private</span> / <span class="hljs-keyword">internal</span><span class="hljs-keyword">expect</span> / <span class="hljs-keyword">actual</span><span class="hljs-keyword">final</span> / <span class="hljs-keyword">open</span> / <span class="hljs-keyword">abstract</span> / <span class="hljs-keyword">sealed</span> / <span class="hljs-keyword">const</span><span class="hljs-keyword">external</span><span class="hljs-keyword">override</span><span class="hljs-keyword">lateinit</span><span class="hljs-keyword">tailrec</span><span class="hljs-keyword">vararg</span><span class="hljs-keyword">suspend</span><span class="hljs-keyword">inner</span><span class="hljs-keyword">enum</span> / <span class="hljs-keyword">annotation</span><span class="hljs-keyword">companion</span><span class="hljs-keyword">inline</span><span class="hljs-keyword">infix</span><span class="hljs-keyword">operator</span><span class="hljs-keyword">data</span></code></pre></div><p>将所有注解放在修饰符前:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-meta">@Named(<span class="hljs-meta-string">"Foo"</span>)</span><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> foo: Foo</code></pre></div><p>尽量省略多余的修饰符(例如 <code>public</code> )。</p><h4 id="注解格式化"><a href="#注解格式化" class="headerlink" title="注解格式化"></a>注解格式化</h4><ul><li>注解通常放在单独的行上,在它们所依附的声明之前,并使用相同的缩进。</li><li>无参数的注解可以放在同一行。</li><li>无参数的单个注解可以与相应的声明放在同一行。</li></ul><h4 id="文件注解"><a href="#文件注解" class="headerlink" title="文件注解"></a>文件注解</h4><p>文件注解位于文件注释(如果有的话)之后、<code>package</code> 语句之前,并且用一个空白行与 <code>package</code> 分开(为了强调其针对文件而不是包)。</p><h4 id="函数格式化"><a href="#函数格式化" class="headerlink" title="函数格式化"></a>函数格式化</h4><ul><li>如果函数签名不适合单行,请使用以下语法:</li></ul><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">longMethodName</span><span class="hljs-params">(</span></span><span class="hljs-function"><span class="hljs-params"> argument: <span class="hljs-type">ArgumentType</span> = defaultValue,</span></span><span class="hljs-function"><span class="hljs-params"> argument2: <span class="hljs-type">AnotherArgumentType</span></span></span><span class="hljs-function"><span class="hljs-params">)</span></span>: ReturnType { <span class="hljs-comment">// 函数体</span>}</code></pre></div><ul><li>函数参数使用常规缩进(四个空格)。</li><li>对于由单个表达式构成的函数体,优先使用表达式形式。</li></ul><h4 id="表达式函数体格式化"><a href="#表达式函数体格式化" class="headerlink" title="表达式函数体格式化"></a>表达式函数体格式化</h4><p>如果函数的表达式函数体与函数声明不适合放在同一行,那么将 <code>=</code> 留在第一行。 将表达式函数体缩进 4 个空格。</p><h4 id="属性格式化"><a href="#属性格式化" class="headerlink" title="属性格式化"></a>属性格式化</h4><ul><li>对于非常简单的只读属性,考虑单行格式。<code>val isEmpty: Boolean get() = size == 0</code></li><li>对于更复杂的属性,总是将 <code>get</code> 与 <code>set</code> 关键字放在不同的行上。</li><li>对于具有初始化器的属性,如果初始化器很长,那么在等号后增加一个换行并将初始化器缩进四个空格。</li></ul><h4 id="格式化控制流语句"><a href="#格式化控制流语句" class="headerlink" title="格式化控制流语句"></a>格式化控制流语句</h4><ul><li>如果 <code>if</code> 或 <code>when</code> 语句的条件有多行,那么在语句体外边总是使用大括号。 将该条件的每个后续行相对于条件语句起始处缩进 4 个空格。 将该条件的右圆括号与左花括号放在单独一行。</li><li>将 <code>else</code>、 <code>catch</code>、 <code>finally</code> 关键字以及 <code>do/while</code> 循环的 <code>while</code> 关键字与之前的花括号放在相同的行上。</li><li>在 <code>when</code> 语句中,如果一个分支不止一行,可以考虑用空行将其与相邻的分支块分开,将短分支放在与条件相同的行上,无需花括号。</li></ul><h4 id="方法调用格式化"><a href="#方法调用格式化" class="headerlink" title="方法调用格式化"></a>方法调用格式化</h4><p>在较长参数列表的左括号后添加一个换行符。按 4 个空格缩进参数。 将密切相关的多个参数分在同一行,在分隔参数名与值的 <code>=</code> 左右留空格。</p><h4 id="链式调用换行"><a href="#链式调用换行" class="headerlink" title="链式调用换行"></a>链式调用换行</h4><ul><li>当对链式调用换行时,将 <code>.</code> 字符或者 <code>?.</code> 操作符放在下一行,并带有单倍缩进。</li><li>调用链的第一个调用通常在换行之前,当然如果能让代码更有意义也可以忽略这点。</li></ul><h4 id="Lambda-表达式格式化"><a href="#Lambda-表达式格式化" class="headerlink" title="Lambda 表达式格式化"></a>Lambda 表达式格式化</h4><ul><li>在 lambda 表达式中,应该在花括号左右以及分隔参数与代码体的箭头左右留空格。 如果一个调用接受单个 lambda 表达式,应该尽可能将其放在圆括号外边传入。</li><li>如果为 lambda 表达式分配一个标签,那么不要在该标签与左花括号之间留空格。</li><li>在多行的 lambda 表达式中声明参数名时,将参数名放在第一行,后跟箭头与换行符。</li></ul><div class="hljs"><pre><code class="hljs kotlin">appendCommaSeparated(properties) { prop -> <span class="hljs-keyword">val</span> propertyValue = prop.<span class="hljs-keyword">get</span>(obj) <span class="hljs-comment">// ……</span>}</code></pre></div><ul><li>如果参数列表太长而无法放在一行上,请将箭头放在单独一行。</li></ul><h3 id="文档注释"><a href="#文档注释" class="headerlink" title="文档注释"></a>文档注释</h3><ul><li>对于较长的文档注释,将开头 <code>/**</code> 放在一个独立行中,并且后续每行都以性好开头。</li><li>简短注释可以放在一行内。</li><li>通常,避免使用 <code>@param</code> 与 <code>@return</code> 标记。而是将参数与返回值的描述直接合并到文档注释中,并在提到参数的任何地方加上参数链接。 只有当需要不适合放进主文本流程的冗长描述时才应使用 <code>@param</code> 与 <code>@return</code>。</li></ul><h3 id="避免重复结构"><a href="#避免重复结构" class="headerlink" title="避免重复结构"></a>避免重复结构</h3><p>一般来说,如果 Kotlin 中的某种语法结构是可选的并且被 IDE 高亮为冗余的,那么应该在代码中省略之。为了清楚起见,不要在代码中保留不必要的语法元素 。</p><ul><li>将简单变量传入到字符串模版中时不要使用花括号。只有用到更长表达式时才使用花括号。</li></ul><h3 id="语言特性的惯用法"><a href="#语言特性的惯用法" class="headerlink" title="语言特性的惯用法"></a>语言特性的惯用法</h3><h4 id="不可变性"><a href="#不可变性" class="headerlink" title="不可变性"></a>不可变性</h4><ul><li>优先使用不可变(而不是可变)数据。初始化后未修改的局部变量与属性,总是将其声明为 <code>val</code> 而不是 <code>var</code> 。</li><li>总是使用不可变集合接口(<code>Collection</code>, <code>List</code>, <code>Set</code>, <code>Map</code>)来声明无需改变的集合。使用工厂函数创建集合实例时,尽可能选用返回不可变集合类型的函数。</li></ul><h4 id="默认参数值"><a href="#默认参数值" class="headerlink" title="默认参数值"></a>默认参数值</h4><p>优先声明带有默认参数的函数而不是声明重载函数。</p><h4 id="类型别名"><a href="#类型别名" class="headerlink" title="类型别名"></a>类型别名</h4><p>如果有一个在代码库中多次用到的函数类型或者带有类型参数的类型,那么最好为它定义一个类型别名:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">typealias</span> MouseClickHandler = (Any, MouseEvent) -> <span class="hljs-built_in">Unit</span><span class="hljs-keyword">typealias</span> PersonIndex = Map<String, Person></code></pre></div><h4 id="Lambda-表达式"><a href="#Lambda-表达式" class="headerlink" title="Lambda 表达式"></a>Lambda 表达式</h4><p>在简短、非嵌套的 lambda 表达式中建议使用 <code>it</code> 用法而不是显式声明参数。而在有参数的嵌套 lambda 表达式中,始终应该显式声明参数。</p><h4 id="在-lambda-表达式中返回"><a href="#在-lambda-表达式中返回" class="headerlink" title="在 lambda 表达式中返回"></a>在 lambda 表达式中返回</h4><p>避免在 lambda 表达式中使用多个返回到标签。请考虑重新组织这样的 lambda 表达式使其只有单一退出点。 如果这无法做到或者不够清晰,请考虑将 lambda 表达式转换为匿名函数。</p><p>不要在 lambda 表达式的最后一条语句中使用返回到标签。</p><h4 id="具名参数"><a href="#具名参数" class="headerlink" title="具名参数"></a>具名参数</h4><p>当一个方法接受多个相同的原生类型参数或者多个 Boolean 类型参数时,请使用具名参数语法, 除非在上下文中的所有参数的含义都已绝对清楚。</p><h4 id="使用条件语句"><a href="#使用条件语句" class="headerlink" title="使用条件语句"></a>使用条件语句</h4><p>优先使用 <code>try</code>、<code>if</code> 与 <code>when</code> 的表达形式。例如:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (x) foo() <span class="hljs-keyword">else</span> bar()<span class="hljs-keyword">return</span> <span class="hljs-keyword">when</span>(x) { <span class="hljs-number">0</span> -> <span class="hljs-string">"zero"</span> <span class="hljs-keyword">else</span> -> <span class="hljs-string">"nonzero"</span>}</code></pre></div><h4 id="if-还是-when"><a href="#if-还是-when" class="headerlink" title="if 还是 when"></a>if 还是 when</h4><p>二元条件优先使用 <code>if</code> 而不是 <code>when</code>。<br>如果有三个或多个选项时优先使用 <code>when</code>。</p><h4 id="在条件中使用可空的-Boolean-值"><a href="#在条件中使用可空的-Boolean-值" class="headerlink" title="在条件中使用可空的 Boolean 值"></a>在条件中使用可空的 Boolean 值</h4><p>如果需要在条件语句中用到可空的 <code>Boolean</code>, 使用 <code>if (value == true)</code> 或 <code>if (value == false)</code> 检测。</p><h4 id="使用循环"><a href="#使用循环" class="headerlink" title="使用循环"></a>使用循环</h4><p>优先使用高阶函数(<code>filter</code>、<code>map</code> 等)而不是循环。例外:<code>forEach</code>(优先使用常规的 <code>for</code> 循环, 除非 <code>forEach</code> 的接收者是可空的或者 <code>forEach</code> 用做长调用链的一部分。)</p><p>当在使用多个高阶函数的复杂表达式与循环之间进行选择时,请了解每种情况下所执行操作的开销并且记得考虑性能因素。</p><h4 id="区间上循环"><a href="#区间上循环" class="headerlink" title="区间上循环"></a>区间上循环</h4><p>使用 <code>until</code> 函数在一个开区间上循环:</p><div class="hljs"><pre><code class="hljs kotlin"><span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..n - <span class="hljs-number">1</span>) { <span class="hljs-comment">/*……*/</span> } <span class="hljs-comment">// 不良</span><span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span> until n) { <span class="hljs-comment">/*……*/</span> } <span class="hljs-comment">// 良好</span></code></pre></div><h4 id="使用字符串"><a href="#使用字符串" class="headerlink" title="使用字符串"></a>使用字符串</h4><ul><li>优先使用字符串模板而不是字符串拼接。</li><li>优先使用多行字符串而不是将 <code>\n</code> 转义序列嵌入到常规字符串字面值中。</li><li>如需在多行字符串中维护缩进,当生成的字符串不需要任何内部缩进时使用 <code>trimIndent</code>,而需要内部缩进时使用 <code>trimMargin</code>。</li></ul>]]></content>
<categories>
<category>LANGUAGE</category>
<category>Kotlin</category>
</categories>
<tags>
<tag>Kotlin基础</tag>
</tags>
</entry>
<entry>
<title>进程和线程</title>
<link href="/2020/07/02/%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B/"/>
<url>/2020/07/02/%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B/</url>
<content type="html"><![CDATA[<p>当应用组件启动且该应用未运行任何其他组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件会在相同的进程和线程(称为“主”线程)中运行。如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。但是,可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。</p><a id="more"></a><h3 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h3><p>各类组件元素(<code><activity></code>, <code><service></code>, <code><receiver></code>, <code><povider></code>)的清单文件条目均支持 <code>android:process</code> 属性,该属性可指定该组件应在哪个进程中运行。</p><p><code><application></code> 元素也支持 <code>android:process</code> 属性,用来设置适用于所有组件的默认值。</p><h3 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h3><h4 id="工作线程"><a href="#工作线程" class="headerlink" title="工作线程"></a>工作线程</h4><p>从其他线程访问界面线程的方法:</p><ul><li><code>Activity.runOnUiThread(Runnable)</code></li><li><code>View.post(Runnable)</code></li><li><code>View.postDealyed(Runnable, long)</code></li></ul><p>上述方法会增加后期维护难度,如果要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 <code>Handler</code> 处理来自界面线程的消息。当然最后的解决方案或许是扩展 <code>AsyncTask</code> 类,此类可简化与界面进行交互所需执行的工作线程任务。</p><h3 id="如何避免应用无响应-ANR"><a href="#如何避免应用无响应-ANR" class="headerlink" title="如何避免应用无响应(ANR)"></a>如何避免应用无响应(ANR)</h3><p>为耗时较长的操作创建工作线程的最有效方法是使用 AsyncTask 类。只需扩展 AsyncTask 并实现 doInBackground() 方法即可执行相应操作。要向用户发布进度变化,可以调用 publishProgress(),它会调用 onProgressUpdate() 回调方法。通过 onProgressUpdate()(在界面线程中运行)的实现,您可以向用户发送通知。例如:</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DownloadFilesTask</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AsyncTask</span><<span class="hljs-title">URL</span>, <span class="hljs-title">Integer</span>, <span class="hljs-title">Long</span>> </span>{ <span class="hljs-comment">// Do the long-running work in here</span> <span class="hljs-function"><span class="hljs-keyword">protected</span> Long <span class="hljs-title">doInBackground</span><span class="hljs-params">(URL... urls)</span> </span>{ <span class="hljs-keyword">int</span> count = urls.length; <span class="hljs-keyword">long</span> totalSize = <span class="hljs-number">0</span>; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((<span class="hljs-keyword">int</span>) ((i / (<span class="hljs-keyword">float</span>) count) * <span class="hljs-number">100</span>)); <span class="hljs-comment">// Escape early if cancel() is called</span> <span class="hljs-keyword">if</span> (isCancelled()) <span class="hljs-keyword">break</span>; } <span class="hljs-keyword">return</span> totalSize; } <span class="hljs-comment">// This is called each time you call publishProgress()</span> <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onProgressUpdate</span><span class="hljs-params">(Integer... progress)</span> </span>{ setProgressPercent(progress[<span class="hljs-number">0</span>]); } <span class="hljs-comment">// This is called when doInBackground() is finished</span> <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPostExecute</span><span class="hljs-params">(Long result)</span> </span>{ showNotification(<span class="hljs-string">"Downloaded "</span> + result + <span class="hljs-string">" bytes"</span>); }}</code></pre></div><p>要执行此工作线程,只需创建一个实例并调用 execute() 即可:<br><code>new DownloadFilesTask().execute(url1, url2, url3);</code></p>]]></content>
<categories>
<category>安卓</category>
<category>进程</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>Intent</title>
<link href="/2020/07/02/Intent/"/>
<url>/2020/07/02/Intent/</url>
<content type="html"><![CDATA[<p><a href="https://developer.android.google.cn/guide/components/intents-filters?hl=zh_cn" target="_blank" rel="noopener"><code>Intent</code></a> 是一个消息传递对象,可以通过多种方式促进组件之间的通信。</p><a id="more"></a><p>其基本用例主要包括以下三个:</p><ul><li>启动 Activity</li><li>启动服务</li><li>传递广播</li></ul><p>Keyword: 闹钟, 日历, 相机, 联系人, 文件存储, 地图, 音乐和视频, 新笔记, 电话, 设置发送短信</p><h2 id="Intent和Intent过滤器"><a href="#Intent和Intent过滤器" class="headerlink" title="Intent和Intent过滤器"></a>Intent和Intent过滤器</h2><blockquote><p>要仅设置数据 URI,请调用 <code>setData()</code>。要仅设置 MIME 类型,请调用 <code>setType()</code>。如有必要,可以使用 <code>setDataAndType()</code> 同时显式设置二者。若要同时设置 URI 和 MIME 类型,请勿调用 <code>setData()</code> 和 <code>setType()</code>,因为它们会互相抵消彼此的值。请始终使用 <code>setDataAndType()</code> 同时设置 URI 和 MIME 类型。<br>要接收隐式 Intent,必须将 CATEGORY_DEFAULT 类别包括在 Intent 过滤器中。方法 startActivity() 和 startActivityForResult() 将按照其声明 CATEGORY_DEFAULT 类别的方式处理所有 Intent。如果未在 Intent 过滤器中声明此类别,则隐式 Intent 不会解析为您的 Activity。 </p></blockquote><h3 id="Intent-解析"><a href="#Intent-解析" class="headerlink" title="Intent 解析"></a>Intent 解析</h3><h4 id="操作测试"><a href="#操作测试" class="headerlink" title="操作测试"></a>操作测试</h4><p>要指定接受的 Intent 操作,Intent 过滤器既可以不声明任何 <code><action></code> 元素,也可以声明多个此类元素。要通过此过滤器,在 Intent 中指定的操作必须与过滤器中列出的某一操作匹配。</p><blockquote><p>如果该过滤器未列出任何操作,则 Intent 没有任何匹配项,因此所有 Intent 均无法通过测试。但是,如果 Intent 未指定操作,则只要过滤器内包含至少一项操作,就可以通过测试。</p></blockquote><h4 id="类别测试"><a href="#类别测试" class="headerlink" title="类别测试"></a>类别测试</h4><p>要指定接受的 Intent 类别,Intent 过滤器既可以不声明任何 <code><category></code> 元素,也可以声明多个此类元素。若要使 Intent 通过类别测试,则 Intent 中的<strong>每个</strong>类别均必须与过滤器中的类别匹配。反之则未必然,Intent 过滤器声明的类别可以超出 Intent 中指定的数量,且 Intent 仍会通过测试。因此,不含类别的 Intent 应当始终会通过此测试,无论过滤器中声明何种类别均是如此。</p><h4 id="数据测试"><a href="#数据测试" class="headerlink" title="数据测试"></a>数据测试</h4><p>要指定接受的 Intent 数据,Intent 过滤器既可以不声明任何 <code><data></code> 元素,也可以声明多个此类元素。每个 <code><data></code> 元素均可指定 URI 结构和数据类型(MIME 媒体类型)。URI 的每个部分都是一个单独的属性:<code>scheme</code>、<code>host</code>、<code>port</code> 和 <code>path</code>:<code><scheme>://<host>:<port>/<path></code>。<br>在 <code><data></code> 元素中,上述每个属性均为可选,但存在线性依赖关系:</p><ul><li>如果未指定架构,则会忽略主机。</li><li>如果未指定主机,则会忽略端口。</li><li>如果未指定架构和主机,则会忽略路径。 </li></ul><p>数据测试会将 Intent 中的 URI 和 MIME 类型与过滤器中指定的 URI 和 MIME 类型进行比较。规则如下:</p><ol><li>仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 Intent 才会通过测试。</li><li>对于包含 URI 但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 Intent,仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。</li><li>仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型但不含 URI 的 Intent 才会通过测试。</li><li>仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 URI 类型和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。如果 Intent 的 URI 与过滤器中的 URI 匹配,或者如果 Intent 具有 <code>content:</code> 或 <code>file:</code> URI 且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。换言之,如果过滤器只是列出 MIME 类型,则假定组件支持 <code>content:</code> 和 <code>file:</code> 数据。</li></ol><blockquote><p>如果 Intent 指定 URI 或 MIME 类型,则数据测试会在 <code><intent-filter></code> 中没有 <code><data></code> 元素时失败。</p></blockquote><h2 id="通用Intent"><a href="#通用Intent" class="headerlink" title="通用Intent"></a>通用Intent</h2><ul><li><a href="https://developer.android.com/guide/components/intents-common#Clock" target="_blank" rel="noopener">闹钟——创建闹铃、创建定时器、显示所有闹铃</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Calendar" target="_blank" rel="noopener">日历——添加日历事件</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Camera" target="_blank" rel="noopener">相机——拍摄相机或视频并将其返回、以静态图像模式启动相机应用、以视频模式启动相机应用</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Contacts" target="_blank" rel="noopener">联系人/人员应用——选择联系人、选择特定联系人数据、查看联系人、编辑现有联系人、插入联系人</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Email" target="_blank" rel="noopener">电子邮件——撰写带有可选附件的电子邮件</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Storage" target="_blank" rel="noopener">文件存储——检索、打开特定类型的文件</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Local" target="_blank" rel="noopener">本地操作-叫车?</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Maps" target="_blank" rel="noopener">地图——显示地图上的位置</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Music" target="_blank" rel="noopener">音乐和视频——播放媒体文件、基于搜索查询播放音乐</a></li><li><a href="https://developer.android.com/guide/components/intents-common#NewNote" target="_blank" rel="noopener">新笔记——创建笔记</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Phone" target="_blank" rel="noopener">电话——发起通话</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Search" target="_blank" rel="noopener">搜索——使用特定应用搜索、执行网页搜索</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Settings" target="_blank" rel="noopener">设置——打开特定设置部分</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Messaging" target="_blank" rel="noopener">发送短信</a></li><li><a href="https://developer.android.com/guide/components/intents-common#Browser" target="_blank" rel="noopener">网络浏览器——加载网址</a></li></ul>]]></content>
<categories>
<category>安卓</category>
<category>组件</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>Activity与一丢丢Fragment</title>
<link href="/2020/07/02/Activity%E4%B8%8E%E4%B8%80%E4%B8%A2%E4%B8%A2Fragment/"/>
<url>/2020/07/02/Activity%E4%B8%8E%E4%B8%80%E4%B8%A2%E4%B8%A2Fragment/</url>
<content type="html"><![CDATA[<p><a href="https://developer.android.google.cn/guide/components/activities/intro-activities?hl=zh_cn" target="_blank" rel="noopener"><code>Activity</code></a> 类是 Android 应用的关键组件,提供窗口供应用在其中绘制界面。</p><a id="more"></a><h2 id="了解-Activity-生命周期"><a href="#了解-Activity-生命周期" class="headerlink" title="了解 Activity 生命周期"></a>了解 Activity 生命周期</h2><p><img src="/img/%E6%8F%92%E5%9B%BE/activity_lifecycle.png" srcset="/img/loading.gif" alt="Activity 生命周期的简化图示"></p><h3 id="onCreate"><a href="#onCreate" class="headerlink" title="onCreate()"></a>onCreate()</h3><p>系统首次创建 Activity 时触发,在此方法中,需执行基本应用启动逻辑(声明界面、定义成员变量、配置某些界面),该逻辑在 Activity 的整个生命周期中只应发生一次。可以在此方法中<strong>将数据绑定到列表、将 Activity 与 <a href="https://developer.android.com/reference/androidx/lifecycle/ViewModel" target="_blank" rel="noopener">ViewModel</a> 相关联、实例化某些类范围变量</strong>。</p><h3 id="onStart"><a href="#onStart" class="headerlink" title="onStart()"></a>onStart()</h3><p>调用此方法使 Activity 对用户可见,为 Activity 进入前台并支持交互做准备。例如,应用此方法来<strong>初始化维护界面的代码</strong>。Activity不会一直处于“已开始”状态,一旦此回调结束,Activity便会进入“已恢复”状态,系统将调用onResume()方法。</p><h3 id="onResume"><a href="#onResume" class="headerlink" title="onResume()"></a>onResume()</h3><p>应用与用户交互的状态,用户会一直保持这种状态,直到某些事件发生,让焦点远离应用。当发生中断事件时,Activity进入“已暂停”状态,系统调用 onPause() 回调。如果Activity从“已暂停”状态返回“已恢复”状态,系统将再次调用 onResume() 方法。</p><h3 id="onPause"><a href="#onPause" class="headerlink" title="onPause()"></a>onPause()</h3><p>系统将此方法视为用户正在离开 Activity 的第一个标志(尽管这并不总是意味着活动正在遭到销毁);此方法表示 Activity 不再位于前台(尽管如果用户处于多窗口模式,Activity 仍然可见)。使用 onPause() 方法暂时或调整当 Activity 处于“已暂停”状态时不应继续(或应有节制地继续)的操作,以及希望很快恢复的操作。Activity 进入此状态有多个原因,例如:</p><ul><li>如 onResume() 部分所述,某个事件会中断应用执行。这是最常见的情况。</li><li>在 Android7.0(API级别24)或更高版本中,有多个应用在多窗口模式下运行。无论何时,都只有一个应用(窗口)可以拥有焦点,因此系统会暂停所有其它应用。</li><li>有新的半透明 Activity(例如对话框)处于开启状态。只要 Activity 仍然部分可见但并未处于焦点之中,它便会一直暂停 </li></ul><p>还可以在此方法中<strong>释放系统资源、传感器(如GPS)手柄,或当 Activity 暂停且用户不需要它们时仍然可能影响电池续航时间的任何资源</strong>。<br>如果处于多窗口模式,则“已暂停”的 Activity 仍完全可见。因此,应考虑使用 onStop() 而非 onPause() 来完全释放或调整与界面相关的资源和操作,以便更好地支持多窗模式。<br>onPause() 执行非常简单,而且不一定要有足够的时间来执行保存操作。因此,<strong>不应该使用 onPause() 来保存应用或用户数据、进行网络调用或执行数据库事务。因为在该方法完成之前,此类工作可能无法完成。相反,应在 onStop() 期间执行高负载的关闭操作。</strong></p><h3 id="onStop"><a href="#onStop" class="headerlink" title="onStop()"></a>onStop()</h3><p>如果Activity对用户不可见,则说明其进入“已停止状态”,此时系统将会调用onStop()回调。在 onStop() 方法中,<strong>应用应释放或调整应用对用户不可见时的无用资源</strong>。例如,应用可以暂停动画效果,或从细粒度位置更新切换到粗粒度位置更新。<br>当 Activity 进入“已停止”状态时,Activity 对象会继续驻留在内存中:该对象将维护所有状态和成员信息,但不会附加到窗口管理器。状态恢复后,Activity 会重新调用这些信息。无需重新初始化在回调方法导致 Activity 进入“已恢复”状态期间创建的组件。<strong>系统还会追踪布局中每个View对象的当前状态</strong>,如果用户在 EditText 微件中输入文本,系统将保留文本内容,因此无需保存和恢复文本。</p><h3 id="onDestory"><a href="#onDestory" class="headerlink" title="onDestory()"></a>onDestory()</h3><p>销毁 Ativity 之前,系统会先调用 onDestroy()。系统调用此回调的原因如下:</p><ul><li>Activity 正在结束(由于用户彻底关闭 Activity 或由于系统为 Activity 调用 finish())。</li><li>由于配置变更(例如设备旋转或多窗口模式),系统暂时销毁 Activity。</li></ul><p>可以使用 <a href="https://developer.android.com/reference/android/app/Activity#isFinishing()" target="_blank" rel="noopener">isFinishing()</a> 方法区分这两种情况。<br>应使用 <a href="https://developer.android.com/reference/androidx/lifecycle/ViewModel" target="_blank" rel="noopener">ViewModel</a> 对象来容纳 Activity 的相关视图数据,如果由于配置变更而重新创建 Activity,则 ViewMode 不必执行任何操作,因为系统将保留 ViewModel 并将其提供给下一个 Activity 实例。如果不重新创建 Activity,ViewModel 将调用 <a href="https://developer.android.com/reference/androidx/lifecycle/ViewModel#onCleared()" target="_blank" rel="noopener">onCleared()</a> 方法,以便在 Activity 遭到销毁前清除所需的任何数据。onDestory() 回调前应释放先前的回调(如onStop())尚未释放的所有资源。</p><h2 id="Activity-状态和从内存中弹出"><a href="#Activity-状态和从内存中弹出" class="headerlink" title="Activity 状态和从内存中弹出"></a>Activity 状态和从内存中弹出</h2><table><thead><tr><th>系统终止进程的可能性</th><th>进程状态</th><th>Activity状态</th></tr></thead><tbody><tr><td>小</td><td>前台(拥有或即将获得焦点)</td><td>已创建 已开始 已恢复</td></tr><tr><td>中</td><td>后台(失去焦点)</td><td>已暂停</td></tr><tr><td>大</td><td>后台(不可见)</td><td>已停止</td></tr><tr><td></td><td>空</td><td>已销毁</td></tr><tr><td>表1.进程生命周期和Activity状态之间的关系</td><td></td><td></td></tr></tbody></table><h3 id="使用-onSaveInstanceState-保存简单轻量的界面状态"><a href="#使用-onSaveInstanceState-保存简单轻量的界面状态" class="headerlink" title="使用 onSaveInstanceState() 保存简单轻量的界面状态"></a>使用 onSaveInstanceState() 保存简单轻量的界面状态</h3><p>当 Activity 开始停止时,系统会调用 onSaveInstanceState() 方法,以便 Activity 可以将状态信息保存到实例状态 Bundle 中。此方法的默认实现保存有关 Activity 视图层次结构状态的瞬态信息,例如 EditText 微件中的文本或 ListView 微件的滚动位置。<br>如要保存持久化数据(例如用户首选项或数据库中的数据),应在 Activity 位于前台时抓住合适机会。如果没有这样的时机,应在执行 onStop() 方法期间保存此类数据。</p><h3 id="使用保存的实例状态恢复-Activity-界面状态"><a href="#使用保存的实例状态恢复-Activity-界面状态" class="headerlink" title="使用保存的实例状态恢复 Activity 界面状态"></a>使用保存的实例状态恢复 Activity 界面状态</h3><ul><li>无论系统是新建 Activity 实例还是重新创建之前的实例,都会调用 onCreate() 方法,所以在尝试读取之前,必须检查状态 Bundle 是否为 null。如果为 null,系统将新建 Activity 实例,而不会恢复之前销毁的实例。</li><li>选择实现系统在 onStart() 方法之后调用的 onRestoreInstanceState(),而不是在 onCreate() 期间恢复状态。仅当存在要恢复的已保存状态时,系统才会调用 onRestoreInstanceState(),因此无需检查 Bundle 是否为 null。</li></ul><h3 id="ActivityA-启动-ActivityB-时"><a href="#ActivityA-启动-ActivityB-时" class="headerlink" title="ActivityA 启动 ActivityB 时"></a>ActivityA 启动 ActivityB 时</h3><ol><li>ActivityA 的 onPause() 方法执行。 </li><li>ActivityB 的 onCreate()、onStart() 和 onResume() 方法依次执行。(ActivityB 现在具有用户焦点。) </li><li>然后,如果 ActivityA 在屏幕上不再可见,则其 onStop() 方法执行。</li></ol><h2 id="启动模式"><a href="#启动模式" class="headerlink" title="启动模式"></a>启动模式</h2><h3 id="使用清单文件设置(launchMode-属性)"><a href="#使用清单文件设置(launchMode-属性)" class="headerlink" title="使用清单文件设置(launchMode 属性)"></a>使用清单文件设置(launchMode 属性)</h3><ul><li><p><code>"standard"</code> <strong>(默认模式)</strong></p><p>默认值。系统在启动该 Activity 的任务中创建 Activty 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。 </p></li><li><p><code>"singleTop"</code><br>如果当前任务的顶部已存在 Activity 的实例,则系统会通过其 onNewIntent() 方法来将 intent 传递给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。 </p></li><li><p><code>"singleTask"</code><br>系统会创建新任务,并实例化新任务的根 Activity。但是,如果另外的任务中已存在该 Activity 的实例,则系统会通过调用其 onNewIntent() 方法将 intent 转送到该现有实例,而不是创建新实例。<strong>Activity 一次只能有一个实例存在。</strong> </p></li><li><p><code>"singleInstance"</code><br>与 <code>"singleTask"</code> 相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。<strong>该 Activity 始终是其任务唯一的成员;</strong>由该 Activity 启动的任何 Activity 都会在其他的任务中打开。</p></li></ul><h3 id="使用Intent标记"><a href="#使用Intent标记" class="headerlink" title="使用Intent标记"></a>使用Intent标记</h3><ul><li><p><code>FLAG_ACTIVITY_NEW_TASK</code><br>在新任务中启动 Activity。如果现在启动的 Activity 已经有任务在运行,则系统会将该任务转到前台并恢复其最后的状态,而 Activity 将在 <code>onNewIntent()</code> 中收到新的 intent。此与 <code>"singleTask"</code> 产生的行为相同。</p></li><li><p><code>FLAG_ACTIVITY_SINGLE_TOP</code><br>如果要启动的 Activity 是当前 Activity(即位于返回堆栈顶部的 Activity),则现有实例会收到对 <code>onNewIntent()</code> 的调用,而不会创建 Activity 的新实例。此与 <code>"singleTop"</code> 产生的行为相同。</p></li><li><p><code>FLAG_ACTIVITY_CLEAR_TOP</code><br>如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其他 Activity,并通过 <code>onNewIntent()</code> 将此 intent 传送给它的已恢复实例(现在位于堆栈顶部)。</p></li></ul><p><code>FLAG_ACTIVITY_CLEAR_TOP</code> 最常与 <code>FLAG_ACTIVITY_NEW_TASK</code> 结合使用。将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将其置于能够相应 intent 的位置。</p><h2 id="处理亲和性"><a href="#处理亲和性" class="headerlink" title="处理亲和性"></a><a href="https://developer.android.com/guide/components/activities/tasks-and-back-stack#Affinities" target="_blank" rel="noopener">处理亲和性</a></h2><p>“亲和性”表示 Activity 倾向于属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务。不过,您可以修改 Activity 的默认亲和性。在不同应用中定义的 Activity 可以具有相同的亲和性,或者在同一应用中定义的 Activity 也可以被指定不同的任务亲和性。</p><h2 id="清除返回堆栈"><a href="#清除返回堆栈" class="headerlink" title="清除返回堆栈"></a><a href="https://developer.android.com/guide/components/activities/tasks-and-back-stack#Clearing" target="_blank" rel="noopener">清除返回堆栈</a></h2><p>如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。</p><h2 id="在进程之间发送数据"><a href="#在进程之间发送数据" class="headerlink" title="在进程之间发送数据"></a>在进程之间发送数据</h2><p>Binder 事务缓冲区的大小固定有限,目前为 1MB,由进程中正在处理的所有事务共享。由于此限制是进程级别而不是 Activity 级别的限制,因此这些事务包括应用中的所有 binder 事务,例如 onSaveInstanceState,startActivity 以及与系统的任何互动。超过大小限制时,将引发 TransactionTooLargeException。 </p><p>对于 savedInstanceState 的具体情况,应将数据量保持在较小的规模,因为只要用户可以返回到该 Activity,系统进程就需要保留所提供的数据(即使 Activity 的进程已终止)。我们建议您将保存的状态保持在 50k 数据以下。 </p><h2 id="Fragment"><a href="#Fragment" class="headerlink" title="Fragment"></a>Fragment</h2><div class="hljs"><pre><code class="hljs java">FragmentManager fragmentManager = getSupportFragmentManager();FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();</code></pre></div><h3 id="add"><a href="#add" class="headerlink" title="add()"></a>add()</h3><div class="hljs"><pre><code class="hljs java">ExampleFragment fragment = <span class="hljs-keyword">new</span> ExampleFragment();fragmentTransaction.add(R.id.fragment_container, fragment);fragmentTransaction.commit();</code></pre></div><h3 id="replace"><a href="#replace" class="headerlink" title="replace()"></a>replace()</h3><div class="hljs"><pre><code class="hljs java"><span class="hljs-comment">// Create new fragment and transaction</span>Fragment newFragment = <span class="hljs-keyword">new</span> ExampleFragment();FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();<span class="hljs-comment">// Replace whatever is in the fragment_container view with this fragment,</span><span class="hljs-comment">// and add the transaction to the back stack</span>transaction.replace(R.id.fragment_container, newFragment);transaction.addToBackStack(<span class="hljs-keyword">null</span>);<span class="hljs-comment">// Commit the transaction</span>transaction.commit();</code></pre></div>]]></content>
<categories>
<category>安卓</category>
<category>组件</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>Resource</title>
<link href="/2020/07/02/Resource/"/>
<url>/2020/07/02/Resource/</url>
<content type="html"><![CDATA[<p><a href="https://developer.android.google.cn/guide/topics/resources/providing-resources?hl=zh_cn" target="_blank" rel="noopener">资源</a>是指代码使用的附加文件和静态内容,例如位图、布局定义、界面字符串、动画说明等。</p><a id="more"></a><h2 id="创建别名资源"><a href="#创建别名资源" class="headerlink" title="创建别名资源"></a>创建别名资源</h2><h3 id="绘制对象"><a href="#绘制对象" class="headerlink" title="绘制对象"></a>绘制对象</h3><p>如要创建指向现有可绘制对象的别名,请使用 <code><drawable></code> 元素。例如:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">resources</span>></span> <span class="hljs-tag"><<span class="hljs-name">drawable</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"icon"</span>></span>@drawable/icon_ca<span class="hljs-tag"></<span class="hljs-name">drawable</span>></span><span class="hljs-tag"></<span class="hljs-name">resources</span>></span></code></pre></div><h3 id="布局"><a href="#布局" class="headerlink" title="布局"></a>布局</h3><p>布局如要创建指向现有布局的别名,请使用包装在 <code><merge></code> 中的 <code><include></code> 元素。例如:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">merge</span>></span> <span class="hljs-tag"><<span class="hljs-name">include</span> <span class="hljs-attr">layout</span>=<span class="hljs-string">"@layout/main_ltr"</span>/></span><span class="hljs-tag"></<span class="hljs-name">merge</span>></span></code></pre></div><h3 id="字符串和其他简单值"><a href="#字符串和其他简单值" class="headerlink" title="字符串和其他简单值"></a>字符串和其他简单值</h3><p>如要创建指向现有字符串的别名,您只需将所需字符串的资源 ID 用作新字符串的值。例如:</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">resources</span>></span> <span class="hljs-tag"><<span class="hljs-name">string</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"hello"</span>></span>Hello<span class="hljs-tag"></<span class="hljs-name">string</span>></span> <span class="hljs-tag"><<span class="hljs-name">string</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"hi"</span>></span>@string/hello<span class="hljs-tag"></<span class="hljs-name">string</span>></span><span class="hljs-tag"></<span class="hljs-name">resources</span>></span><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">resources</span>></span> <span class="hljs-tag"><<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"red"</span>></span>#f00<span class="hljs-tag"></<span class="hljs-name">color</span>></span> <span class="hljs-tag"><<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"highlight"</span>></span>@color/red<span class="hljs-tag"></<span class="hljs-name">color</span>></span><span class="hljs-tag"></<span class="hljs-name">resources</span>></span></code></pre></div><h2 id="提供备用资源"><a href="#提供备用资源" class="headerlink" title="提供备用资源"></a>提供备用资源</h2><p><a href="https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources" target="_blank" rel="noopener">提供备用资源</a><br>几乎每个应用都应提供备用资源,以便支持特定的设备配置。例如,对于不同的屏幕密度和语言,您应分别加入备用可绘制对象资源和备用字符串资源。在运行时,Android 会检测当前设备配置并为应用加载合适的资源。</p><h2 id="res-目录中支持的资源目录"><a href="#res-目录中支持的资源目录" class="headerlink" title="res/目录中支持的资源目录"></a>res/目录中支持的资源目录</h2><p><a href="https://developer.android.com/guide/topics/resources/providing-resources#ResourceTypes" target="_blank" rel="noopener">res/目录中支持的资源目录</a></p><h2 id="AssetManager-访问原始文件"><a href="#AssetManager-访问原始文件" class="headerlink" title="AssetManager,访问原始文件"></a>AssetManager,访问原始文件</h2><p><a href="https://developer.android.com/reference/android/content/res/AssetManager" target="_blank" rel="noopener">AssetManager,访问原始文件</a></p><h2 id="内嵌复杂的-XML-资源"><a href="#内嵌复杂的-XML-资源" class="headerlink" title="内嵌复杂的 XML 资源"></a>内嵌复杂的 XML 资源</h2><p><a href="https://developer.android.com/guide/topics/resources/complex-xml-resources" target="_blank" rel="noopener">内嵌复杂的 XML 资源</a><br>某些资源类型是由 XML 文件表示的多个复杂资源合成的。例如动画矢量可绘制对象就是封装矢量可绘制对象和动画的可绘制资源。这需要使用至少 3 个 XML 文件。使用 AAPT 的内嵌资源格式,您可以在同一 XML 文件中定义所有三种资源。由于我们正在合成一个动画矢量可绘制对象,因此我们将该文件放在 <code>res/drawable/</code> 下。</p><h2 id="颜色状态列表"><a href="#颜色状态列表" class="headerlink" title="颜色状态列表"></a>颜色状态列表</h2><p><a href="https://developer.android.com/guide/topics/resources/color-list-resource" target="_blank" rel="noopener">颜色状态列表</a><br>主要是此页下的几个关于控件不同状态的布尔值。</p><h2 id="定义位图的重力-android-gravity"><a href="#定义位图的重力-android-gravity" class="headerlink" title="定义位图的重力 android:gravity"></a>定义位图的重力 android:gravity</h2><p><a href="https://developer.android.com/guide/topics/resources/drawable-resource" target="_blank" rel="noopener">定义位图的重力 android:gravity</a><br>(其下的一张表格)定义位图的重力。重力指示当位图小于容器时,可绘制对象在其容器中放置的位置。</p>]]></content>
<categories>
<category>安卓</category>
<category>其他</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>安卓导航组件</title>
<link href="/2020/07/02/%E5%AE%89%E5%8D%93%E5%AF%BC%E8%88%AA%E7%BB%84%E4%BB%B6/"/>
<url>/2020/07/02/%E5%AE%89%E5%8D%93%E5%AF%BC%E8%88%AA%E7%BB%84%E4%BB%B6/</url>
<content type="html"><![CDATA[<p><a href="https://developer.android.google.cn/guide/navigation?hl=zh" target="_blank" rel="noopener">导航</a>是指支持用户导航、进入和退出应用中不同内容片段的交互。</p><a id="more"></a><h2 id="组成"><a href="#组成" class="headerlink" title="组成"></a>组成</h2><p>导航组件由以下三个关键部分组成:</p><ul><li>导航图 res/navigation/ 目录下文件 </li><li><code>NavHost</code>:显示导航图中目标的空白容器。导航组件包含一个默认 <code>NavHost</code> 实现(<code>NavHostFragment</code>),可显示 Fragment 目标。</li><li><code>NavController</code>:在 <code>NavHost</code> 中管理应用导航的对象。当用户在整个应用中移动时,<code>NavController</code> 会安排 <code>NavHost</code> 中目标内容的交换。 </li></ul><h2 id="创建导航图"><a href="#创建导航图" class="headerlink" title="创建导航图"></a>创建导航图</h2><p>导航图是一种资源文件,其中包含所有目的地和操作。该图表会显示应用的所有导航路径。<br>要向项目添加导航图,请执行以下操作:</p><ol><li>在 “Project” 窗口中,右键点击 <code>res</code> 目录,然后依次选择 <strong>New > Android Resource File</strong>。此时系统会显示 <strong>New Resource File</strong> 对话框。</li><li>在 <strong>File name</strong> 字段中输入名称,例如 “nav_graph”。</li><li>从 <strong>Resource type</strong> 下拉列表中选择 Navigation,然后点击 OK。</li></ol><p>当您添加首个导航图时,Android Studio 会在 <code>res</code> 目录内创建一个 <code>navigation</code> 资源目录。该目录包含您的导航图资源文件(例如 <code>nav_graph.xml</code>)。</p><h2 id="向Activity添加NavHost"><a href="#向Activity添加NavHost" class="headerlink" title="向Activity添加NavHost"></a>向Activity添加NavHost</h2><h3 id="通过XML添加NavHostFragment"><a href="#通过XML添加NavHostFragment" class="headerlink" title="通过XML添加NavHostFragment"></a>通过XML添加NavHostFragment</h3><div class="hljs"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">android.support.constraint.ConstraintLayout</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:app</span>=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:tools</span>=<span class="hljs-string">"http://schemas.android.com/tools"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"match_parent"</span></span><span class="hljs-tag"> <span class="hljs-attr">tools:context</span>=<span class="hljs-string">".MainActivity"</span>></span> <span class="hljs-tag"><<span class="hljs-name">androidx.appcompat.widget.Toolbar</span></span><span class="hljs-tag"> <span class="hljs-attr">...</span>/></span> <span class="hljs-tag"><<span class="hljs-name">fragment</span></span><span class="hljs-tag"> <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/nav_host_fragment"</span></span> <!-- NavHost实现的类名称 --> android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" <span class="hljs-comment"><!-- 确保NavHostFragment会拦截系统返回按钮。只能有一个默认NavHost。如果同一布局中有多个主机,务必仅指定一个默认NavHost。 --></span> app:defaultNavHost="true" <span class="hljs-comment"><!-- 将NavHostFrament与导航图相关联。导航图会在此NavHostFragment中指定用户可以导航到的所有目的地。</span><span class="hljs-comment"> app:navGraph="@navigation/nav_graph" /></span><span class="hljs-comment"></span><span class="hljs-comment"> <com.google.android.material.bottomnavigation.BottomNavigationView</span><span class="hljs-comment"> .../></span><span class="hljs-comment"></android.support.constraint.ConstraintLayout></span></code></pre></div><h3 id="使用-Layout-Editor-向-Activity-添加-NavHostFragment"><a href="#使用-Layout-Editor-向-Activity-添加-NavHostFragment" class="headerlink" title="使用 Layout Editor 向 Activity 添加 NavHostFragment"></a>使用 Layout Editor 向 Activity 添加 NavHostFragment</h3><ol><li>在项目文件列表中,双击 Activity 的布局 XML 文件,以在 Layout Editor 中将其打开。</li><li>在 <strong>Palette</strong> 窗格内,选择 Containers 类别。</li><li>将 NavHostFragment 视图拖动到 Activity 上。</li><li>在随即显示的 <strong>Navigation Graphs</strong> 对话框中,选择要与此 <code>NavHostFragment</code> 相关联的对应导航图,然后点击 OK。</li></ol><h2 id="将某个屏幕指定为起始目的地"><a href="#将某个屏幕指定为起始目的地" class="headerlink" title="将某个屏幕指定为起始目的地"></a>将某个屏幕指定为起始目的地</h2><ol><li>在 <strong>Design</strong> 标签页中,点击相应目的地,使其突出显示。</li><li>点击 <strong>Assign start destination</strong>(小房子) 按钮 。或者,您可以右键点击该目的地,然后点击 <strong>Set as Start Destination</strong>。</li></ol><h2 id="设计导航图"><a href="#设计导航图" class="headerlink" title="设计导航图"></a>设计导航图</h2><ul><li><a href="https://developer.android.com/guide/navigation/navigation-design-graph#top-level_navigation_graph" target="_blank" rel="noopener">顶级导航图</a></li><li><a href="https://developer.android.com/guide/navigation/navigation-design-graph#navigation_across_library_modules" target="_blank" rel="noopener">在库模块中导航</a></li></ul><h3 id="嵌套图表"><a href="#嵌套图表" class="headerlink" title="嵌套图表"></a>嵌套图表</h3><p>要将目的地归入一个嵌套图表中,操作如下:</p><ol><li>在 Navigation Editor 中,按住 <code>shift</code> 键,然后点击想要添加到嵌套图表中的目的地。</li><li>右键单击以打开上下文菜单,然后依次选择 <strong>Move to Nested Graph > New Graph</strong>。目的地包含在嵌套图表中。<br>在代码中,传递将根图连接到嵌套图表的操作的操作的资源ID:</li></ol><div class="hljs"><pre><code class="hljs java">Navigation.findNavController(view).navigate(ID);</code></pre></div><h4 id="通过-lt-include-gt-引用其他导航图表"><a href="#通过-lt-include-gt-引用其他导航图表" class="headerlink" title="通过 <include> 引用其他导航图表"></a>通过 <code><include></code> 引用其他导航图表</h4><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">include</span> <span class="hljs-attr">app:graph</span>=<span class="hljs-string">"@navigation/included_graph"</span> /></span></code></pre></div><h3 id="全局操作"><a href="#全局操作" class="headerlink" title="全局操作"></a>全局操作</h3><p>对于应用中的任何可通过多条路径到达的目的地,您都应定义可转到它的相应全局操作。全局操作可用于从任意位置导航到某目的地。</p><h4 id="创建全局操作"><a href="#创建全局操作" class="headerlink" title="创建全局操作"></a>创建全局操作</h4><p>要创建全局操作,请执行以下操作:</p><ol><li>在 <strong>Graph Editor</strong> 中,点击一个目的地,使其突出显示。</li><li>右键点击该目的地,以显示上下文菜单。</li><li>依次选择 <strong>Add Action > Global</strong>。此时系统会在该目的地左侧显示一个箭头。</li></ol><h2 id="导航到目的地"><a href="#导航到目的地" class="headerlink" title="导航到目的地"></a>导航到目的地</h2><p>导航到目的地是使用 <code>NavController</code> 完成的,后者是一个在 <code>NavHost</code> 中管理应用导航的对象。每个 <code>NavHost</code> 均有自己的相应 <code>NavController</code> 。可以使用以下方法之一检索 <code>NavController</code> :</p><p><strong>Kotlin</strong>:</p><ul><li>Fragment.findNavController()</li><li>View.findNavController()</li><li>Activity.findNavController(viewId: Int) </li></ul><p><strong>Java</strong>:</p><ul><li>NavHostFragment.findNavController(Fragment)</li><li>Navigation.findNavController(Activity, @IdRes int viewId)</li><li>Navigation.findNavController(View) </li></ul><p>检索 <code>NavController</code> 之后,可以调用navigate()的某个重载,以在各个目的地之间导航。</p><h3 id="使用-ID-导航"><a href="#使用-ID-导航" class="headerlink" title="使用 ID 导航"></a>使用 ID 导航</h3><p>对于按钮,可以使用 Navigation 类的 createNavigateOnClickListener(ID) 导航到目的地。</p><h4 id="为导航提供导航选项"><a href="#为导航提供导航选项" class="headerlink" title="为导航提供导航选项"></a>为导航提供导航选项</h4><p>在导航图中定义操作时,Navigation 会生成相应的 <code>NavAction</code> 类,其中包含为该操作定义的配置,包括如下内容:</p><ul><li>目的地:目标目的地的资源 ID。</li><li>默认参数:android.os.Bundle,包含目标目的地的默认值(如有提供)。</li><li>导航选项:导航选项,表示为 <code>NavOptions</code> 。此类包含从目标目的地往返的所有特殊配置,包括动画资源配置、弹出行为以及是否应在单一顶级模式下启动目的地。</li></ul><h3 id="使用URI导航"><a href="#使用URI导航" class="headerlink" title="使用URI导航"></a>使用URI导航</h3><p>使用 URI 进行导航时,返回堆栈不会重置。这与其他深层链接导航不同,后者在导航时会替换返回堆栈。不过,<code>popUpTo</code> 和 <code>popUpToInclusive</code> 仍会从返回堆栈中移除目的地,就像您使用 ID 导航一样。</p><h3 id="导航和返回堆栈"><a href="#导航和返回堆栈" class="headerlink" title="导航和返回堆栈"></a>导航和返回堆栈</h3><p>每次调用 <a href="https://developer.android.com/reference/androidx/navigation/NavController#navigate(int)" target="_blank" rel="noopener"><code>navigate()</code></a> 方法都会将另一目的地放置到堆栈的顶部。点按<strong>向上</strong>或<strong>返回</strong>会分别调用 <a href="https://developer.android.com/reference/androidx/navigation/NavController#navigateUp()" target="_blank" rel="noopener"><code>NavController.navigateUp()</code></a> 和 <a href="https://developer.android.com/reference/androidx/navigation/NavController#popBackStack()" target="_blank" rel="noopener"><code>NavController.popBackStack()</code></a> 方法,用于移除(或弹出)堆栈顶部的目的地。<br><code>NavController.popBackStack()</code> 会返回一个布尔值,表明它是否已成功返回到另一个目的地。当返回 <code>false</code> 时,最常见的情况是手动弹出导航表的起始目的地。<br>如果该方法返回 <code>false</code>,则 <code>NavController.getCurrentDestination()</code> 会返回 <code>null</code> 。此时应导航到新目的地,或通过对 Activity 调用 <code>finish()</code> 来处理弹出情况,如下例所示:</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (!navController.popBackStack()) { <span class="hljs-comment">// Call finish() on your Activity</span> finish();}</code></pre></div><h3 id="popUpTo-和-popUpToInclusive"><a href="#popUpTo-和-popUpToInclusive" class="headerlink" title="popUpTo 和 popUpToInclusive"></a>popUpTo 和 popUpToInclusive</h3><p>使用操作进行导航时,可以选择从返回堆栈上弹出其他目的地。例如,如果应用具有初始登录流程,那么在用户登录后,应将所有与登录相关的目的地从返回堆栈上弹出,这样返回按钮就不会将用户带回登录流程。<br>要在从一个目的地导航到另一个目的地时弹出目的地,可以在关联的 <code><action></code> 元素中添加 <code>app:popUpTo</code> 属性。<code>app:popUpTo</code> 会告知 Navigation 库在调用 <code>navigate()</code> 的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。<br>还可以添加 <code>app:popUpToInclusive="true"</code>,以表明在 <code>app:popUpTo</code> 中指定的目的地也应从返回堆栈中移除。<br><a href="https://developer.android.com/guide/navigation/navigation-navigate#pop" target="_blank" rel="noopener">示例</a>(有助于理解)。 </p><h3 id="条件导航"><a href="#条件导航" class="headerlink" title="条件导航"></a>条件导航</h3><p>在为应用设计导航时,可能需要基于条件逻辑将用户转到某一个目的地而非另一个。<a href="https://developer.android.com/guide/navigation/navigation-conditional" target="_blank" rel="noopener">例如,可能具有一些需要用户登录的目的地,或者可能在游戏中针对获胜或失败的玩家提供了不同的目的地。</a></p><h2 id="在目的地间传递数据"><a href="#在目的地间传递数据" class="headerlink" title="在目的地间传递数据"></a>在目的地间传递数据</h2><h3 id="定义目的地参数"><a href="#定义目的地参数" class="headerlink" title="定义目的地参数"></a>定义目的地参数</h3><p>要在目的地之间传递数据,首先按照以下步骤将参数添加到接收它的目的地来定义参数:</p><ol><li>在 Navigation Editor 中,点击接收参数的目的地。 </li><li>在 <strong>Attributes</strong> 面板中点击 <strong>Arguments</strong> 处的 Add(+)。</li><li>在弹出的面板中输入 Name、Type、参数是否为 Array、参数是否可为 null(不论基础类型的 null 性如何,数组始终可为 null),以及 Default Value(若需要),然后点击 <strong>Add</strong>。 </li></ol><h3 id="使用-Safe-Args-传递安全的数据"><a href="#使用-Safe-Args-传递安全的数据" class="headerlink" title="使用 Safe Args 传递安全的数据"></a>使用 Safe Args 传递安全的数据</h3><p>要将Safe Args添加到项目,首先在顶级 <code>build.gradle</code> 文件中包含以下 <code>classpath</code> :</p><div class="hljs"><pre><code class="hljs gradle"><span class="hljs-keyword">buildscript</span> { <span class="hljs-keyword">repositories</span> { google() } <span class="hljs-keyword">dependencies</span> { <span class="hljs-keyword">def</span> nav_version = <span class="hljs-string">"2.3.0-alpha01"</span> <span class="hljs-keyword">classpath</span> <span class="hljs-string">"androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"</span> }}</code></pre></div><p>同时必须应用以下两个可用插件之一。<br>要生成适用于 Java 或 Java 和 Kotlin 混合模块的 Java 语言代码,请将以下行添加到应用或模块的 <code>build.gradle</code> 文件中:</p><div class="hljs"><pre><code class="hljs gradle">apply plugin: <span class="hljs-string">"androidx.navigation.safeargs"</span></code></pre></div><p>此外,要生成适用于 Kotlin 独有的模块的 Kotlin 代码,请添加以下行:</p><div class="hljs"><pre><code class="hljs gradle">apply plugin: androidx.navigation.safeargs.kotlin</code></pre></div><h4 id="将Safe-Args用于全局操作"><a href="#将Safe-Args用于全局操作" class="headerlink" title="将Safe Args用于全局操作"></a>将Safe Args用于全局操作</h4><p>将 Safe Args 用于全局操作时,必须为根 <code><navigation></code> 元素提供一个 <code>android:id</code> 值。Navigation会根据 <code>android:id</code> 值为 <code><navigation></code> 元素生成一个 <code>Directions</code> 类。</p><h3 id="使用Bundle对象在目的地之间传递参数"><a href="#使用Bundle对象在目的地之间传递参数" class="headerlink" title="使用Bundle对象在目的地之间传递参数"></a>使用Bundle对象在目的地之间传递参数</h3><p>传递</p><div class="hljs"><pre><code class="hljs java">Bundle bundle = <span class="hljs-keyword">new</span> Bundle();bundle.putString(<span class="hljs-string">"amount"</span>, <span class="hljs-string">"amount"</span>);Navigation.findNavController(view).navigate(ID, bundle);</code></pre></div><p>接收</p><div class="hljs"><pre><code class="hljs java">String amount = getArguments().getString(<span class="hljs-string">"amount"</span>);</code></pre></div><h3 id="将数据传递给起始目的地"><a href="#将数据传递给起始目的地" class="headerlink" title="将数据传递给起始目的地"></a>将数据传递给起始目的地</h3><p>您可以将数据传递给应用的起始目的地。首先,您必须显式构建一个 <code>Bundle</code> 来存储数据。然后,使用以下方法之一将该 <code>Bundle</code> 传递给起始目的地:</p><ul><li>如果您要以编程方式创建 <code>NavHost</code>,请调用 <code>NavHostFragment.create(R.navigation.graph, args)</code>,其中 args 是存储数据的 <code>Bundle</code>。</li><li>或者,您也可以通过调用以下 <code>NavController.setGraph()</code> 过载之一来设置起始目的地参数:<ul><li>使用图表 ID:<code>navController.setGraph(R.navigation.graph, args)</code></li><li>使用图表本身:<code>navController.setGraph(navGraph, args)</code></li></ul></li></ul><p>要检索起始目的地中的数据,请调用 <code>Fragment.getArguments()</code>。</p><h2 id="为目的地创建深层链接"><a href="#为目的地创建深层链接" class="headerlink" title="为目的地创建深层链接"></a>为目的地创建深层链接</h2><h3 id="创建显示深层链接"><a href="#创建显示深层链接" class="headerlink" title="创建显示深层链接"></a>创建显示深层链接</h3><p>当用户通过显式深层链接打开您的应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 <code><navigation></code> 元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,他们会返回到相应的导航堆栈,就像从入口点进入您的应用一样。</p><p>您可以使用 <a href="https://developer.android.com/reference/androidx/navigation/NavDeepLinkBuilder" target="_blank" rel="noopener"><code>NavDeepLinkBuilder</code></a> 类构建 <a href="https://developer.android.com/reference/android/app/PendingIntent" target="_blank" rel="noopener"><code>PendingIntent</code></a>,如下例所示。请注意,如果提供的上下文不是 <code>Activity</code>,构造函数会使用<a href="https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentForPackage(java.lang.String)" target="_blank" rel="noopener"><code>PackageManager.getLaunchIntentForPackage()</code></a> 作为默认Activity 来启动(如果有)。</p><div class="hljs"><pre><code class="hljs java">PendingIntent pendingIntent = <span class="hljs-keyword">new</span> NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph) .setDestination(R.id.android) .setArguments(args) .createPendingIntent();</code></pre></div><p>如果已有 <code>NavController</code>,则还可以通过 <a href="https://developer.android.com/reference/androidx/navigation/NavController#createDeepLink()" target="_blank" rel="noopener"><code>NavController.createdDeepLink()</code></a> 创建深层链接。</p><h3 id="创建隐式深层链接"><a href="#创建隐式深层链接" class="headerlink" title="创建隐式深层链接"></a>创建隐式深层链接</h3><p><a href="https://developer.android.com/guide/navigation/navigation-deep-link#implicit" target="_blank" rel="noopener">创建隐式深层链接</a></p><h2 id="在目的地之间添加动画过渡效果"><a href="#在目的地之间添加动画过渡效果" class="headerlink" title="在目的地之间添加动画过渡效果"></a>在目的地之间添加动画过渡效果</h2><p><a href="https://developer.android.com/guide/navigation/navigation-animate-transitions" target="_blank" rel="noopener">在目的地之间添加动画过渡效果</a></p><h2 id="使用NavigationUI更新界面组件"><a href="#使用NavigationUI更新界面组件" class="headerlink" title="使用NavigationUI更新界面组件"></a>使用NavigationUI更新界面组件</h2><h3 id="监听导航事件"><a href="#监听导航事件" class="headerlink" title="监听导航事件"></a>监听导航事件</h3><p><code>NavController</code> 提供 <code>OnDestinationChangedListener()</code> 接口,该接口在当前目的地或其参数发生更改时调用。可以通过<a href="https://developer.android.com/reference/androidx/navigation/NavController#addOnDestinationChangedListener%28androidx.navigation.NavController.OnDestinationChangedListener%29" target="_blank" rel="noopener"><code>addOnDestinationChangedListener()</code></a> 方法注册新监听器。</p><p>举例来说,使用此监听器可以在应用的一些区域显示常见界面元素,而在另外一些区域隐藏这些元素。</p><div class="hljs"><pre><code class="hljs java">navController.addOnDestinationChangedListener(<span class="hljs-keyword">new</span> NavController.OnDestinationChangedListener() { <span class="hljs-meta">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onDestinationChanged</span><span class="hljs-params">(@NonNull NavController controller,</span></span><span class="hljs-function"><span class="hljs-params"> @NonNull NavDestination destination, @Nullable Bundle arguments)</span> </span>{ <span class="hljs-keyword">if</span>(destination.getId() == R.id.full_screen_destination) { toolbar.setVisibility(View.GONE); bottomNavigationView.setVisibility(View.GONE); } <span class="hljs-keyword">else</span> { toolbar.setVisibility(View.VISIBLE); bottomNavigationView.setVisibility(View.VISIBLE); } }});</code></pre></div><h3 id="顶部应用栏"><a href="#顶部应用栏" class="headerlink" title="顶部应用栏"></a>顶部应用栏</h3><h4 id="AppBarConfiguration"><a href="#AppBarConfiguration" class="headerlink" title="AppBarConfiguration"></a>AppBarConfiguration</h4><p><code>NavigationUI</code> 使用 <a href="https://developer.android.com/reference/androidx/navigation/ui/AppBarConfiguration" target="_blank" rel="noopener"><code>AppBarConfiguration</code></a> 对象管理在应用显示区域左上角的导航按钮行为。默认情况下,如果用户位于导航图的顶级目的地,则导航按钮会隐藏并且在任何其他目的地显示为向上按钮。<br>要将导航图的起始目的地用作唯一顶级目的地,可以创建<code>AppBarConfiguration</code> 对象并传入相应的导航图,如下所示:</p><div class="hljs"><pre><code class="hljs java">AppBarConfiguration appBarConfiguration = <span class="hljs-keyword">new</span> AppBarConfiguration.Builder(navController.getGraph()).build();</code></pre></div><p>如果想自定义哪些目的地被视为顶级目的地,则可以改为将一组目的地ID传递给构造函数,如下所示:</p><div class="hljs"><pre><code class="hljs java">AppBarConfiguration appBarConfiguration = <span class="hljs-keyword">new</span> AppBarConfiguration.Builder(R.id.main, R.id.android).build();</code></pre></div><h4 id="创建工具栏"><a href="#创建工具栏" class="headerlink" title="创建工具栏"></a>创建工具栏</h4><ol><li>在主 Activity 中定义工具栏</li></ol><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">androidx.appcompat.widget.Toolbar</span></span><span class="hljs-tag"><span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span></span><span class="hljs-tag"><span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span></span><span class="hljs-tag"><span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/tool_bar"</span></span><span class="hljs-tag"><span class="hljs-attr">tools:ignore</span>=<span class="hljs-string">"MissingConstraints"</span> /></span></code></pre></div><ol><li>通过 Activity 的 <code>onCreate()</code> 方法调用 <a href="https://developer.android.com/reference/androidx/navigation/ui/NavigationUI#setupWithNavController(android.support.v7.widget.Toolbar,%20androidx.navigation.NavController,%20androidx.navigation.ui.AppBarConfiguration)" target="_blank" rel="noopener"><code>setupWithNavController()</code></a></li></ol><div class="hljs"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{ setContentView(R.layout.activity_main); ... NavController navController = Navigation.findNavController(<span class="hljs-keyword">this</span>, R.id.nav_host_fragment); AppBarConfiguration appBarConfiguration = <span class="hljs-keyword">new</span> AppBarConfiguration.Builder(navController.getGraph()).build(); Toolbar toolbar = findViewById(R.id.toolbar); NavigationUI.setupWithNavController(toolbar, navController);}</code></pre></div><h5 id="包含-CollapsingToolbarLayout"><a href="#包含-CollapsingToolbarLayout" class="headerlink" title="包含 CollapsingToolbarLayout"></a>包含 CollapsingToolbarLayout</h5><div class="hljs"><pre><code class="hljs java">NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration);</code></pre></div><h5 id="操作栏添加导航支持"><a href="#操作栏添加导航支持" class="headerlink" title="操作栏添加导航支持"></a>操作栏添加导航支持</h5><p>要向默认操作栏添加导航支持,可通过 Activity 的 <code>onCreate()</code> 方法调用<a href="https://developer.android.com/reference/androidx/navigation/ui/NavigationUI#setupActionBarWithNavController(android.support.v7.app.AppCompatActivity,%20androidx.navigation.NavController,%20androidx.navigation.ui.AppBarConfiguration)" target="_blank" rel="noopener"><code>setupActionBarWithNavController()</code></a>。</p><div class="hljs"><pre><code class="hljs java">NavigationUI.setupActionBarWithNavController(<span class="hljs-keyword">this</span>, navController, appBarConfiguration);</code></pre></div><p>接着,替换 <code>onSupportNavigateUp()</code> 以处理向上导航:</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onSupportNavigateUp</span><span class="hljs-params">()</span> </span>{ NavController navController = Navigation.findNavController(<span class="hljs-keyword">this</span>, R.id.nav_host_fragment); <span class="hljs-keyword">return</span> NavigationUI.navigateUp(navController, appBarConfiguration) || <span class="hljs-keyword">super</span>.onSupportNavigateUp();}</code></pre></div><h3 id="添加抽屉式导航栏"><a href="#添加抽屉式导航栏" class="headerlink" title="添加抽屉式导航栏"></a>添加抽屉式导航栏</h3><p>抽屉式导航栏图标会显示在使用 <code>DrawerLayout</code> 的<strong>所有顶级目的地</strong>上。顶级目的地是应用的根级目的地。它们不会在应用栏中显示向上按钮。</p><ol><li>声明 <code>DrawerLayout</code> 为根视图。</li><li>将 <code>DrawerLayout</code> 连接到导航图,具体方法是将其传递给 <code>AppBarConfiguration</code>,如下所示:</li></ol><div class="hljs"><pre><code class="hljs java">AppBarConfiguration appBarConfiguration = <span class="hljs-keyword">new</span> AppBarConfiguration.Builder(navController.getGraph()) .setDrawerLayout(drawerLayout) .build();</code></pre></div><ol><li>在主 Activity 类中,通过主 Activity 的 <code>onCreate()</code> 方法调用<a href="https://developer.android.com/reference/androidx/navigation/ui/NavigationUI#setupWithNavController(android.support.v7.widget.Toolbar,%20androidx.navigation.NavController,%20androidx.navigation.ui.AppBarConfiguration)" target="_blank" rel="noopener"><code>setupWithNavController()</code></a>,如下所示:</li></ol><div class="hljs"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{ setContentView(R.layout.activity_main); ... NavController navController = Navigation.findNavController(<span class="hljs-keyword">this</span>, R.id.nav_host_fragment); NavigationView navView = findViewById(R.id.nav_view); NavigationUI.setupWithNavController(navView, navController);}</code></pre></div><h3 id="底部导航栏"><a href="#底部导航栏" class="headerlink" title="底部导航栏"></a>底部导航栏</h3><ol><li>在主 Activity 中定义底部导航栏。</li></ol><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">com.google.android.material.bottomnavigation.BottomNavigationView</span></span><span class="hljs-tag"> <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/bottom_nav"</span></span><span class="hljs-tag"> <span class="hljs-attr">app:menu</span>=<span class="hljs-string">"@menu/menu_bottom_nav"</span> /></span></code></pre></div><ol><li>在主 Activity 类中,通过主 Activity 的 <code>onCreate()</code> 方法调用 <a href="https://developer.android.com/reference/androidx/navigation/ui/NavigationUI#setupWithNavController(android.support.v7.widget.Toolbar,%20androidx.navigation.NavController,%20androidx.navigation.ui.AppBarConfiguration)" target="_blank" rel="noopener"><code>setupWithNavController()</code></a> ,如下所示:</li></ol><div class="hljs"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{ setContentView(R.layout.activity_main); ... NavController navController = Navigation.findNavController(<span class="hljs-keyword">this</span>, R.id.nav_host_fragment); BottomNavigationView bottomNav = findViewById(R.id.bottom_nav); NavigationUI.setupWithNavController(bottomNav, navController);}</code></pre></div><h3 id="将目的地(Fragment)关联到菜单项(MenuItem)"><a href="#将目的地(Fragment)关联到菜单项(MenuItem)" class="headerlink" title="将目的地(Fragment)关联到菜单项(MenuItem)"></a>将目的地(Fragment)关联到菜单项(MenuItem)</h3><p>如果 <code>MenuItem</code> 的 <code>id</code> 与目的地的 <code>id</code> 匹配,则 <code>NavController</code> 可以导航至该目的地。<br>菜单项</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">menu</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>></span> ... <span class="hljs-tag"><<span class="hljs-name">item</span></span><span class="hljs-tag"> <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@id/details_page_fragment"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:icon</span>=<span class="hljs-string">"@drawable/ic_details"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:title</span>=<span class="hljs-string">"@string/details"</span> /></span><span class="hljs-tag"></<span class="hljs-name">menu</span>></span></code></pre></div><p>目的地</p><div class="hljs"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version="1.0" encoding="utf-8"?></span><span class="hljs-tag"><<span class="hljs-name">navigation</span> <span class="hljs-attr">xmlns:app</span>=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:tools</span>=<span class="hljs-string">"http://schemas.android.com/tools"</span></span><span class="hljs-tag"> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span></span><span class="hljs-tag"> <span class="hljs-attr">...</span> ></span> ... <span class="hljs-tag"><<span class="hljs-name">fragment</span> <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/details_page_fragment"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:label</span>=<span class="hljs-string">"@string/details"</span></span><span class="hljs-tag"> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"com.example.android.myapp.DetailsFragment"</span> /></span><span class="hljs-tag"></<span class="hljs-name">navigation</span>></span></code></pre></div><p>使用 <code>onOptionsItemSelected()</code> 替换Activity的 <code>onCreateOptionsMenu()</code> 以调用 <code>onNavDestinationSelected()</code> 将菜单项与目的地相关联,如下所示:</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onOptionsItemSelected</span><span class="hljs-params">(MenuItem item)</span> </span>{ NavController navController = Navigation.findNavController(<span class="hljs-keyword">this</span>, R.id.nav_host_fragment); <span class="hljs-keyword">return</span> NavigationUI.onNavDestinationSelected(item, navController) || <span class="hljs-keyword">super</span>.onOptionsItemSelected(item);}</code></pre></div><h2 id="使用-ViewPager-创建包含标签页的滑动视图"><a href="#使用-ViewPager-创建包含标签页的滑动视图" class="headerlink" title="使用 ViewPager 创建包含标签页的滑动视图"></a>使用 ViewPager 创建包含标签页的滑动视图</h2><p><a href="https://developer.android.com/guide/navigation/navigation-swipe-view" target="_blank" rel="noopener">示例链接</a></p><h2 id="以编程方式与导航组件交互"><a href="#以编程方式与导航组件交互" class="headerlink" title="以编程方式与导航组件交互"></a>以编程方式与导航组件交互</h2><div class="hljs"><pre><code class="hljs java">NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);getSupportFragmentManager().beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) <span class="hljs-comment">// this is the equivalent to app:defaultNavHost="true"</span> .commit();</code></pre></div><p>请注意,<code>setPrimaryNavigationFragment(finalHost)</code> 允许您的 <code>NavHost</code> 截获对系统“返回”按钮的按下操作。您也可以添加 <code>app:defaultNavHost="true"</code>,在 <code>NavHost</code> XML 中实现此行为。如果要实现<a href="https://developer.android.com/guide/navigation/navigation-custom-back" target="_blank" rel="noopener">自定义“返回”按钮行为</a>,并且不希望 <code>NavHost</code> 截获对“返回”按钮的按下操作,您可以将 <code>null</code> 传递给 <code>setPrimaryNavigationFragment()</code>。</p>]]></content>
<categories>
<category>安卓</category>
<category>组件</category>
</categories>
<tags>
<tag>基础知识</tag>
</tags>
</entry>
<entry>
<title>Retrofit使用</title>
<link href="/2020/07/01/Retrofit/"/>
<url>/2020/07/01/Retrofit/</url>
<content type="html"><![CDATA[<p>Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是由 OkHttp 完成,而 Retrofit 仅负责网络请求接口的封装。</p><a id="more"></a><h3 id="1-添加-Retrofit-库的依赖"><a href="#1-添加-Retrofit-库的依赖" class="headerlink" title="1. 添加 Retrofit 库的依赖"></a>1. 添加 Retrofit 库的依赖</h3><div class="hljs"><pre><code class="hljs java"><span class="hljs-comment">// 将Java版本设置为1.8*或更高版本。不然会报错(java.lang.NoSuchMethodError: No static method metafactory...)</span><span class="hljs-comment">// 在android下添加</span><span class="hljs-comment">// compileOptions {</span><span class="hljs-comment">// sourceCompatibility JavaVersion.VERSION_1_8</span><span class="hljs-comment">// targetCompatibility JavaVersion.VERSION_1_8</span><span class="hljs-comment">// }</span> implementation <span class="hljs-string">'com.squareup.retrofit2:retrofit:2.9.0'</span></code></pre></div><h3 id="2-添加-Converters"><a href="#2-添加-Converters" class="headerlink" title="2. 添加 Converters"></a>2. 添加 Converters</h3><div class="hljs"><pre><code class="hljs java">Gson: com.squareup.retrofit2:converter-gsonJackson: com.squareup.retrofit2:converter-jacksonMoshi: com.squareup.retrofit2:converter-moshiProtobuf: com.squareup.retrofit2:converter-protobufWire: com.squareup.retrofit2:converter-wireSimple XML: com.squareup.retrofit2:converter-simplexmlJAXB: com.squareup.retrofit2:converter-jaxbScalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars</code></pre></div><h3 id="3-添加网络权限"><a href="#3-添加网络权限" class="headerlink" title="3. 添加网络权限"></a>3. 添加网络权限</h3><div class="hljs"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.INTERNET"</span>/></span></code></pre></div><h3 id="4-创建装载服务器返回数据的类"><a href="#4-创建装载服务器返回数据的类" class="headerlink" title="4. 创建装载服务器返回数据的类"></a>4. 创建装载服务器返回数据的类</h3><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ResultData</span> </span>{ ... <span class="hljs-comment">// 根据返回数据的格式和数据解析方式(Json、XML等)定义</span>}</code></pre></div><h3 id="5-创建用于配置网络请求的接口"><a href="#5-创建用于配置网络请求的接口" class="headerlink" title="5. 创建用于配置网络请求的接口"></a>5. 创建用于配置网络请求的接口</h3><p>Retrofit 将 Http 请求抽象成 Java 接口,采用<strong>注解</strong>描述网络请求参数和配置网络请求参数</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IGetRequest</span> </span>{ <span class="hljs-meta">@GET</span>(<span class="hljs-string">"getIpInfo.php?ip=59.108.54.37"</span>) <span class="hljs-function">Call<ResultData> <span class="hljs-title">getCall</span><span class="hljs-params">()</span></span>;}</code></pre></div><h4 id="请求方法注解"><a href="#请求方法注解" class="headerlink" title="请求方法注解"></a>请求方法注解</h4><ul><li>@GET</li><li>@POST</li><li>@PUT</li><li>@DELETE</li><li>@HEAD(常用)</li></ul><h4 id="标记类注解"><a href="#标记类注解" class="headerlink" title="标记类注解"></a>标记类注解</h4><ul><li>@FormUrlEncoded 表单请求</li><li>@Multipart 注解表示允许多个 @Part</li><li>@Streaming 代表响应的数据以流的形式返回,如果不适用它,则默认会把全部数据加载到内存,所以下载大文件时需要加上这个注解。</li></ul><h4 id="参数类注解"><a href="#参数类注解" class="headerlink" title="参数类注解"></a>参数类注解</h4><h5 id="GET-请求访问网络"><a href="#GET-请求访问网络" class="headerlink" title="GET 请求访问网络"></a>GET 请求访问网络</h5><ul><li><p>@Path 动态配置 URL 地址</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IGetRequest</span> </span>{ <span class="hljs-meta">@GET</span>(<span class="hljs-string">"{path}/getIpInfo.php?ip=59.108.54.37"</span>) <span class="hljs-function">Call<ResultData> <span class="hljs-title">getCall</span><span class="hljs-params">(@Path(<span class="hljs-string">"path"</span>)</span> String path)</span>;}</code></pre></div><p>在 GET 注解中包含了 <code>{path}</code>,它对应着 @Path 注解中的 <code>"path"</code>,用来替换 GET 注解中的 <code>{path}</code> 的正是需要传入的 <code>String path</code> 的值。</p></li><li><p>@Query 动态指定查询条件</p></li><li><p>@QueryMap 动态指定查询条件组</p></li></ul><h5 id="POST-请求访问网络"><a href="#POST-请求访问网络" class="headerlink" title="POST 请求访问网络"></a>POST 请求访问网络</h5><ul><li><p>@Field 传输数据类型为键值对</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IGetRequest</span> </span>{ <span class="hljs-meta">@FormUrlEncoded</span> <span class="hljs-meta">@POST</span>(<span class="hljs-string">"getIpInfo.php"</span>) <span class="hljs-function">Call<IpModel> <span class="hljs-title">getIpMsg</span><span class="hljs-params">(@Field(<span class="hljs-string">"ip"</span>)</span> String first)</span>;}</code></pre></div><p>首先用 <code>@FormUrlEncoded</code> 注解来标明这是一个表单请求,然后在 <code>getIpMsg</code> 方法中使用 <code>@Field</code> 注解来标示所对应的 String 类型数据的键,从而组成一组键值对进行传递。</p></li><li><p>@Body 传输数据类型 JSON 字符串</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IGetRequest</span> </span>{ <span class="hljs-meta">@POST</span>(<span class="hljs-string">"getIpInfo.php"</span>) <span class="hljs-function">Call<IpModel> <span class="hljs-title">getIpMsg</span><span class="hljs-params">(@Body Ip ip)</span></span>;}</code></pre></div><p>用 <code>@Body</code> 这个注解标识参数对象即可,Retrofit 会将 Ip 对象转换为字符串。</p></li><li><p>@Part 单个文件上传</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UploadFileForPart</span> </span>{ <span class="hljs-meta">@Multipart</span> <span class="hljs-meta">@POST</span>(<span class="hljs-string">"user/photo"</span>) <span class="hljs-function">Call<User> <span class="hljs-title">updateUser</span><span class="hljs-params">(@Part MultipartBody.Part photo, @Part(<span class="hljs-string">"description"</span>)</span> RequestBody description)</span>;}</code></pre></div><p><code>@Multipart</code> 注解表示允许多个 <code>@Part</code>。<code>updateUser</code> 方法的第一个参数是准备上传的图片文件,另一个参数是 RequestBody 类型,用来传递简单的键值对。</p></li><li><p>@PartMap 多个文件上传</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UploadFileForPart</span> </span>{ <span class="hljs-meta">@Multipart</span> <span class="hljs-meta">@POST</span>(<span class="hljs-string">"user/photo"</span>) <span class="hljs-function">Call<User> <span class="hljs-title">updateUser</span><span class="hljs-params">(@PartMap Map<String, RequestBody> photos, @Part(<span class="hljs-string">"description"</span>)</span> RequestBody description)</span>;}</code></pre></div><p>其它和单文件上传类似,只是使用 Map 封装了上传的文件。</p></li></ul><h5 id="Header-消息报头"><a href="#Header-消息报头" class="headerlink" title="Header 消息报头"></a>Header 消息报头</h5><p>在 HTTP 请求中,为了防止攻击和过滤掉不安全的访问,或者添加特殊加密的访问,通常会在消息报头中携带一些特殊的消息头处理。Retrofit 提供了 @Header 来添加消息报头。添加消息报头有两种方式:一种是静态的,另一种是动态的。</p><h6 id="静态方式"><a href="#静态方式" class="headerlink" title="静态方式"></a>静态方式</h6><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">SomeService</span> </span>{ <span class="hljs-meta">@GET</span>(<span class="hljs-string">"some/endpoint"</span>) <span class="hljs-meta">@Headers</span>(<span class="hljs-string">"Accept-Encoding: application/json"</span>) <span class="hljs-function">Call<ResponseBody> <span class="hljs-title">getCarType</span><span class="hljs-params">()</span></span>;}</code></pre></div><p>使用 <code>@Headers</code> 注解添加消息报头。如果想要添加多个消息报头,可以使用 {} 包含起来:</p><div class="hljs"><pre><code class="hljs java"><span class="hljs-meta">@Headers</span>({ <span class="hljs-string">"Accept-Encoding: application/json"</span>, <span class="hljs-string">"User-Agent: MoonRetrofit"</span>})</code></pre></div><h6 id="动态方式"><a href="#动态方式" class="headerlink" title="动态方式"></a>动态方式</h6><div class="hljs"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">SomeService</span> </span>{ <span class="hljs-meta">@GET</span>(<span class="hljs-string">"some/endpoint"</span>) <span class="hljs-function">Call<ResponseBody> <span class="hljs-title">getCarType</span><span class="hljs-params">(@Header(<span class="hljs-string">"Location"</span>)</span> String location)</span>;}</code></pre></div><p>详细解释见如下<a href="https://www.jianshu.com/p/a3e162261ab6" target="_blank" rel="noopener">博文链接</a></p><h3 id="6-创建-Retrofit-实例"><a href="#6-创建-Retrofit-实例" class="headerlink" title="6. 创建 Retrofit 实例"></a>6. 创建 Retrofit 实例</h3><p>Retrofit 是通过建造者模式构建出来的。请求 URL 是拼接而成的,它是由 baseUrl 传入的 <code>URL</code> 加上请求网络接口的 <code>@GET("getIpInfo.php?ip=59.108.54.37")</code> 中的 URL 拼接而成的。</p><div class="hljs"><pre><code class="hljs java">Retrofit retrofit = <span class="hljs-keyword">new</span> Retrofit.Builder() .baseUrl(Url) .addConverterFactory(GsonConverterFactory.create()) .build()</code></pre></div><h3 id="7-创建网络请求接口实例"><a href="#7-创建网络请求接口实例" class="headerlink" title="7. 创建网络请求接口实例"></a>7. 创建网络请求接口实例</h3><div class="hljs"><pre><code class="hljs java"><span class="hljs-comment">// 创建网络请求接口的实例</span>IGetRequest request = retrofit.create(IGetRequest<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;<span class="hljs-comment">// 对发送请求进行封装</span>Call<ResultData> call = request.getCall();</code></pre></div><h3 id="8-发送网络请求(异步-or-同步),-and-处理"><a href="#8-发送网络请求(异步-or-同步),-and-处理" class="headerlink" title="8. 发送网络请求(异步 or 同步), and 处理"></a>8. 发送网络请求(异步 or 同步), and 处理</h3><div class="hljs"><pre><code class="hljs java"><span class="hljs-comment">//发送网络请求(异步)</span>call.enqueue(<span class="hljs-keyword">new</span> Callback<ResultData>() { <span class="hljs-comment">//请求成功时回调</span> <span class="hljs-meta">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onResponse</span><span class="hljs-params">(Call<ResultData> call, Response<ResultData> response)</span> </span>{ <span class="hljs-comment">//处理结果</span> } <span class="hljs-comment">//请求失败时候的回调</span> <span class="hljs-meta">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span><span class="hljs-params">(Call<ResultData> call, Throwable throwable)</span> </span>{ <span class="hljs-comment">//提示失败</span> }});<span class="hljs-comment">// 发送网络请求(同步)</span>Response<ResultData> response = call.execute();</code></pre></div><h3 id="9-中断网络请求"><a href="#9-中断网络请求" class="headerlink" title="9. 中断网络请求"></a>9. 中断网络请求</h3><p><code>call.cancel()</code></p>]]></content>
<categories>
<category>安卓</category>
<category>开源库</category>
</categories>
<tags>
<tag>网络框架</tag>
</tags>
</entry>
<entry>
<title>Markdown语法</title>
<link href="/2020/07/01/Markdown%E8%AF%AD%E6%B3%95/"/>
<url>/2020/07/01/Markdown%E8%AF%AD%E6%B3%95/</url>
<content type="html"><![CDATA[<p>Markdown 是一种轻量级标记语言,允许人们使用易读易写的纯文本格式编写文档。</p><a id="more"></a><h1 id="一级标题"><a href="#一级标题" class="headerlink" title="一级标题"></a>一级标题</h1><h2 id="二级标题"><a href="#二级标题" class="headerlink" title="二级标题"></a>二级标题</h2><h3 id="三级标题"><a href="#三级标题" class="headerlink" title="三级标题"></a>三级标题</h3><h4 id="四级标题"><a href="#四级标题" class="headerlink" title="四级标题"></a>四级标题</h4><h5 id="五级标题"><a href="#五级标题" class="headerlink" title="五级标题"></a>五级标题</h5><h6 id="六级标题"><a href="#六级标题" class="headerlink" title="六级标题"></a>六级标题</h6><div class="hljs"><pre><code class="hljs markdown">----<span class="hljs-section"># 一级标题</span><span class="hljs-section">## 二级标题</span><span class="hljs-section">### 三级标题</span><span class="hljs-section">#### 四级标题</span><span class="hljs-section">##### 五级标题</span><span class="hljs-section">###### 六级标题</span></code></pre></div><h2 id="调出vscode-MarkDown预览框"><a href="#调出vscode-MarkDown预览框" class="headerlink" title="调出vscode MarkDown预览框"></a>调出vscode MarkDown预览框</h2><ol><li>Crtl+shift+p,输入Markdown</li><li>Ctrl+k,放掉,再按v </li><li>Ctrl+Shift+v </li></ol><h2 id="段落格式"><a href="#段落格式" class="headerlink" title="段落格式"></a>段落格式</h2><p>换行(两个以上空格加回车)<br>或者使用一个空行来表示重新开始一个段落<br><em>斜体文字</em><br><em>斜体文字</em><br><strong>粗体文字</strong><br><strong>粗体文字</strong><br><strong><em>粗斜体文字</em></strong><br><strong><em>粗斜体文字</em></strong> </p><div class="hljs"><pre><code class="hljs markdown"><span class="hljs-emphasis">*斜体文字*</span> <span class="hljs-emphasis">_斜体文字_</span> <span class="hljs-strong">**粗体文字**</span> <span class="hljs-strong">__粗体文字__</span> <span class="hljs-strong">***粗斜体文字**</span>* <span class="hljs-strong">___粗斜体文字__</span>_</code></pre></div><p>三个以上的星号、减号、底线来建立一个分割线,行内不能有其它东西,星号或减号中间可以插入空格</p><hr><hr><hr><hr><hr><hr><div class="hljs"><pre><code class="hljs markdown"><span class="hljs-emphasis">***</span>---<span class="hljs-emphasis">___</span><span class="hljs-bullet">* </span><span class="hljs-emphasis">* *</span><span class="hljs-bullet">- </span>- -<span class="hljs-emphasis">_ _</span> _</code></pre></div><p><del>删除线,不明显吗?</del><br><u>下划线</u> </p><div class="hljs"><pre><code class="hljs markdown">~~删除线,不明显吗?~~ <span class="xml"><span class="hljs-tag"><<span class="hljs-name">u</span>></span></span>下划线<span class="xml"><span class="hljs-tag"></<span class="hljs-name">u</span>></span></span></code></pre></div><p>我要学习[^what]<br> [^what]: 我要学习!!</p><div class="hljs"><pre><code class="hljs markdown">我要学习 [^what] [<span class="hljs-symbol">^what</span>]:<span class="hljs-link">我要学习!!</span></code></pre></div><ul><li><input checked="" disabled="" type="checkbox"> 我要好好学习 <ul><li><input disabled="" type="checkbox"> 还没学呢</li></ul></li></ul><div class="hljs"><pre><code class="hljs markdwon">- [x] 我要好好学习 - [ ] 还没学呢</code></pre></div><h2 id="列表"><a href="#列表" class="headerlink" title="列表"></a>列表</h2><h3 id="无序列表"><a href="#无序列表" class="headerlink" title="无序列表"></a>无序列表</h3><ul><li>第一项</li><li>第二项</li><li>第三项</li></ul><ul><li>第一项</li><li>第二项</li><li>第三项</li></ul><ul><li>第一项</li><li>第二项 </li><li>第三项 </li></ul><div class="hljs"><pre><code class="hljs markdwon">* 第一项* 第二项* 第三项+ 第一项+ 第二项+ 第三项- 第一项- 第二项- 第三项</code></pre></div><h3 id="有序列表-数字加"><a href="#有序列表-数字加" class="headerlink" title="有序列表(数字加.)"></a>有序列表(数字加.)</h3><ol><li>第一项 </li><li>第二项 </li><li>第三项 </li></ol><div class="hljs"><pre><code class="hljs markdown"><span class="hljs-bullet">1. </span>第一项 <span class="hljs-bullet">2. </span>第二项 <span class="hljs-bullet">3. </span>第三项</code></pre></div><h3 id="列表嵌套"><a href="#列表嵌套" class="headerlink" title="列表嵌套"></a>列表嵌套</h3><p>列表嵌套只需在子列表中的选项添加四个空格即可</p><ol><li>第一项<ul><li>第一一项</li><li>第一二项</li></ul></li><li>第二项<ul><li>第二一项</li><li>第二二项</li></ul></li></ol><div class="hljs"><pre><code class="hljs markdown"><span class="hljs-bullet">1. </span>第一项<span class="hljs-bullet"> - </span>第一一项<span class="hljs-bullet"> - </span>第一二项<span class="hljs-bullet">2. </span>第二项<span class="hljs-bullet"> - </span>第二一项<span class="hljs-bullet"> - </span>第二二项</code></pre></div><h2 id="区块"><a href="#区块" class="headerlink" title="区块"></a>区块</h2><p>Markdown区块引用是在段落开头使用>符号,然后后面紧跟一个空格符号:</p><blockquote><p>区块引用 </p><blockquote><p>我要学习 </p><blockquote><p>学的不仅是技术更是梦想 </p></blockquote></blockquote></blockquote><div class="hljs"><pre><code class="hljs markdown"><span class="hljs-quote">> 区块引用 </span><span class="hljs-quote">> > 我要学习 </span><span class="hljs-quote">> > > 学的不仅是技术更是梦想</span></code></pre></div><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><h3 id="如果是段落上的一个函数或片段的代码可以用反引号把它包起来,例如:-print-函数"><a href="#如果是段落上的一个函数或片段的代码可以用反引号把它包起来,例如:-print-函数" class="headerlink" title="如果是段落上的一个函数或片段的代码可以用反引号把它包起来,例如: print()函数"></a>如果是段落上的一个函数或片段的代码可以用反引号把它包起来,例如: <code>print()</code>函数</h3><h3 id="代码区块"><a href="#代码区块" class="headerlink" title="代码区块"></a>代码区块</h3><h4 id="代码区块使用4个空格或者一个制表符,例如:"><a href="#代码区块使用4个空格或者一个制表符,例如:" class="headerlink" title="代码区块使用4个空格或者一个制表符,例如:"></a>代码区块使用4个空格或者一个制表符,例如:</h4><div class="hljs"><pre><code>private void Toast(Context context, Sting message) { Toast.makeText(context,string,Toast.LENGTH_SHORT).show();}</code></pre></div><h4 id="也可以用-包裹一段代码,并指定一种语言,(也可以不指定):"><a href="#也可以用-包裹一段代码,并指定一种语言,(也可以不指定):" class="headerlink" title="也可以用```包裹一段代码,并指定一种语言,(也可以不指定):"></a>也可以用```包裹一段代码,并指定一种语言,(也可以不指定):</h4><div class="hljs"><pre><code class="hljs javascript">$(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ alert(<span class="hljs-string">'RUNOOB'</span>);});</code></pre></div><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li>这是我的博客<a href="https://i.csdn.net/" target="_blank" rel="noopener">CSDN</a></li><li><a href="https://i.csdn.net/" target="_blank" rel="noopener">https://i.csdn.net/</a></li><li>高级链接,可以通过变量来设置一个链接,变量赋值在文档末尾进行:<ul><li>这个是我的博客地址<a href="https://i.csdn.net/" target="_blank" rel="noopener">CSDN</a></li></ul></li></ul><h2 id="图片-bu-shi-tai-hao"><a href="#图片-bu-shi-tai-hao" class="headerlink" title="图片(bu shi tai hao)"></a>图片(bu shi tai hao)</h2><h3 id="Markdown图片语法格式如下:"><a href="#Markdown图片语法格式如下:" class="headerlink" title="Markdown图片语法格式如下:"></a>Markdown图片语法格式如下:</h3><div class="hljs"><pre><code class="hljs markdown">![<span class="hljs-string">alt 属性文本</span>](<span class="hljs-link">图片地址</span>)![<span class="hljs-string">alt 属性文本</span>](<span class="hljs-link">图片地址"可选标题"</span>)</code></pre></div><h2 id="表格"><a href="#表格" class="headerlink" title="表格"></a>表格</h2><h3 id="Markdown制作表格使用-来分隔不同的单元格,使用-来分隔表头和其它行"><a href="#Markdown制作表格使用-来分隔不同的单元格,使用-来分隔表头和其它行" class="headerlink" title="Markdown制作表格使用|来分隔不同的单元格,使用-来分隔表头和其它行"></a>Markdown制作表格使用|来分隔不同的单元格,使用-来分隔表头和其它行</h3><p>语法格式如下:</p><div class="hljs"><pre><code class="hljs markdown">| 表头 | 表头 || ---- | ---- || 单元格 | 单元格 || 单元格 | 单元格 |</code></pre></div><table><thead><tr><th>表头</th><th>表头</th></tr></thead><tbody><tr><td>单元格</td><td>单元格</td></tr><tr><td>单元格</td><td>单元格</td></tr></tbody></table><h3 id="对齐方式"><a href="#对齐方式" class="headerlink" title="对齐方式"></a>对齐方式</h3><ul><li>-: 设置内容和标题栏居右对齐。</li><li>:- 设置内容和标题栏居左对齐。</li><li>:-: 设置内容和标题栏居中对齐。 </li></ul><table><thead><tr><th align="left">左对齐</th><th align="right">右对齐</th><th align="center">居中对齐</th></tr></thead><tbody><tr><td align="left">单元格</td><td align="right">单元格</td><td align="center">单元格</td></tr><tr><td align="left">单元格</td><td align="right">单元格</td><td align="center">单元格</td></tr></tbody></table><h2 id="高级技巧"><a href="#高级技巧" class="headerlink" title="高级技巧"></a>高级技巧</h2><ul><li>支持HTML元素</li><li>反斜杠转义</li></ul>]]></content>
<categories>
<category>一些新东西</category>
</categories>
<tags>
<tag>Markdown</tag>
</tags>
</entry>
<entry>
<title>git指令</title>
<link href="/2020/07/01/git%E6%8C%87%E4%BB%A4/"/>
<url>/2020/07/01/git%E6%8C%87%E4%BB%A4/</url>
<content type="html"><![CDATA[<p>Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或大或小地项目。</p><a id="more"></a><h2 id="Git-基本操作"><a href="#Git-基本操作" class="headerlink" title="Git 基本操作"></a>Git 基本操作</h2><h3 id="1-设置Git使用时的姓名和邮箱地址"><a href="#1-设置Git使用时的姓名和邮箱地址" class="headerlink" title="1. 设置Git使用时的姓名和邮箱地址"></a>1. 设置Git使用时的姓名和邮箱地址</h3><ul><li>git config – global user.name name</li><li>git config – global user.email email</li></ul><h3 id="2-查看Git姓名和邮箱地址"><a href="#2-查看Git姓名和邮箱地址" class="headerlink" title="2. 查看Git姓名和邮箱地址"></a>2. 查看Git姓名和邮箱地址</h3><ul><li>git config – user.name</li><li>git confit – user.email</li></ul><h3 id="3-基本操作"><a href="#3-基本操作" class="headerlink" title="3. 基本操作"></a>3. 基本操作</h3><ul><li>git init 初始化仓库</li><li>git status 查看仓库的状态</li><li>git add 向缓存区添加文件</li><li>git commit -m ‘描述’ 保存仓库的历史记录</li><li>git log 查看提交日志</li><li>git log –pretty=short 只显示提交信息的第一行</li><li>git log ‘filename’ 只显示指定目录、文件的日志</li><li>git log -p 显示文件的改动</li><li>git diff 查看更改前后的区别 <strong>不妨在执行git commit之前先执行git diff HEAD查看本次提交与上次提交有什么区别</strong></li><li>git branch 显示分支一览表</li><li>git checkout -b 创建切换分支 git branch name, git checkout name</li><li>git checkout - 切换回上一个分支</li><li>git merge 合成分支 为了在历史记录中明确记录下本次分支合并,需要在合并时加上参数no-ff</li><li>git log –graph 以图表形式查看分支</li><li>git reset –hard 回溯历史版本</li><li>git reflog 查看当前仓库的操作日志,以便找到误操作的哈希值</li><li>git commit –amend 修改提交信息 在合并特性分支之前,如果发现已提交的内容有些小错误,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录</li><li>git rebase -i 压缩历史 git rebase -i HEAD~2 用此命令可以选定当前分支包含HEAD(最新提交)在内的两个最新历史记录为对象,并在编辑器中打开</li><li>git remote add ‘远程仓库的名字(origin)’ ‘github链接’ 将本地仓库与远端仓库建立一个链接</li><li>git push -u ‘远程仓库的名字’ ‘当前分支的名字’ 推送更新</li><li>git clone ‘项目链接’</li><li>git pull 获取最新的远程仓库分支</li></ul>]]></content>
<categories>
<category>版本控制</category>
</categories>
<tags>
<tag>Git</tag>
</tags>