-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
393 lines (226 loc) · 233 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>郑林飞</title>
<icon>https://www.gravatar.com/avatar/36c77883f376208e5fbb4908e54b4c2b</icon>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2018-06-05T10:07:45.954Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>郑林飞</name>
<email>[email protected]</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>V1、V2签名包和快速集成美团多渠道打包</title>
<link href="http://yoursite.com/2018/05/18/V1%E3%80%81V2%E7%AD%BE%E5%90%8D%E5%8C%85%E5%92%8C%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E7%BE%8E%E5%9B%A2%E5%A4%9A%E6%B8%A0%E9%81%93%E6%89%93%E5%8C%85/"/>
<id>http://yoursite.com/2018/05/18/V1、V2签名包和快速集成美团多渠道打包/</id>
<published>2018-05-18T12:00:10.000Z</published>
<updated>2018-06-05T10:07:45.954Z</updated>
<content type="html"><![CDATA[<p>本文主要介绍Android V1、V2签名包的基本概念和使用场景以及如何快速掌握美团多渠道打包。</p><ol><li>美团多渠道打包的快速使用和集成</li><li>Android Studio 2.3版本及以上,V1、V2版本签名包的相关说明</li><li>V1、V2版本签名包的比较以及在本文提供的多渠道打包方案下,两种签名包的选择</li></ol><p>开始</p><p>首先下载美团打包工具,下载完毕以后,解压出来会有2个包,一个是Python的安装环境,一个是美团打包的工具。其中,美团的打包工具是一个压缩包,解压之后,有如下文件。<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>下载无误之后,即可开始快速集成美团多渠道集成步骤:</p><p>步骤1:配置Python环境</p><p>这里就不讲解了,到python官网上下载最新的python安装包然后安装就行了</p><p>步骤2:</p><p>集成好Python的环境配置以后,打开下载好的美团打包工具。也就是本文第一张图,首先,有一个JavaUtil,这个是获取渠道信息的代码(可以自己Copy到项目,根据项目需求去集成使用);还有一个PythonTool文件夹,这个就是美团打包的核心,点进这个文件夹之后,如图:<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="2.webp" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>我们只需要把生成好的正式签名包,copy在这里。(这里只是演示,也就是app-release.apk这个文件)我们只需要把写好的正式签名包,copy在这里。注意:必须是同级目录。<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="3.webp" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>然后,点击MultiChannelBuildTool这个python文件,点击一下即可!<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="4.webp" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>点击之后,会生成一个紫色框的文件夹,我们点进去该文件夹<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="5.webp" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>一瞬间生成了这么多个apk,耗时半秒不到。</p><p>基本的多渠道打包使用就已经完成了。</p><p>十分钟不到吧应该。是的,已经打包完毕了。</p><p>问题1:如何配置其他的渠道?</p><p>info文件夹,目录下的channel就是用来存放打包渠道的,注意:多个渠道之间请用换行隔开、多个渠道之间请用换行隔开(不建议使用Windows下面的txt直接打开,强烈建议使用notepad++等第三方文字编辑工具使用编辑!!!)</p><p>问题2:如何获取渠道信息?</p><p>在前面说到了,给大家准备的下载链接里面,解压缩后有个JavaUtil的包,这里面就是获取渠道信息的代码,开发者可以根据这个工具类,去获取渠道号。或者根据开发需要自己集成使用即可。</p><p>关于V1、V2签名包的说明:</p><p>说完了美团多渠道打包,我们在说说V1、V2签名包。</p><p>如果我们升级到最新的AS(笔者的是3.0),点击打包选项,细心的你会看到这样的打包界面:<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="6.webp" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>老版本的AS,只有V1选择,也就是紫色的框框。但是新版本的AS,默认勾选的是V2版本(笔者的AS默认是勾选的V2版本)。如果只勾选V2版本,打出来的签名包,很多机型会直接提示安装失败。解决办法下面会说。</p><p>概述:</p><p>在Android 7.0中引入了APK Signature Scheme v2,v1是jar Signature来自JDK。</p><p>V1:应该是通过ZIP条目进行验证,这样APK 签署后可进行许多修改,这样可以移动甚至重新压缩文件。</p><p>V2:验证压缩文件的所有字节,而不是单个 ZIP 条目,因此,在签名后无法再更改(包括 zipalign)。正因如此,现在在编译过程中,我们将压缩、调整和签署合并成一步完成。好处显而易见,更安全而且新的签名可缩短在设备上进行验证的时间(不需要费时地解压缩然后验证),从而加快应用安装速度。</p><p>刚才说了,如果只勾选V2版本,打出来的签名包,很多机型会直接提示安装失败。下面就给大家提供两种解决方案</p><p>解决方案一:</p><p>V1和V2的签名使用:</p><p>1)只勾选V1签名并不会影响什么,但是在7.0上不会使用更安全的验证方式</p><p>2)只勾选V2签名7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证</p><p>3)同时勾选V1和V2则所有机型都没问题。</p><p>解决办法二:</p><p>还有一种快速高效的代码解决方式,我们可以直接在app的build.gradle的android标签下,加入以下代码也可以解决只勾选V2版本签名包部分机型会提示安装失败的问题。<br><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="7.webp" alt="" title=""> </div> <div class="image-caption"></div> </figure></p><p>V2签名包是从Android7.0系统出现的新的签名机制,这个新机制使得apk的签名方式更加安全。首先我们应该尝试把V1和V2两个选项全部勾选,并尽可能的去使用他们,但是如果全部勾选完毕出现了问题,那么我们可以忽略这种新的签名机制,只勾选第一个选项(V1),依旧使用我们之前老的签名机制,也就是说,V1版本更加快速方便。</p><p>为什么这里会提到V1、V2的区别。因为这里的美团多渠道打包只能适用于V1版本的签名包,MMP</p><p>所以,个人还是推荐使用V1打包方式。因为稳定,所以放心。</p>]]></content>
<summary type="html">
<p>本文主要介绍Android V1、V2签名包的基本概念和使用场景以及如何快速掌握美团多渠道打包。</p>
<ol>
<li>美团多渠道打包的快速使用和集成</li>
<li>Android Studio 2.3版本及以上,V1、V2版本签名包的相关说明</li>
<li>V
</summary>
<category term="打包" scheme="http://yoursite.com/tags/%E6%89%93%E5%8C%85/"/>
</entry>
<entry>
<title>apk瘦身</title>
<link href="http://yoursite.com/2018/05/02/apk%E7%98%A6%E8%BA%AB/"/>
<id>http://yoursite.com/2018/05/02/apk瘦身/</id>
<published>2018-05-02T11:24:49.000Z</published>
<updated>2018-06-05T10:07:45.960Z</updated>
<content type="html"><![CDATA[<ol><li>代码混淆<br>主流开源项目的混淆规则列表<br><a href="https://github.com/krschultz/android-proguard-snippets" target="_blank" rel="noopener">https://github.com/krschultz/android-proguard-snippets</a></li><li>Android Studio → Analyze → Inspect Code<br>删掉无用的代码和图片</li><li>使用tinypng、智图等图片压缩工具对图片进行压缩<br><a href="https://tinypng.com/" target="_blank" rel="noopener">https://tinypng.com/</a><br><a href="http://zhitu.isux.us/" target="_blank" rel="noopener">http://zhitu.isux.us/</a></li><li>资源文件混淆<br>介绍:<a href="http://www.tuicool.com/articles/qMVbIbi" target="_blank" rel="noopener">http://www.tuicool.com/articles/qMVbIbi</a><br>github:<a href="https://github.com/shwenzhang/AndResGuard" target="_blank" rel="noopener">https://github.com/shwenzhang/AndResGuard</a></li><li>字体文件压缩<br>github:<a href="https://github.com/forJrking/FontZip" target="_blank" rel="noopener">https://github.com/forJrking/FontZip</a></li><li><p>减少第三库的使用</p></li><li><p>使用9.png图<br>上跟左,表示拉伸;右跟下,表示内容范围</p></li><li>用代码代替图片<br>shape代替圆形</li><li><p>用RotateDrawable代替仅仅是方向不同的“内容相同”的图片</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>这里两个图片是两个按钮箭头,但是仅仅方向不同而已,其实可以只用其中一个图片即可,而另一个用RotateDrawable来让其“调转”180度</p><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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="utf-8"?></span><br><span class="line"><span class="tag"><<span class="name">rotate</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:drawable</span>=<span class="string">"@drawable/ic_arrow_left"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:fromDegrees</span>=<span class="string">"180"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:pivotX</span>=<span class="string">"50%"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:pivotY</span>=<span class="string">"50%"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:toDegrees</span>=<span class="string">"180"</span> /></span></span><br></pre></td></tr></table></figure></li><li><p>用layer-list来制作多层图片从而达到复用<br><img src="2.jpg" alt=""><br>有些需求中需要一种图片,但是明显这个图片是其他几个图片简单叠加而已,那么可以使用layer-list来达到目的</p><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><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></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="utf-8"?></span><br><span class="line"><span class="tag"><<span class="name">layer-list</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">item</span>></span></span><br><span class="line"> <span class="comment"><!-- 最底层的图片,以x,y轴坐标为中心进行旋转--></span></span><br><span class="line"> <span class="tag"><<span class="name">rotate</span> <span class="attr">android:pivotX</span>=<span class="string">"0"</span> <span class="attr">android:pivotY</span>=<span class="string">"0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:fromDegrees</span>=<span class="string">"-10"</span> <span class="attr">android:toDegrees</span>=<span class="string">"-10"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bitmap</span> <span class="attr">android:src</span>=<span class="string">"@mipmap/ic_launcher"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">rotate</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">item</span>></span></span><br><span class="line"> <span class="comment"><!-- 第二层的图片,以x,y轴坐标为中心进行旋转--></span></span><br><span class="line"> <span class="tag"><<span class="name">item</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">rotate</span> <span class="attr">android:pivotX</span>=<span class="string">"0"</span> <span class="attr">android:pivotY</span>=<span class="string">"0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:fromDegrees</span>=<span class="string">"15"</span> <span class="attr">android:toDegrees</span>=<span class="string">"15"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bitmap</span> <span class="attr">android:src</span>=<span class="string">"@mipmap/ic_launcher"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">rotate</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">item</span>></span></span><br><span class="line"> <span class="comment"><!-- 最上层的图片,以x,y轴坐标为中心进行旋转--></span></span><br><span class="line"> <span class="tag"><<span class="name">item</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">rotate</span> <span class="attr">android:pivotX</span>=<span class="string">"0"</span> <span class="attr">android:pivotY</span>=<span class="string">"0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:fromDegrees</span>=<span class="string">"35"</span> <span class="attr">android:toDegrees</span>=<span class="string">"55"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bitmap</span> <span class="attr">android:src</span>=<span class="string">"@mipmap/ic_launcher"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">rotate</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">item</span>></span></span><br><span class="line"><span class="tag"></<span class="name">layer-list</span>></span></span><br></pre></td></tr></table></figure></li><li><p>通过配置resConfig可以选择只打包哪几种语言,如果只保留默认英语和中文语言,配置如下</p><figure class="highlight gradle"><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><span class="line">android{</span><br><span class="line"> defaultConfig{</span><br><span class="line"> resConfigs <span class="string">"zh"</span>, <span class="string">"en"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>谷歌redex对字节码进行优化<br><a href="https://github.com/facebook/redex.git" target="_blank" rel="noopener">https://github.com/facebook/redex.git</a></p></li><li>避免枚举<br>一个枚举可以为您的应用程序的classes.dex文件添加大约1.0到1.4 KB的大小。</li></ol>]]></content>
<summary type="html">
<ol>
<li>代码混淆<br>主流开源项目的混淆规则列表<br><a href="https://github.com/krschultz/android-proguard-snippets" target="_blank" rel="noopener">https://gi
</summary>
<category term="优化" scheme="http://yoursite.com/tags/%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>程序员英语口语等级考试</title>
<link href="http://yoursite.com/2018/05/02/%E7%A8%8B%E5%BA%8F%E5%91%98%E8%8B%B1%E8%AF%AD%E5%8F%A3%E8%AF%AD%E7%AD%89%E7%BA%A7%E8%80%83%E8%AF%95/"/>
<id>http://yoursite.com/2018/05/02/程序员英语口语等级考试/</id>
<published>2018-05-02T11:17:25.000Z</published>
<updated>2018-06-05T10:07:45.972Z</updated>
<content type="html"><![CDATA[<p>这个世界上有四种英语——美式英语、英式英语、中国式英语,还有一个程序员英语!程序员英语有个特点,那就是不同公司、不同大会,对同一个单词,可能都有多种不同的读法,与编辑器圣战、IDE圣战、终端圣战一样,也许你也有曾经看不惯他人的发音,下面就来个小测试吧,看看你在圣战中,到底站在哪一方!<br>注意,文中给出的中文标注仅用来区分大致的发音,具体的发音已音标为准</p><h3 id="发音练习之初级篇"><a href="#发音练习之初级篇" class="headerlink" title="发音练习之初级篇"></a>发音练习之初级篇</h3><p><strong>App</strong><br>[ˈæp],跟我读『爱普』,不是『爱屁屁』啊<br><strong>Java</strong><br>[ˈdʒɑːvə],跟我读『扎哇』,不是『夹娃』啊<br><strong>Chrome</strong><br>[krəʊm],跟我读『克弱姆』啊<br><strong>Adobe</strong><br>[əˈdəʊbi],跟我读『额刀比』啊<br><strong>Skype</strong><br>[ˈskaɪp],跟我读『死盖破』,不是『死盖屁』啊<br><strong>Youtube</strong><br>You-tube [tju:b],跟我读『优tiu波』,不是『优土比』啊<br><strong>Ubuntu</strong><br>[uˈbuntuː],跟我读『乌班图』,不是『优班图』啊</p><h3 id="发言联系之高级篇"><a href="#发言联系之高级篇" class="headerlink" title="发言联系之高级篇"></a>发言联系之高级篇</h3><p><strong>jpg</strong><br>[ˈdʒeɪpɛɡ],跟我读『杰派哥』啊<br><strong>Null</strong><br>[nʌl],跟我读『闹』,不是『努努』(我知道你日语好)<br><strong>Cache</strong><br>[kæʃ],跟我读『凯西』,不是『卡车』啊<br><strong>Mac OS X</strong><br>X不是『艾克斯』啊,是罗马数字十!所以要读『Mac OS Ten』啊<br><strong>Ajax</strong><br>[‘eidʒæks],跟我读『诶贾克斯』,不是『阿贾克斯』啊<br><strong>Icon</strong><br>跟我读『爱康』,不是『爱啃』啊<br><strong>Angular</strong><br>[‘æŋgjʊlə],跟我读『安古拉』,不是『安哥拉』啊<br><strong>Issue</strong><br>[‘ɪʃuː],跟我读『衣休』,不是『唉休』啊<br><strong>Module</strong><br>[‘mɒdjuːl],跟我读『幕久』,不是『幕抖』啊<br><strong>Maven</strong><br>[‘meɪvn],跟我读『梅文』,不是『马文』啊<br><strong>Django</strong><br>跟我读『江狗』,不是『大江狗』啊</p>]]></content>
<summary type="html">
<p>这个世界上有四种英语——美式英语、英式英语、中国式英语,还有一个程序员英语!程序员英语有个特点,那就是不同公司、不同大会,对同一个单词,可能都有多种不同的读法,与编辑器圣战、IDE圣战、终端圣战一样,也许你也有曾经看不惯他人的发音,下面就来个小测试吧,看看你在圣战中,到底站
</summary>
<category term="其他" scheme="http://yoursite.com/tags/%E5%85%B6%E4%BB%96/"/>
</entry>
<entry>
<title>Android运行时权限详解</title>
<link href="http://yoursite.com/2018/01/11/Android%E8%BF%90%E8%A1%8C%E6%97%B6%E6%9D%83%E9%99%90%E8%AF%A6%E8%A7%A3/"/>
<id>http://yoursite.com/2018/01/11/Android运行时权限详解/</id>
<published>2018-01-11T12:38:09.000Z</published>
<updated>2018-06-05T10:07:45.952Z</updated>
<content type="html"><![CDATA[<h3 id="Android系统权限"><a href="#Android系统权限" class="headerlink" title="Android系统权限"></a>Android系统权限</h3><p>Android 是一个权限分隔的操作系统,其中每个应用都有其独特的系统标识(Linux 用户 ID 和组 ID)。系统各部分也分隔为不同的标识。Linux 据此将不同的应用以及应用与系统分隔开来。在默认情况下任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。包括读取或写入用户的私有数据(例如联系人或电子邮件)、读取或写入其他应用程序的文件、执行网络访问、使设备保持唤醒状态等。</p><p>在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装,App一旦安装后后授权不可取消。</p><p>Android6.0引入了新的权限模式,将系统权限区分为正常权限和危险权限。开发者在使用到危险权限相关的功能时,不仅需要在Manifest文件中配置,还需要在代码中动态获取权限,如果没有确认获取到权限而直接执行相应所需权限的代码,将导致App崩溃。另外,Android6.0 以上系统,App 退到后台,修改应用权限,再次 App 回到前台,会出现应用新开进程重启。</p><h3 id="权限的使用"><a href="#权限的使用" class="headerlink" title="权限的使用"></a>权限的使用</h3><p>Android App 默认无任何权限,如果需要使用系统权限必须在AndroidManifest.xml文件中声明权限。<br><figure class="highlight"><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><span class="line"><span class="comment">//声明网络使用权限</span></span><br><span class="line"><manifest xmlns:android=<span class="string">"http://schemas.android.com/apk/res/android"</span></span><br><span class="line"> <span class="keyword">package</span>=<span class="string">"com.example.permission"</span>></span><br><span class="line"></span><br><span class="line"><uses-permission android:name=<span class="string">"android.permission.INTERNE"</span>/></span><br><span class="line"> </span><br><span class="line"><application ...></span><br><span class="line"> ...</span><br><span class="line"></application></span><br><span class="line"></span><br><span class="line"></manifest></span><br></pre></td></tr></table></figure></p><p>如果所需权限为正常权限(即不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。</p><p>如果所需权限为危险权限(即可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。Android 发出请求的方式取决于系统版本(即targetSdkVersion):</p><ul><li>如果运行在 Android 6.0 及以上版本,App targetSdkVersion 大于23,则需要在运行时向用户请求权限,并且需要在 App 使用相关的权限之前检查自身是否已被授予该权限。</li><li>如果运行在 Android 6.0 以下版本,或App targetSdkVersion 小于23(此时设备可以是Android 6.0 (API level 23)或者更高),则系统会在用户安装则系统会在用户安装App时要求用户授予权限,系统就告诉用户App需要什么权限组。如果App将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。<h4 id="正常权限"><a href="#正常权限" class="headerlink" title="正常权限"></a>正常权限</h4>正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。此类权限都是正常保护的权限,只需要在Manifest文件中简单声明,安装即授权。</li><li>ACCESS_LOCATION_EXTRA_COMMANDS</li><li>ACCESS_NETWORK_STATE</li><li>ACCESS_NOTIFICATION_POLICY</li><li>ACCESS_WIFI_STATE</li><li>BLUETOOTH</li><li>BLUETOOTH_ADMIN</li><li>BROADCAST_STICKY</li><li>CHANGE_NETWORK_STATE</li><li>CHANGE_WIFI_MULTICAST_STATE</li><li>CHANGE_WIFI_STATE</li><li>DISABLE_KEYGUARD</li><li>EXPAND_STATUS_BAR</li><li>GET_PACKAGE_SIZE</li><li>INSTALL_SHORTCUT</li><li>INTERNET</li><li>KILL_BACKGROUND_PROCESSES</li><li>MODIFY_AUDIO_SETTINGS</li><li>NFC</li><li>READ_SYNC_SETTINGS</li><li>READ_SYNC_STATS</li><li>RECEIVE_BOOT_COMPLETED</li><li>REORDER_TASKS</li><li>REQUEST_IGNORE_BATTERY_OPTIMIZATIONS</li><li>REQUEST_INSTALL_PACKAGES</li><li>SET_ALARM</li><li>SET_TIME_ZONE</li><li>SET_WALLPAPER</li><li>SET_WALLPAPER_HINTS</li><li>TRANSMIT_IR</li><li>UNINSTALL_SHORTCUT</li><li>USE_FINGERPRINT</li><li>VIBRATE</li><li>WAKE_LOCK</li><li>WRITE_SYNC_SETTINGS<h4 id="危险权限"><a href="#危险权限" class="headerlink" title="危险权限"></a>危险权限</h4></li></ul><p>危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。</p><p><strong>CALENDAR</strong></p><ul><li>READ_CALENDAR</li><li>WRITE_CALENDAR</li></ul><p><strong>CAMERA</strong></p><ul><li>CAMERA</li></ul><p><strong>CONTACTS</strong></p><ul><li>READ_CONTACTS</li><li>WRITE_CONTACTS</li><li>GET_ACCOUNTS</li></ul><p><strong>LOCATION</strong></p><ul><li>ACCESS_FINE_LOCATION</li><li>ACCESS_COARSE_LOCATION</li></ul><p><strong>MICROPHONE</strong></p><ul><li>RECORD_AUDIO</li></ul><p><strong>PHONE</strong></p><ul><li>READ_PHONE_STATE</li><li>READ_PHONE_NUMBERS</li><li>CALL_PHONE</li><li>ANSWER_PHONE_CALLS (must request at runtime)</li><li>READ_CALL_LOG</li><li>WRITE_CALL_LOG</li><li>ADD_VOICEMAIL</li><li>USE_SIP</li><li>PROCESS_OUTGOING_CALLS</li></ul><p><strong>SENSORS</strong></p><ul><li>BODY_SENSORS</li></ul><p><strong>SMS</strong></p><ul><li>SEND_SMS</li><li>RECEIVE_SMS</li><li>READ_SMS</li><li>RECEIVE_WAP_PUSH</li><li>RECEIVE_MMS</li></ul><p><strong>STORAGE</strong></p><ul><li>READ_EXTERNAL_STORAGE</li><li>WRITE_EXTERNAL_STORAGE</li></ul><h4 id="特殊权限"><a href="#特殊权限" class="headerlink" title="特殊权限"></a>特殊权限</h4><p>有许多权限其行为方式与正常权限及危险权限都不同。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特别敏感,因此大多数应用不应该使用它们。如果某应用需要其中一种权限,必须在清单中声明该权限,并且发送请求用户授权的 intent。系统将向用户显示详细信息,以响应该 intent。</p><h3 id="权限组"><a href="#权限组" class="headerlink" title="权限组"></a>权限组</h3><p>所有危险权限都拥有对应权限组,如果运行在 Android 6.0 及以上版本,App targetSdkVersion 大于23,则当用户请求危险权限时系统会发生以下行为:</p><ul><li>如果应用未拥有Manifest列出的危险权限所在的权限组任一权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。</li><li>如果应用已拥有Manifest列出的危险权限所在的权限组其他任一权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限。</li></ul><h3 id="Android-O的运行时权限策略变化"><a href="#Android-O的运行时权限策略变化" class="headerlink" title="Android O的运行时权限策略变化"></a>Android O的运行时权限策略变化</h3><ul><li>在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。对于针对Android O的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。</li><li>例如,假设某个应用在其清单中列出READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE。应用请求READ_EXTERNAL_STORAGE,并且用户授予了该权限,如果该应用针对的是API级别24或更低级别,系统还会同时授予WRITE_EXTERNAL_STORAGE,因为该权限也属于STORAGE权限组并且也在清单中注册过。如果该应用针对的是Android O,则系统此时仅会授予READ_EXTERNAL_STORAGE,不过在该应用以后申请WRITE_EXTERNAL_STORAGE权限时,系统会立即授予该权限,而不会提示用户。</li><li>我们申请了WRITE_EXTERNAL_STORAGE权限,在Android O之前,我们同时会得到READ_EXTERNAL_STORAGE权限,我们在其它地方涉及到读取存储卡的操作时只需要判断有WRITE_EXTERNAL_STORAGE权限就去读取了。此时应用如果安装在Android O的系统中我们会发现,判断了有WRITE_EXTERNAL_STORAGE权限后去读取存储卡内容时应用崩溃了,原因就是我们没有申请READ_EXTERNAL_STORAGE权限。</li></ul><h3 id="运行时请求权限"><a href="#运行时请求权限" class="headerlink" title="运行时请求权限"></a>运行时请求权限</h3><h4 id="检查权限及兼容"><a href="#检查权限及兼容" class="headerlink" title="检查权限及兼容"></a>检查权限及兼容</h4><p><strong>(1)对于运行在 Android 6.0及以上 App targetSdkVersion 大于23的应用</strong></p><ul><li><p>如果App需要用到危险权限,需要这一权限的操作时都必须检查自己是否拥有该权限。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> permissionCheck = ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.WRITE_CALENDAR);</span><br></pre></td></tr></table></figure></li><li><p>如果应用已经具有了该权限,此方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回 PERMISSION_DENIED,此时应用应当进行权限申请。</p></li></ul><p><strong>(2)对于运行在 Android 6.0及以上 App targetSdkVersion 大于23的应用</strong></p><ul><li>在App安装时会询问AndroidManifest.xml文件中的权限,用户也可以在设置列表中手动关闭/开启相关权限。</li></ul><p><strong>(3)对于运行在 Android 6.0以下 App targetSdkVersion 大于23的应用</strong></p><p>对于运行在 Android 6.0以下 App targetSdkVersion 大于23的应用,默认情况下是会采取旧的权限机制,然而,一些国产手机在6.0之前就引入了权限管理系统,所以必须对其进行兼容。</p><ul><li><p>下面我们来看ActivityCompat.requestPermissions()方法。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">requestPermissions</span><span class="params">(<span class="keyword">final</span> @NonNull Activity activity,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">final</span> @NonNull String[] permissions, <span class="keyword">final</span> @IntRange(from = <span class="number">0</span>)</span> <span class="keyword">int</span> requestCode) </span>{</span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT >= <span class="number">23</span>) {</span><br><span class="line"> ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (activity <span class="keyword">instanceof</span> OnRequestPermissionsResultCallback) {</span><br><span class="line"> Handler handler = <span class="keyword">new</span> Handler(Looper.getMainLooper());</span><br><span class="line"> handler.post(<span class="keyword">new</span> Runnable() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span>[] grantResults = <span class="keyword">new</span> <span class="keyword">int</span>[permissions.length];</span><br><span class="line"></span><br><span class="line"> PackageManager packageManager = activity.getPackageManager();</span><br><span class="line"> String packageName = activity.getPackageName();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> permissionCount = permissions.length;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < permissionCount; i++) {</span><br><span class="line"> grantResults[i] = packageManager.checkPermission(</span><br><span class="line"> permissions[i], packageName);</span><br><span class="line"> }</span><br><span class="line"> ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(</span><br><span class="line"> requestCode, permissions, grantResults);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>系统版本小于23时,使用packageManager.checkPermission()对权限进行校验,而当App在AndroidManifest.xml中声明权限时,会返回PERMISSION_GRANTED,显然,这一校验方法在Android 6.0以下将失效。</p></li><li><p>PermissionChecker.checkSelfPermission()<br>PermissionChecker是 Support V4 包下一个专门检查权限的工具类</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">checkPermission</span><span class="params">(@NonNull Context context, @NonNull String permission,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> pid, <span class="keyword">int</span> uid, String packageName)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {</span><br><span class="line"> <span class="keyword">return</span> PERMISSION_DENIED;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String op = AppOpsManagerCompat.permissionToOp(permission);</span><br><span class="line"> <span class="keyword">if</span> (op == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> PERMISSION_GRANTED;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (packageName == <span class="keyword">null</span>) {</span><br><span class="line"> String[] packageNames = context.getPackageManager().getPackagesForUid(uid);</span><br><span class="line"> <span class="keyword">if</span> (packageNames == <span class="keyword">null</span> || packageNames.length <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> PERMISSION_DENIED;</span><br><span class="line"> }</span><br><span class="line"> packageName = packageNames[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (AppOpsManagerCompat.noteProxyOp(context, op, packageName)</span><br><span class="line"> != AppOpsManagerCompat.MODE_ALLOWED) {</span><br><span class="line"> <span class="keyword">return</span> PERMISSION_DENIED_APP_OP;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> PERMISSION_GRANTED;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>checkSelfPermission通过上述四个判断语句进行权限校验</p><ul><li>context.checkPermission()实际上调用的还是上述packageManager.checkPermission()方法进行校验。</li><li><p>AppOpsManagerCompat.permissionToOp()调用了IMPL.permissionToOp(permission)方法</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><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> AppOpsManagerImpl IMPL;</span><br><span class="line"><span class="keyword">static</span> {</span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT >= <span class="number">23</span>) {</span><br><span class="line"> IMPL = <span class="keyword">new</span> AppOpsManager23();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> IMPL = <span class="keyword">new</span> AppOpsManagerImpl();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>可以看到,在Android6.0以下设备中,会使用AppOpsManagerImpl,其permissionToOp()方法进行权限检查,其默认返回null,所以PermissionChecker.checkSelfPermission() 同样会失效。</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><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">AppOpsManagerImpl</span> </span>{</span><br><span class="line"> AppOpsManagerImpl() {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">permissionToOp</span><span class="params">(String permission)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">noteOp</span><span class="params">(Context context, String op, <span class="keyword">int</span> uid, String packageName)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> MODE_IGNORED;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">noteProxyOp</span><span class="params">(Context context, String op, String proxiedPackageName)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> MODE_IGNORED;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>此外,Google官方还提供了AppOpsManager类来检查权限</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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">checkOp</span><span class="params">(String op, <span class="keyword">int</span> uid, String packageName)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> checkOp(strOpToOp(op), uid, packageName);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@hide</span> </span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">checkOp</span><span class="params">(<span class="keyword">int</span> op, <span class="keyword">int</span> uid, String packageName)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">int</span> mode = mService.checkOperation(op, uid, packageName);</span><br><span class="line"> <span class="keyword">if</span> (mode == MODE_ERRORED) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SecurityException(buildSecurityExceptionMsg(op, uid, packageName));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> mode;</span><br><span class="line"> } <span class="keyword">catch</span> (RemoteException e) {</span><br><span class="line"> <span class="keyword">throw</span> e.rethrowFromSystemServer();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>为了更好的兼容不同厂家的国产手机,建议针对不同的系统版本使用不同的权限校验策略,对于 Android 6.0以上设备,使用PermissionChecker.checkSelfPermission()进行权限校验,对于Android 6.0 以下设备,可通过触发try catch后的危险权限代码检查是否有权限,以期准确校验权限,对用户进行引导,具体可参考AndPermission等第三方库。</p><h3 id="请求权限"><a href="#请求权限" class="headerlink" title="请求权限"></a>请求权限</h3><p>对于App targetSdkVersion 大于23的应用,在应用需要使用的危险权限,必须要进行动态权限申请。Android 提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框。</p></li></ul><p>调用requestPermissions() 方法,可进行动态权限申请,该方法是异步的。在用户响应对话框之后,系统会回调onRequestPermissionsResult。<br><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><span class="line"><span class="keyword">if</span> (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS)</span><br><span class="line"> != PackageManager.PERMISSION_GRANTED) {</span><br><span class="line"> <span class="keyword">if</span> (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,</span><br><span class="line"> Manifest.permission.READ_CONTACTS)) {</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ActivityCompat.requestPermissions(thisActivity,</span><br><span class="line"> <span class="keyword">new</span> String[]{Manifest.permission.READ_CONTACTS},</span><br><span class="line"> MY_PERMISSIONS_REQUEST_READ_CONTACTS);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="处理权限请求回调"><a href="#处理权限请求回调" class="headerlink" title="处理权限请求回调"></a>处理权限请求回调</h3><p>在用户响应对话框之后,系统会回调 onRequestPermissionsResult() 方法。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onRequestPermissionsResult</span><span class="params">(<span class="keyword">int</span> requestCode,</span></span></span><br><span class="line"><span class="function"><span class="params"> String permissions[], <span class="keyword">int</span>[] grantResults)</span> </span>{</span><br><span class="line"> <span class="keyword">switch</span> (requestCode) {</span><br><span class="line"> <span class="keyword">case</span> MY_PERMISSIONS_REQUEST_READ_CONTACTS: {</span><br><span class="line"> <span class="keyword">if</span> (grantResults.length > <span class="number">0</span></span><br><span class="line"> && grantResults[<span class="number">0</span>] == PackageManager.PERMISSION_GRANTED) {</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="权限申请被拒绝"><a href="#权限申请被拒绝" class="headerlink" title="权限申请被拒绝"></a>权限申请被拒绝</h3><p>如果用户拒绝了某项权限请求,应用应采取适当的操作进行引导,应用应当显示一个对话框,解释应用为什么需要此权限,以及使用该权限有何影响。</p><p>当系统要求用户授予权限时,用户可以选择指统不再要求提供该权限。这种情况下,无论应用在什么时候使用 requestPermissions() 请求该权限,系统都会立即拒绝此请求。系统会调用您的 onRequestPermissionsResult() 回调方法,并传递 PERMISSION_DENIED,如果用户再次明确拒绝了权限的请求,系统将采用相同方式操作。这意味着当调用 requestPermissions()时,不一定会出现系统权限请求弹窗。</p><p>此时可借助shouldShowRequestPermissionRationale()这个回调方法,如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。如果用户拒绝了权限请求,并在权限请求系统对话框中选择了不再询问选项,此方法将返回 false。如果设备默认禁止应用具有该权限,此方法也会返回 false。</p><p><strong>shouldShowRequestPermissionRationale()</strong></p><ul><li>望文生义,是否应该显示请求权限的说明。</li><li>第一次请求权限时,用户拒绝了,调用shouldShowRequestPermissionRationale()后返回true,应该显示一些为什么需要这个权限的说明。</li><li>用户在第一次拒绝某个权限后,下次再次申请时,授权的dialog中将会出现“不再提醒”选项,一旦选中勾选了,那么下次申请将不会提示用户。</li><li>第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项,调用shouldShowRequestPermissionRationale()后返回false。</li><li>设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()返回false 。</li><li>加这个提醒的好处在于,用户拒绝过一次权限后我们再次申请时可以提醒该权限的重要性,免得再次申请时用户勾选“不再提醒”并决绝,导致下次申请权限直接失败。</li></ul><h3 id="关于运行时权限的一些建议"><a href="#关于运行时权限的一些建议" class="headerlink" title="关于运行时权限的一些建议"></a>关于运行时权限的一些建议</h3><p><strong>(1)只请求需要的权限,减少请求的次数,或用隐式Intent来让其他的应用来处理。</strong></p><ul><li>使用Intent,你不需要设计界面,由第三方的应用来完成所有操作。比如打电话、选择图片等。</li><li>如果请求权限,你可以完全控制用户体验,自己定义UI。但是用户也可以拒绝权限,就意味着你的应用不能执行这个特殊操作。</li></ul><p><strong>(2)防止一次请求太多的权限或请求次数太多,用户可能对你的应用感到厌烦,在应用启动的时候,最好先请求应用必须的一些权限,非必须权限在使用的时候才请求,建议整理并按照上述分类管理自己的权限:</strong></p><ul><li>普通权限(Normal PNermissions):只需要在Androidmanifest.xml中声明相应的权限,安装即许可。</li><li>需要运行时申请的权限(Dangerous Permissions):</li><li>必要权限:最好在应用启动的时候,进行请求许可的一些权限(主要是应用中主要功能需要的权限)。</li></ul><p>附带权限:不是应用主要功能需要的权限(如:选择图片时,需要读取SD卡权限)。</p><p>解释你的应用为什么需要这些权限:在你调用requestPermissions()之前,你为什么需要这个权限。<br>例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在requestpermissions()之前告诉用户你为什么需要这个权限。</p><p><strong>(3)使用兼容库support-v4中的方法</strong></p><ul><li>PermissionChecker.checkSelfPermission() 或者 ContextCompat.checkSelfPermission()</li><li>ActivityCompat.requestPermissions()</li><li>ActivityCompat.shouldShowRequestPermissionRationale()</li></ul><h3 id="国产机权限问题整理"><a href="#国产机权限问题整理" class="headerlink" title="国产机权限问题整理"></a>国产机权限问题整理</h3><ul><li>部分中国厂商生产手机(例如小米)的Rationale功能,在第一次拒绝后,第二次申请时不会返回true,并且会回调申请失败,也就是说在第一次拒绝后默认勾选了不再提示。</li><li>部分中国厂商生产手机(例如小米、华为)在申请权限时,用户点击确定授权后,还是回调我们申请失败,这个时候其实我们是拥有权限的,所以我们可以在失败的方法中使用AppOpsManager进行权限判断。</li><li>部分中国厂商生产手机(例如vivo、Oppo)在用户允许权限,并且回调了权限授权成功的方法,但是实际执行代码时并没有这个权限,建议开发者在回调成功的方法中也利用AppOpsManager判断下。</li><li>在某些手机的Setting中授权后实际检查时还是没有权限,部分执行代码也是没有权限。</li></ul><h3 id="从系统版本看国产机型的权限申请特点"><a href="#从系统版本看国产机型的权限申请特点" class="headerlink" title="从系统版本看国产机型的权限申请特点"></a>从系统版本看国产机型的权限申请特点</h3><ul><li>5.0:此时 google 还未着手处理动态权限申请这么个东西,但是我们的小米、魅族等厂商就开始提前设置了强大的权限管理,所以 6.0 权限申请代码在 5.0 上压根不管用,但是说来也简单,5.0 的权限申请对话框激活就是靠触发危险权限代码,然后根据返回值来判断权限是否获取到了(不同手机的返回值判断方式不同,此处需要一一定制)。</li><li>6.0:国产大部分机型手机的申请权限实际上应该细致地分为申请权限和应用权限 。它们的 ContextCompat.checkSelfPermission(Context, String) 判断是根据是否 AndroidManifest.xml 中声明了该权限来决定返回值,在 AndroidManifest.xml 中声明了权限就返回 true,当然也会有一些会返回 false,这个是申请权限的过程。而真正对话框的弹出是在开发者应用权限的过程中,什么叫做应用权限?就是调用了会触发权限的代码,这个时候就会激活对话框,但是如果仅到这里那就 too young too simple 了,当用户点击拒绝授权时,还是可能会回调授权成功的方法。另外,国产机大部分权限是有三个状态——询问、允许、拒绝——大部分权限都是询问状态,但是有些权限默认是允许状态,有些是拒绝状态,这就导致了调用 ContextCompat.checkSelfPermission(Context, String) 方法时会更畸形,例如小米手机的获取 READ_PHONE_STATE 状态,默认是授予状态。</li></ul>]]></content>
<summary type="html">
<h3 id="Android系统权限"><a href="#Android系统权限" class="headerlink" title="Android系统权限"></a>Android系统权限</h3><p>Android 是一个权限分隔的操作系统,其中每个应用都有其独特的系
</summary>
<category term="其他" scheme="http://yoursite.com/tags/%E5%85%B6%E4%BB%96/"/>
</entry>
<entry>
<title>Android混淆总结</title>
<link href="http://yoursite.com/2017/12/24/Android%E6%B7%B7%E6%B7%86%E6%80%BB%E7%BB%93/"/>
<id>http://yoursite.com/2017/12/24/Android混淆总结/</id>
<published>2017-12-23T19:27:42.000Z</published>
<updated>2018-06-05T10:07:45.935Z</updated>
<content type="html"><![CDATA[<p>通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也有效缩减了apk的体积。</p><h3 id="开启混淆"><a href="#开启混淆" class="headerlink" title="开启混淆"></a>开启混淆</h3><p>在基于Android Studio项目的app module的build.gradle中有如下默认代码片段:</p><figure class="highlight gradle"><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><span class="line">buildTypes {</span><br><span class="line"> release {</span><br><span class="line"> minifyEnabled <span class="keyword">true</span></span><br><span class="line"> shrinkResources <span class="keyword">true</span></span><br><span class="line"> proguardFiles getDefaultProguardFile(<span class="string">'proguard-android.txt'</span>), <span class="string">'proguard-rules.pro'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>minifyEnabled true 代表要发布的release包的混淆配置,默认不开启混淆,设为true表示开启混淆。</p><p>shrinkResources true 代表开启资源文件压缩。</p><p>proguard-android.txt 代表系统默认的混淆规则配置文件,该文件在<android sdk目录="">/tools/proguard下,一般不要更改该配置文件。</android></p><p>proguard-rules.pro 代表当前module的混淆配置文件,可以通过修改该文件来添加当前项目的混淆规则。</p><h3 id="编写混淆配置文件"><a href="#编写混淆配置文件" class="headerlink" title="编写混淆配置文件"></a>编写混淆配置文件</h3><p>以下是系统的proguard-android.txt</p><figure class="highlight gradle"><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></pre></td><td class="code"><pre><span class="line"># <span class="keyword">This</span> is a configuration <span class="keyword">file</span> <span class="keyword">for</span> ProGuard.</span><br><span class="line"># http:<span class="comment">//proguard.sourceforge.net/index.html#manual/usage.html</span></span><br><span class="line"># 混淆时不使用大小写混合类名</span><br><span class="line">-dontusemixedcaseclassnames</span><br><span class="line"># 不跳过library中的非<span class="keyword">public</span>的类</span><br><span class="line">-dontskipnonpubliclibraryclasses</span><br><span class="line"># 打印混淆的详细信息</span><br><span class="line">-verbose</span><br><span class="line"># Optimization is turned off by <span class="keyword">default</span>. Dex does not like code run</span><br><span class="line"># through the ProGuard optimize and preverify steps (and performs some</span><br><span class="line"># of these optimizations on its own).</span><br><span class="line"># 关闭优化(原因见上边的原英文注释)</span><br><span class="line">-dontoptimize</span><br><span class="line"># 不进行预校验,可加快混淆速度</span><br><span class="line">-dontpreverify</span><br><span class="line"># Note that <span class="keyword">if</span> you want to enable optimization, you cannot just</span><br><span class="line"># <span class="keyword">include</span> optimization flags in your own <span class="keyword">project</span> configuration <span class="keyword">file</span>;</span><br><span class="line"># instead you will need to point to the</span><br><span class="line"># <span class="string">"proguard-android-optimize.txt"</span> <span class="keyword">file</span> instead of <span class="keyword">this</span> one <span class="keyword">from</span> your</span><br><span class="line"># <span class="keyword">project</span>.properties <span class="keyword">file</span>.</span><br><span class="line"># 保留注解中的参数</span><br><span class="line">-keepattributes *Annotation*</span><br><span class="line"># 不混淆如下两个谷歌服务类</span><br><span class="line">-keep <span class="keyword">public</span> <span class="keyword">class</span> com.google.vending.licensing.ILicensingService</span><br><span class="line">-keep <span class="keyword">public</span> <span class="keyword">class</span> com.android.vending.licensing.ILicensingService</span><br><span class="line"># <span class="keyword">For</span> <span class="keyword">native</span> methods, see http:<span class="comment">//proguard.sourceforge.net/manual/examples.html#native</span></span><br><span class="line"># 不混淆包含<span class="keyword">native</span>方法的类的类名以及<span class="keyword">native</span>方法名</span><br><span class="line">-keepclasseswithmembernames <span class="keyword">class</span> * {</span><br><span class="line"> <span class="keyword">native</span> <methods>;</span><br><span class="line">}</span><br><span class="line"># keep setters in Views so that animations can still work.</span><br><span class="line"># see http:<span class="comment">//proguard.sourceforge.net/manual/examples.html#beans</span></span><br><span class="line"># 不混淆View中的setXxx()和getXxx()方法,以保证属性动画正常工作</span><br><span class="line">-keepclassmembers <span class="keyword">public</span> <span class="keyword">class</span> * <span class="keyword">extends</span> android.view.View {</span><br><span class="line"> <span class="keyword">void</span> set*(***);</span><br><span class="line"> *** get*();</span><br><span class="line">}</span><br><span class="line"># We want to keep methods in Activity that could be used in the XML attribute onClick</span><br><span class="line"># 不混淆Activity中参数是View的方法,例如,一个控件通过android:onClick=<span class="string">"clickMethodName"</span>绑定点击事件,混淆后会导致点击事件失效</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> * <span class="keyword">extends</span> android.app.Activity {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> *(android.view.View);</span><br><span class="line">}</span><br><span class="line"># <span class="keyword">For</span> enumeration classes, see http:<span class="comment">//proguard.sourceforge.net/manual/examples.html#enumerations</span></span><br><span class="line"># 不混淆枚举类中的values()和valueOf()方法</span><br><span class="line">-keepclassmembers enum * {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> **[] values();</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> ** valueOf(java.lang.String);</span><br><span class="line">}</span><br><span class="line"># 不混淆Parcelable实现类中的CREATOR字段,以保证Parcelable机制正常工作</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> * <span class="keyword">implements</span> android.os.Parcelable {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> android.os.Parcelable$Creator CREATOR;</span><br><span class="line">}</span><br><span class="line"># 不混淆R文件中的所有静态字段,以保证正确找到每个资源的id</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> **.R$* {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <fields>;</span><br><span class="line">}</span><br><span class="line"># The support library contains references to newer platform versions.</span><br><span class="line"># Don<span class="string">'t warn about those in case this app is linking against an older</span></span><br><span class="line"><span class="string"># platform version. We know about them, and they are safe.</span></span><br><span class="line"><span class="string"># 不对android.support包下的代码警告(如果我们打包的版本低于support包下某些类的使用版本,会出现警告的问题)</span></span><br><span class="line"><span class="string">-dontwarn android.support.**</span></span><br><span class="line"><span class="string"># Understand the @Keep support annotation.</span></span><br><span class="line"><span class="string"># 不混淆Keep类</span></span><br><span class="line"><span class="string">-keep class android.support.annotation.Keep</span></span><br><span class="line"><span class="string"># 不混淆使用了注解的类及类成员</span></span><br><span class="line"><span class="string">-keep @android.support.annotation.Keep class * {*;}</span></span><br><span class="line"><span class="string"># 如果类中有使用了注解的方法,则不混淆类和类成员</span></span><br><span class="line"><span class="string">-keepclasseswithmembers class * {</span></span><br><span class="line"><span class="string"> @android.support.annotation.Keep <methods>;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string"># 如果类中有使用了注解的字段,则不混淆类和类成员</span></span><br><span class="line"><span class="string">-keepclasseswithmembers class * {</span></span><br><span class="line"><span class="string"> @android.support.annotation.Keep <fields>;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string"># 如果类中有使用了注解的构造函数,则不混淆类和类成员</span></span><br><span class="line"><span class="string">-keepclasseswithmembers class * {</span></span><br><span class="line"><span class="string"> @android.support.annotation.Keep <init>(...);</span></span><br><span class="line"><span class="string">}</span></span><br></pre></td></tr></table></figure><p><strong>keep关键字:</strong></p><table><thead><tr><th>关键字</th><th>含义</th></tr></thead><tbody><tr><td>keep</td><td>保留类和类成员,防止被混淆或移除</td></tr><tr><td>keepnames</td><td>保留类和类成员,防止被混淆,但没有被引用的类成员会被移除</td></tr><tr><td>keepclassmembers</td><td>只保留类成员,防止被混淆或移除</td></tr><tr><td>keepclassmembernames</td><td>只保留类成员,防止被混淆,但没有被引用的成员会被移除</td></tr><tr><td>keepclasseswithmembers</td><td>保留类和类成员,防止被混淆或移除,如果指定的类成员不存在还是会被混淆</td></tr><tr><td>keepclasseswithmembernames</td><td>保留类和类成员,防止被混淆,如果指定的类成员不存在还是会被混淆,没有被引用的类成员会被移除</td></tr></tbody></table><p><strong>相关通配符:</strong></p><table><thead><tr><th>通配符</th><th>含义</th></tr></thead><tbody><tr><td>*</td><td>匹配任意长度字符,但不含包名分隔符.。例如一个类的全包名路径是com.othershe.test.Person,使用com.othershe.test.<em>、com.othershe.test.</em>都是可以匹配的,但com.othershe.*就不能匹配</td></tr><tr><td>**</td><td>匹配任意长度字符,并包含包名分隔符.。例如要匹配com.othershe.test.**包下的所有内容</td></tr><tr><td><em>*</em></td><td>匹配任意参数类型。例如<strong><em> getName(</em></strong>)可匹配String getName(String)</td></tr><tr><td>…</td><td>匹配任意长度的任意类型参数。例如void setName(…)可匹配void setName(String firstName, String secondName)</td></tr><tr><td><fileds></td><td>匹配类、接口中所有字段</td></tr><tr><td><methods></td><td>匹配类、接口中所有方法</td></tr><tr><td><init></td><td>匹配类中所有构造函数</td></tr></tbody></table><p>以上就是混淆的基本语法,系统的proguard-android.txt已经为我们完成了大部分基础的混淆配置工作,至于编写当前app module下的proguard-rules.pro,只需要针对当前项目添加一些特有的配置,避免某些重要的东西被混淆掉就可以了,我们主要考虑以下几点:</p><ol><li>在AndroidManifest.xml中注册的继承四大组件的子类的类名以及重写的方法名都不会被混淆。</li></ol><p>比如,如果希望项目中android.support.v4.app.Fragment子类的类名和重写父类的方法名不被混淆可以添加如下配置<br><figure class="highlight gradle"><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><span class="line"># 不混淆Fragment的子类类名以及onCreate()、onCreateView()方法名</span><br><span class="line">-keep <span class="keyword">public</span> <span class="keyword">class</span> * <span class="keyword">extends</span> android.support.v4.app.Fragment {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> onCreate(android.os.Bundle);</span><br><span class="line"> <span class="keyword">public</span> android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><ol><li><p>不混淆某个特定的类和类中所有成员</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-keep <span class="keyword">class</span> com.test.utils.CommonUtil { *; }</span><br></pre></td></tr></table></figure></li><li><p>不混淆某个目录下的文件,例如使用Gson时,数据bean不能被混淆,需要如下配置:</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># com.test.bean代表所有bean所在的目录</span><br><span class="line">-keep <span class="keyword">class</span> com.test.bean.** { *; }</span><br></pre></td></tr></table></figure></li><li><p>保留泛型</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-keepattributes Signature</span><br></pre></td></tr></table></figure></li><li><p>保留用于调试堆栈跟踪的行号信息</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-keepattributes SourceFile,LineNumberTable</span><br></pre></td></tr></table></figure></li><li><p>WebView中使用了JS调用,需要添加如下配置:</p><figure class="highlight gradle"><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><span class="line">-keepclassmembers <span class="keyword">class</span> fqcn.of.javascript.<span class="keyword">interface</span>.<span class="keyword">for</span>.webview {</span><br><span class="line"> <span class="keyword">public</span> *;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>项目中使用的第三方库的混淆规则,这里列举几个常用的:</p><figure class="highlight gradle"><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></pre></td><td class="code"><pre><span class="line"># okhttp</span><br><span class="line">-dontwarn okhttp3.**</span><br><span class="line">-dontwarn okio.**</span><br><span class="line">-dontwarn javax.annotation.**</span><br><span class="line">-keepnames <span class="keyword">class</span> okhttp3.internal.publicsuffix.PublicSuffixDatabase</span><br><span class="line"># Retrofit</span><br><span class="line">-dontwarn okio.**</span><br><span class="line">-dontwarn javax.annotation.**</span><br><span class="line">-dontnote retrofit2.Platform</span><br><span class="line">-dontwarn retrofit2.Platform$Java8</span><br><span class="line">-keepattributes Signature</span><br><span class="line">-keepattributes Exceptions</span><br><span class="line"># RxJava RxAndroid</span><br><span class="line">-dontwarn sun.misc.**</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> rx.internal.util.unsafe.*ArrayQueue*Field* {</span><br><span class="line"> <span class="keyword">long</span> producerIndex;</span><br><span class="line"> <span class="keyword">long</span> consumerIndex;</span><br><span class="line">}</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {</span><br><span class="line"> rx.internal.util.atomic.LinkedQueueNode producerNode;</span><br><span class="line">}</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {</span><br><span class="line"> rx.internal.util.atomic.LinkedQueueNode consumerNode;</span><br><span class="line">}</span><br><span class="line"># Gson</span><br><span class="line">-keep <span class="keyword">class</span> com.google.gson.stream.** { *; }</span><br><span class="line">-keepattributes EnclosingMethod</span><br><span class="line"># xxx代表model类的全包名路径</span><br><span class="line">-keep <span class="keyword">class</span> xxx.** { *; }</span><br><span class="line"># butterknie</span><br><span class="line">-keep <span class="keyword">class</span> butterknife.** { *; }</span><br><span class="line">-dontwarn butterknife.internal.**</span><br><span class="line">-keep <span class="keyword">class</span> **$$ViewBinder { *; }</span><br><span class="line">-keepclasseswithmembernames <span class="keyword">class</span> * {</span><br><span class="line"> @butterknife.* <fields>;</span><br><span class="line">}</span><br><span class="line">-keepclasseswithmembernames <span class="keyword">class</span> * {</span><br><span class="line"> @butterknife.* <methods>;</span><br><span class="line">}</span><br><span class="line"># eventbus</span><br><span class="line">-keepattributes *Annotation*</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> ** {</span><br><span class="line"> @org.greenrobot.eventbus.Subscribe <methods>;</span><br><span class="line">}</span><br><span class="line">-keep enum org.greenrobot.eventbus.ThreadMode { *; }</span><br><span class="line"># Only required <span class="keyword">if</span> you use AsyncExecutor</span><br><span class="line">-keepclassmembers <span class="keyword">class</span> * <span class="keyword">extends</span> org.greenrobot.eventbus.util.ThrowableFailureEvent {</span><br><span class="line"> <init>(java.lang.Throwable);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><h3 id="查看混淆结果"><a href="#查看混淆结果" class="headerlink" title="查看混淆结果"></a>查看混淆结果</h3><p>混淆后打包,会在app module/build/outputs/mapping/release目录下生成如下文件</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><ul><li>dump.txt:描述apk文件中所有类的内部结构</li><li>mapping.txt:混淆前后的类、类成员、方法的对照关系(重要,追溯Crash堆栈信息要用到)</li><li>resources.txt:资源文件的压缩信息</li><li>seeds.txt:未被混淆的类和成员</li><li>usage.txt:被移除的代码</li></ul><p>最后还是有必要看一下混淆后的代码结构,验证混淆是否成功。一个简单的办法,Android Studio的Build菜单下有一个Analyze APK选项,只需要先选择要分析的apk包,在之后的界面点击classes.dex即可看到混淆后的代码结构:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="2.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>如果看到a b c d e 这样的包名,说明已经混淆成功了。</p><h3 id="追溯Crash信息"><a href="#追溯Crash信息" class="headerlink" title="追溯Crash信息"></a>追溯Crash信息</h3><p>代码混淆后,也会导致Crash堆栈信息被混淆,难以阅读,增加定位问题位置的难度,一个混淆后的Crash堆栈信息类似这样,核心的信息都没了:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="3.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>为了解决这个问题,可以使用<sdk目录>\tools\proguard\bin下的proguardgui.bat脚本将Crash堆栈信息还原到混淆前的状态。步骤如下:</sdk目录></p><ul><li>双击打开脚本,选择左边的ReTrace选项</li><li>选择Mapping file文件,也就是混淆后打包后在app module/build/outputs/mapping/release下生成的mapping.txt</li><li>拷贝混淆后的堆栈信息</li><li>点击右下角的ReTrace!按钮,完成Crash堆栈信息的追溯</li></ul><p>如下图中间部分就是追溯到的原Crash堆栈信息:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="4.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>代码混淆基本的内容就这些了。</p>]]></content>
<summary type="html">
<p>通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也有效缩减了apk的体积。</p>
<h3 id="开启混淆"><a href=
</summary>
<category term="优化" scheme="http://yoursite.com/tags/%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>Android性能优化</title>
<link href="http://yoursite.com/2017/11/12/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
<id>http://yoursite.com/2017/11/12/Android性能优化/</id>
<published>2017-11-12T14:06:01.000Z</published>
<updated>2018-06-05T10:07:45.929Z</updated>
<content type="html"><![CDATA[<p>Android性能优化,是Android开发中的重中之中,本文将讲述如何进行性能优化。</p><p>实际项目中的Android性能优化主要有如下几个方面:</p><ol><li>编写高效代码—开发中总结出的一些小的性能Tips</li><li>Layout布局优化</li><li>内存优化</li></ol><h3 id="编写高效代码"><a href="#编写高效代码" class="headerlink" title="编写高效代码"></a>编写高效代码</h3><p>编写高效代码的两个原则</p><ol><li>不要写不需要的代码</li><li>不要分配不必要的内存</li></ol><p>以上两个原则,似乎感觉是废话,但确实是编程的最高境界,也是我们编写代码的过程中时刻需要思考和注意的两个方面。</p><p>那么如何做到如上两点呢?下面列出了一些实际开发中的例子。</p><p><strong>避免产生不必要的对象</strong></p><p>例如:</p><ul><li><p>int的数组比Integer对象数组要好得多。两个平行的int数组要比一个(int,int)型的对象数组高效。这对于其他任何基本数据类型的组合都通用</p></li><li><p>两个平行数组Foo[],Bar[]会优于一个(Foo,Bar)对象的数组</p></li><li><p>通常来讲,尽量避免创建短时零时对象.少的对象创建意味着低频的垃圾回收</p></li></ul><p>对象的分配和回收都是需要代价的;分配的内存越多,就会引起强制的内存回收;给用户体验增加小的停顿间隙,从而影响用户体验。</p><p>用户能感觉到卡顿的时间延迟是100ms ~ 200ms。</p><p><strong>用静态代替虚拟</strong></p><p>如果方法不需要访问某对像的字段,将该方法设置为静态,调用速度会提升15%~20%</p><p>对于常量使用static final </p><p>static final int i = 1;<br>static final String s = “a”;</p><blockquote><p>注:这种优化仅仅是针对基本数据类型和String类型常量的,而非任意的引用类型。但尽可能的将常量声明为static final是一种好的做法。</p></blockquote><p><strong>避免内部的getter和setter</strong></p><p><strong>使用增强for循环</strong></p><p>增强for循环要比普通循环快3倍</p><p><strong>避免使用浮点数</strong></p><p>通常,浮点数会比整型慢2/3</p><p><strong>在没有JIT的设备上,调用方法所传递的对象采用具体的类型而非接口类型会更高效</strong></p><ul><li>void methodA(List<string> list);</string></li><li>void methodA(ArrayList<string> list);</string></li></ul><p>如上,后一种比前一种更高效。</p><p><strong>数据库操作方法的优化<br>尽量利用原生的SQL语句</strong></p><p>原生的SQL省去了拼接sql语句的步骤,要比SqliteDatabase提供的insert、query、 update、delete等函数效率高。当数据库越大,差别也越大</p><p>当操作条数较多时,利用事务进行批处理</p><p>这样SQLite将把全部要执行的SQL语句先缓存在内存当中,然后等到COMMIT的时候一次性的写入数据库,这样数据库文件只被打开关闭了一次,效率自然大大的提高</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><span class="line">db.beginTransaction(); </span><br><span class="line"><span class="keyword">for</span>(Collection c:colls){</span><br><span class="line"> insert(db, c);</span><br><span class="line">} </span><br><span class="line">db.setTransactionSuccessful();</span><br></pre></td></tr></table></figure><p><strong>Http请求方式的选择</strong><br>Android 内置了两种HTTP方式:HttpURLConnection 和 Apache HttpClient。这两种都支持HTTPS、流式上传和下载、可配置超时、IPv6和连接池。在Gingerbread或者更高版本时,推荐使用HttpURLConnection。</p><p>这是因为: HttpURLConnection API 更简单,包更小。同时对传输数据的压缩和响应的缓存处理减少了网络带宽、提高了速度,也节省了电量。</p><h3 id="优化布局"><a href="#优化布局" class="headerlink" title="优化布局"></a>优化布局</h3><p>Layouts是Android应用里直接影响用户体验的一个关键部分。如果Layout设计的不好,可能导致你的应用大量的内存占用从而导致UI响应很慢。Android SDK提供了工具帮助你分析你的Layouts的性能问题。</p><p><strong>使用Hierarchy Viewer</strong></p><p>Hierarchy Viewer工具位于SDK \tools\目录下,该工具能分析出你的布局不合理和可以优化的地方。</p><p>大多数情况下,布局渲染时间差别较大的原因是在LinaerLayout里使用了layout_weight。这将会增加测量(Measure)的时间。你应该仔细的考虑是否有必要使用layout weight。</p><p><strong>使用Lint</strong></p><p>使用Lint — 查看你的view层级哪些地方可以优化</p><ul><li><p>使用compound drawables - 一个包含了ImageView与TextView的LinearLayout可以被当作一个compound drawable来处理</p></li><li><p>使用<merge> - 如果FramLayout仅仅是一个纯粹的(没有设置背景,间距等)布局根元素,我们可以使用merge标签来当作根标签</merge></p></li><li><p>无用的分支 - 如果一个layout并没有任何子组件,那么可以被移除,这样可以提高效率</p></li><li><p>无用的父控件 - 如果一个layout只有子控件,没有兄弟控件,并且不是一个ScrollView或者根节点,而且没有设置背景,那么我们可以移除这个父控件,直接把子控件提升为父控件</p></li><li><p>深层次的layout - 尽量减少内嵌的层级,考虑使用更多平级的组件 RelativeLayout or GridLayout来提升布局性能,默认最大的深度是10</p></li></ul><p><strong>其他一些布局要点</strong></p><ul><li>使用include标签</li><li>使用ViewStub标签</li></ul><h3 id="优化App内存"><a href="#优化App内存" class="headerlink" title="优化App内存"></a>优化App内存</h3><p>为了垃圾回收器能回收你系统的内存,你应该避免引起内存泄露,而且要在合适的时间点释放被引用的对象。</p><p><strong>慎用Service</strong></p><ol><li><p>Service执行完后台任务后要停止</p></li><li><p>使用IntentService</p></li></ol><p>IntentService不同于普通的Service之处是:</p><ul><li><p>提交的task系统会post到子线程运行</p></li><li><p>当后台运行的task完成时,系统会stop掉IntentService</p></li></ul><p><strong>Release memory when your user interface becomes hidden</strong></p><p>例如,在该onStop()里做释放资源(例如网络连接、注销广播等)的工作</p><p><strong>使用优化后的集合容器</strong></p><p>例如:SparseArray、SparseBooleanArray、LongSpareArray …..</p><p><strong>尽量避免使用枚举</strong></p><p>相比于静态常量,枚举会有超过其两倍以上的内存开销,在android中需严格避免使用枚举</p><p><strong>避免使用依赖注入框架</strong></p><p><strong>使用ProGuard消除没有使用的代码</strong></p><p><strong>使用zipalign优化和对齐你的apk</strong></p><p><strong>使用MAT分析和优化内存</strong></p><ol><li><p>I/O使用后需要关闭,数据库和Cursor等使用后要关闭</p></li><li><p>使用finalize()+MAT 分析内存泄露</p></li></ol><p>end </p><p>Android优化主要就是内存、布局和性能的优化,本文总结了Android中优化的一些知识点。如果还有其他我没有讲到的,欢迎给我留言。</p>]]></content>
<summary type="html">
<p>Android性能优化,是Android开发中的重中之中,本文将讲述如何进行性能优化。</p>
<p>实际项目中的Android性能优化主要有如下几个方面:</p>
<ol>
<li>编写高效代码—开发中总结出的一些小的性能Tips</li>
<li>Layout布局优化<
</summary>
<category term="优化" scheme="http://yoursite.com/tags/%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title>给RecyclerView加上折叠的效果</title>
<link href="http://yoursite.com/2017/10/21/%E7%BB%99RecyclerView%E5%8A%A0%E4%B8%8A%E6%8A%98%E5%8F%A0%E7%9A%84%E6%95%88%E6%9E%9C/"/>
<id>http://yoursite.com/2017/10/21/给RecyclerView加上折叠的效果/</id>
<published>2017-10-20T20:37:21.000Z</published>
<updated>2018-06-05T10:07:45.973Z</updated>
<content type="html"><![CDATA[<p>RecyclerView 有很高的自由度,可以说只有想不到没有做不到。这次用超简单的方法,让 RecyclerView 带上折叠的效果。</p><p>效果是这样的。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>总结一下这个列表的特点,就是以下三点:</p><ul><li>重叠效果;</li><li>层次感;</li><li>首项的差动。<br>下面我们来一个个解决。</li></ul><p>我们新建一个 ParallaxRecyclerView,让它继承 RecyclerView,并使用 LinearLayoutManager 作为布局管理器。</p><p><strong>重叠效果</strong></p><p>其实就是每一项都搭一部分在它前面那项而已。我们知道,RecyclerView 可以通过设置 ItemDecoration来实现列表的间隔效果,有没有想过要是把间隔设为负数会怎么样?比如:</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><span class="line">addItemDecoration(<span class="keyword">new</span> ItemDecoration() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getItemOffsets</span><span class="params">(Rect outRect, View view, RecyclerView parent, State state)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.getItemOffsets(outRect, view, parent, state);</span><br><span class="line"> outRect.bottom = -dp2px(context, <span class="number">10</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>没错,这就实现了我们的重叠效果。</p><p><strong>层次感</strong></p><p>在 Material Design里是有Z轴这个概念的,我们可以给控件设置垂直于屏幕的高度,让不在同一高度的控件看起来有层次感。当然,我们要用 Material Design 的控件才有这个属性,这里我用的是 CardView。</p><p>我们给 ParallaxRecyclerView 增加一个滑动监听,在 onScrolled 方法里面做如下设置:</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><span class="line">LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();</span><br><span class="line"><span class="keyword">int</span> firstPosition = layoutManager.findFirstVisibleItemPosition();</span><br><span class="line"><span class="keyword">int</span> lastPosition = layoutManager.findLastVisibleItemPosition();</span><br><span class="line"><span class="keyword">int</span> visibleCount = lastPosition - firstPosition;</span><br><span class="line"><span class="comment">//重置控件的高度</span></span><br><span class="line"><span class="keyword">int</span> elevation = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = firstPosition - <span class="number">1</span>; i <= (firstPosition + visibleCount) + <span class="number">1</span>; i++) {</span><br><span class="line"> View view = layoutManager.findViewByPosition(i);</span><br><span class="line"> <span class="keyword">if</span> (view != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (view <span class="keyword">instanceof</span> CardView) {</span><br><span class="line"> ((CardView) view).setCardElevation(dp2px(context, elevation));</span><br><span class="line"> elevation += <span class="number">5</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中,setCardElevation 方法就是用来给 CardView 设置高度的,这里让每一项的高度比它的上一项高 5dp。</p><p><strong>首项的差动</strong></p><p>最后,我们想给第一项增加一个差动效果,这个同样在 onScrolled方法里面做处理就好了:</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><span class="line">View firstView = layoutManager.findViewByPosition(firstPosition);</span><br><span class="line"><span class="keyword">float</span> firstViewTop = firstView.getTop();</span><br><span class="line">firstView.setTranslationY(-firstViewTop / <span class="number">2.0f</span>);</span><br></pre></td></tr></table></figure><p>这样相当于第一项的滑动速度变成原来的一半。但这也会导致一个问题, 由于改变了控件的位置,当这个控件被复用时,会出现位置不正确的情况。所以我们在设置高度的时候,可以顺便把控件的位置复原了:</p><figure class="highlight plain"><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><span class="line">float translationY = view.getTranslationY();</span><br><span class="line">if (i > firstPosition && translationY != 0) {</span><br><span class="line"> view.setTranslationY(0);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样就完成了一个带有简单折叠效果的 RecyclerView 了,妥妥的。</p>]]></content>
<summary type="html">
<p>RecyclerView 有很高的自由度,可以说只有想不到没有做不到。这次用超简单的方法,让 RecyclerView 带上折叠的效果。</p>
<p>效果是这样的。</p>
<figure class="image-bubble">
<di
</summary>
<category term="控件" scheme="http://yoursite.com/tags/%E6%8E%A7%E4%BB%B6/"/>
</entry>
<entry>
<title>Service总结</title>
<link href="http://yoursite.com/2017/09/28/Service%E6%80%BB%E7%BB%93/"/>
<id>http://yoursite.com/2017/09/28/Service总结/</id>
<published>2017-09-27T20:14:37.000Z</published>
<updated>2018-06-05T10:07:45.953Z</updated>
<content type="html"><![CDATA[<h3 id="启动Service的方式"><a href="#启动Service的方式" class="headerlink" title="启动Service的方式"></a>启动Service的方式</h3><ul><li><p>Context.startService(Intent):</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><span class="line">Intent intent = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, PlayService.class);</span><br><span class="line">startService(intent);</span><br></pre></td></tr></table></figure></li><li><p>Context.bindService(Intent,ServiceConnection,BIND_AUTO_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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> ServiceConnection conn = <span class="keyword">new</span> ServiceConnection() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceConnected</span><span class="params">(ComponentName name, IBinder service)</span> </span>{</span><br><span class="line"> Log.e(<span class="string">"result"</span>,<span class="string">"绑定成功"</span>);</span><br><span class="line"> PlayService.MyBinder binder = (PlayService.MyBinder) service;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceDisconnected</span><span class="params">(ComponentName name)</span> </span>{</span><br><span class="line"> Log.e(<span class="string">"result"</span>,<span class="string">"解绑成功"</span>);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">Intent intent = <span class="keyword">new</span> Intent(<span class="keyword">this</span>,PlayService.class);</span><br><span class="line">bindService(intent,conn,BIND_AUTO_CREATE);</span><br></pre></td></tr></table></figure></li></ul><h4 id="两种启动方式区别:"><a href="#两种启动方式区别:" class="headerlink" title="两种启动方式区别:"></a>两种启动方式区别:</h4><ol><li>startService启动之后,如果没有调用stopSelf()或者stopService()就会一直在后台运行。bindService启动Service之后,在启动它的组件被销毁后也会解绑并销毁。</li><li>startService()启动Service到结束,Service经历生命周期的是onCreate()、onStartCommand()、onDestory()。而bindService()启动Service,Service经历的生命周期是onCreate()、onBind()、onUnbind()、onDestory()</li><li>如果即startService又bindService启动了Service,要分别unBindService()、stopSelf()/stopService()关闭Service。这里关闭没有顺序限制的,比如:先startService,后bindService。结束时先进行unBindService,stopService,还是顺序反过来都是没有问题的。<figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure></li></ol><h4 id="启动之后再次启动的生命周期"><a href="#启动之后再次启动的生命周期" class="headerlink" title="启动之后再次启动的生命周期"></a>启动之后再次启动的生命周期</h4><ol><li>在startService之后,再进行startService,只会再次执行Service的onStartCommond方法,而不会再执行onCreate</li><li>在bindService之后,再进行bindService,不会再执行Service的onBind。</li></ol><h3 id="和Activity交互"><a href="#和Activity交互" class="headerlink" title="和Activity交互"></a>和Activity交互</h3><ul><li><p>Activity在启动Service的时候,可以通过Intent.putExtra来给Service传递数据(两种方式均可)</p></li><li><p>通过bindService()启动Service,通过onBind()返回的Binder来指挥Service来进行操作</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><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyBinder</span> <span class="keyword">extends</span> <span class="title">Binder</span></span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">action</span><span class="params">()</span></span></span><br><span class="line"><span class="function">}</span></span><br></pre></td></tr></table></figure></li><li><p>Service给Activity回传数据,可以通过在Service中发送广播,在Activity注册广播接收数据</p></li></ul><h3 id="Service是运行在UI线程的"><a href="#Service是运行在UI线程的" class="headerlink" title="Service是运行在UI线程的"></a>Service是运行在UI线程的</h3><ul><li>如果需要在Service中做耗时操作,需要另外起一个线程。</li><li>当然你还可以使用IntentService</li></ul><h3 id="IntentService"><a href="#IntentService" class="headerlink" title="IntentService"></a>IntentService</h3><p><strong>IntentService优点如下</strong></p><ul><li>Service中的事情处理完成之后,它会调用stopSelf()结束自己</li><li>Service可以直接处理耗时操作</li></ul><p>IntentService的基本使用如下:<br><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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DownloadService</span> <span class="keyword">extends</span> <span class="title">IntentService</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DownloadService</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(<span class="string">"DownLoadService"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate();</span><br><span class="line"> Log.e(<span class="string">"result"</span>, <span class="string">"onCreate"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStart</span><span class="params">(@Nullable Intent intent, <span class="keyword">int</span> startId)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onStart(intent, startId);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">onStartCommand</span><span class="params">(@Nullable Intent intent, <span class="keyword">int</span> flags, <span class="keyword">int</span> startId)</span> </span>{</span><br><span class="line"> Log.e(<span class="string">"result"</span>, <span class="string">"onStartCommand"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.onStartCommand(intent, flags, startId);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onHandleIntent</span><span class="params">(Intent intent)</span> </span>{</span><br><span class="line"> Log.e(<span class="string">"result"</span>, <span class="string">"onHandleIntent"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>需要注意的是:它的构造方法必须给他提供一个无参构造方法,名字就命名为类名即可。</p><p><strong>IntentService的原理</strong></p><p>IntentService封装了HandlerThread和Handler,HandlerThread就是一个Thread(具体再分析),Handler handler = new Handler(handerThread.getLooper()),handler是由子线程中的Looper构造的,相当于在子线程中有个一个MessageQueue消息队列,我们每次startService(),就是sendMessage()添加一个消息到队列中,然后该childThread的Looper不断的从MessageQueue中取出消息去处理。</p><p>记住:Handler构造中的Looper在哪个线程,该Handler就为哪个线程服务。如果是无参构造就是MainThread,因为内部调用了looper.myLooper()。</p><h3 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h3><p>onStartCommand()方法的返回值问题:</p><ol><li>START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。</li><li>START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。</li><li>START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。</li><li>START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。</li></ol>]]></content>
<summary type="html">
<h3 id="启动Service的方式"><a href="#启动Service的方式" class="headerlink" title="启动Service的方式"></a>启动Service的方式</h3><ul>
<li><p>Context.startService(
</summary>
<category term="控件" scheme="http://yoursite.com/tags/%E6%8E%A7%E4%BB%B6/"/>
</entry>
<entry>
<title>Android通用流行框架大全</title>
<link href="http://yoursite.com/2017/09/10/Android%E9%80%9A%E7%94%A8%E6%B5%81%E8%A1%8C%E6%A1%86%E6%9E%B6%E5%A4%A7%E5%85%A8/"/>
<id>http://yoursite.com/2017/09/10/Android通用流行框架大全/</id>
<published>2017-09-09T21:56:39.000Z</published>
<updated>2018-06-05T10:07:45.952Z</updated>
<content type="html"><![CDATA[<h3 id="1-缓存"><a href="#1-缓存" class="headerlink" title="1. 缓存"></a>1. 缓存</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>DiskLruCache</td><td>Java实现基于LRU的磁盘缓存</td></tr><tr><td>Robospice</td><td></td></tr></tbody></table><h3 id="2-图片加载"><a href="#2-图片加载" class="headerlink" title="2.图片加载"></a>2.图片加载</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Android Universal Image Loader</td><td>一个强大的加载,缓存,展示图片的库</td></tr><tr><td>Picasso</td><td>一个强大的图片下载与缓存的库</td></tr><tr><td>Fresco</td><td>一个用于管理图像和他们使用的内存的库</td></tr><tr><td>Glide</td><td>一个图片加载和缓存的库</td></tr></tbody></table><h3 id="3-图片处理"><a href="#3-图片处理" class="headerlink" title="3. 图片处理"></a>3. 图片处理</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Picasso-transformations</td><td>一个为Picasso提供多种图片变换的库</td></tr><tr><td>Glide-transformations</td><td>一个为Glide提供多种图片变换的库</td></tr><tr><td>Android-gpuimage</td><td>基于OpenGL的Android过滤器</td></tr></tbody></table><h3 id="4-网络请求"><a href="#4-网络请求" class="headerlink" title="4. 网络请求"></a>4. 网络请求</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Android Async HTTP</td><td>Android异步HTTP库</td></tr><tr><td>AndroidAsync</td><td>异步Socket,HTTP(客户端+服务器),WebSocket,和socket.io库。基于NIO而不是线程。</td></tr><tr><td>OkHttp</td><td>一个Http与Http/2的客户端</td></tr><tr><td>Retrofit</td><td>类型安全的Http客户端</td></tr><tr><td>Volley</td><td>Google推出的Android异步网络请求框架和图片加载框架</td></tr></tbody></table><h3 id="5-网络解析"><a href="#5-网络解析" class="headerlink" title="5. 网络解析"></a>5. 网络解析</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Gson</td><td>一个Java序列化/反序列化库,可以将JSON和java对象互相转换</td></tr><tr><td>Jackson</td><td>Jackson可以轻松地将Java对象转换成json对象和xml文档,同样也可以将json、xml转换成Java对象</td></tr><tr><td>Fastjson</td><td>Java上一个快速的JSON解析器/生成器</td></tr><tr><td>HtmlPaser</td><td>一种用来解析单个独立html或嵌套html的方式</td></tr><tr><td>Jsoup</td><td>一个以最好的DOM,CSS和jQuery解析html的库</td></tr></tbody></table><h3 id="6-数据库"><a href="#6-数据库" class="headerlink" title="6. 数据库"></a>6. 数据库</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>OrmLite</td><td>JDBC和Android的轻量级ORM java包</td></tr><tr><td>Sugar</td><td>用超级简单的方法处理Android数据库</td></tr><tr><td>GreenDAO</td><td>一种轻快地将对象映射到SQLite数据库的ORM解决方案</td></tr><tr><td>ActiveAndroid</td><td>以活动记录方式为Android SQLite提供持久化</td></tr><tr><td>SQLBrite</td><td>SQLiteOpenHelper 和ContentResolver的轻量级包装</td></tr><tr><td>Realm</td><td>移动数据库:一个SQLite和ORM的替换品</td></tr></tbody></table><h3 id="7-依赖注入"><a href="#7-依赖注入" class="headerlink" title="7. 依赖注入"></a>7. 依赖注入</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>ButterKnife</td><td>将Android视图和回调方法绑定到字段和方法上</td></tr><tr><td>Dagger2</td><td>一个Android和java快速依赖注射器。</td></tr><tr><td>AndroidAnotations</td><td>快速安卓开发。易于维护</td></tr><tr><td>RoboGuice</td><td>Android平台的Google Guice</td></tr></tbody></table><h3 id="8-图表"><a href="#8-图表" class="headerlink" title="8. 图表"></a>8. 图表</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>WilliamChart</td><td>创建图表的Android库</td></tr><tr><td>HelloCharts</td><td>兼容到API8的Android图表库</td></tr><tr><td>MPAndroidChart</td><td>一个强大的Android图表视图/图形库</td></tr></tbody></table><h3 id="9-后台处理"><a href="#9-后台处理" class="headerlink" title="9. 后台处理"></a>9. 后台处理</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Tape</td><td>一个轻快的,事务性的,基于文件的FIFO的库</td></tr><tr><td>Android Priority Job Queue</td><td>一个专门为Android轻松调度任务的工作队列</td></tr></tbody></table><h3 id="10-事件总线"><a href="#10-事件总线" class="headerlink" title="10. 事件总线"></a>10. 事件总线</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>EventBus</td><td>安卓优化的事件总线,简化了活动、片段、线程、服务等的通信</td></tr><tr><td>Otto</td><td>一个基于Guava的增强的事件总线</td></tr></tbody></table><h3 id="11-响应式编程"><a href="#11-响应式编程" class="headerlink" title="11. 响应式编程"></a>11. 响应式编程</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>RxJava</td><td>JVM上的响应式扩展</td></tr><tr><td>RxJavaJoins</td><td>为RxJava提供Joins操作</td></tr><tr><td>RxAndroid</td><td>Android上的响应式扩展,在RxJava基础上添加了Android线程调度</td></tr><tr><td>RxBinding</td><td>提供用RxJava绑定Android UI的API</td></tr><tr><td>Agera</td><td>Android上的响应式编程</td></tr></tbody></table><h3 id="12-Log框架"><a href="#12-Log框架" class="headerlink" title="12. Log框架"></a>12. Log框架</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Logger</td><td>简单,漂亮,强大的Android日志工具</td></tr><tr><td>Hugo</td><td>在调试版本上注解的触发方法进行日志记录</td></tr><tr><td>Timber</td><td>一个小的,可扩展的日志工具</td></tr><tr><td>Xutils</td><td></td></tr></tbody></table><h3 id="13-测试框架"><a href="#13-测试框架" class="headerlink" title="13. 测试框架"></a>13. 测试框架</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Mockito</td><td>Java编写的Mocking单元测试框架</td></tr><tr><td>Robotium</td><td>Android UI 测试</td></tr><tr><td>Robolectric</td><td>Android单元测试框架</td></tr></tbody></table><p>Android自带很多测试工具:JUnit,Monkeyrunner,UiAutomator,Espresso等</p><h3 id="14-调试框架"><a href="#14-调试框架" class="headerlink" title="14. 调试框架"></a>14. 调试框架</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>Stetho</td><td>调试Android应用的桥梁,使得可以利用Chrome开发者工具进行调试</td></tr></tbody></table><h3 id="15-性能优化"><a href="#15-性能优化" class="headerlink" title="15. 性能优化"></a>15. 性能优化</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>LeakCanary</td><td>内存泄漏检测工具</td></tr><tr><td>ACRA</td><td>Android应用程序崩溃报告</td></tr></tbody></table><h3 id="16-数据统计"><a href="#16-数据统计" class="headerlink" title="16. 数据统计"></a>16. 数据统计</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>友盟统计</td><td></td></tr><tr><td>百度统计</td><td></td></tr></tbody></table><h3 id="17-崩溃收集"><a href="#17-崩溃收集" class="headerlink" title="17. 崩溃收集"></a>17. 崩溃收集</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>腾讯bugly</td><td></td></tr><tr><td>bugtags</td><td></td></tr></tbody></table><h3 id="18-即时通讯"><a href="#18-即时通讯" class="headerlink" title="18. 即时通讯"></a>18. 即时通讯</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>环信</td><td></td></tr><tr><td>融云</td><td></td></tr><tr><td>阿里百川</td><td></td></tr></tbody></table><h3 id="19-推送"><a href="#19-推送" class="headerlink" title="19. 推送"></a>19. 推送</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>小米推送</td><td></td></tr><tr><td>极光推送</td><td></td></tr></tbody></table><p>个推</p><h3 id="20-内嵌浏览器"><a href="#20-内嵌浏览器" class="headerlink" title="20. 内嵌浏览器"></a>20. 内嵌浏览器</h3><table><thead><tr><th>名称</th><th>描述</th></tr></thead><tbody><tr><td>腾讯X5内核</td><td></td></tr><tr><td>百度T5内核</td><td></td></tr></tbody></table>]]></content>
<summary type="html">
<h3 id="1-缓存"><a href="#1-缓存" class="headerlink" title="1. 缓存"></a>1. 缓存</h3><table>
<thead>
<tr>
<th>名称</th>
<th>描述</th>
</tr>
</thead>
<tb
</summary>
<category term="其他" scheme="http://yoursite.com/tags/%E5%85%B6%E4%BB%96/"/>
</entry>
<entry>
<title>http和https</title>
<link href="http://yoursite.com/2017/08/21/http%E5%92%8Chttps/"/>
<id>http://yoursite.com/2017/08/21/http和https/</id>
<published>2017-08-20T20:33:46.000Z</published>
<updated>2018-06-05T10:07:45.962Z</updated>
<content type="html"><![CDATA[<h3 id="HTTP"><a href="#HTTP" class="headerlink" title="HTTP"></a>HTTP</h3><p><strong>基础</strong></p><blockquote><p>超文本传输协议(HTTP,HyperText Transfer Protocol),默认端口:80</p></blockquote><p><strong>特点</strong></p><p>无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。</p><p>无状态:HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。</p><p><strong>请求报文</strong></p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>一般由请求行、请求头、空行、请求体四部分组成。</p><p><strong>响应报文</strong></p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="2.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>一般由状态行、消息报头、空行、响应正文四部分组成。</p><p><strong>常见状态码</strong></p><ul><li>200 OK:客户端请求成功</li><li>302 Move temporarily:请求的资源临时从不同的 URI响应请求,重定向</li><li>304 Not Modified:有效缓存</li><li>400 Bad Request:客户端请求有语法错误,不能被服务器所理解</li><li>403 Forbidden:服务器收到请求,但是拒绝提供服务</li><li>404 Not Found:请求失败,请求所希望得到的资源未被在服务器上发现</li><li>500 Internal Server Error:服务器发生不可预期的错误</li><li>503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常</li></ul><p>常见通用报头</p><p>Cache-Control:指定请求和响应遵循的缓存机制</p><p><strong>常用请求报头</strong></p><ul><li>Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机</li><li>User-Agent:发送请求的浏览器类型、操作系统等信息</li><li>Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息</li><li>Accept-Encoding:客户端可识别的数据编码</li><li>Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接</li><li>Referer:允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等</li><li>Range:可以请求实体的一个或者多个子范围</li></ul><p>常见响应报头</p><p>Location:用于重定向接受者到一个新的位置,常用在更换域名的时候<br>Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的</p><p><strong>常见实体报头</strong></p><ul><li>Content-Type:发送给接收者的实体正文的媒体类型</li><li>Content-Lenght:实体正文的长度</li><li>Content-Encoding:实体报头被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制</li><li>Last-Modified:实体报头用于指示资源的最后修改日期和时间</li><li>Expires:实体报头给出响应过期的日期和时间</li></ul><p><strong>SPDY</strong></p><p>2012年google提出的SPDY方案,主要解决:</p><ol><li>降低延迟:SPDY采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式;</li><li>请求优先级:SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应;</li><li>header压缩:选择合适的压缩算法可以减小包的大小和数量;</li><li>基于HTTPS的加密协议传输</li><li>服务端推送</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="3.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p><strong>Http2.0</strong></p><p>可以看成是SPDY的升级版。</p><p><strong>Http2.0与SPDY的区别</strong></p><ul><li>HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS</li><li>HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE</li></ul><p><strong>Http2.0新特性</strong></p><ul><li>新的二进制格式:HTTP1.x的解析是基于文本的</li><li>多路复用:即连接共享,即每一个request都是是用作连接共享机制的</li><li>header压缩</li><li>服务端推送</li></ul><h3 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h3><blockquote><p>(Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,即HTTP下加入SSL层,端口:443</p></blockquote><p><strong>HTTPS握手流程</strong></p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="4.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><ol><li>客户端的浏览器向服务器传送客户端SSL协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息;</li><li>服务器向客户端传送SSL协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书;</li><li>客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的CA是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步;</li><li>用户端随机产生一个用于后面通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器;</li><li>如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器;</li><li>如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CA是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码);</li><li>服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于SSL协议的安全数据通讯的加解密通讯。同时在SSL通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化;</li><li>客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束;</li><li>服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束;</li><li>SSL 的握手部分结束,SSL安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。</li></ol><h3 id="Android中处理Https"><a href="#Android中处理Https" class="headerlink" title="Android中处理Https"></a>Android中处理Https</h3><p><strong>处理 javax.net.ssl.SSLHandshakeException: 证书验证失败</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * HTTPS未知的证书颁发机构处理方法</span></span><br><span class="line"><span class="comment"> * Android客户端存储证书</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> input 待信任的CA证书流</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> SSLContext</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SSLContext <span class="title">getSSLContext</span><span class="params">(InputStream input)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> SSLContext sslContext = SSLContext.getInstance(<span class="string">"TLS"</span>);</span><br><span class="line"> CertificateFactory cf = CertificateFactory.getInstance(<span class="string">"X.509"</span>);</span><br><span class="line"> Certificate ca = cf.generateCertificate(input);</span><br><span class="line"></span><br><span class="line"> KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());</span><br><span class="line"> keyStore.load(<span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> keyStore.setCertificateEntry(<span class="string">"ca"</span>, ca);</span><br><span class="line"></span><br><span class="line"> TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());</span><br><span class="line"> tmf.init(keyStore);</span><br><span class="line"></span><br><span class="line"> sslContext.init(<span class="keyword">null</span>, tmf.getTrustManagers(), <span class="keyword">null</span>);</span><br><span class="line"> input.close();</span><br><span class="line"> <span class="keyword">return</span> sslContext;</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchAlgorithmException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (CertificateException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (KeyStoreException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (KeyManagementException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>单独使用SSL和HTTP</strong></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><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 单独使用SSL + HTTP时</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> trustManagers</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SSLContext <span class="title">getSSLContext</span><span class="params">(TrustManager[] trustManagers)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> SSLContext sslContext = SSLContext.getInstance(<span class="string">"TLS"</span>);</span><br><span class="line"> sslContext.init(<span class="keyword">null</span>, trustManagers, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">return</span> sslContext;</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchAlgorithmException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (KeyManagementException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> TrustManager[] sDefaultTrustManagers = <span class="keyword">new</span> TrustManager[] {<span class="keyword">new</span> X509TrustManager() {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">checkClientTrusted</span><span class="params">(X509Certificate[] chain, String authType)</span> <span class="keyword">throws</span> CertificateException </span>{</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> 2018/1/23双向校验中,向服务端发客户端证书</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">checkServerTrusted</span><span class="params">(X509Certificate[] chain, String authType)</span> <span class="keyword">throws</span> CertificateException </span>{</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> 2018/1/23 双向校验中,校验服务端证书</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> X509Certificate[] getAcceptedIssuers() {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> X509Certificate[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line">}};</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> HostnameVerifier sHostnameVerifier = <span class="keyword">new</span> HostnameVerifier() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">verify</span><span class="params">(String hostname, SSLSession session)</span> </span>{</span><br><span class="line"> <span class="comment">// 不验证主机名</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h3 id="HTTP"><a href="#HTTP" class="headerlink" title="HTTP"></a>HTTP</h3><p><strong>基础</strong></p>
<blockquote>
<p>超文本传输协议(HTTP,HyperText
</summary>
<category term="http" scheme="http://yoursite.com/tags/http/"/>
</entry>
<entry>
<title>Android消息机制(Handler)</title>
<link href="http://yoursite.com/2017/07/13/Android%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6-Handler/"/>
<id>http://yoursite.com/2017/07/13/Android消息机制-Handler/</id>
<published>2017-07-12T20:20:05.000Z</published>
<updated>2018-06-05T10:07:45.930Z</updated>
<content type="html"><![CDATA[<h3 id="答疑解惑"><a href="#答疑解惑" class="headerlink" title="答疑解惑"></a>答疑解惑</h3><ul><li><p>系统为什么不允许在子线程中访问UI?</p><p> Android的UI控件不是线程安全的</p><p> 增加上锁机制会导致: UI访问逻辑复杂、降低UI访问效率</p></li><li><p>区分线程的数据存储</p><p> ThreadLocal</p></li></ul><h3 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h3><p><strong>MessageQueue</strong></p><p>内部由单链表实现,主要包含两个操作:插入(enqueueMessage)和读取(next)。</p><p><strong>Looper</strong></p><p>从MessageQueue中不停查看是否有新消息,如果有新消息立即处理。</p><ul><li>系统已经为主线程创建了Looper,可以使用getMainLooper获取</li><li>其他线程使用Looper.prepare()获取,使用Looper.loop()启动</li><li>loop方法是一个死循环,运行在创建Looper的线程</li></ul><p><strong>Handler</strong></p><p>负责发送和接收消息。可以通过post和send方法发送消息,post方法最终也会走入send的逻辑。</p><p>Handler工作过程:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>Handler消息处理流程:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="2.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure>]]></content>
<summary type="html">
<h3 id="答疑解惑"><a href="#答疑解惑" class="headerlink" title="答疑解惑"></a>答疑解惑</h3><ul>
<li><p>系统为什么不允许在子线程中访问UI?</p>
<p> Android的UI控件不是线程安全的</p>
<
</summary>
<category term="通信" scheme="http://yoursite.com/tags/%E9%80%9A%E4%BF%A1/"/>
</entry>
<entry>
<title>匿名类用到的变量为什么一定要是final的呢?</title>
<link href="http://yoursite.com/2017/06/12/%E5%8C%BF%E5%90%8D%E7%B1%BB%E7%94%A8%E5%88%B0%E7%9A%84%E5%8F%98%E9%87%8F%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%80%E5%AE%9A%E8%A6%81%E6%98%AFfinal%E7%9A%84%E5%91%A2/"/>
<id>http://yoursite.com/2017/06/12/匿名类用到的变量为什么一定要是final的呢/</id>
<published>2017-06-11T19:47:16.000Z</published>
<updated>2018-06-05T10:07:45.971Z</updated>
<content type="html"><![CDATA[<p>提起final变量,大家通常知道这些:</p><p>final成员变量表示常量,只能被赋值一次,赋值后值不再改变。</p><p>final类不能被继承,没有子类,final类中的方法默认是final的。</p><p>final方法不能被子类的方法覆盖,但可以被继承。</p><p>final不能用于修饰构造方法</p><p>final 常与 static一起用,作为常量来使用。</p><p><strong>提问:在一些方法的参数中定义为final是干嘛的?</strong></p><p><strong>答: 不希望这个变量在方法里面被修改,防止无意的修改而影响到调用方法外的变量。</strong></p><p>不知道这个答案是从哪个语言带过来的, 感觉把final当成const了。</p><p>假设出现以下方法定义:<br><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><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fuc</span><span class="params">(<span class="keyword">final</span> String string)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fuc1</span><span class="params">(String string)</span></span>;</span><br></pre></td></tr></table></figure></p><p>对于这种写法,String本身就是一个不可以变的对象,并且其是作为基本参数类型,传参进入后,方法里面改变了String的值外面也不会改变,因此这两种写法实际结果是一样.只不过加了final,在string被赋值时IDE会直接报红。<br><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><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fuc</span><span class="params">(<span class="keyword">final</span> A a)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fuc</span><span class="params">(A a)</span></span>;</span><br></pre></td></tr></table></figure></p><p>对于这种写法, A是一个对象,这样写不是代表A里面内容不能被改变,而是a不能被赋值为新对象了.</p><h3 id="为什么使用匿名内部类的时候参数一定要加上final"><a href="#为什么使用匿名内部类的时候参数一定要加上final" class="headerlink" title="为什么使用匿名内部类的时候参数一定要加上final"></a>为什么使用匿名内部类的时候参数一定要加上final</h3><p>让我们写一个类看一下<br><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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>{</span><br><span class="line"> String name;</span><br><span class="line"> <span class="keyword">int</span> age;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">final</span> Person a)</span> </span>{</span><br><span class="line"> <span class="keyword">new</span> Thread() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.run();</span><br><span class="line"> a.name = <span class="string">"hello"</span>;</span><br><span class="line"> }</span><br><span class="line"> }.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>javac一下该文件,生成了3个class文件,我们看其中的两个</p><p>内部类Person的class<br><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><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span>$<span class="title">Person</span> </span>{</span><br><span class="line"> String name;</span><br><span class="line"> <span class="keyword">int</span> age;</span><br><span class="line"></span><br><span class="line"> Test$Person(Test var1) {</span><br><span class="line"> <span class="keyword">this</span>.<span class="keyword">this</span>$<span class="number">0</span> = var1;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>匿名内部类Thread的class<br><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span>$1 <span class="keyword">extends</span> <span class="title">Thread</span> </span>{</span><br><span class="line"> Test$<span class="number">1</span>(Test var1, Person var2) {</span><br><span class="line"> <span class="keyword">this</span>.<span class="keyword">this</span>$<span class="number">0</span> = var1;</span><br><span class="line"> <span class="keyword">this</span>.val$a = var2;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.run();</span><br><span class="line"> <span class="keyword">this</span>.val$a.name = <span class="string">"hello"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>从这个匿名内部类的class文件我们可以看出两点</p><ol><li>其构造参数中增加了一个调用类.即我们所说的持有外部类的引用.</li><li>我们定义的final参数被当做构造方法传了进来.至于为什么要把这个参数当做构造函数参数传进来,因为调用它的方法参数是存在栈里面的,其生命周期随着这个方法的调用结束而结束.而我们的异步任务可不是,有可能会执行很长时间。</li></ol><p>那么final的关键字作用就凸显了,Person参数要拷贝到内部类中,而拷贝会带来不一致性, func中是一个异步的操作,负责改变a的name的值. 假设Person a 不是final的.那么a可以被任意指向新的对象,那么传给这个异步任务的对象还是老对象,这就造成了不一致.因此Java需要强制约束对象的一致性.因此必须是final的。</p>]]></content>
<summary type="html">
<p>提起final变量,大家通常知道这些:</p>
<p>final成员变量表示常量,只能被赋值一次,赋值后值不再改变。</p>
<p>final类不能被继承,没有子类,final类中的方法默认是final的。</p>
<p>final方法不能被子类的方法覆盖,但可以被继承。<
</summary>
<category term="Java" scheme="http://yoursite.com/tags/Java/"/>
</entry>
<entry>
<title>通过H5唤起本地app</title>
<link href="http://yoursite.com/2017/05/09/%E9%80%9A%E8%BF%87H5%E5%94%A4%E8%B5%B7%E6%9C%AC%E5%9C%B0app/"/>
<id>http://yoursite.com/2017/05/09/通过H5唤起本地app/</id>
<published>2017-05-08T21:33:43.000Z</published>
<updated>2018-06-05T10:07:45.986Z</updated>
<content type="html"><![CDATA[<p>H5如何打开或者说唤起手机本地的app,有以下两种:</p><p>第一种方式:</p><p>通过在html的a标签里面的href中直接配置android端的schema,当然,如果有host其他的配置,跟在后面就可以了,android端配置和代码如下:</p><p>android端配置:<br><figure class="highlight"><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><span class="line"><activity android:name = <span class="string">".MainActivity"</span>></span><br><span class="line"> <intent-filter></span><br><span class="line"> <action android:name = <span class="string">"android.intent.action.MAIN"</span>/></span><br><span class="line"> <category android:name = <span class="string">"android.intent.category.LAUNCHER"</span>/></span><br><span class="line"> </intent-filter></span><br><span class="line"> <intent-filter></span><br><span class="line"> <action android:name=<span class="string">"android.intent.action.VIEW"</span>/></span><br><span class="line"> <category android:name=<span class="string">"android.intent.category.DEFAULT"</span>/></span><br><span class="line"> <category android:name=<span class="string">"android.intent.category.BROWSABLE"</span>/></span><br><span class="line"> <data android:host=<span class="string">"baidu.com"</span></span><br><span class="line"> android:scheme=<span class="string">"testapp"</span>/></span><br><span class="line"> </intent-filter></span><br><span class="line"></activity></span><br></pre></td></tr></table></figure></p><p>注:如果这个是配置在启动页要和标签并列在一起,不然运行后手机app的图标会没有;注意schema协议要小写,否则会有不能响应的异常!</p><p>html代码:</p><figure class="highlight html"><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><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"Content-Type"</span> <span class="attr">content</span>=<span class="string">"text/html; charset=UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Insert title here<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"testapp://baidu.com/?pid=1"</span>></span>打开app<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>这里我们来看看schema拼接协议的格式:</p><p>< a href=”[scheme]://[host]/[path]?[query]”>启动应用程序< /a></p><p>各个项目含义如下所示:</p><p>scheme:判别启动的App。</p><p>host:适当记述</p><p>path:传值时必须的key ※没有也可以</p><p>query:获取值的Key和Value ※没有也可以</p><p>以上就能实现打开本地的app了,当然是在app存在的情况下,否则的话没有反应。</p><p>我们有些时候在唤起本地app的时候可能会向app传递一些参数,这些参数我们就可以配置在path或者query里,我们只需要在oncreate里面获取就可以了。</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></pre></td><td class="code"><pre><span class="line">Intent intent = getIntent();</span><br><span class="line"> Uri uri = intent.getData();</span><br><span class="line"> <span class="keyword">if</span> (uri != <span class="keyword">null</span>) {</span><br><span class="line"> String pid = uri.getQueryParameter(<span class="string">"pid"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果还想要获取android里面配置的schema协议的话,还可以这样:</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></pre></td><td class="code"><pre><span class="line">Uri uri = getIntent().getData();</span><br><span class="line"><span class="keyword">if</span>(uri != <span class="keyword">null</span>) { </span><br><span class="line"> <span class="comment">// 完整的url信息</span></span><br><span class="line"> String url = uri.toString();</span><br><span class="line"> Log.e(TAG, <span class="string">"url: "</span> + uri); </span><br><span class="line"> <span class="comment">// scheme部分</span></span><br><span class="line"> String scheme = uri.getScheme();</span><br><span class="line"> Log.e(TAG, <span class="string">"scheme: "</span> + scheme); </span><br><span class="line"> <span class="comment">// host部分</span></span><br><span class="line"> String host = uri.getHost();</span><br><span class="line"> Log.e(TAG, <span class="string">"host: "</span> + host); </span><br><span class="line"> <span class="comment">//port部分</span></span><br><span class="line"> <span class="keyword">int</span> port = uri.getPort();</span><br><span class="line"> Log.e(TAG, <span class="string">"host: "</span> + port); </span><br><span class="line"> <span class="comment">// 访问路径</span></span><br><span class="line"> String path = uri.getPath();</span><br><span class="line"> Log.e(TAG, <span class="string">"path: "</span> + path);</span><br><span class="line"> List<String> pathSegments = uri.getPathSegments(); </span><br><span class="line"> <span class="comment">// Query部分</span></span><br><span class="line"> String query = uri.getQuery();</span><br><span class="line"> Log.e(TAG, <span class="string">"query: "</span> + query); </span><br><span class="line"> <span class="comment">//获取指定参数值</span></span><br><span class="line"> String pid = uri.getQueryParameter(<span class="string">"pid"</span>);</span><br><span class="line"> Log.e(TAG, <span class="string">"pid: "</span> + pid);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如何判断一个Schema是否有效 :</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><span class="line">PackageManager packageManager = getPackageManager();</span><br><span class="line">Intent intent = newIntent(Intent.ACTION_VIEW, Uri.parse(<span class="string">"testapp://baidu.com:80/article?goodsId=10011002"</span>));</span><br><span class="line">List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, <span class="number">0</span>);</span><br><span class="line">booleanisValid = !activities.isEmpty();</span><br><span class="line"><span class="keyword">if</span>(isValid) {</span><br><span class="line"> startActivity(intent);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种方式也是我百度到的最多的方式,但是这样就带来了一个问题了,上面的需求说的是“在页面上有一个连接, 如果用户安装了APP,则点击打开对应的APP;如果用户没有安装,则点击打开对应的设置连接”,这明显就不符合需求了,这只能作为一些个别需求来使用了。</p><p>第二种方式:</p><p>既然通过在href配置schema协议不行,那就只能通过js代码来实现了,只有这样才能根据判断实现app有的时候就打开,没有的时候就跳转到下载链接下载。</p><p>我们知道,js是无法判断手机是否安装了某款app的,所以我们只能够曲线救国了,我们可以获取时间如果,长时间不能呼起app则默认为没有安装这款app,然后跳转到下载页。当然这不是我想出来的,是网上的各位大佬的想法。在这里又要细分为两种情况了。</p><p>1.直接唤醒</p><p>说明:通过h5可换醒app,如访问一个URL,点击按钮,打开应用,如果该应用APP没有安装,那么直接跳转到App Store的APP下载页面,通过点击的方式兼容性较好,如果安装了app,在手机各大浏览器(360浏览器、uc浏览器、搜狗浏览器、QQ浏览器、百度浏览器 )和QQ客户端中,能唤醒。微信、新浪微博客户端、腾讯微博客户端无法唤醒。</p><p>代码如下:</p><figure class="highlight html"><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">xmlns</span>=<span class="string">http://www.w3.org/1999/xhtml</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">Content-Type</span> <span class="attr">content</span>=<span class="string">"text/html;charset=utf-8"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"http://libs.baidu.com/jquery/1.9.0/jquery.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">title</span>></span>点击唤醒demo<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined">#zjmobliestart{font-size:40px;}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!--</span></span><br><span class="line"><span class="comment">说明:通过h5可换醒app,如访问一个URL,点击按钮,打开应用,如果该应用APP没有安装,那么直接跳转到App Store的APP下载页面,通过点击的方式。兼容性较好,如果安装了app,在手机各大浏览器(360浏览器 uc浏览器 搜狗浏览器 QQ浏览器 百度浏览器 )和</span></span><br><span class="line"><span class="comment">QQ客户端中,能唤醒。微信 新浪微博客户端 腾讯微博客户端无法唤醒。</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"zjmobile://platformapi/startapp"</span> <span class="attr">id</span>=<span class="string">"zjmobliestart"</span> <span class="attr">target</span>=<span class="string">"_blank"</span>></span>唤醒浙江移动手机营业厅!<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>></span><span class="undefined"> </span></span><br><span class="line"><span class="undefined">function applink(){ </span></span><br><span class="line"><span class="undefined"> return function(){ </span></span><br><span class="line"><span class="undefined"> var clickedAt = +new Date; </span></span><br><span class="line"><span class="undefined"> setTimeout(function(){</span></span><br><span class="line"><span class="undefined"> !window.document.webkitHidden && setTimeout(function(){ </span></span><br><span class="line"><span class="undefined"> if (+new Date - clickedAt < 2000){ </span></span><br><span class="line"><span class="undefined"> window.location = 'https://itunes.apple.com/us/app/zhe-jiang-yi-dong-shou-ji/id898243566#weixin.qq.com'; </span></span><br><span class="line"><span class="undefined"> } </span></span><br><span class="line"><span class="undefined"> }, 500);</span></span><br><span class="line"><span class="undefined"> }, 500) </span></span><br><span class="line"><span class="undefined"> }; </span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined">applink();</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>2.点击唤醒</p><p>说明:通过h5可换醒app,如访问一个URL就能直接打开应用,如果该应用APP没有安装,那么直接跳转到App Store的APP下载页面。兼容性一般:在手机各大浏览器(360浏览器、uc浏览器、搜狗浏览器 QQ浏览器、百度浏览器 )能唤醒。微信、QQ客户端、新浪微博客户端、 腾讯微博客户端无法唤醒。</p><p>代码如下:</p><figure class="highlight html"><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></pre></td><td class="code"><pre><span class="line"><span class="meta"><!Doctype html></span><span class="tag"><<span class="name">html</span> <span class="attr">xmlns</span>=<span class="string">http://www.w3.org/1999/xhtml</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">Content-Type</span> <span class="attr">content</span>=<span class="string">"text/html;charset=utf-8"</span>></span><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"http://libs.baidu.com/jquery/1.9.0/jquery.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">title</span>></span>直接唤醒demo<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined">#zjmobliestart{font-size:40px;}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!--</span></span><br><span class="line"><span class="comment">说明:通过h5可换醒app,如访问一个URL就能直接打开应用,如果该应用APP没有安装,那么直接跳转到App Store的APP下载页面</span></span><br><span class="line"><span class="comment">兼容性一般:在手机各大浏览器(360浏览器 uc浏览器 搜狗浏览器 QQ浏览器 百度浏览器 )能唤醒。微信 QQ客户端 新浪微博客户端 腾讯微博客户端无法唤醒。</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">p</span> <span class="attr">id</span>=<span class="string">"zjmobliestart"</span>></span>唤醒浙江移动手机营业厅!<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>></span><span class="undefined"> function applink(){ </span></span><br><span class="line"><span class="undefined"> window.location = 'zjmobile://platformapi/startapp'; </span></span><br><span class="line"><span class="undefined"> var clickedAt = +new Date; </span></span><br><span class="line"><span class="undefined"> setTimeout(function(){</span></span><br><span class="line"><span class="undefined"> !window.document.webkitHidden && setTimeout(function(){ </span></span><br><span class="line"><span class="undefined"> if (+new Date - clickedAt < 2000){ </span></span><br><span class="line"><span class="undefined"> window.location = 'https://itunes.apple.com/us/app/zhe-jiang-yi-dong-shou-ji/id898243566#weixin.qq.com'; </span></span><br><span class="line"><span class="undefined"> } </span></span><br><span class="line"><span class="undefined"> }, 500); </span></span><br><span class="line"><span class="undefined"> }, 500) </span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined">document.getElementById("zjmobliestart").onclick = applink();</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>这样就完成了我们的需求了。</p><p>还要注意的是,如果是在微信中唤起本地app,手机的微信中,是利用微信内置的浏览器打开那个简单的HTML页面,注意:直接打开scheme://host/datastring是不可行的,微信不会把这串字符解析成网址,必须包装成网页才能借助微信的浏览器打开。进入后就是我们刚刚设计的页面。这个时候,直接点击“启动应用程序”是不会唤醒之前安装的APP的,因为微信做了屏蔽,你需要在右上角的菜单中选择“在浏览器中打开”。这个时候就可以唤醒你想唤醒的app了。</p>]]></content>
<summary type="html">
<p>H5如何打开或者说唤起手机本地的app,有以下两种:</p>
<p>第一种方式:</p>
<p>通过在html的a标签里面的href中直接配置android端的schema,当然,如果有host其他的配置,跟在后面就可以了,android端配置和代码如下:</p>
<p>a
</summary>
<category term="其他" scheme="http://yoursite.com/tags/%E5%85%B6%E4%BB%96/"/>
</entry>
<entry>
<title>Android中的各种Span</title>
<link href="http://yoursite.com/2017/04/17/Android%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8DSpan/"/>
<id>http://yoursite.com/2017/04/17/Android中的各种Span/</id>
<published>2017-04-16T20:38:02.000Z</published>
<updated>2018-06-05T10:07:45.920Z</updated>
<content type="html"><![CDATA[<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>在android.text.style包下,有一些Span类,可以提供我们完成一些在TextView中的特殊内容。(比如:部分内容颜色、字体、大小不同等等)。本篇博客主要讲解一些span的用法,以及结合Android动画机制制作出十分酷炫的动画Span。</p><h3 id="给字符添加边框"><a href="#给字符添加边框" class="headerlink" title="给字符添加边框"></a>给字符添加边框</h3><p>FrameSpan实现给相应的字符序列添加边框的效果,整体思路其实比较简单。</p><ol><li>计算字符序列的宽度;</li><li>根据计算的宽度、上下坐标、起始坐标绘制矩形;</li><li>绘制文字</li></ol><p>展现效果如下所示:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="1.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>再来看一下代码,其实代码十分简单。<br><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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FrameSpan</span> <span class="keyword">extends</span> <span class="title">ReplacementSpan</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Paint mPaint;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> mWidth;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">FrameSpan</span><span class="params">()</span> </span>{</span><br><span class="line"> mPaint = <span class="keyword">new</span> Paint();</span><br><span class="line"> mPaint.setStyle(Paint.Style.STROKE);</span><br><span class="line"> mPaint.setColor(Color.BLUE);</span><br><span class="line"> mPaint.setAntiAlias(<span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">(Paint paint, CharSequence text, <span class="keyword">int</span> start, <span class="keyword">int</span> end, Paint.FontMetricsInt fm)</span> </span>{</span><br><span class="line"> <span class="comment">//return text with relative to the Paint</span></span><br><span class="line"> mWidth = (<span class="keyword">int</span>) paint.measureText(text, start, end);</span><br><span class="line"> <span class="keyword">return</span> mWidth;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">draw</span><span class="params">(Canvas canvas, CharSequence text, <span class="keyword">int</span> start, <span class="keyword">int</span> end, <span class="keyword">float</span> x, <span class="keyword">int</span> top, <span class="keyword">int</span> y, <span class="keyword">int</span> bottom, Paint paint)</span> </span>{</span><br><span class="line"> <span class="comment">//draw the frame with custom Paint</span></span><br><span class="line"> canvas.drawRect(x, top, x + mWidth, bottom, mPaint);</span><br><span class="line"> canvas.drawText(text, start, end, x, y, paint);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>用法示例:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"> TextView tv = findViewById(R.id.id_tv_framespan);</span><br><span class="line"> <span class="keyword">final</span> SpannableString spannableString = <span class="keyword">new</span> SpannableString(</span><br><span class="line"> <span class="string">"helloworld,helloworld!helloworld,helloworld!helloworld,"</span> +</span><br><span class="line"> <span class="string">"helloworld!helloworld,helloworld!"</span>);</span><br><span class="line"> spannableString.setSpan(<span class="keyword">new</span> FrameSpan(), <span class="number">0</span>, <span class="number">30</span>, Spanned.SPAN_INCLUSIVE_INCLUSIVE);</span><br><span class="line"> tv.setText(spannableString);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="图文垂直居中"><a href="#图文垂直居中" class="headerlink" title="图文垂直居中"></a>图文垂直居中</h3><p>系统提供的ImageSpan和DynamicDrawableSpan只能实现图片和文字底部对齐或者是baseline对齐,现在VerticalImageSpan可以实现图片和文字居中对齐。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="2.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>图中的图片保持了和文字居中对齐,现在来看看VerticalImageSpan的源码。<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">VerticalImageSpan</span> <span class="keyword">extends</span> <span class="title">ImageSpan</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> Drawable drawable;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">VerticalImageSpan</span><span class="params">(Drawable drawable)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(drawable);</span><br><span class="line"> <span class="keyword">this</span>.drawable=drawable;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">(Paint paint, CharSequence text, <span class="keyword">int</span> start, <span class="keyword">int</span> end, Paint.FontMetricsInt fontMetricsInt)</span> </span>{</span><br><span class="line"> Drawable drawable = getDrawable();</span><br><span class="line"> <span class="keyword">if</span>(drawable==<span class="keyword">null</span>){</span><br><span class="line"> drawable= <span class="keyword">this</span>.drawable;</span><br><span class="line"> }</span><br><span class="line"> Rect rect = drawable.getBounds();</span><br><span class="line"> <span class="keyword">if</span> (fontMetricsInt != <span class="keyword">null</span>) {</span><br><span class="line"> Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();</span><br><span class="line"> <span class="keyword">int</span> fontHeight = fmPaint.bottom - fmPaint.top;</span><br><span class="line"> <span class="keyword">int</span> drHeight = rect.bottom - rect.top;</span><br><span class="line"> <span class="keyword">int</span> top = drHeight / <span class="number">2</span> - fontHeight / <span class="number">4</span>;</span><br><span class="line"> <span class="keyword">int</span> bottom = drHeight / <span class="number">2</span> + fontHeight / <span class="number">4</span>;</span><br><span class="line"> fontMetricsInt.ascent = -bottom;</span><br><span class="line"> fontMetricsInt.top = -bottom;</span><br><span class="line"> fontMetricsInt.bottom = top;</span><br><span class="line"> fontMetricsInt.descent = top;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> rect.right;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">draw</span><span class="params">(Canvas canvas, CharSequence text, <span class="keyword">int</span> start, <span class="keyword">int</span> end, <span class="keyword">float</span> x, <span class="keyword">int</span> top, <span class="keyword">int</span> y, <span class="keyword">int</span> bottom, Paint paint)</span> </span>{</span><br><span class="line"> Drawable drawable = getDrawable();</span><br><span class="line"> canvas.save();</span><br><span class="line"> <span class="keyword">int</span> transY = ((bottom - top) - drawable.getBounds().bottom) / <span class="number">2</span> + top;</span><br><span class="line"> canvas.translate(x, transY);</span><br><span class="line"> drawable.draw(canvas);</span><br><span class="line"> canvas.restore();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>在geSize方法中通过fontMetricsInt设置从而实现图片和文字居中对齐,其实计算的根本为计算baseline的位置,因为TextView是按照baseline对齐的。</p><p>分析getSize方法可以知道这个图片的baseline为图片中央往下fontHeight / 2,这样也就实现了图片和文字的居中对齐。</p><p>用法示例:<br><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><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"> TextView tv = findViewById(R.id.id_tv_framespan);</span><br><span class="line"> Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher);</span><br><span class="line"> drawable.setBounds(<span class="number">0</span>, <span class="number">0</span>, <span class="number">50</span>, <span class="number">50</span>);</span><br><span class="line"> <span class="keyword">final</span> SpannableString spannableString = <span class="keyword">new</span> SpannableString(</span><br><span class="line"> <span class="string">"helloworld,helloworld!helloworld,helloworld!helloworld,"</span> +</span><br><span class="line"> <span class="string">"helloworld!helloworld,helloworld!"</span>);</span><br><span class="line"> spannableString.setSpan(<span class="keyword">new</span> VerticalImageSpan(drawable), <span class="number">0</span>, <span class="number">1</span>, Spanned.SPAN_INCLUSIVE_INCLUSIVE);</span><br><span class="line"> tv.setText(spannableString);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="字体多色渐变"><a href="#字体多色渐变" class="headerlink" title="字体多色渐变"></a>字体多色渐变</h3><p>彩虹样的Span,其实实现起来也是很简单的,主要是用到了Paint的Shader技术,效果如下所示:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="3.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>源代码如下所示:<br><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><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">RainbowSpan</span> <span class="keyword">extends</span> <span class="title">CharacterStyle</span> <span class="keyword">implements</span> <span class="title">UpdateAppearance</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span>[] colors;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">RainbowSpan</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> colors = context.getResources().getIntArray(R.array.rainbow);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateDrawState</span><span class="params">(TextPaint paint)</span> </span>{</span><br><span class="line"> paint.setStyle(Paint.Style.FILL);</span><br><span class="line"> Shader shader = <span class="keyword">new</span> LinearGradient(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, paint.getTextSize() * colors.length, colors, <span class="keyword">null</span>, Shader.TileMode.MIRROR);</span><br><span class="line"> Matrix matrix = <span class="keyword">new</span> Matrix();</span><br><span class="line"> matrix.setRotate(<span class="number">90</span>);</span><br><span class="line"> shader.setLocalMatrix(matrix);</span><br><span class="line"> paint.setShader(shader);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>由于paint使用shader是从上到下进行绘制,因此这里需要用到矩阵,然后将矩阵旋转90度。</p><p>用法示例:<br><figure class="highlight plain"><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><span class="line">@Override</span><br><span class="line">protected void onCreate(Bundle savedInstanceState) {</span><br><span class="line"> super.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"> TextView tv = findViewById(R.id.id_tv_framespan);</span><br><span class="line"> final SpannableString spannableString = new SpannableString(</span><br><span class="line"> "helloworld,helloworld!helloworld,helloworld!helloworld," +</span><br><span class="line"> "helloworld!helloworld,helloworld!");</span><br><span class="line"> spannableString.setSpan(new RainbowSpan(this), 0, 40, Spanned.SPAN_INCLUSIVE_INCLUSIVE);</span><br><span class="line"> tv.setText(spannableString);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="字体多色渐变动画效果"><a href="#字体多色渐变动画效果" class="headerlink" title="字体多色渐变动画效果"></a>字体多色渐变动画效果</h3><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="5.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>如果要实现一个动画的彩虹样式,那么该如何实现呢?</p><p>其实结合上面的RainbowSpan和AnimateForegroundColorSpan的例子便可以实现AnimatedRainbowSpan。</p><p>实现思路:通过ObjectAnimator动画调整RainbowSpan中矩阵的平移,从而实现动画彩虹的效果。</p><p>代码如下所示:</p><p>首先是AnimatedColorSpan<br><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><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">AnimatedColorSpan</span> <span class="keyword">extends</span> <span class="title">CharacterStyle</span> <span class="keyword">implements</span> <span class="title">UpdateAppearance</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span>[] colors;</span><br><span class="line"> <span class="keyword">private</span> Shader shader = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">private</span> Matrix matrix = <span class="keyword">new</span> Matrix();</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">float</span> translateXPercentage = <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AnimatedColorSpan</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> colors = context.getResources().getIntArray(R.array.rainbow);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setTranslateXPercentage</span><span class="params">(<span class="keyword">float</span> percentage)</span> </span>{</span><br><span class="line"> translateXPercentage = percentage;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">float</span> <span class="title">getTranslateXPercentage</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> translateXPercentage;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateDrawState</span><span class="params">(TextPaint paint)</span> </span>{</span><br><span class="line"> paint.setStyle(Paint.Style.FILL);</span><br><span class="line"> <span class="keyword">float</span> width = paint.getTextSize() * colors.length;</span><br><span class="line"> <span class="keyword">if</span> (shader == <span class="keyword">null</span>) {</span><br><span class="line"> shader = <span class="keyword">new</span> LinearGradient(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, width, colors, <span class="keyword">null</span>, Shader.TileMode.MIRROR);</span><br><span class="line"> }</span><br><span class="line"> matrix.reset();</span><br><span class="line"> matrix.setRotate(<span class="number">90</span>);</span><br><span class="line"> matrix.postTranslate(width * translateXPercentage, <span class="number">0</span>);</span><br><span class="line"> shader.setLocalMatrix(matrix);</span><br><span class="line"> paint.setShader(shader);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>配合属性动画:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AnimatedRainbowSpanActivity</span> <span class="keyword">extends</span> <span class="title">Activity</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_animated_rainbow_span);</span><br><span class="line"> <span class="keyword">final</span> TextView textView = (TextView) findViewById(R.id.text);</span><br><span class="line"> String text = textView.getText().toString();</span><br><span class="line"> AnimatedColorSpan span = <span class="keyword">new</span> AnimatedColorSpan(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">final</span> SpannableString spannableString = <span class="keyword">new</span> SpannableString(text);</span><br><span class="line"> String substring = getString(R.string.animated_rainbow_span).toLowerCase();</span><br><span class="line"> <span class="keyword">int</span> start = text.toLowerCase().indexOf(substring);</span><br><span class="line"> <span class="keyword">int</span> end = start + substring.length();</span><br><span class="line"> spannableString.setSpan(span, start, end, <span class="number">0</span>);</span><br><span class="line"> ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(</span><br><span class="line"> span, ANIMATED_COLOR_SPAN_FLOAT_PROPERTY, <span class="number">0</span>, <span class="number">100</span>);</span><br><span class="line"> objectAnimator.setEvaluator(<span class="keyword">new</span> FloatEvaluator());</span><br><span class="line"> objectAnimator.addUpdateListener(<span class="keyword">new</span> ValueAnimator.AnimatorUpdateListener() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAnimationUpdate</span><span class="params">(ValueAnimator animation)</span> </span>{</span><br><span class="line"> textView.setText(spannableString);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> objectAnimator.setInterpolator(<span class="keyword">new</span> LinearInterpolator());</span><br><span class="line"> objectAnimator.setDuration(DateUtils.MINUTE_IN_MILLIS * <span class="number">3</span>);</span><br><span class="line"> objectAnimator.setRepeatCount(ValueAnimator.INFINITE);</span><br><span class="line"> objectAnimator.start();</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Property<AnimatedColorSpan, Float> ANIMATED_COLOR_SPAN_FLOAT_PROPERTY</span><br><span class="line"> = <span class="keyword">new</span> Property<AnimatedColorSpan, Float>(Float.class, <span class="string">"ANIMATED_COLOR_SPAN_FLOAT_PROPERTY"</span>) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(AnimatedColorSpan span, Float value)</span> </span>{</span><br><span class="line"> span.setTranslateXPercentage(value);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Float <span class="title">get</span><span class="params">(AnimatedColorSpan span)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> span.getTranslateXPercentage();</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="打字效果"><a href="#打字效果" class="headerlink" title="打字效果"></a>打字效果</h3><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="6.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure><p>有了上面的例子,写TypeWriterSpan就变得十分简单了。</p><p>先创建TypeWriterSpanGroup<br><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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TypeWriterSpanGroup</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">float</span> mAlpha;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ArrayList<MutableForegroundColorSpan> mSpans;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">TypeWriterSpanGroup</span><span class="params">(<span class="keyword">float</span> alpha)</span> </span>{</span><br><span class="line"> mAlpha = alpha;</span><br><span class="line"> mSpans = <span class="keyword">new</span> ArrayList<MutableForegroundColorSpan>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addSpan</span><span class="params">(MutableForegroundColorSpan span)</span> </span>{</span><br><span class="line"> span.setAlpha((<span class="keyword">int</span>) (mAlpha * <span class="number">255</span>));</span><br><span class="line"> mSpans.add(span);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAlpha</span><span class="params">(<span class="keyword">float</span> alpha)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> size = mSpans.size();</span><br><span class="line"> <span class="keyword">float</span> total = <span class="number">1.0f</span> * size * alpha;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> index = <span class="number">0</span> ; index < size; index++) {</span><br><span class="line"> MutableForegroundColorSpan span = mSpans.get(index);</span><br><span class="line"> <span class="keyword">if</span>(total >= <span class="number">1.0f</span>) {</span><br><span class="line"> span.setAlpha(<span class="number">255</span>);</span><br><span class="line"> total -= <span class="number">1.0f</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> span.setAlpha((<span class="keyword">int</span>) (total * <span class="number">255</span>));</span><br><span class="line"> total = <span class="number">0.0f</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">float</span> <span class="title">getAlpha</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> mAlpha;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>然后添加Span与添加动画,整体使用示例如下:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"> <span class="keyword">final</span> TextView tv = findViewById(R.id.id_tv_framespan);</span><br><span class="line"> String val = <span class="string">"helloworld,helloworld!helloworld,helloworld!helloworld"</span>;</span><br><span class="line"> <span class="keyword">final</span> SpannableString spannableString = <span class="keyword">new</span> SpannableString(val);</span><br><span class="line"> <span class="comment">// 添加Span</span></span><br><span class="line"> <span class="keyword">final</span> TypeWriterSpanGroup group = <span class="keyword">new</span> TypeWriterSpanGroup(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> index = <span class="number">0</span> ; index <= val.length()-<span class="number">1</span> ; index++) {</span><br><span class="line"> MutableForegroundColorSpan span = <span class="keyword">new</span> MutableForegroundColorSpan();</span><br><span class="line"> group.addSpan(span);</span><br><span class="line"> spannableString.setSpan(span, index, index + <span class="number">1</span>, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 添加动画</span></span><br><span class="line"> ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(group, TYPE_WRITER_GROUP_ALPHA_PROPERTY, <span class="number">0.0f</span>, <span class="number">1.0f</span>);</span><br><span class="line"> objectAnimator.addUpdateListener(<span class="keyword">new</span> ValueAnimator.AnimatorUpdateListener() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAnimationUpdate</span><span class="params">(ValueAnimator animation)</span> </span>{</span><br><span class="line"> <span class="comment">//refresh</span></span><br><span class="line"> tv.setText(spannableString);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> objectAnimator.setDuration(<span class="number">5000</span>);</span><br><span class="line"> objectAnimator.start();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>动画属性变化器代码如下:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Property<TypeWriterSpanGroup, Float> TYPE_WRITER_GROUP_ALPHA_PROPERTY =</span><br><span class="line"> <span class="keyword">new</span> Property<TypeWriterSpanGroup, Float>(Float.class, <span class="string">"TYPE_WRITER_GROUP_ALPHA_PROPERTY"</span>) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(TypeWriterSpanGroup spanGroup, Float value)</span> </span>{</span><br><span class="line"> spanGroup.setAlpha(value);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Float <span class="title">get</span><span class="params">(TypeWriterSpanGroup spanGroup)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> spanGroup.getAlpha();</span><br><span class="line"> }</span><br><span class="line"> };</span><br></pre></td></tr></table></figure></p><p>涉及到的类:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MutableForegroundColorSpan</span> <span class="keyword">extends</span> <span class="title">CharacterStyle</span> <span class="keyword">implements</span> <span class="title">UpdateAppearance</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"MutableForegroundColorSpan"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> mColor = Color.BLACK;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> mAlpha = <span class="number">0</span> ;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateDrawState</span><span class="params">(TextPaint tp)</span> </span>{</span><br><span class="line"> tp.setColor(mColor);</span><br><span class="line"> tp.setAlpha(mAlpha);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getColor</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> mColor;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setColor</span><span class="params">(<span class="keyword">int</span> color)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.mColor = color;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAlpha</span><span class="params">(<span class="keyword">int</span> alpha)</span> </span>{</span><br><span class="line"> mAlpha = alpha;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>在android.text.style包下,有一些Span类,可以提供我们完成一些在TextView中的特殊内容。(比如:部分内容颜色、字体
</summary>
<category term="控件" scheme="http://yoursite.com/tags/%E6%8E%A7%E4%BB%B6/"/>
</entry>
<entry>
<title>实现上滑屏幕,隐藏底部菜单栏</title>
<link href="http://yoursite.com/2017/03/26/%E5%AE%9E%E7%8E%B0%E4%B8%8A%E6%BB%91%E5%B1%8F%E5%B9%95%EF%BC%8C%E9%9A%90%E8%97%8F%E5%BA%95%E9%83%A8%E8%8F%9C%E5%8D%95%E6%A0%8F/"/>
<id>http://yoursite.com/2017/03/26/实现上滑屏幕,隐藏底部菜单栏/</id>
<published>2017-03-25T22:02:50.000Z</published>
<updated>2018-06-05T10:07:45.972Z</updated>
<content type="html"><![CDATA[<p>实现列表上滑的同时,隐藏底部菜单栏,下滑的同时,显示底部菜单栏。<br><a id="more"></a><br>本文主要实现的功能是底部菜单栏随用户手势滑动而变化。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="https://upload-images.jianshu.io/upload_images/1432108-401de738bac93f86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/228" alt="image" title="正常状态"> </div> <div class="image-caption">正常状态</div> </figure><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="https://upload-images.jianshu.io/upload_images/1432108-fb2706ee8245bef1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/228" alt="image" title="上滑之后"> </div> <div class="image-caption">上滑之后</div> </figure><h3 id="布局代码"><a href="#布局代码" class="headerlink" title="布局代码"></a>布局代码</h3><p>这个功能实现起来比较简单,主要利用了CoordinatorLayout的layout_behavior的属性。具体代码如下:</p><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><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><span class="line"><span class="tag"><<span class="name">android.support.design.widget.CoordinatorLayout</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"match_parent"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">include</span> <span class="attr">layout</span>=<span class="string">"@layout/toolbar"</span>/></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">include</span> <span class="attr">layout</span>=<span class="string">"@layout/container"</span>/></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">android.support.design.widget.BottomNavigationView</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:id</span>=<span class="string">"@+id/bottom_navigation"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">style</span>=<span class="string">"@style/Widget.Design.BottomNavigationView"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_alignParentBottom</span>=<span class="string">"true"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_gravity</span>=<span class="string">"bottom"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:background</span>=<span class="string">"@color/viewBackground"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:elevation</span>=<span class="string">"16dp"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:itemIconTint</span>=<span class="string">"@drawable/nav_item_color_state"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:itemTextColor</span>=<span class="string">"@drawable/nav_item_color_state"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:layout_behavior</span>=<span class="string">"com.testapp.widget.behavior.BottomNavigationBehavior"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:menu</span>=<span class="string">"@menu/bottom_navigation_main"</span>/></span></span><br></pre></td></tr></table></figure><p>上面是activity_main的布局代码,第一个是菜单栏,第二个是内容界面,第三个是bottom。bottom导航栏这里采用是BottomNavigationView,具体用法不再介绍。<br>这里主要看一下BottomNavigationView的app:layout_behavior属性,该属性是协调布局特有的。网上的一版用法是</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app:layout_behavior="@string/appbar_scrolling_view_behavior"</span><br></pre></td></tr></table></figure><p>虽然表面上看是一个字符串,其实在里面调用的也是一个view类。这次我们通过自定义这个behavior类,实现底部菜单栏的显隐性。</p><h3 id="java实现类"><a href="#java实现类" class="headerlink" title="java实现类"></a>java实现类</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BottomNavigationBehavior</span> <span class="keyword">extends</span> <span class="title">CoordinatorLayout</span>.<span class="title">Behavior</span><<span class="title">View</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> ObjectAnimator outAnimator, inAnimator;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BottomNavigationBehavior</span><span class="params">(Context context, AttributeSet attrs)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(context, attrs);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 垂直滑动</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onStartNestedScroll</span><span class="params">(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, <span class="keyword">int</span> nestedScrollAxes)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNestedPreScroll</span><span class="params">(CoordinatorLayout coordinatorLayout, View child, View target, <span class="keyword">int</span> dx, <span class="keyword">int</span> dy, <span class="keyword">int</span>[] consumed)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (dy > <span class="number">0</span>) {<span class="comment">// 上滑隐藏</span></span><br><span class="line"> <span class="keyword">if</span> (outAnimator == <span class="keyword">null</span>) {</span><br><span class="line"> outAnimator = ObjectAnimator.ofFloat(child, <span class="string">"translationY"</span>, <span class="number">0</span>, child.getHeight());</span><br><span class="line"> outAnimator.setDuration(<span class="number">200</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!outAnimator.isRunning() && child.getTranslationY() <= <span class="number">0</span>) {</span><br><span class="line"> outAnimator.start();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (dy < <span class="number">0</span>) {<span class="comment">// 下滑显示</span></span><br><span class="line"> <span class="keyword">if</span> (inAnimator == <span class="keyword">null</span>) {</span><br><span class="line"> inAnimator = ObjectAnimator.ofFloat(child, <span class="string">"translationY"</span>, child.getHeight(), <span class="number">0</span>);</span><br><span class="line"> inAnimator.setDuration(<span class="number">200</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!inAnimator.isRunning() && child.getTranslationY() >= child.getHeight()) {</span><br><span class="line"> inAnimator.start();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个类的就是刚刚那个app:layout_behavior=”com.meiji.toutiao.widget.behavior.BottomNavigationBehavior”属性标明的类,看起来很简单,继承了Behavior抽象类,然后实现了两个方法。</p><p><strong>onStartNestedScroll</strong>:这个方法主要用于监听协调布局的子view的滚动事件,当此方法返回true,表示要消耗此动作,继而执行下面的onNestedPreScroll方法,我们在代码中返回的是,滚动轴是不是竖直滚动轴。如果是的话,就返回true<br><strong>onNestedPreScroll</strong>:这个方法就比较简单了,当用户上滑的时候,隐藏底部菜单栏,这里使用了动画退出,使用了ObjectAnimator.ofFloat方法,第一个是view对象,指的就是bottom,第二个是Y轴的变化,第三个是Y轴变化的多少,接下来设置动画秒数。</p>]]></content>
<summary type="html">
<p>实现列表上滑的同时,隐藏底部菜单栏,下滑的同时,显示底部菜单栏。<br>
</summary>
<category term="控件" scheme="http://yoursite.com/tags/%E6%8E%A7%E4%BB%B6/"/>
</entry>
</feed>