-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
318 lines (153 loc) · 204 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>基于Python的音高分析器</title>
<link href="/2023/02/07/pitch-analys/"/>
<url>/2023/02/07/pitch-analys/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h3 id="前提背景"><a href="#前提背景" class="headerlink" title="前提背景"></a>前提背景</h3><p>之前在B站看到一个分析歌手在各大晚会的歌唱表演是否为假唱,通过分析声音的频率还有连续稳定性来判断,觉得很有意思来研究一下。</p><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>首先找到了tensorflow的Spice声音分析模型,查看了下<a href="https://tensorflow.google.cn/hub/tutorials/spice?hl=zh-cn">官方文档</a>照着示例用了一下,步骤比较繁琐且不易懂,其使用的plotly python生成数据图性能较差生成较慢。</p><p>于是找到了另一个开源库 <a href="https://parselmouth.readthedocs.io/en/stable/">Parselmouth</a>,<br>Parselmouth是Praat软件的 Python 库,Parselmouth 直接访问 Praat 的 C/C++ 代码(这意味着算法及其输出与 Praat 中的完全相同)并提供对程序数据的高效访问,但也提供了一个看起来与任何其他 Python 库没有区别的界面,<br>使用起来效率更高且相对简单易懂。</p><h3 id="基础数据处理"><a href="#基础数据处理" class="headerlink" title="基础数据处理"></a>基础数据处理</h3><p>获取频率</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python">sound = parselmouth.Sound(tmp_name)<br><span class="hljs-comment"># 获取频率数组</span><br>pitch_track = sound.to_pitch().selected_array[<span class="hljs-string">'frequency'</span>]<br></code></pre></td></tr></table></figure><p>计算音高,首先了解了下音高的定义及对应频率算法,参考<a href="https://www.jianshu.com/p/0fc2163cfc50">音高、音名与对应频率</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs python">pitches = <span class="hljs-built_in">list</span>(pitch_track)<br>A4 = <span class="hljs-number">440</span> <span class="hljs-comment">#以A4为基准频率</span><br>C0 = A4 * <span class="hljs-built_in">pow</span>(<span class="hljs-number">2</span>, -<span class="hljs-number">4.75</span>) <span class="hljs-comment">#计算最低C0音高后面作为对比</span><br>note_names = [<span class="hljs-string">"C"</span>, <span class="hljs-string">"C#"</span>, <span class="hljs-string">"D"</span>, <span class="hljs-string">"D#"</span>, <span class="hljs-string">"E"</span>, <span class="hljs-string">"F"</span>, <span class="hljs-string">"F#"</span>, <span class="hljs-string">"G"</span>, <span class="hljs-string">"G#"</span>, <span class="hljs-string">"A"</span>, <span class="hljs-string">"A#"</span>, <span class="hljs-string">"B"</span>]<br>notes = []<br>times = []<br><span class="hljs-keyword">for</span> i, freq <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(pitches):<br> times.append(i / <span class="hljs-number">100</span>)<br> <span class="hljs-comment"># if freq < 329:</span><br> <span class="hljs-comment"># notes.append(None)</span><br> <span class="hljs-comment"># continue</span><br> <span class="hljs-keyword">if</span> freq == <span class="hljs-number">0.0</span>:<br> notes.append(<span class="hljs-literal">None</span>)<br> <span class="hljs-keyword">continue</span><br> h = <span class="hljs-built_in">round</span>(<span class="hljs-number">12</span> * math.log2(freq / C0))<br> octave = h // <span class="hljs-number">12</span><br> n = h % <span class="hljs-number">12</span><br> minute = <span class="hljs-built_in">int</span>(i / <span class="hljs-number">60</span> / <span class="hljs-number">100</span>)<br> second = <span class="hljs-built_in">round</span>(i / <span class="hljs-number">100</span> % <span class="hljs-number">60</span>, <span class="hljs-number">1</span>)<br> note = note_names[n] + <span class="hljs-built_in">str</span>(octave) + <span class="hljs-string">'-'</span> + <span class="hljs-built_in">str</span>(minute) + <span class="hljs-string">'m'</span> + <span class="hljs-built_in">str</span>(second) + <span class="hljs-string">'s'</span><br> notes.append(note)<br></code></pre></td></tr></table></figure><p>获取到对应时间点的频率及计算出来的音高,数据处理完毕</p><h3 id="前端展示"><a href="#前端展示" class="headerlink" title="前端展示"></a>前端展示</h3><p>相对复杂一些</p><p>使用plotly js进行数据展示,性能较好且考虑到跨平台使用了Flask开放对应接口使用<br>音频图展示比较容易但是动画相对复杂一些,plotly自带的动画插入帧可以实现基本的波形动画,但是无法获取每一帧的事件,没有办法进行回调处理。<br>于是曲线救国使用setInterval定时绘制标头及计算滚动rangeslider</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//计算range范围</span><br><span class="hljs-keyword">var</span> xrange = <span class="hljs-number">20</span>; <span class="hljs-comment">//当前屏展示的时间范围</span><br><span class="hljs-keyword">var</span> startX = x/<span class="hljs-number">100</span>;<br>startX = startX>xrange/<span class="hljs-number">5</span>*<span class="hljs-number">2</span>?startX-xrange/<span class="hljs-number">5</span>*<span class="hljs-number">2</span>:<span class="hljs-number">0</span>;<br><span class="hljs-title class_">Plotly</span>.<span class="hljs-title function_">update</span>(<span class="hljs-string">"plot"</span>, {}, {<span class="hljs-attr">xaxis</span>:{<span class="hljs-attr">range</span>:[startX, startX+xrange]}});<br></code></pre></td></tr></table></figure><p>setInterval会有一些延迟,目前还没有找到好的解决方案</p><h3 id="使用地址及效果展示"><a href="#使用地址及效果展示" class="headerlink" title="使用地址及效果展示"></a>使用地址及效果展示</h3><p><strong><a href="https://taotail.pythonanywhere.com/">https://taotail.pythonanywhere.com/</a></strong><br><img src="/2023/02/07/pitch-analys/%E9%A3%9E%E5%84%BF%E4%B9%90%E5%9B%A2-%E6%9C%88%E7%89%99%E6%B9%BE.png" alt="图片"></p>]]></content>
<categories>
<category>Python</category>
<category>Flask</category>
</categories>
<tags>
<tag>Pitch</tag>
<tag>Plotly</tag>
<tag>parsermouth</tag>
<tag>tensorflow</tag>
</tags>
</entry>
<entry>
<title>HtmlGenerator(2)</title>
<link href="/2022/10/17/HtmlGenerator2/"/>
<url>/2022/10/17/HtmlGenerator2/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h3 id="对上一篇进行补充"><a href="#对上一篇进行补充" class="headerlink" title="对上一篇进行补充"></a>对上一篇进行补充</h3><p>链接<a href="/2022/05/13/HtmlGenerator/">HTML表格生成器</a></p><h4 id="新增列单元格自动合并功能"><a href="#新增列单元格自动合并功能" class="headerlink" title="新增列单元格自动合并功能"></a>新增列单元格自动合并功能</h4><p>大概思路就是统计指定列,记录内容连续相等的单元格,然后进行HTML合并操作</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 生成HTML表格</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> tableStyle 表格样式 example "<table style=\"text-align:center;\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">"</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> columnsStyle 指定某列样式 </span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> cellStyles 指定某个单元格列样式 row<field, style></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> mergerFields 需要合并的列</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">generateTable</span><span class="hljs-params">(String tableStyle, Map<Integer,String> columnStyles, Map<Integer, Map<String, String>> cellStyles, String... mergerColFields)</span> {<br> <br> Map<Integer, Map<String, Integer>> mergeCols = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>(); <span class="hljs-comment">//存储合并单元格信息</span><br> Map<String, Integer> startRowMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>();<br> Map<String, Integer> rowsCountMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>();<br> <span class="hljs-keyword">for</span>(String mergerColField:mergerColFields) {<br> rowsCountMap.put(mergerColField, <span class="hljs-number">0</span>);<br> startRowMap.put(mergerColField, <span class="hljs-number">0</span>);<br> }<br> Map<String, Object> lastValues = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>();<br> <span class="hljs-type">int</span> <span class="hljs-variable">row</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br> <span class="hljs-comment">//统计合并信息</span><br> <span class="hljs-keyword">for</span>(Object element : collection) {<br> <span class="hljs-keyword">for</span>(String mergerColField:mergerColFields) {<br> <span class="hljs-type">Object</span> <span class="hljs-variable">fieldValue</span> <span class="hljs-operator">=</span> getFieldValueByName(element, mergerColField);<br> <span class="hljs-keyword">if</span>(row==<span class="hljs-number">0</span>) {<br> lastValues.put(mergerColField, fieldValue);<span class="hljs-comment">//初次使用需要</span><br> }<br> <span class="hljs-type">Object</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> lastValues.get(mergerColField);<br> <span class="hljs-keyword">if</span>((value!=<span class="hljs-literal">null</span> && value.equals(fieldValue)) || (value==<span class="hljs-literal">null</span> && fieldValue==<span class="hljs-literal">null</span>)) {<br> rowsCountMap.put(mergerColField, rowsCountMap.get(mergerColField) + <span class="hljs-number">1</span>);<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(rowsCountMap.get(mergerColField) ><span class="hljs-number">1</span>) {<br> Map<String, Integer> fieldCols = mergeCols.get(row) == <span class="hljs-literal">null</span>?<span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>():mergeCols.get(row);<br> fieldCols.put(mergerColField, rowsCountMap.get(mergerColField));<br> mergeCols.put(startRowMap.get(mergerColField), fieldCols);<br> rowsCountMap.put(mergerColField, <span class="hljs-number">1</span>);<br> startRowMap.put(mergerColField, row);<br> } <span class="hljs-keyword">else</span> {<br> startRowMap.put(mergerColField, row);<br> }<br> lastValues.put(mergerColField, fieldValue);<br> <span class="hljs-keyword">if</span>(row == collection.size()-<span class="hljs-number">1</span> && rowsCountMap.get(mergerColField)><span class="hljs-number">1</span>) {<br> <span class="hljs-type">int</span> <span class="hljs-variable">startRow</span> <span class="hljs-operator">=</span> startRowMap.get(mergerColField);<br> Map<String, Integer> fieldCols = mergeCols.get(startRow) == <span class="hljs-literal">null</span>?<span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>():mergeCols.get(startRow);<br> fieldCols.put(mergerColField, rowsCountMap.get(mergerColField));<br> mergeCols.put(startRow, fieldCols);<br> }<br> }<br> row++;<br> }<br> <span class="hljs-type">StringBuilder</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();<br> <span class="hljs-keyword">if</span>(tableStyle == <span class="hljs-literal">null</span>) {<br> content.append(DEFAULT_TABLE_STYLE);<br> } <span class="hljs-keyword">else</span> {<br> content.append(tableStyle);<br> }<br><br> <span class="hljs-comment">//表头创建</span><br> content.append(<span class="hljs-string">"<tr>"</span>);<br> <span class="hljs-keyword">for</span>(String key:<span class="hljs-built_in">this</span>.keySet()) {<br> content.append(<span class="hljs-string">"<td><h4>"</span>).append(get(key)).append(<span class="hljs-string">"</h4>"</span>);<br> }<br> content.append(<span class="hljs-string">"</tr>"</span>);<br> <span class="hljs-comment">//表体创建</span><br> row = <span class="hljs-number">0</span>;<br> rowsCountMap.clear();<br> <span class="hljs-keyword">for</span>(Object element : collection) {<br> <span class="hljs-keyword">if</span>(columnStyles!=<span class="hljs-literal">null</span> && columnStyles.get(row) != <span class="hljs-literal">null</span>) {<br> content.append(<span class="hljs-string">"<tr style\""</span>).append(columnStyles.get(row)).append(<span class="hljs-string">"\">"</span>);<br> } <span class="hljs-keyword">else</span> {<br> content.append(<span class="hljs-string">"<tr>"</span>);<br> }<br> Map<String, Integer> fieldCols = mergeCols.get(row);<br> rowsCountMap.forEach((k,v) -> {<br> rowsCountMap.put(k, v-<span class="hljs-number">1</span>);<br> });<br> <span class="hljs-keyword">for</span>(String key:<span class="hljs-built_in">this</span>.keySet()) {<br> <span class="hljs-keyword">if</span>(rowsCountMap.get(key)!=<span class="hljs-literal">null</span> && rowsCountMap.get(key) > <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>;<br> content.append(<span class="hljs-string">"<td "</span>);<br> <span class="hljs-keyword">if</span>(fieldCols!=<span class="hljs-literal">null</span> && fieldCols.get(key)!=<span class="hljs-literal">null</span>) {<br> rowsCountMap.put(key, fieldCols.get(key));<br> content.append(<span class="hljs-string">"rowspan=\""</span>).append(fieldCols.get(key)).append(<span class="hljs-string">"\""</span>);<br> }<br> <span class="hljs-keyword">if</span>(cellStyles!=<span class="hljs-literal">null</span> && cellStyles.get(<span class="hljs-number">0</span>)!=<span class="hljs-literal">null</span> && cellStyles.get(<span class="hljs-number">0</span>).get(key) != <span class="hljs-literal">null</span>) {<br> content.append(<span class="hljs-string">"style=\""</span>).append(cellStyles.get(<span class="hljs-number">0</span>).get(key)).append(<span class="hljs-string">"\""</span>);<br> }<br> content.append(<span class="hljs-string">">"</span>);<br> <span class="hljs-type">Object</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> getFieldValueByName(element, key);<br> <span class="hljs-keyword">if</span>(value != <span class="hljs-literal">null</span>) {<br> <span class="hljs-keyword">if</span>(value <span class="hljs-keyword">instanceof</span> java.math.BigDecimal) {<br> <span class="hljs-keyword">if</span>(bigDecimalFormat == HtmlGeneratorEnum.BIGDECIMAL_PERCENTAGE_FORMAT) {<br> value = ((BigDecimal) value).multiply(BigDecimal.valueOf(<span class="hljs-number">100</span>)).setScale(<span class="hljs-number">2</span>);<br> }<br> content.append(value).append(<span class="hljs-string">"%"</span>);<br> } <span class="hljs-keyword">else</span> {<br> content.append(value);<br> }<br> }<br> content.append(<span class="hljs-string">"</td>"</span>);<br> }<br> content.append(<span class="hljs-string">"</tr>"</span>);<br> row++;<br> }<br> content.append(<span class="hljs-string">"</table>"</span>);<br><span class="hljs-comment">//String s = content.toString();</span><br> <span class="hljs-keyword">return</span> content.toString();<br>}<br></code></pre></td></tr></table></figure><h4 id="单元格横向合并思路差不多,需求暂时没有,后续可能会写"><a href="#单元格横向合并思路差不多,需求暂时没有,后续可能会写" class="headerlink" title="单元格横向合并思路差不多,需求暂时没有,后续可能会写"></a>单元格横向合并思路差不多,需求暂时没有,后续可能会写</h4>]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>HtmlGenerator</tag>
</tags>
</entry>
<entry>
<title>基于OKHttp实现WebSocket客户端</title>
<link href="/2022/07/05/websocket-client-with-okhttp/"/>
<url>/2022/07/05/websocket-client-with-okhttp/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h2 id="新建WebSocketService类"><a href="#新建WebSocketService类" class="headerlink" title="新建WebSocketService类"></a>新建WebSocketService类</h2><h3 id="新建接口类为了后续回调使用"><a href="#新建接口类为了后续回调使用" class="headerlink" title="新建接口类为了后续回调使用"></a>新建接口类为了后续回调使用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * WebSocket监听事件接口</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">IWebSocketServiceListener</span> {<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onWSClosed</span><span class="hljs-params">(WebSocket webSocket, <span class="hljs-type">int</span> code, String reason)</span>;<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onWSClosing</span><span class="hljs-params">(WebSocket webSocket, <span class="hljs-type">int</span> code, String reason)</span>;<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onWSFailure</span><span class="hljs-params">(WebSocket webSocket, Throwable t, Response response)</span>;<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onWSMessage</span><span class="hljs-params">(WebSocket webSocket, String text)</span>;<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onWSMessage</span><span class="hljs-params">(WebSocket webSocket, ByteString bytes)</span>;<br><br>}<br><br></code></pre></td></tr></table></figure><h3 id="新建内部类继承WebSocketListener以及自定义回调接口"><a href="#新建内部类继承WebSocketListener以及自定义回调接口" class="headerlink" title="新建内部类继承WebSocketListener以及自定义回调接口"></a>新建内部类继承WebSocketListener以及自定义回调接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyWebSocketListener</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">WebSocketListener</span> {<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onOpen</span><span class="hljs-params">(WebSocket webSocket, Response response)</span> {<br> <span class="hljs-built_in">super</span>.onOpen(webSocket, response);<br> <span class="hljs-comment">//自定义心跳检测</span><br> heartBeatTimer = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Timer</span>();<br> heartBeatTimerTask = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TimerTask</span>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> {<br> webSocket.send(<span class="hljs-string">"ping"</span>);<br> }<br> };<br> heartBeatTimer.schedule(heartBeatTimerTask, <span class="hljs-number">0</span>, <span class="hljs-number">10000</span>);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onClosed</span><span class="hljs-params">(WebSocket webSocket, <span class="hljs-type">int</span> code, String reason)</span> {<br> <span class="hljs-built_in">super</span>.onClosed(webSocket, code, reason);<br> webSocketServiceListener.onWSClosed(webSocket, code, reason);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onClosing</span><span class="hljs-params">(WebSocket webSocket, <span class="hljs-type">int</span> code, String reason)</span> {<br> <span class="hljs-built_in">super</span>.onClosing(webSocket, code, reason);<br> webSocketServiceListener.onWSClosing(webSocket, code, reason);<br> <span class="hljs-comment">//失败重试</span><br> <span class="hljs-keyword">if</span>(retryTimes<MAX_RETRY_TIMES){<br> connect();<br> retryTimes ++;<br> }<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onFailure</span><span class="hljs-params">(WebSocket webSocket, Throwable t, Response response)</span> {<br> <span class="hljs-built_in">super</span>.onFailure(webSocket, t, response);<br> webSocketServiceListener.onWSFailure(webSocket, t, response);<br> <span class="hljs-comment">//失败重试</span><br><span class="hljs-comment">// if(retryTimes<MAX_RETRY_TIMES){</span><br><span class="hljs-comment">// connect();</span><br><span class="hljs-comment">// retryTimes ++;</span><br><span class="hljs-comment">// }</span><br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(WebSocket webSocket, String text)</span> {<br> <span class="hljs-built_in">super</span>.onMessage(webSocket, text);<br> <span class="hljs-type">JSONObject</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> JSONObject.parseObject(text);<br> <span class="hljs-keyword">if</span>(<span class="hljs-string">"ping"</span>.equals(json.getString(<span class="hljs-string">"type"</span>))){<br> heartBeat();<br> }<br> webSocketServiceListener.onWSMessage(webSocket, text);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(WebSocket webSocket, ByteString bytes)</span> {<br> <span class="hljs-built_in">super</span>.onMessage(webSocket, bytes);<br> webSocketServiceListener.onWSMessage(webSocket, bytes);<br> }<br>}<br><br></code></pre></td></tr></table></figure><h3 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebSocketService</span> {<br> <br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> OkHttpClient mClient;<br> <span class="hljs-keyword">private</span> WebSocket websocket;<br> <span class="hljs-keyword">private</span> IWebSocketServiceListener webSocketServiceListener;<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 重试次数</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> <span class="hljs-variable">retryTimes</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 最大重试次数</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">MAX_RETRY_TIMES</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-title function_">WebSocketService</span><span class="hljs-params">(IWebSocketServiceListener webSocketServiceListener)</span>{<br> <span class="hljs-built_in">this</span>.webSocketServiceListener = webSocketServiceListener;<br> connect();<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 建立websocket连接</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">connect</span><span class="hljs-params">()</span>{<br> <span class="hljs-keyword">if</span>(mClient == <span class="hljs-literal">null</span>){<br> mClient = <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpClient</span>.Builder()<br> .readTimeout(<span class="hljs-number">5</span>, TimeUnit.SECONDS)<span class="hljs-comment">//设置读取超时时间</span><br> .writeTimeout(<span class="hljs-number">5</span>, TimeUnit.SECONDS)<span class="hljs-comment">//设置写的超时时间</span><br> .connectTimeout(<span class="hljs-number">5</span>, TimeUnit.SECONDS)<span class="hljs-comment">//设置连接超时时间</span><br><span class="hljs-comment">// .pingInterval(10, TimeUnit.SECONDS) //OkHttp自带心跳检测,如果有自定义的需要使用heartBeatTimer进行定时发送心跳检测</span><br> .build();<br> }<br> <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">"wss://..."</span>;<br> <span class="hljs-type">Request</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Request</span>.Builder().get().url(url).build();<br> websocket = mClient.newWebSocket(request, <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyWebSocketListener</span>());<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 建立websocket连接</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">reConnect</span><span class="hljs-params">()</span>{<br> close();<br> connect();<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 发送消息</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> msg</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">sendMsg</span><span class="hljs-params">(String msg)</span>{<br> <span class="hljs-keyword">return</span> websocket.send(msg);<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">close</span><span class="hljs-params">()</span>{<br> <span class="hljs-keyword">if</span>(websocket != <span class="hljs-literal">null</span>){<br> websocket.close(<span class="hljs-number">1001</span>, <span class="hljs-string">"客户端主动关闭连接"</span>);<br> websocket = <span class="hljs-literal">null</span>;<br> }<br> }<br><br> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyWebSocketListener</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">WebSocketListener</span> {<br> <br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onOpen</span><span class="hljs-params">(WebSocket webSocket, Response response)</span> {<br> <span class="hljs-built_in">super</span>.onOpen(webSocket, response);<br> <span class="hljs-comment">//自定义心跳检测</span><br> heartBeatTimer = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Timer</span>();<br> heartBeatTimerTask = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TimerTask</span>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> {<br> webSocket.send(<span class="hljs-string">"ping"</span>);<br> }<br> };<br> heartBeatTimer.schedule(heartBeatTimerTask, <span class="hljs-number">0</span>, <span class="hljs-number">10000</span>);<br> }<br> <br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onClosed</span><span class="hljs-params">(WebSocket webSocket, <span class="hljs-type">int</span> code, String reason)</span> {<br> <span class="hljs-built_in">super</span>.onClosed(webSocket, code, reason);<br> webSocketServiceListener.onWSClosed(webSocket, code, reason);<br> }<br> <br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onClosing</span><span class="hljs-params">(WebSocket webSocket, <span class="hljs-type">int</span> code, String reason)</span> {<br> <span class="hljs-built_in">super</span>.onClosing(webSocket, code, reason);<br> webSocketServiceListener.onWSClosing(webSocket, code, reason);<br> <span class="hljs-comment">//失败重试</span><br> <span class="hljs-keyword">if</span>(retryTimes<MAX_RETRY_TIMES){<br> reConnect();<br> retryTimes ++;<br> }<br> }<br> <br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onFailure</span><span class="hljs-params">(WebSocket webSocket, Throwable t, Response response)</span> {<br> <span class="hljs-built_in">super</span>.onFailure(webSocket, t, response);<br> webSocketServiceListener.onWSFailure(webSocket, t, response);<br> <span class="hljs-comment">//失败重试</span><br> <span class="hljs-comment">// if(retryTimes<MAX_RETRY_TIMES){</span><br> <span class="hljs-comment">// reConnect();</span><br> <span class="hljs-comment">// retryTimes ++;</span><br> <span class="hljs-comment">// }</span><br> }<br> <br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(WebSocket webSocket, String text)</span> {<br> <span class="hljs-built_in">super</span>.onMessage(webSocket, text);<br> <span class="hljs-type">JSONObject</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> JSONObject.parseObject(text);<br> <span class="hljs-keyword">if</span>(<span class="hljs-string">"ping"</span>.equals(json.getString(<span class="hljs-string">"type"</span>))){<br> heartBeat();<br> }<br> webSocketServiceListener.onWSMessage(webSocket, text);<br> }<br> <br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(WebSocket webSocket, ByteString bytes)</span> {<br> <span class="hljs-built_in">super</span>.onMessage(webSocket, bytes);<br> webSocketServiceListener.onWSMessage(webSocket, bytes);<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><p>首先在需要使用的类中实现IWebSocketServiceListener接口<br>然后初始化webSocketService</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">WebSocketService = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocketService</span>(<span class="hljs-built_in">this</span>);<br></code></pre></td></tr></table></figure><p>然后在接口方法中实现具体的业务逻辑就可以了</p>]]></content>
<categories>
<category>Java</category>
<category>Net</category>
</categories>
<tags>
<tag>OKHttp</tag>
<tag>WebSocket</tag>
<tag>WebSocket Client</tag>
</tags>
</entry>
<entry>
<title>Java实现简易的消息队列</title>
<link href="/2022/05/26/EasyMessageQueue/"/>
<url>/2022/05/26/EasyMessageQueue/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>最近有个需求需要实现消息发送的频率限制,并且延后发送。使用了一个自定义的简易消息队列</p><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>定义一个Map用来存储不同消息类型的队列,队列使用的阻塞队列ArrayBlockingQueue</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ConcurrentHashMap<String, ArrayBlockingQueue<DingTalkRobotSendVO>> robotSendQueue = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span><>();<br></code></pre></td></tr></table></figure><p>一些队列及ScheduledThreadPoolExecutor线程池参数配置</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 阻塞队列大小</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">ROBOT_SEND_QUEUE_SIZE</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 队列插入移出等待时间</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Long</span> <span class="hljs-variable">ROBOT_SEND_QUEUE_WAIT_TIME</span> <span class="hljs-operator">=</span> <span class="hljs-number">3L</span>;<br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 定时任务线程池</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ScheduledExecutorService threadPool;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 一个周期内最多发送次数</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">SEND_MAX_COUNT</span> <span class="hljs-operator">=</span> <span class="hljs-number">20</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 初始化线程池</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">static</span> {<br> <span class="hljs-type">ThreadFactory</span> <span class="hljs-variable">threadFactory</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NameTreadFactory</span>();<br> <span class="hljs-type">RejectedExecutionHandler</span> <span class="hljs-variable">handler</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyIgnorePolicy</span>();<br> threadPool = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ScheduledThreadPoolExecutor</span>(<span class="hljs-number">5</span>, threadFactory, handler);<br>}<br></code></pre></td></tr></table></figure><p>线程池线程命名规则</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NameTreadFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">ThreadFactory</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">AtomicInteger</span> <span class="hljs-variable">mThreadNum</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>(<span class="hljs-number">1</span>);<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> Thread <span class="hljs-title function_">newThread</span><span class="hljs-params">(Runnable r)</span> {<br> <span class="hljs-type">Thread</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(r, <span class="hljs-string">"DingTalkRobotSend-Thread-"</span> + mThreadNum.getAndIncrement());<br> LOG.info(t.getName() + <span class="hljs-string">" has been created"</span>);<br> <span class="hljs-keyword">return</span> t;<br> }<br>}<br></code></pre></td></tr></table></figure><p>线程池拒绝策略</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyIgnorePolicy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">RejectedExecutionHandler</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">rejectedExecution</span><span class="hljs-params">(Runnable r, ThreadPoolExecutor e)</span> {<br> doLog(r, e);<br> }<br><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doLog</span><span class="hljs-params">(Runnable r, ThreadPoolExecutor e)</span> {<br> LOG.info(<span class="hljs-string">"线程池请求拒绝completedTaskCount: "</span> + e.getCompletedTaskCount());<br> }<br>}<br></code></pre></td></tr></table></figure><p>核心队列消费线程定时任务</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 为每一个队列发布定时任务任务</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> queue</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addTask</span><span class="hljs-params">(<span class="hljs-keyword">final</span> ArrayBlockingQueue<DingTalkRobotSendVO> queue)</span> {<br> <span class="hljs-comment">//等待上一个任务结束时才开始计时</span><br> threadPool.scheduleWithFixedDelay(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> {<br> <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br> LOG.info(<span class="hljs-string">"Start Queue Size:"</span> + queue.size());<br> <span class="hljs-keyword">while</span>(queue.size()><span class="hljs-number">0</span> && count < SEND_MAX_COUNT) {<br> count ++;<br> <span class="hljs-type">DingTalkRobotSendVO</span> <span class="hljs-variable">robotSendVO</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">//取出数据若无则等待加入队列元素一定时间</span><br> robotSendVO = queue.poll(ROBOT_SEND_QUEUE_WAIT_TIME, TimeUnit.SECONDS);<br> Thread.sleep(<span class="hljs-number">500</span>);<br> LOG.info(robotSendVO.toString());<br> } <span class="hljs-keyword">catch</span> (InterruptedException e) {<br> LOG.error(<span class="hljs-string">"队列取出阻塞异常{}"</span>, e);<br> }<br> }<br> LOG.info(<span class="hljs-string">"End Queue Size:"</span> + queue.size());<br> }<br> }, <span class="hljs-number">1</span>, <span class="hljs-number">60</span>, TimeUnit.SECONDS);<br>}<br></code></pre></td></tr></table></figure><p>添加任务到消息队列</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java">ArrayBlockingQueue<DingTalkRobotSendVO> queue;<br><span class="hljs-comment">//ConcurrentHashMap写是线程安全读是不安全的,此处加锁为了避免读取不到robotSendQueue数据而重复添加覆盖队列</span><br><span class="hljs-keyword">synchronized</span> (QueueManager.robotSendQueue) {<br> queue = QueueManager.robotSendQueue.get(sendType);<br> <span class="hljs-keyword">if</span>(queue == <span class="hljs-literal">null</span>) {<br> queue = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayBlockingQueue</span><DingTalkRobotSendVO>(QueueManager.ROBOT_SEND_QUEUE_SIZE, <span class="hljs-literal">true</span>);<br> QueueManager.robotSendQueue.put(dingTalkRobotSendVO.getRobotUrl(), queue);<br> QueueManager.addTask(queue);<br> LOG.info(<span class="hljs-string">"加入任务组"</span> + dingTalkRobotSendVO.getRobotUrl());<br> }<br>}<br><span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">//添加任务到消息队列,若消息队列已满等待一定时间</span><br> <span class="hljs-type">boolean</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> queue.offer(dingTalkRobotSendVO, QueueManager.ROBOT_SEND_QUEUE_WAIT_TIME, TimeUnit.SECONDS);<br> <span class="hljs-keyword">if</span>(result) {<br> LOG.info(<span class="hljs-string">"加入队列成功"</span>);<br> } <span class="hljs-keyword">else</span> {<br> LOG.info(<span class="hljs-string">"加入队列失败"</span>);<br> }<br>} <span class="hljs-keyword">catch</span> (InterruptedException e) {<br> LOG.error(<span class="hljs-string">"加入队列阻塞异常{}"</span>, e);<br>}<br></code></pre></td></tr></table></figure><h3 id="附上完整代码"><a href="#附上完整代码" class="headerlink" title="附上完整代码"></a>附上完整代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 队列管理工具</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> XX</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">QueueManager</span> {<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">LOG</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(QueueManager.class);<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 所有队列存储</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ConcurrentHashMap<String, ArrayBlockingQueue<DingTalkRobotSendVO>> robotSendQueue = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span><>();<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 阻塞队列大小</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">ROBOT_SEND_QUEUE_SIZE</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 队列插入移出等待时间</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Long</span> <span class="hljs-variable">ROBOT_SEND_QUEUE_WAIT_TIME</span> <span class="hljs-operator">=</span> <span class="hljs-number">3L</span>;<br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 定时任务线程池</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ScheduledExecutorService threadPool;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 一周期内最多发送次数</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">SEND_MAX_COUNT</span> <span class="hljs-operator">=</span> <span class="hljs-number">20</span>;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">IDingTalkService</span> <span class="hljs-variable">dingTalkService</span> <span class="hljs-operator">=</span> (IDingTalkService) ServerContext<br>.getApplicationContext().getBean(<span class="hljs-string">"dingTalkService"</span>);<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 初始化线程池</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">static</span> {<br> <span class="hljs-type">ThreadFactory</span> <span class="hljs-variable">threadFactory</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NameTreadFactory</span>();<br> <span class="hljs-type">RejectedExecutionHandler</span> <span class="hljs-variable">handler</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyIgnorePolicy</span>();<br> threadPool = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ScheduledThreadPoolExecutor</span>(<span class="hljs-number">5</span>, threadFactory, handler);<br>}<br> <br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 为每一个队列发布定时任务任务</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> queue</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addTask</span><span class="hljs-params">(<span class="hljs-keyword">final</span> ArrayBlockingQueue<DingTalkRobotSendVO> queue)</span> {<br> <span class="hljs-comment">//等待上一个任务结束时才开始计时</span><br> threadPool.scheduleWithFixedDelay(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() {<br><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> {<br><span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>LOG.info(<span class="hljs-string">"Start Queue Size:"</span> + queue.size());<br><span class="hljs-keyword">while</span>(queue.size()><span class="hljs-number">0</span> && count < SEND_MAX_COUNT) {<br>count ++;<br><span class="hljs-type">DingTalkRobotSendVO</span> <span class="hljs-variable">robotSendVO</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br><span class="hljs-keyword">try</span> {<br>robotSendVO = queue.poll(ROBOT_SEND_QUEUE_WAIT_TIME, TimeUnit.SECONDS);<br>Thread.sleep(<span class="hljs-number">500</span>);<br>LOG.info(robotSendVO.toString());<br>} <span class="hljs-keyword">catch</span> (InterruptedException e) {<br>LOG.error(<span class="hljs-string">"队列取出阻塞异常{}"</span>, e);<br>}<br>}<br>LOG.info(<span class="hljs-string">"End Queue Size:"</span> + queue.size());<br>}<br>}, <span class="hljs-number">1</span>, <span class="hljs-number">60</span>, TimeUnit.SECONDS);<br> }<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 线程命名规则</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> XX</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NameTreadFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">ThreadFactory</span> {<br><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">AtomicInteger</span> <span class="hljs-variable">mThreadNum</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>(<span class="hljs-number">1</span>);<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> Thread <span class="hljs-title function_">newThread</span><span class="hljs-params">(Runnable r)</span> {<br> <span class="hljs-type">Thread</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(r, <span class="hljs-string">"DingTalkRobotSend-Thread-"</span> + mThreadNum.getAndIncrement());<br> LOG.info(t.getName() + <span class="hljs-string">" has been created"</span>);<br> <span class="hljs-keyword">return</span> t;<br> }<br> }<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 拒绝策略</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> XX</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyIgnorePolicy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">RejectedExecutionHandler</span> {<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">rejectedExecution</span><span class="hljs-params">(Runnable r, ThreadPoolExecutor e)</span> {<br> doLog(r, e);<br> }<br><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doLog</span><span class="hljs-params">(Runnable r, ThreadPoolExecutor e)</span> {<br> LOG.info(<span class="hljs-string">"线程池请求拒绝completedTaskCount: "</span> + e.getCompletedTaskCount());<br> }<br> }<br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Java</category>
<category>多线程</category>
</categories>
<tags>
<tag>java</tag>
<tag>Queue</tag>
<tag>MessageQueue</tag>
<tag>ConcurrentHashMap</tag>
</tags>
</entry>
<entry>
<title>记录一下Spring框架中的频率次数拦截限制</title>
<link href="/2022/05/24/RequestLimit/"/>
<url>/2022/05/24/RequestLimit/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h2 id="定义注解"><a href="#定义注解" class="headerlink" title="定义注解"></a>定义注解</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Documented</span><br><span class="hljs-meta">@Target(ElementType.METHOD)</span> <span class="hljs-comment">// 说明该注解只能放在方法上面</span><br><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> RequestLimit {<br> <span class="hljs-type">long</span> <span class="hljs-title function_">time</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-number">60000</span>; <span class="hljs-comment">// 限制时间 单位:毫秒</span><br> <span class="hljs-type">int</span> <span class="hljs-title function_">count</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-number">20</span>; <span class="hljs-comment">// 允许请求的次数</span><br>}<br></code></pre></td></tr></table></figure><h2 id="配置拦截器"><a href="#配置拦截器" class="headerlink" title="配置拦截器"></a>配置拦截器</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ReqLimitInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">HandlerInterceptor</span> {<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ConcurrentHashMap<String, ExpiringMap<Object, Object>> book = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span><String, ExpiringMap<Object, Object>>();<br><br><span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">preHandle</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> {<br><span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> request.getParameter(<span class="hljs-string">"robotUrl"</span>)==<span class="hljs-literal">null</span>?request.getRequestURI():request.getParameter(<span class="hljs-string">"robotUrl"</span>);<br><span class="hljs-type">HandlerMethod</span> <span class="hljs-variable">handlerMethod</span> <span class="hljs-operator">=</span> (HandlerMethod) handler;<br> <span class="hljs-comment">// 获取接口方法的注解</span><br> <span class="hljs-type">RequestLimit</span> <span class="hljs-variable">annotation</span> <span class="hljs-operator">=</span> handlerMethod.getMethodAnnotation(RequestLimit.class);<br> <span class="hljs-keyword">if</span> (annotation == <span class="hljs-literal">null</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br><span class="hljs-comment">// 获取Map对象, 如果没有则返回默认值使用book.getOrDefault</span><br><span class="hljs-comment">// 第一个参数是key, 第二个参数是默认值</span><br>ExpiringMap<Object, Object> expiringMap = book.get(request.getRequestURI()) == <span class="hljs-literal">null</span>?<br>ExpiringMap.builder().variableExpiration().build():book.get(request.getRequestURI());<br><br><span class="hljs-type">Integer</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> (Integer) expiringMap.get(url);<br><span class="hljs-keyword">if</span>(count == <span class="hljs-literal">null</span>) {<br>count = <span class="hljs-number">0</span>;<br>}<br><br><span class="hljs-keyword">if</span> (count >= annotation.count()) { <span class="hljs-comment">// 超过次数,不执行目标方法</span><br><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TechnologyException</span>(<span class="hljs-string">"ERR-0300"</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>[] {annotation.time(), annotation.count()});<br>} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count == <span class="hljs-number">0</span>){ <span class="hljs-comment">// 第一次请求时,设置有效时间</span><br>expiringMap.put(url, count + <span class="hljs-number">1</span>, ExpirationPolicy.CREATED, annotation.time(), TimeUnit.MILLISECONDS);<br>} <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 未超过次数, 记录加一</span><br>expiringMap.put(url, count + <span class="hljs-number">1</span>);<br>}<br>book.put(request.getRequestURI(), expiringMap);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br><br><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">afterCompletion</span><span class="hljs-params">(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)</span><br><span class="hljs-keyword">throws</span> Exception {<br><br>}<br><br><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">postHandle</span><span class="hljs-params">(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)</span><br><span class="hljs-keyword">throws</span> Exception {<br><br>}<br>}<br></code></pre></td></tr></table></figure><p>这里使用了一个三方net.jodah.expiringmap.ExpiringMap用于实现Map数据过期自动删除,也可以使用redis缓存等实现。<br>同时除了拦截器也可以使用AOP实现。<br>需要配合异常拦截器返回异常结果</p><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><p>Controller方法加上注解即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestLimit(time = 60000, count = 20)</span><br></code></pre></td></tr></table></figure><h3 id="SpringMVC需要在配置文件中添加配置"><a href="#SpringMVC需要在配置文件中添加配置" class="headerlink" title="SpringMVC需要在配置文件中添加配置"></a>SpringMVC需要在配置文件中添加配置</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-comment"><!-- 拦截器 --></span><br><span class="hljs-tag"><<span class="hljs-name">mvc:interceptors</span>></span><br> <span class="hljs-comment"><!--拦截所有资源--></span><br> <span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"*.ReqLimitInterceptor"</span>></span><span class="hljs-tag"></<span class="hljs-name">bean</span>></span><br><span class="hljs-tag"></<span class="hljs-name">mvc:interceptors</span>></span><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Java</category>
<category>Spring</category>
</categories>
<tags>
<tag>Spring</tag>
<tag>SpringMVC</tag>
<tag>SpringBoot</tag>
<tag>Interceptor</tag>
<tag>拦截器</tag>
<tag>频率限制</tag>
</tags>
</entry>
<entry>
<title>HtmlGenerator-HTML表格等生成器</title>
<link href="/2022/05/13/HtmlGenerator/"/>
<url>/2022/05/13/HtmlGenerator/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">HTMLGenerator</span> <span class="hljs-variable">htmlGenerator</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HTMLGenerator</span>(traceHeaderVOs);<br>htmlGenerator.put(<span class="hljs-string">"XXX"</span>, <span class="hljs-string">"表头名"</span>); <span class="hljs-comment">//XXX属性名</span><br>...<br>htmlGenerator.generateTable();<br></code></pre></td></tr></table></figure><h3 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br></pre></td><td class="code"><pre><code class="hljs java"><br><span class="hljs-keyword">import</span> java.beans.PropertyDescriptor;<br><span class="hljs-keyword">import</span> java.lang.reflect.Method;<br><span class="hljs-keyword">import</span> java.util.Collection;<br><span class="hljs-keyword">import</span> java.util.LinkedHashMap;<br><span class="hljs-keyword">import</span> java.util.Map;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Description</span> HTML生成器</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> XX</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Param</span> </span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> </span><br><span class="hljs-comment">**/</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HTMLGenerator</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">LinkedHashMap</span><String, String> {<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">long</span> <span class="hljs-variable">serialVersionUID</span> <span class="hljs-operator">=</span> -<span class="hljs-number">2844894776581720941L</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 生成数据集合</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> Collection<?> collection;<br><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">HTMLGenerator</span><span class="hljs-params">(Collection<?> collection)</span> {<br><span class="hljs-built_in">super</span>();<br><span class="hljs-built_in">this</span>.collection = collection;<br>}<br><br><span class="hljs-comment">//private static HTMLGenerator htmlGenerator;</span><br><span class="hljs-comment">//</span><br><span class="hljs-comment">//public static synchronized HTMLGenerator getInstance(Collection<?> collection) {</span><br><span class="hljs-comment">//if(htmlGenerator == null) {</span><br><span class="hljs-comment">//htmlGenerator = new HTMLGenerator();</span><br><span class="hljs-comment">//}</span><br><span class="hljs-comment">//htmlGenerator.setCollection(collection);</span><br><span class="hljs-comment">//return htmlGenerator;</span><br><span class="hljs-comment">//}</span><br><br><span class="hljs-keyword">public</span> Collection<?> getCollection() {<br><span class="hljs-keyword">return</span> collection;<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setCollection</span><span class="hljs-params">(Collection<?> collection)</span> {<br><span class="hljs-built_in">this</span>.collection = collection;<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 默认表格样式</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">DEFAULT_TABLE_STYLE</span> <span class="hljs-operator">=</span> <span class="hljs-string">"<style>.pure-table{text-align:center;border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}</style>"</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 附加样式</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">String</span> <span class="hljs-variable">style</span> <span class="hljs-operator">=</span> <span class="hljs-string">""</span>;<br><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">generateTable</span><span class="hljs-params">()</span> {<br><span class="hljs-keyword">return</span> generateTable(<span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);<br>}<br><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">generateTable</span><span class="hljs-params">(String tableStyle)</span> {<br><span class="hljs-keyword">return</span> generateTable(tableStyle, <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 生成HTML表格</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> tableStyle 表格样式 example "<table style=\"text-align:center;\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">"</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> columnsStyles 指定某列样式 </span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> cellStyles 指定某个单元格列样式</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">generateTable</span><span class="hljs-params">(String tableStyle, Map<Integer,String> columnsStyles, Map<Integer,RowColumnPair> cellStyles)</span> {<br><span class="hljs-comment">//TODO columnsStyle</span><br><span class="hljs-comment">//TODO cellStyles</span><br><span class="hljs-type">StringBuilder</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();<br>content.append(DEFAULT_TABLE_STYLE);<br><span class="hljs-keyword">if</span>(tableStyle == <span class="hljs-literal">null</span>) {<br>content.append(<span class="hljs-string">"<table class=\"pure-table\">"</span>);<br>} <span class="hljs-keyword">else</span> {<br>content.append(tableStyle);<br>}<br><span class="hljs-comment">//表头创建</span><br>content.append(<span class="hljs-string">"<tr>"</span>);<br><span class="hljs-keyword">for</span>(String key:<span class="hljs-built_in">this</span>.keySet()) {<br>content.append(<span class="hljs-string">"<td><h4>"</span>).append(get(key)).append(<span class="hljs-string">"</h4>"</span>);<br>}<br>content.append(<span class="hljs-string">"</tr>"</span>);<br><span class="hljs-comment">//表体创建</span><br><span class="hljs-type">int</span> <span class="hljs-variable">row</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br><span class="hljs-keyword">for</span>(Object element : collection) {<br>content.append(<span class="hljs-string">"<tr>"</span>);<br><span class="hljs-keyword">for</span>(String key:<span class="hljs-built_in">this</span>.keySet()) {<br>content.append(<span class="hljs-string">"<td>"</span>).append(getFieldValueByName(element, key)).append(<span class="hljs-string">"</td>"</span>);<br>}<br>content.append(<span class="hljs-string">"</tr>"</span>);<br>row++;<br>}<br>content.append(<span class="hljs-string">"</table>"</span>);<br><span class="hljs-keyword">return</span> content.toString();<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 通过反射获取参数对应的属性值</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> o 对象</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> fieldName 字段名</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> String <span class="hljs-title function_">getFieldValueByName</span><span class="hljs-params">(Object o, String fieldName)</span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-type">PropertyDescriptor</span> <span class="hljs-variable">pd</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PropertyDescriptor</span>(fieldName, o.getClass());<br> <span class="hljs-type">Method</span> <span class="hljs-variable">getMethod</span> <span class="hljs-operator">=</span> pd.getReadMethod();<br> <span class="hljs-type">Object</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> getMethod.invoke(o);<br> <span class="hljs-type">return</span> <span class="hljs-variable">null</span> <span class="hljs-operator">=</span>= obj ? <span class="hljs-literal">null</span> : obj.toString(); <br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>}<br><br> }<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 用于定位表格单元格</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> XX</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RowColumnPair</span> {<br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 属性名</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> String columnField;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 样式</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> String style;<br><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">RowColumnPair</span><span class="hljs-params">(String columnField, String style)</span> {<br><span class="hljs-built_in">super</span>();<br><span class="hljs-built_in">this</span>.columnField = columnField;<br><span class="hljs-built_in">this</span>.style = style;<br>}<br><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">getColumnField</span><span class="hljs-params">()</span> {<br><span class="hljs-keyword">return</span> columnField;<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setColumnField</span><span class="hljs-params">(String columnField)</span> {<br><span class="hljs-built_in">this</span>.columnField = columnField;<br>}<br><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">getStyle</span><span class="hljs-params">()</span> {<br><span class="hljs-keyword">return</span> style;<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setStyle</span><span class="hljs-params">(String style)</span> {<br><span class="hljs-built_in">this</span>.style = style;<br>}<br><br>}<br><br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>HtmlGenerator</tag>
</tags>
</entry>
<entry>
<title>RxJava、Retrofit结合使用进行网络请求</title>
<link href="/2022/05/11/RxJavaWithRetrofit/"/>
<url>/2022/05/11/RxJavaWithRetrofit/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>上两篇文章分别提到了retrofit以及RxJava的使用以及好处,这篇文章开始介绍它们的结合使用</p><h3 id="首先引入相关的包"><a href="#首先引入相关的包" class="headerlink" title="首先引入相关的包"></a>首先引入相关的包</h3><pre><code>implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' //rxjavaimplementation 'com.squareup.retrofit2:retrofit:2.9.0' //retrofitimplementation 'com.squareup.retrofit2:retrofit-converters:2.9.0' //rxjava2对应适配包(二选1)implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' //rxjava3对应适配包(二选1)implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.1'</code></pre><h3 id="创建retrofit接口"><a href="#创建retrofit接口" class="headerlink" title="创建retrofit接口"></a>创建retrofit接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> io.reactivex.rxjava3.core.Flowable;<br><span class="hljs-keyword">import</span> io.reactivex.rxjava3.core.Observable;<br><span class="hljs-keyword">import</span> okhttp3.MultipartBody;<br><span class="hljs-keyword">import</span> okhttp3.ResponseBody;<br><span class="hljs-keyword">import</span> retrofit2.Response;<br><span class="hljs-keyword">import</span> retrofit2.http.Body;<br><span class="hljs-keyword">import</span> retrofit2.http.Field;<br><span class="hljs-keyword">import</span> retrofit2.http.FormUrlEncoded;<br><span class="hljs-keyword">import</span> retrofit2.http.GET;<br><span class="hljs-keyword">import</span> retrofit2.http.Header;<br><span class="hljs-keyword">import</span> retrofit2.http.Headers;<br><span class="hljs-keyword">import</span> retrofit2.http.Multipart;<br><span class="hljs-keyword">import</span> retrofit2.http.POST;<br><span class="hljs-keyword">import</span> retrofit2.http.Part;<br><span class="hljs-keyword">import</span> retrofit2.http.Query;<br><span class="hljs-keyword">import</span> retrofit2.http.Url;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">IRetrofitApi</span> {<br> <span class="hljs-meta">@FormUrlEncoded</span><br> <span class="hljs-meta">@Headers({"Domain-Name: api"})</span><br> <span class="hljs-meta">@POST("payment/alipay")</span><br> Observable<Response<String>> <span class="hljs-title function_">alipay</span><span class="hljs-params">(<span class="hljs-meta">@Field("user_id")</span> String userId, <span class="hljs-meta">@Field("money")</span> <span class="hljs-type">int</span> money)</span>;<br>}<br></code></pre></td></tr></table></figure><p>其中RxJava的Observable替换了retrofit的Call类型,Response<String>泛型String可替换成返回的数据类型,我这里因为要解密返回密文所以使用String接收</String></p><h3 id="创建retrofit接口调用类"><a href="#创建retrofit接口调用类" class="headerlink" title="创建retrofit接口调用类"></a>创建retrofit接口调用类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RetrofitSource</span> {<br> <span class="hljs-keyword">private</span> IRetrofitApi api;<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">RetrofitSource</span> <span class="hljs-variable">retrofitSource</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> RetrofitSource <span class="hljs-title function_">instance</span><span class="hljs-params">()</span>{<br> <span class="hljs-keyword">if</span>(retrofitSource == <span class="hljs-literal">null</span>) {<br> retrofitSource = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RetrofitSource</span>();<br> }<br> <span class="hljs-keyword">return</span> retrofitSource;<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-title function_">RetrofitSource</span><span class="hljs-params">()</span> {<br> <span class="hljs-type">OkHttpClient</span> <span class="hljs-variable">mClient</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpClient</span>.Builder()<br> .addInterceptor(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Interceptor</span>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> okhttp3.Response <span class="hljs-title function_">intercept</span><span class="hljs-params">(Chain chain)</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-keyword">try</span> {<br> Request.<span class="hljs-type">Builder</span> <span class="hljs-variable">builder</span> <span class="hljs-operator">=</span> chain.request().newBuilder();<br> builder.addHeader(<span class="hljs-string">"Accept-Charset"</span>, <span class="hljs-string">"UTF-8"</span>) <span class="hljs-comment">//统一添加请求头</span><br> .addHeader(<span class="hljs-string">"Accept"</span>, <span class="hljs-string">" application/json"</span>);<br><br> <span class="hljs-type">Request</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> builder.build();<br> <span class="hljs-keyword">return</span> chain.proceed(request);<br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> e.printStackTrace();<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br> }<br> }).build();<br> <span class="hljs-type">Retrofit</span> <span class="hljs-variable">retrofit</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br> .baseUrl(AppConfig.host + API_VERSION)<br><span class="hljs-comment">// .addConverterFactory(GsonConverterFactory.create())</span><br> .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) <span class="hljs-comment">//用于上面提到的的Retrofit</span><br> .addConverterFactory(StringConverterFactory.create())<br> .client(mClient)<br> .build();<br> api = retrofit.create(IRetrofitApi.class);<br> }<br><br>}<br></code></pre></td></tr></table></figure><h2 id="初始化解析"><a href="#初始化解析" class="headerlink" title="初始化解析"></a>初始化解析</h2><h3 id="初始化OkHttpClient"><a href="#初始化OkHttpClient" class="headerlink" title="初始化OkHttpClient"></a>初始化OkHttpClient</h3><p>主要是添加拦截器来修改请求的配置</p><h3 id="初始化retrofit及retrofitApi"><a href="#初始化retrofit及retrofitApi" class="headerlink" title="初始化retrofit及retrofitApi"></a>初始化retrofit及retrofitApi</h3><pre><code>.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) </code></pre><p>用于上面提到的的Retrofit的Call转换成RxJava的Observable。</p><pre><code>.addConverterFactory(StringConverterFactory.create())</code></pre><p>用于将请求结果的解析成对应类型数据。</p><p>解析Gson可以使用</p><pre><code>.addConverterFactory(GsonConverterFactory.create())</code></pre><p>不要忘记单独添加依赖</p><pre><code>compile 'com.squareup.retrofit2:converter-gson:2.9.0'</code></pre><h2 id="请求方法"><a href="#请求方法" class="headerlink" title="请求方法"></a>请求方法</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> Observable<Response<String>> <span class="hljs-title function_">alipay</span><span class="hljs-params">(String userId, <span class="hljs-type">int</span> money)</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.api.alipay(userId, money);<br>}<br></code></pre></td></tr></table></figure><p>初始化时Retrofit已经用动态代理生成Observable网络请求对象,所以这里调用接口方法能直接进行网络接口请求</p><h1 id="使用RxJava调用接口"><a href="#使用RxJava调用接口" class="headerlink" title="使用RxJava调用接口"></a>使用RxJava调用接口</h1><p>RxJava调用可以简化代码使代码更清晰,以及能切换线程的方法,在android中实现结果返回时操作UI线程。省去了Android handler或者AsyncTask的复杂创建,抽象了HTTP请求接口,使代码结构更清楚</p><h2 id="自定义观察者基类"><a href="#自定义观察者基类" class="headerlink" title="自定义观察者基类"></a>自定义观察者基类</h2><p>创建一个观察者抽象基类实现Observer接口中onSubscribe onNext onComplete方法,泛型是返回的数据类型,新增了自己的两个回调接口方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">BaseObserver</span><T> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Observer</span><Response<String>> {<br><br> <span class="hljs-keyword">private</span> T result; <span class="hljs-comment">//返回结果</span><br><br> <span class="hljs-keyword">public</span> T <span class="hljs-title function_">getResult</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> result;<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setResult</span><span class="hljs-params">(T result)</span> {<br> <span class="hljs-built_in">this</span>.result = result;<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onSubscribe</span><span class="hljs-params">(<span class="hljs-meta">@NonNull</span> Disposable d)</span> {<br> <br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onNext</span><span class="hljs-params">(<span class="hljs-meta">@NonNull</span> Response<String> stringResponse)</span> {<br> ...<br> <span class="hljs-comment">//这边进行数据的封装处理</span><br> <span class="hljs-comment">//成功回调onSuccess(result)接口</span><br> <span class="hljs-comment">//失败回调onFail(msg)接口</span><br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onComplete</span><span class="hljs-params">()</span> {<br><br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 成功回调</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> result</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onSuccess</span><span class="hljs-params">(T result)</span>;<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 错误回调</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> msg</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onFail</span><span class="hljs-params">(String msg)</span>;<br><br>}<br></code></pre></td></tr></table></figure><h2 id="RetrofitSource调用接口并传入Observer对象处理"><a href="#RetrofitSource调用接口并传入Observer对象处理" class="headerlink" title="RetrofitSource调用接口并传入Observer对象处理"></a>RetrofitSource调用接口并传入Observer对象处理</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java">RetrofitSource.instance().alipay(uid)<br> .subscribeOn(Schedulers.io())<br> .observeOn(AndroidSchedulers.mainThread()) <span class="hljs-comment">//切换andoirdUI线程操作</span><br> .subscribe(<span class="hljs-keyword">new</span> <span class="hljs-title class_">BaseObserver</span><BaseResponse<ResultBean>>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onError</span><span class="hljs-params">(<span class="hljs-meta">@io</span>.reactivex.rxjava3.annotations.NonNull Throwable e)</span> {<br> <span class="hljs-comment">//请求失败回调</span><br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onSuccess</span><span class="hljs-params">(BaseResponse<ResultBean> result)</span> {<br> <span class="hljs-comment">//获取结果并进行UI操作</span><br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onFail</span><span class="hljs-params">(String msg)</span> {<br> <span class="hljs-comment">//返回结果处理失败回调</span><br> }<br> });<br> }<br></code></pre></td></tr></table></figure><p>到此已经能使用此方法进行网络请求操作了,结构清晰,代码简洁。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>对于网络访问的抽象与优化,实际上是个非常难的课题,在Retrofit之前,大家努力的方向基本上都是Volley/OkHttp这种围绕底层网络访问的工作。<br>因为越底层的东西越容易抽象,越上升到接近业务层,就越容易在纷扰的业务层中迷失。<br>Retrofit能精准地抓到Call网络工作对象这个关键点,并能通过一系列精巧的设计实现对这种类型“飘忽不定”的对象的自动化定制生产,着实令人赞叹。</p>]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>RxAndroid</tag>
<tag>Java</tag>
<tag>原创</tag>
<tag>Android</tag>
</tags>
</entry>
<entry>
<title>RxJava的使用</title>
<link href="/2022/05/11/RxJava/"/>
<url>/2022/05/11/RxJava/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><h3 id="RxJava介绍"><a href="#RxJava介绍" class="headerlink" title="RxJava介绍"></a>RxJava介绍</h3><p>首先要说明的一点,RxAndroid和RxJava是差不多的东西,只不过RxAndroid 针对Android平台做了一点调整。那么RxJava是什么?在其github上是这样讲的:一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库。这么讲可能还有点绕口,简单的讲实际上最重要的就是异步两字,RxJava可以简单的实现异步操作,并且不管逻辑多么复杂,它始终能够保持简洁性。<br>通常在Android中,非UI线程是不能更新UI界面的,而一些耗时的操作我们又不能放在UI线程,否则会导致界面卡顿。这种情况下,我们就需要切换线程来实现,即Handler和AsyncTask来实现,但是这两种都有个缺陷,代码非常多,非常杂,可读性非常差。所以,RxJava出现了,它能够两行代码就实现线程切换,非常的简单,使用起来就会让人感觉很爽,再也不用为异步操作写如此繁重的代码了。</p><h3 id="RxJava基本用法"><a href="#RxJava基本用法" class="headerlink" title="RxJava基本用法"></a>RxJava基本用法</h3><p>RxJava最核心的两个东西是Observables(被观察者,事件源)和Observer/Subscriber(观察者),还有将他们联系在一起的操作subscribe(订阅)。当被观察者发生变化时观察者能即使做出相应,就好像我们的按钮事件一样:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java">button.setOnClickListener(<span class="hljs-keyword">new</span> <span class="hljs-title class_">View</span>.OnClickListener() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onClick</span><span class="hljs-params">(View v)</span> {<br><br> }<br> });<br></code></pre></td></tr></table></figure><p>在这里button就是被观察者,OnClickListener就是观察者,setOnClickListener这个方法就相当于订阅操作,当button被按下时,OnClickListener监听到变化,调用OnClick做出反应,RxJava实现的就是类似这样的一个过程。注意这里的观察者有两种Observer,Subscriber,这两个其实是差不多的,Subscriber是对Observer的一种扩展,内部增加了OnStart方法,在事件未发送之前订阅,用于做一些准备工作,并且还有unsubscribe()用于取消订阅。<br>让我们来看一下ObServer的内部实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Observer</span><T> {<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onCompleted</span><span class="hljs-params">()</span>;<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onError</span><span class="hljs-params">(Throwable e)</span>;<br><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onNext</span><span class="hljs-params">(T t)</span>;<br><br>}<br></code></pre></td></tr></table></figure><p>可以看到ObServer本身是一个接口,内部有onNext(T t)方法:观测到所检测的被观察者有变化时做出相应反应。onCompleted()方法:RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。onCompleted():事件队列发生异常,要调用的方法。我们在定义一个观察者的时候,需要实现这些方法,来完成事件队列。<br>观察者有了,那么被观察者Observables怎么创建呢,RxJava提供了一系列操作符供我们调用,其中就有很多创建型操作符,举个例子,创建一个Observables,发出hello world字符串给观察者:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">Observable<String> myObservable = Observable.create( <br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Observable</span>.OnSubscribe<String>() { <br> <span class="hljs-meta">@Override</span> <br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">call</span><span class="hljs-params">(Subscriber<? <span class="hljs-built_in">super</span> String> sub)</span> { <br> sub.onNext(<span class="hljs-string">"Hello, world!"</span>); <br> sub.onCompleted(); <br> } <br> } );<br></code></pre></td></tr></table></figure><p>既然有了Observables,那我们就可以根据这个Observables创建一个观察者了,如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java">Observer<String> TestObserver=<span class="hljs-keyword">new</span> <span class="hljs-title class_">Observer</span><String>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onCompleted</span><span class="hljs-params">()</span> {<br><br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onError</span><span class="hljs-params">(Throwable e)</span> {<br><br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onNext</span><span class="hljs-params">(String s)</span> {<br> Log.i(TAG,s);<br> }<br> }<br></code></pre></td></tr></table></figure><p>这样我们就可以愉快的订阅了:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">myObservable.subscribe(TestObserver);<br></code></pre></td></tr></table></figure><p>这样,一个简单的RxJava订阅流程就完成了。这里可能很多人就有疑问了,关键的异步呢,体现在哪了?其实这个例子可能不是很明显,因为被观察者并不是一个耗时线程,不能很直观的体现异步。如果myObservable这是一个异步任务,比如网络请求,那么我们订阅之后,TestObserver会一直监听myObservable是否有返回,如果有,那么就做出响应,本质是一样的。</p><h3 id="RxJava的操作符"><a href="#RxJava的操作符" class="headerlink" title="RxJava的操作符"></a>RxJava的操作符</h3><p>RxJava一个强大的地方在于它的异步,另外一个强大的地方就在于它提供了强大的操作符支持。这里说明一下几个常用的操作符:</p><ul><li> ceate操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">Observable<String> myObservable = Observable.create( <br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Observable</span>.OnSubscribe<String>() { <br> <span class="hljs-meta">@Override</span> <br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">call</span><span class="hljs-params">(Subscriber<? <span class="hljs-built_in">super</span> String> sub)</span> { <br> sub.onNext(<span class="hljs-string">"Hello, world!"</span>); <br> sub.onCompleted(); <br> } <br> } );<br></code></pre></td></tr></table></figure><p>ceate操作符创建一个被观察者,在call方法里持有一个观察者Subscriber参数,当这个Observable被订阅时,执行观察者相应的方法。</p><ul><li> just操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Observable<String> myObservable = Observable.just(<span class="hljs-string">"Hello, world!"</span>);<br></code></pre></td></tr></table></figure><p>上面的代码可以用这一句话,代替。just操作符的功能就是将一个对象转化为Observable。</p><ul><li> from操作符</li></ul><p> <code class=" language-csharp">Observable<String> myObservable = Observable.from("Hello"," world!");</String></code></p><figure class="highlight golo"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs golo"><br>既然有了将单一对象转化为<span class="hljs-keyword">Observable</span>的操作符,那么必须要有将多个对象转化为<span class="hljs-keyword">Observable</span>的操作符,那就是from,from接收一个对象数组,然后逐一发射给观察者。<br><br>现在,用一个例子来说明其他操作符,比如我们有这样一个方法,根据学生姓名关键字查询学生列表,返回一个<span class="hljs-keyword">Observable</span>。<br><br>```java<br><span class="hljs-keyword">Observable</span><List<Student>> query(String name);<br></code></pre></td></tr></table></figure><p>然后我们的需求是一个一个的输出学生姓名,实现如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).subscribe(list -> {<br><span class="hljs-keyword">for</span>(Student student:list){<br> Log.i(TAG,student.getName());<br>});<br></code></pre></td></tr></table></figure><ul><li> flatMap操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .subscribe(student ->Log.i(TAG,student.getName());<br>);<br></code></pre></td></tr></table></figure><p>上面的例子用flatMap操作符,就可以变得很简洁,flatMap操作符的功能是接收一个接收一个Observable的输出作为输入,同时输出另外一个Observable,通常是接收一个list,然后逐一发送list的元素。比如这边的Student数组,变成了逐一发送student的Observable。</p><ul><li> Map操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .Map(student-><span class="hljs-keyword">return</span> student.getGrade())<br> .subscribe(grade->Log.i(TAG,grade+<span class="hljs-string">""</span>);<br>);<br></code></pre></td></tr></table></figure><p>现在我们只想输出每个学生的成绩,我们就需要Map操作符,它的功能是接收一种类型的Observable,转化为另外一种Observable,比如这边的Student类型转化为了Int型的Observable。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .Map(student-><span class="hljs-keyword">return</span> student.getGrade())<br> .subscribe(grade->Log.i(TAG,grade+<span class="hljs-string">""</span>);<br>);<br></code></pre></td></tr></table></figure><ul><li> filter操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .Map(student-><span class="hljs-keyword">return</span> student.getGrade())<br> .filter(grade->grade><span class="hljs-number">80</span>)<br> .subscribe(grade->Log.i(TAG,grade+<span class="hljs-string">""</span>);<br>);<br></code></pre></td></tr></table></figure><p>顾名思义filter操作符就是过滤用的,相当于加个判断条件,比如这边的就是加上分数大于80的条件.</p><ul><li> take操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .Map(student-><span class="hljs-keyword">return</span> student.getGrade())<br> .filter(grade->grade><span class="hljs-number">80</span>)<br> .take(<span class="hljs-number">5</span>)<br> .subscribe(grade->Log.i(TAG,grade+<span class="hljs-string">""</span>);<br>);<br></code></pre></td></tr></table></figure><p>take操作符的功能是限定个数,比如这边的功能就是限定我最多需要5个成绩。</p><ul><li> doOnNext操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .Map(student-><span class="hljs-keyword">return</span> student.getGrade())<br> .filter(grade->grade><span class="hljs-number">80</span>)<br> .take(<span class="hljs-number">5</span>)<br> .doOnNext(grade->save(grade))<br> .subscribe(grade->Log.i(TAG,grade+<span class="hljs-string">""</span>);<br>);<br></code></pre></td></tr></table></figure><p>doOnNext()允许我们在每次输出一个元素之前做一些额外的事情,比如这里的我们用来保存成绩。</p><ul><li> subscribeOn/observeOn操作符</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java">query(<span class="hljs-string">"王"</span>).flatMap(list -> Observable.from(list)) <br> .Map(student-><span class="hljs-keyword">return</span> student.getGrade())<br> .filter(grade->grade><span class="hljs-number">80</span>)<br> .take(<span class="hljs-number">5</span>)<br> .doOnNext(grade->save(grade))<br> .subscribeOn(Schedulers.io())<br> .observeOn(AndroidSchedulers.mainThread())<br> .subscribe(grade->Log.i(TAG,grade+<span class="hljs-string">""</span>);<br>);<br></code></pre></td></tr></table></figure><p>这两个操作符一般都是成对出现的,他们的功能就是切换线程。subscribeOn是指定被观察者的线程,observeOn是指定观察者的线程。比如这个例子中前面的订阅的工作在IO线程做,后面的打印功能在主线程做。</p><ul><li>小结<br> 怎么样,看起来我好像做了很多事情,又有判断数据,又有保存数据,又有选取数据,关键还有线程切换,然而,我实际上就写了那么一点代码,看起来是不是酷!这就是RxJava的魅力所在。</li></ul><h3 id="RxAndroid"><a href="#RxAndroid" class="headerlink" title="RxAndroid"></a>RxAndroid</h3><p>一开始说了,RxAndroid其实跟RxJava是差不多的,但是总归还是有一点变化的。比如Android上会有生命周期的问题,可能会导致内存泄漏:Observable持有Context导致的内存泄露。在这个问题上,我们的解决方法是这样的:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> Subscription mTestSubscription= Subscriptions.empty();<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">test</span><span class="hljs-params">()</span>{<br>mTestSubscription=myObservable.subscribe(TestObserver);<br>}<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onDestroy</span><span class="hljs-params">()</span> {<br> <span class="hljs-built_in">super</span>.onDestroy();<br> <span class="hljs-keyword">if</span> (mTestSubscription != <span class="hljs-literal">null</span> && !mTestSubscription.isUnsubscribed()) {<br> mTestSubscription.unsubscribe();<br> }<br><br> }<br></code></pre></td></tr></table></figure><p>就是在订阅的时候,用一个Subscription来保存它,然后在退出这个Activity的时候取消订阅。<br>另外还有一些专门为Android设计的RxView,比如以下防抖动的View:</p><p><code>java RxView.clicks(btn_click) .throttleFirst(3, TimeUnit.SECONDS) .subscribe();</code></p><p>参考:<a href="https://www.jianshu.com/p/d9fca152017b">https://www.jianshu.com/p/d9fca152017b</a></p>]]></content>
<categories>
<category>Java</category>
<category>RxJava</category>
</categories>
<tags>
<tag>RxJava</tag>
<tag>RxAndroid</tag>
<tag>Observer</tag>
<tag>Subscriber</tag>
</tags>
</entry>
<entry>
<title>retrofit的使用</title>
<link href="/2022/05/10/retrofit/"/>
<url>/2022/05/10/retrofit/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>Retrofit是squareup公司的开源力作,和同属squareup公司开源的OkHttp,一个负责网络调度,一个负责网络执行,为Android开发者提供了即方便又高效的网络访问框架。</p><p>不过,对于Retrofit这样设计精妙、代码简洁、使用方便的优秀开源项目,不能仅知道如何扩展和使用,或者仅研究它采用的技术或模式,“技”当然重要,但不能忽视了背后的“道”。</p><p>对于Retrofit,我们还应该看到的,是她在优化App架构方面的努力,以及她在提升开发效率方面的借鉴和启示。</p><p>本文试图通过一个具体场景,先总结Retrofit在架构中起到的作用,再分析其实现原理,最后探讨Retrofit给我们带来的启示。</p><p>我们先通过一个简单的应用场景来回顾Retrofit的使用过程。</p><h1 id="基本场景"><a href="#基本场景" class="headerlink" title="基本场景"></a>基本场景</h1><p>通常来说,使用Retrofit要经过这样几个步骤</p><ol><li><p>引用<br> 在gradle文件中引用retrofit</p><p> compile ‘com.squareup.retrofit2:retrofit:2.3.0’<br> compile ‘com.squareup.retrofit2:retrofit-converters:2.3.0’<br> compile ‘com.squareup.retrofit2:retrofit-adapters:2.3.0’</p></li></ol><p>如果需要使用更多扩展功能,比如gson转换,rxjava适配等,可以视自己需要继续添加引用</p><pre><code>compile 'com.squareup.retrofit2:converter-gson:2.3.0'compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'</code></pre><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs less"><br>如果现有的扩展包不能满足需要,还可以自己扩展<span class="hljs-selector-tag">converter</span>,<span class="hljs-selector-tag">adapter</span>等。<br><br><span class="hljs-number">1</span>. 定义接口<br> <span class="hljs-selector-tag">Retrofit</span>要求定义一个网络请求的接口,接口函数里要定义<span class="hljs-selector-tag">url</span>路径、请求参数、返回类型。<br><br>```<span class="hljs-selector-tag">java</span><br><span class="hljs-selector-tag">public</span> <span class="hljs-selector-tag">interface</span> <span class="hljs-selector-tag">INetApiService</span> {<br> <span class="hljs-variable">@GET</span>(<span class="hljs-string">"/demobiz/api.php"</span>)<br> Call<BizEntity> getBizInfo(<span class="hljs-variable">@Query</span>(<span class="hljs-string">"id"</span>) String id);<br>}<br></code></pre></td></tr></table></figure><p>在这个接口定义中,用注解@GET(“/demobiz/api.php”)声明了url路径,用注解@Query(“id”) 声明了请求参数。<br>最重要的是,用Call<BizEntity>声明了返回值是一个Retrofit的Call对象,并且声明了这个对象处理的数据类型为BizEntity,BizEntity是我们自定义的数据模型。</BizEntity></p><ol><li>依次获得<strong>Retrofit对象、接口实例对象、网络工作对象</strong><br> 首先,需要新建一个retrofit对象。<br> 然后,根据上一步的接口,实现一个retrofit加工过的接口对象。<br> 最后,调用接口函数,得到一个可以执行网络访问的网络工作对象。</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//新建一个Retrofit对象</span><br>Retrofit retrofit=<span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br>.baseUrl(Config.DOMAIN)<span class="hljs-comment">//要访问的网络地址域名,如http://www.zhihu.com</span><br>.addConverterFactory(GsonConverterFactory.create())<br>.build();<br>...<br><br><span class="hljs-comment">//用retrofit加工出对应的接口实例对象</span><br>INetApiService netApiService= retrofit.create(INetApiService.class);<br><span class="hljs-comment">//可以继续加工出其他接口实例对象</span><br>IOtherService otherService= retrofit.create(IOtherService.class);<br>···<br><br><span class="hljs-comment">//调用接口函数,获得网络工作对象</span><br>Call<BizEntity> callWorker= netApiService.getBizInfo(<span class="hljs-string">"id001"</span>);<br></code></pre></td></tr></table></figure><p>这个复杂的过程下来,最终得到的callWorker对象,才可以执行网络访问。</p><ol><li>访问网络数据<br> 用上一步获取的worker对象,执行网络请求</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java">callWorker.enqueue(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Callback</span><BizEntity>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onResponse</span><span class="hljs-params">(Call<BizEntity> call, Response<BizEntity> response)</span> {...}<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onFailure</span><span class="hljs-params">(Call<BizEntity> call, Throwable t)</span> {...}<br> });<br></code></pre></td></tr></table></figure><p>在回调函数里,取得我们需要的BizEntity数据对象。<br>网络访问结束。</p><h1 id="角色与作用"><a href="#角色与作用" class="headerlink" title="角色与作用"></a>角色与作用</h1><p>我们从上面的应用场景可以看出,Retrofit并不做网络请求,只是生成一个能做网络请求的对象。<br>Retrofit的作用是<strong>按照接口去定制Call网络工作对象</strong></p><p>什么意思?就是说:<br><strong>Retrofit不直接做网络请求<br>Retrofit不直接做网络请求<br>Retrofit不直接做网络请求</strong><br>重要的事情说三遍。</p><p>网络请求的目标虽然是数据,但是我们需要为这个数据写大量的配套代码,发起请求的对象Call,接收数据的对象CallBack,做数据转换的对象Converter,以及检查和处理异常的对象等。<br>这对于一个项目的开发、扩展和维护来说,都是成本和风险。</p><p>而Retrofit做的事情,就是为开发者节省这部分的工作量,Retrofit一方面从底层统一用OkHttp去做网络处理;另一方面在外层灵活提供能直接融入业务逻辑的Call网络访问对象。</p><p>具体来说,Retrofit只负责生产对象,生产能做网络请求的工作对象,他有点像一个工厂,只提供产品,工厂本身不处理网络请求,产品才能处理网络请求。<br>Retrofit在网络请求中的作用大概可以这样理解:</p><p><img src="/2022/05/10/retrofit/35dfaaa7.png"></p><p> Retrofit的作用</p><p>我们看到,从一开始,Retrofit要提供的就是个Call工作对象。<br>换句话说,对于给Retrofit提供的那个接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">INetApiService</span> {<br> <span class="hljs-meta">@GET("/demobiz/api.php")</span><br> Call<BizEntity> <span class="hljs-title function_">getBizInfo</span><span class="hljs-params">(<span class="hljs-meta">@Query("id")</span> String id)</span>;<br>}<br></code></pre></td></tr></table></figure><p>这个接口并不是传统意义上的网络请求接口,这个接口不是用来获取数据的接口,而是用来生产对象的接口,<strong>这个接口相当于一个工厂,接口中每个函数的返回值不是网络数据,而是一个能进行网络请求的工作对象,我们要先调用函数获得工作对象,再用这个工作对象去请求网络数据。</strong></p><p>所以Retrofit的实用价值意义在于,他能根据你的接口定义,灵活地生成对应的网络工作对象,然后你再择机去调用这个对象访问网络。<br>理解了这一点,我们才能去扩展Retrofit,并理解Retrofit的设计思想。</p><h1 id="功能扩展"><a href="#功能扩展" class="headerlink" title="功能扩展"></a>功能扩展</h1><p>我们先来看Retrofit能扩展哪些功能,然后再去理解Retrofit的工作原理。<br>Retrofit主要可以扩展三个地方:</p><ol><li>OkHttpClient<br> Retrofit使用OkHttpClient来实现网络请求,这个OkHttpClient虽然不能替换为其他的网络执行框架比如Volley,但是Retrofit允许我们使用自己扩展OkHttpClient,一般最常扩展的就是Interceptor拦截器了</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">OkHttpClient</span> <span class="hljs-variable">mClient</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpClient</span>.Builder()<br> .addInterceptor(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Interceptor</span>() {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> Response <span class="hljs-title function_">intercept</span><span class="hljs-params">(Chain chain)</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-keyword">try</span> {<br> Request.<span class="hljs-type">Builder</span> <span class="hljs-variable">builder</span> <span class="hljs-operator">=</span> chain.request().newBuilder();<br> builder.addHeader(<span class="hljs-string">"Accept-Charset"</span>, <span class="hljs-string">"UTF-8"</span>);<br> builder.addHeader(<span class="hljs-string">"Accept"</span>, <span class="hljs-string">" application/json"</span>);<br> builder.addHeader(<span class="hljs-string">"Content-type"</span>, <span class="hljs-string">"application/json"</span>);<br> <span class="hljs-type">Request</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> builder.build();<br> <span class="hljs-keyword">return</span> chain.proceed(request);<br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> e.printStackTrace();<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br> }<br> }).build();<br><br><span class="hljs-type">Retrofit</span> <span class="hljs-variable">retrofit</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br> .baseUrl(Config.DOMAIN)<br> .addConverterFactory(GsonConverterFactory.create())<br> .client(mClient)<br> .build();<br></code></pre></td></tr></table></figure><ol><li> addConverterFactory</li></ol><p>扩展的是对返回的数据类型的自动转换,把一种数据对象转换为另一种数据对象。<br>在上述场景中,GsonConverterFactory可以把Http访问得到的json字符串转换为Java数据对象BizEntity,这个BizEntity是在INetApiService接口中要求的的。<br>这种转换我们自己也经常做,很好理解。<br>如果现有的扩展包不能满足需要,可以继承Retrofit的接口。retrofit2.Converter<F,T>,自己实现Converter和ConverterFactory。<br>在创建Retrofit对象时,可以插入我们自定义的ConverterFactory。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//retrofit对象</span><br>Retrofit retrofit=<span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br>.baseUrl(Config.DOMAIN)<br>.addConverterFactory(GsonConverterFactory.create())<br>.addConverterFactory(YourConverterFactory.create())<span class="hljs-comment">//添加自定义Converter</span><br>.build();<br></code></pre></td></tr></table></figure><ol><li> addCallAdapterFactory</li></ol><p>扩展的是对网络工作对象callWorker的自动转换,把Retrofit中执行网络请求的Call对象,转换为接口中定义的Call对象。<br>这个转换不太好理解,我们可以对照下图来理解:</p><p><img src="/2022/05/10/retrofit/d496aeae.png"></p><p> callAdapter转换Call对象</p><p>Retrofit本身用一个OkHttpCall的类负责处理网络请求,而我们在接口中定义需要定义很多种Call,例如Call<BizEntity>,或者Flowable<BizEntity>等,接口里的Call和Retrofit里的OkHttpCall并不一致,所以我们需要用一个CallAdapter去做一个适配转换。<br>(Retrofit底层虽然使用了OkHttpClient去处理网络请求,但她并没有使用okhttp3.call这个Call接口,而是自己又建了一个retrofit2.Call接口,OkHttpCall继承的是retrofit2.Call,与okhttp3.call只是引用关系。<br>这样的设计符合依赖倒置原则,可以尽可能的与OkHttpClient解耦。)</BizEntity></BizEntity></p><p>这其实是Retrofit非常核心,也非常好用的一个设计,如果我们在接口中要求的函数返回值是个RxJava的Flowable对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">INetApiService</span> {<br> <span class="hljs-meta">@GET("/demobiz/api.php")</span><br> Flowable<BizEntity> <span class="hljs-title function_">getBizInfo</span><span class="hljs-params">(<span class="hljs-meta">@Query("id")</span> String id)</span>;<br>}<br></code></pre></td></tr></table></figure><p>那么我们只需要为Retrofit添加对应的扩展</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//retrofit对象</span><br>Retrofit retrofit=<span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br>.baseUrl(Config.DOMAIN)<br>.addConverterFactory(GsonConverterFactory.create())<br>.addCallAdapterFactory(RxJava2CallAdapterFactory.create())<br>.build();<br></code></pre></td></tr></table></figure><p>就能得到Flowable类型的callWorker对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//用retrofit加工出对应的接口实例对象</span><br>INetApiService netApiService= retrofit.create(INetApiService.class);<br>···<br><span class="hljs-comment">//调用接口函数,获得网络工作对象</span><br>Flowable<BizEntity> callWorker= netApiService.getBizInfo(<span class="hljs-string">"id001"</span>);<br></code></pre></td></tr></table></figure><p>在这里,callAdapter做的事情就是把retrofit2.Call对象适配转换为Flowable<T>对象。<br>同样,如果现有的扩展包不能满足需要,可以继承Retrofit的接口retrofit2.CallAdapter<R,T>,自己实现CallAdapter和CallAdapterFactory。</T></p><h1 id="Retrofit实现原理"><a href="#Retrofit实现原理" class="headerlink" title="Retrofit实现原理"></a>Retrofit实现原理</h1><p>Retrofit固然设计精妙,代码简洁,使用方便,但相应的,我们要理解Retrofit的实现原理也不太容易,这么精妙的设计是极佳的研究素材,我们不能仅仅停留在知道怎么使用,怎么扩展的阶段,那实在是对这个优秀开源项目的浪费。<br>其实,Retrofit使用的,就是动态代理,方法注解、建造者和适配器等成熟的技术或模式,但是由于她的设计紧凑,而且动态代理屏蔽了很多过程上的细节,所以比较难以理解。</p><h1 id="Retrofit实现原理——从动态代理开始"><a href="#Retrofit实现原理——从动态代理开始" class="headerlink" title="Retrofit实现原理——从动态代理开始"></a>Retrofit实现原理——从动态代理开始</h1><p>从前面的使用场景可知,retrofit会生成一个接口实例。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//用retrofit加工出对应的接口实例对象</span><br>INetApiService netApiService= retrofit.create(INetApiService.class);<br></code></pre></td></tr></table></figure><p>到Retrofit源码里看create函数,是一个动态代理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <T> T <span class="hljs-title function_">create</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Class<T> service)</span> {<br> ...<br> <span class="hljs-keyword">return</span> (T) Proxy.newProxyInstance(service.getClassLoader(), <span class="hljs-keyword">new</span> <span class="hljs-title class_">Class</span><?>[] { service },<br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InvocationHandler</span>() {<br> ...<br> ServiceMethod<Object, Object> serviceMethod =<br> (ServiceMethod<Object, Object>) loadServiceMethod(method);<br> OkHttpCall<Object> okHttpCall = <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpCall</span><>(serviceMethod, args);<br> <span class="hljs-keyword">return</span> serviceMethod.callAdapter.adapt(okHttpCall);<br> }<br> });<br>}<br></code></pre></td></tr></table></figure><p>要理解动态代理,最好要看到动态生成的代理类。</p><p>由于动态代理是在运行时动态生成的代理类,用常规的反编译方法无法查看,一般要使用Java提供的sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces)函数生成代理类,函数会返回byte[]字节码,然后对字节码反编译得到Java代码。<br>有一个小问题是,AndroidStudio并不提供sun.misc这个包,我们需要用IntelliJ或者Eclipse建立一个Java工程,在Java环境里调用这个函数。</p><p>拿到的代理类,大概是这样的:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">INetApiService</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Proxy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">INetApiService</span> {<br> ...<span class="hljs-comment">//一些Object自带方法</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Method m3;<span class="hljs-comment">//接口定义的方法</span><br> <span class="hljs-keyword">static</span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">//Object自带方法的初始化</span><br> m0,m1,m2 = ...<br> <span class="hljs-comment">//接口中定义的方法</span><br> m3 = Class.forName(<span class="hljs-string">"com.demo.net$INetApiService"</span>)<span class="hljs-comment">//反射接口类</span><br> .getMethod(<span class="hljs-string">"getBizInfo"</span>,<span class="hljs-comment">//反射函数</span><br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Class</span>[] { Class.forName(<span class="hljs-string">"java.lang.String"</span>) });<span class="hljs-comment">//反射参数</span><br> <span class="hljs-comment">//接口中定义的其他方法</span><br> ...<br> } <br> ...<br> }<br><span class="hljs-comment">//返回接口实例对象</span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">INetApiService</span> <span class="hljs-params">(InvocationHandler invocationHandler)</span>{<br> <span class="hljs-built_in">super</span>(invocationHandler);<br>}<br><span class="hljs-comment">//</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> Call <span class="hljs-title function_">getBizInfo</span><span class="hljs-params">(String str)</span>{<br> ...<br> <span class="hljs-keyword">try</span>{<span class="hljs-comment">//用Handler去调用</span><br> <span class="hljs-keyword">return</span> (Call)<span class="hljs-built_in">this</span>.h.invoke(<span class="hljs-built_in">this</span>, m3, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>[]{str});<br> }<br>}<br><br>}<br></code></pre></td></tr></table></figure><p>我们可以看到,代理类生成的是一个INetApiService接口的实例对象,该对象的getBizInfo函数返回的是接口中定义的Call网络工作对象,这也体现了Retrofit的核心价值,生成接口定义的Call网络工作对象。</p><p>那么,这个Call网络工作对象是如何生成的呢,上面动态代理生成的代码是这样的:</p><pre><code>return (Call)this.h.invoke(this, m3, new Object[]{str});</code></pre><p>也就是说,这个Call网络工作对象是在InvocationHandler中实现的,也就是在Retrofit.create函数中,由InvocationHandler实现的。</p><p>这样我们就明白了,<strong>Retrofit使用动态代理,其实是为了开发者在写代码时方便调用,而真正负责生产Call网络工作对象的,还是Retrofit.create函数中定义的这个InvocationHandler</strong>,这个InvocationHandler的代码我们再贴一遍:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">new</span> <span class="hljs-title class_">InvocationHandler</span>() {<br> ...<br> ServiceMethod<Object, Object> serviceMethod =<br> (ServiceMethod<Object, Object>) loadServiceMethod(method);<br> OkHttpCall<Object> okHttpCall = <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpCall</span><>(serviceMethod, args);<br> <span class="hljs-keyword">return</span> serviceMethod.callAdapter.adapt(okHttpCall);<br>}<br></code></pre></td></tr></table></figure><p>ServiceMethod能让我们准确解析到INetApiService中定义的函数,为最后的适配转换提供转换目标,详细分析我们后面再说,先看适配转换的过程。</p><p>我们看到,Retrofit内部默认使用OkHttpCall对象去处理网络请求,但是返回的网络工作对象是经过适配器转换的,转换成接口定义的那种Call网络工作对象。</p><p>这个适配转换,就是Retrofit能按照接口去定制Call网络工作对象的秘密。</p><h1 id="Retrofit实现原理——适配转换Call对象"><a href="#Retrofit实现原理——适配转换Call对象" class="headerlink" title="Retrofit实现原理——适配转换Call对象"></a>Retrofit实现原理——适配转换Call对象</h1><p>我们在初始化Retrofit对象时,好像不添加CallAdapterFactory也能实现适配转换。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//retrofit对象</span><br>Retrofit retrofit=<span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br>.baseUrl(Config.DOMAIN)<br>.addConverterFactory(GsonConverterFactory.create())<br><span class="hljs-comment">//可以不添加CallAdapterFactory</span><br>.build();<br></code></pre></td></tr></table></figure><p>这是怎么回事呢,我们知道Retrofit使用了建造者模式,建造者模式的特定就是实现了建造和使用的分离,所以建造者模式的建造函数里,一般会有很复杂的对象创建和初始化过程,所以我们要看一下Retrofit的build函数。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> Retrofit <span class="hljs-title function_">build</span><span class="hljs-params">()</span> {<br> ...<br> okhttp3.Call.<span class="hljs-type">Factory</span> <span class="hljs-variable">callFactory</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.callFactory;<br> <span class="hljs-keyword">if</span> (callFactory == <span class="hljs-literal">null</span>) {<br> callFactory = <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpClient</span>();<span class="hljs-comment">//使用OkHttpClient处理网络请求</span><br> }<br> ...<br> <span class="hljs-comment">//根据当前运行平台,设置默认的callAdapterFactory</span><br> adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));<br> ...<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>(callFactory, baseUrl, converterFactories, adapterFactories,<br> callbackExecutor, validateEagerly);<br> }<br></code></pre></td></tr></table></figure><p>这段代码里,我们看到Retrofit使用OkHttpClient处理网络请求,并且会添加默认的callAdapterFactory,这个platform是一个简单工厂,能根据当前系统平台去生成对应的callAdapterFactory</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Platform <span class="hljs-title function_">findPlatform</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">try</span> {<br> Class.forName(<span class="hljs-string">"android.os.Build"</span>);<br> <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT != <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Android</span>();<span class="hljs-comment">//根据当前系统平台返回相应的对象</span><br> }<br> ...<br> }<br> ...<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Android</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Platform</span> {<br> ...<br> <span class="hljs-meta">@Override</span> CallAdapter.Factory <span class="hljs-title function_">defaultCallAdapterFactory</span><span class="hljs-params">(<span class="hljs-meta">@Nullable</span> Executor callbackExecutor)</span> {<br> <span class="hljs-keyword">if</span> (callbackExecutor == <span class="hljs-literal">null</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AssertionError</span>();<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ExecutorCallAdapterFactory</span>(callbackExecutor);<br> }<br> ...<br> }<br></code></pre></td></tr></table></figure><p>这个Platform是Retrofit在Builder的构造函数里初始化的。</p><p>所以,在Retrofit.build()函数中,我们为Retrofit默认添加的callAdapterFactory,是在Platform中为Android系统设定的ExecutorCallAdapterFactory。<br>我们看ExecutorCallAdapterFactory的代码,这是一个工厂类,可以返回CallAdapter对象:</p><p> <code class=" language-tsx">@Override<br> public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {<br> ...<br> return new CallAdapter<Object, call<?>>() {<br> ...<br> // 转换后 转换前,也就是OkHttpCall<br> @Override public Call<Object> adapt(Call<Object> call) {<br> return new ExecutorCallbackCall<>(callbackExecutor, call);<br> }<br> };<br> }</Object></Object></Object,></code></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs swift"><br>在adapt函数中,适配器会把<span class="hljs-type">Retrofit中用来访问网络的OkHttpCall,转换为一个ExecutorCallbackCall</span>(继承了<span class="hljs-type">INetApiService接口里要求返回的网络工作对象retrofit2</span>.<span class="hljs-type">Call</span>),<br>这个例子里面,由于<span class="hljs-type">OkHttpCall和ExecutorCallbackCall都实现了retrofit2</span>.<span class="hljs-type">Call接口,结果出现了从Call</span><<span class="hljs-type">Object</span>>转换为<span class="hljs-type">Call</span><<span class="hljs-type">Object</span>>的情况,这可能不容易理解,我们换个<span class="hljs-type">RxJava2CallAdapterFactory来看看</span><br><br>```java<br><span class="hljs-comment">//RxJava2CallAdapterFactory中</span><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-type">CallAdapter</span><?, ?> <span class="hljs-keyword">get</span>(<span class="hljs-type">Type</span> returnType, <span class="hljs-type">Annotation</span>[] annotations, <span class="hljs-type">Retrofit</span> retrofit) {<br> <span class="hljs-operator">...</span><br> <span class="hljs-keyword">return</span> new <span class="hljs-type">RxJava2CallAdapter</span>(responseType, scheduler, isAsync, isResult, isBody, isFlowable,<br> isSingle, isMaybe, <span class="hljs-literal">false</span>);<br>}<br> <span class="hljs-comment">//RxJava2CallAdapter中</span><br> <span class="hljs-comment">// 转换后 转换前,也就是OkHttpCall</span><br> <span class="hljs-meta">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-type">Object</span> adapt(<span class="hljs-type">Call</span><<span class="hljs-type">R</span>> call) {<br> <span class="hljs-operator">...</span><br> <span class="hljs-type">Observable</span><?> observable;<br> <span class="hljs-operator">...</span><br> <span class="hljs-keyword">return</span> observable;<br> }<br></code></pre></td></tr></table></figure><p>这个CallAdapter的转换就比较明显了,把retrofit2.Call对象通过适配器转换为了一个实为Observable<?>的Object对象。</p><p>至此,我们可以理解Retrofit根据接口定义动态生产Call网络请求工作对象的原理了,其实就是通过适配器把retrofit2.Call对象转换为目标对象。</p><p>至于适配器转换过程中,如何实现的对象转换,就可以根据需求来自由实现了,比如利用静态代理等,如有必要,我们可以自行开发扩展,Retrofit框架并不限制我们对于适配器的实现方式。</p><h1 id="Retrofit实现原理——函数解析、网络请求和数据转换"><a href="#Retrofit实现原理——函数解析、网络请求和数据转换" class="headerlink" title="Retrofit实现原理——函数解析、网络请求和数据转换"></a>Retrofit实现原理——函数解析、网络请求和数据转换</h1><p>在前面分析中,我们知道了Retrofit的整体工作流程,就是Retrofit用动态代理生成Call网络请求对象,在这个过程中,用适配器把Retrofit底层的retrofit2.Call对象转换为INetApiService中定义的Call网络请求对象(如Flowable)。</p><p>问题是,Retrofit具体是如何知道了INetApiService中定义的Call网络请求对象,如何实现网络请求,以及如何执行的数据转换呢?</p><p>具体过程如下;<br>首先,根据INetApiService中定义的函数,解析函数,得到函数的具体定义,并生成对应的ServiceMethod。<br>然后,根据这个ServiceMethod,实现一个OkHttpCall的Call对象,负责在Retrofit底层实现网络访问。<br>其中,在网络访问返回了网络数据时,根据ServiceMethod实现数据转换。<br>最后,利用上一小节中匹配的适配器,把OkHttpCall对象转换为INetApiService要求的Call网络请求对象。</p><p>所以,我们要了解的就是函数解析、网络请求和数据转换这三个动作,至于最后的适配转换,在上一节中已经分析过了。</p><p><strong>1. 函数解析</strong><br>在接口函数里,用注解描述了输入参数,用Java对象定义了返回值类型,所以对输入参数和返回值,ServiceMethod采取了不同的方式去处理。<br><strong>输入参数</strong><br>输入参数是用来描述url的,它的处理相对简单,ServiceMethod会根据反射得到的Method,取得Annotation注解信息,这些注解是Retrofit自己预定义好的(retrofit2.http.*),ServiceMethod根据预先的定义,直接判断注解所属的逻辑分支,在有网络请求时分情况进行处理,就能得到目标url,http请求头等数据。<br><strong>返回值</strong><br>返回值是需要用CallAdapter去适配的,所以核心在于生成对应的CallAdapter。<br>在Retrofit生成Call网络工作对象时,她通过动态代理获取到了接口函数的Method定义,从这个Method中可以获取函数定义的返回对象类型,由于这个转换是需要CallAdapterFactory生产CallAdapter对象去实现,而Retrofit事先并不知道要使用哪个Factory,所以她是遍历所有的CallAdapterFactory,根据目标函数的返回值类型,让每个Factory都去尝试生产一个CallAdapter,哪个成功就用哪个。</p><p><strong>2. 网络请求</strong><br>OkHttpCall继承的retrofit2.Call接口是为了依赖倒置解耦的,真正的网络请求是由OkHttpCall内部引用的okhttp3.call处理的,这个okhttp3.call是<br>借道ServiceMethod获取的Retrofit中的callFactory,也就是Retrofit中的OkHttpClient。</p><p>整个引用链条是这样的:<br>OkHttpCall–okhttp3.call<br>–><br>ServiceMethod–callFactory<br>–><br>Retrofit.build()–callFactory//**(如未扩展赋值)<strong>new OkHttpClient();<br>–><br>Retrofit.Builder().client(mClient)//</strong>(可能有扩展赋值)**扩展过的OkHttpClient</p><p>最终的网络请求是由OkHttpCall调用OkHttpClient发出的,调用和回调等过程,也就是在OkHttpCall中处理的。</p><p>网络请求的生成过程中,为了使用接口函数中定义的参数,OkHttpCall会调用ServiceMethod来生成Request请求对象,再交给OkHttpCall去处理。</p><p><strong>3. 数据转换</strong><br>因为回调是在OkHttpCall中处理的,所以对回调数据的转换也在OkHttpCall中触发,为了符合接口函数中定义的返回数据类型,OkHttpCall会调用ServiceMethod来转换Response返回数据对象。</p><p>OkHttpCall对返回的网络数据,会调用一个serviceMethod.toResponse(ResponseBody body)函数,函数中执行的是:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">R <span class="hljs-title function_">toResponse</span><span class="hljs-params">(ResponseBody body)</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-keyword">return</span> responseConverter.convert(body);<br> }<br></code></pre></td></tr></table></figure><p>这个函数可以把原始的okhttp3. ResponseBody数据转换为INetApiService接口中要求的数据类型(如BizEntity类型)。<br>从代码可以看出,实现数据转换的核心对象其实是responseConverter,这个Converter实际上要依次经过Retrofit的建造和ServiceMethod的建造后,才能确定下来的。</p><p><strong>Retrofit建造时添加数据转换工厂</strong><br>Retrofit里有converterFactries列表,这是在我们初始化Retrofit实例时添加的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//retrofit对象</span><br><span class="hljs-type">Retrofit</span> <span class="hljs-variable">retrofit</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Retrofit</span>.Builder()<br> .baseUrl(Config.DOMAIN)<br> .addConverterFactory(GsonConverterFactory.create())<br> .addConverterFactory(YourConverterFactory.create())<span class="hljs-comment">//添加自定义Converter</span><br> .build();<br></code></pre></td></tr></table></figure><p><strong>ServiceMethod建造时设定数据转换器</strong><br>ServiceMethod在建造时,就已经确定了对应的是INetApiService中的哪个函数,所以需要明确设定自己的Converter<R,T>转换对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> ServiceMethod <span class="hljs-title function_">build</span><span class="hljs-params">()</span> {<br> ...<br> responseConverter = createResponseConverter();<br> ...<br> }<br></code></pre></td></tr></table></figure><p>这需要调用Retrofit</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> Converter<ResponseBody, T> <span class="hljs-title function_">createResponseConverter</span><span class="hljs-params">()</span> {<br> ...<br> retrofit.responseBodyConverter(responseType, annotations);<br> }<br></code></pre></td></tr></table></figure><p>Retrofit会在自己的转换器工厂列表中遍历每个ConverterFactory,尝试根据ServiceMethod所对应的目标数据类型,找到Converter数据转换类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> start, count = converterFactories.size(); i < count; i++) {<br> Converter<ResponseBody, ?> converter =<br> converterFactories.get(i).responseBodyConverter(type, annotations, <span class="hljs-built_in">this</span>);<br> <span class="hljs-keyword">if</span> (converter != <span class="hljs-literal">null</span>) {<br> <span class="hljs-comment">//noinspection unchecked</span><br> <span class="hljs-keyword">return</span> (Converter<ResponseBody, T>) converter;<br> }<br> }<br></code></pre></td></tr></table></figure><p>以Gson转换为例,GsonConverterFactory会通过getAdapter来尝试匹配目标数据类型:</p><pre><code>public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {...}</code></pre><p>如果可以匹配,那么前面调用serviceMethod.toResponse(ResponseBody body)函数时,会调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">R <span class="hljs-title function_">toResponse</span><span class="hljs-params">(ResponseBody body)</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-keyword">return</span> responseConverter.convert(body);<br>}<br></code></pre></td></tr></table></figure><p>在调用这段代码时,其实就是调用了Gson中最终执行数据转换的代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span> <span class="hljs-keyword">public</span> T <span class="hljs-title function_">convert</span><span class="hljs-params">(ResponseBody value)</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-type">JsonReader</span> <span class="hljs-variable">jsonReader</span> <span class="hljs-operator">=</span> gson.newJsonReader(value.charStream());<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">return</span> adapter.read(jsonReader);<br> } <span class="hljs-keyword">finally</span> {<br> value.close();<br> }<br> }<br></code></pre></td></tr></table></figure><p>总结来说,Retrofit在类的单一职责方面分隔的很好,OkHttpCall类只负责网络交互,凡是需要知道函数定义的,都交给ServiceMethod类去处理,而ServiceMethod类对使用者不公开,因为Retrofit是个外观模式,而所有需要扩展的都在Retrofit的建造者中实现,他们的分工大概是这样的:</p><p><img src="/2022/05/10/retrofit/25f4d8a2.png"></p><p> 三个类的分工</p><p>这三个类分工合作,共同实现了函数解析、网络访问和数据转换,并保留了良好的可扩展性。</p><h1 id="Retrofit实现原理——整体结构与分工实现"><a href="#Retrofit实现原理——整体结构与分工实现" class="headerlink" title="Retrofit实现原理——整体结构与分工实现"></a>Retrofit实现原理——整体结构与分工实现</h1><p>至此,Retrofit的实现细节就已经基本清楚了,他用动态代理去定制接口定义的Call网络工作对象,用适配器去把底层的Call对象转换为目标Call对象,用函数解析/OkHttpClient/数据转换等实现对Call对象的适配转换,并能处理真正的网络请求。<br>这里面涉及的整体结构和角色分工,大概可以这样表示:</p><p><img src="/2022/05/10/retrofit/951b3a66.png"></p><p> 整体结构与角色分工</p><p>其中,扩展适配器、扩展数据转换和扩展OkHttpClient,虽然都是通过Retrofit实现扩展,但真正的使用者是Retrofit内部的ServiceMethod、OkHttpCall和okhttp3.call等类或对象。</p><h1 id="反推Retrofit的设计过程"><a href="#反推Retrofit的设计过程" class="headerlink" title="反推Retrofit的设计过程"></a>反推Retrofit的设计过程</h1><p>如果我们不直接正面分析Retrofit的结构设计和技术细节,而是先从Retrofit的功能和作用入手,倒过来推测Retrofit的目标,进而分析其架构和搭建细节,Retrofit为什么会设计成这样就很好理解了。</p><p>Retrofit的功能是<strong>按照接口定义,自动定制Call网络工作对象</strong>,所以Retrofit的目标应该就是避免为网络访问开发大量的配套代码。</p><p>为了实现这一目标,Retrofit需要分析哪些是易变的,哪些是不变的,然后分别处理。</p><p>由于Retrofit提供网络访问的工作对象,又是服务于具体业务,所以可以分网络访问和具体业务两部分来分析。</p><p><strong>网络访问的不变性</strong><br>对于网络访问来说,不变的是一定有一个实现网络访问的对象,Retrofit选用了自家的OkHttpClient,不过为了把Retrofit和OkHttp两个项目解耦合,Retrofit根据依赖倒置原则,定义了Retrofit自己的Call即<strong>retrofit2.call</strong>,并定义了操作网络请求的<strong>OkHttpCall</strong>。</p><p><strong>网络访问的易变性</strong><br>对于网络访问来说,易变的是网络访问的url、请求方式(get/post等)、Http请求的Header设置与安全设置等,以及返回的数据类型。</p><p>针对易变的url和请求方式,Retrofit使用了<strong>方法注解</strong>的方式,可读性良好,扩展性优异,但这需要实现对接口函数中注解的解析,这样就有了<strong>ServiceMethod</strong>。<br>针对Http请求的各种设置,其实Retrofit没做什么,因为Retrofit使用的OkHttp有<strong>拦截器</strong>机制,可以应付这种变化。<br>针对返回的数据类型,由于目标数据类型与业务有关,是不确定的,Retrofit无法提供一个万能的转换类,所以Retrofit提供了扩展接口,允许开发者自己定义<strong>ConverterFactory和Converter</strong>,去实现潜在的数据类型转换。</p><p><strong>具体业务的不变性</strong><br>对于具体业务来说,不变的是一定要有一个Call网络工作对象,所以Retrofit可以有一个生产对象的机制(像工厂一样)</p><p><strong>具体业务的易变性</strong><br>对于具体业务来说,易变的就是这个Call网络工作对象的类型,不仅有CallBacl回调、可能还有Flowable工作流、或者其他潜在的对象类型。</p><p>针对这种Call对象的易变性,Retrofit也是无法提供一个万能的实现类,所以也是提供了扩展解耦,允许开发者自己定义<strong>CallAdapterFactory和CallAdapter</strong>,去实现潜在的Call类型转换。</p><p>因为这种Call对象的生产需要有大量的配套代码,为了简化代码,Retrofit使用<strong>动态代理</strong>来生产这个对象。</p><p>最后,因为需要处理的方法和对象太多太复杂,需要使用<strong>建造者模式</strong>来把建造过程和使用过程分离开。</p><p>这样倒着走一遍之后,我们再看Retrofit的设计和实现原理,就会觉得水到渠成,对于Retrofit精妙的设计更会有一种切身体会。</p><h1 id="借鉴与启示"><a href="#借鉴与启示" class="headerlink" title="借鉴与启示"></a>借鉴与启示</h1><p>在上文的反推过程中,我们可窥见(瞎猜)Jake大神的一些思路:</p><ol><li>万物皆对象<br> 网络访问后,回调数据是个对象;网络访问本身也是个对象。</li><li>依赖倒置<br> 哪怕是使用自家的OkHttp,哪怕底层调用的始终是OkHttpClient,也需要依赖一个抽象的retrofit2.Call接口,依赖于抽象,而不是依赖于具体。</li><li>单一职责<br> 类的职责需要维持单一,流程需要但是超出自己职责的功能,去调用相关的类实现,比如OkHttpClient和ServiceMethod的各自职责与调用关系。</li><li>迪米特法则<br> 内部实现再复杂,对于外部调用者也只展示他需要的那些功能,例如Retrofit。</li><li>自动>人工<br> 动态代理的使用,可以用自动生成的模板代码,减轻人工编写配套代码的工作量,成本更低,风险更低。</li><li>利用工厂类开放扩展<br> 对于流程确定,但方法不能确定的,利用工厂类,对调用者开放扩展能力。</li><li>利用多个工厂类组成扩展列表<br> 如果1个工厂类不能实现兼得,何不设置一个工厂类列表,在多个工厂类中,看哪个工厂类能解决问题。</li><li>利用建造者模式把建造和使用分离<br> 这样使用者不需要关系复杂的建造过程,例如Retrofit和ServiceMethod。</li><li>利用外观模式减少对复杂子系统的操作<br> 虽然有复杂的子系统协同工作,调用者只需要调用最外层的Retrofit即可。</li><li>其他<br>开放封闭、接口隔离、里式替换、静态代理等设计原则或设计模式都有体现也都很熟悉了,就不再啰嗦。</li></ol><p>最后感叹一下。</p><p>对于网络访问的抽象与优化,实际上是个非常难的课题,在Retrofit之前,大家努力的方向基本上都是Volley/OkHttp这种围绕底层网络访问的工作。<br>因为越底层的东西越容易抽象,越上升到接近业务层,就越容易在纷扰的业务层中迷失。<br>Retrofit能精准地抓到Call网络工作对象这个关键点,并能通过一系列精巧的设计实现对这种类型“飘忽不定”的对象的自动化定制生产,着实令人赞叹。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="http://square.github.io/retrofit/">Retrofit</a><br><a href="https://www.jianshu.com/p/308f3c54abdd">你真的会用Retrofit2吗?Retrofit2完全教程</a><br><a href="https://www.jianshu.com/p/c1a3a881a144">Retrofit2 源码解析</a><br><a href="https://www.jianshu.com/p/3b5ef96d1a09">Retrofit 框架源码学习</a><br><a href="https://blog.piasy.com/2016/06/25/Understand-Retrofit/">拆轮子系列:拆 Retrofit</a><br><a href="http://blog.csdn.net/self_study/article/details/55050627">Android 动态代理以及利用动态代理实现 ServiceHook</a></p><p>作者:蓝灰_q<br>链接:<a href="https://www.jianshu.com/p/f57b7cdb1c99">https://www.jianshu.com/p/f57b7cdb1c99</a><br>来源:简书<br>著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。</p>]]></content>
<categories>
<category>Java</category>
<category>retrofit</category>
</categories>
<tags>
<tag>retrofit</tag>
<tag>OkHttp</tag>
<tag>squareup</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/2021/06/24/hello-world/"/>
<url>/2021/06/24/hello-world/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo new <span class="hljs-string">"My New Post"</span><br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo server<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo generate<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo deploy<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>]]></content>
</entry>
<entry>
<title>全景图</title>
<link href="/2021/06/23/My-Gallery/"/>
<url>/2021/06/23/My-Gallery/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>后续放出</p><p><img src="/image/0d5b857819f4b3bb068b9d7142c830bddca9f8427fd8d98dd8d93ffbf15e6374.jpg" alt="1"></p>]]></content>
<tags>
<tag>全景图</tag>
</tags>
</entry>
</search>