-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
592 lines (519 loc) · 41.2 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>A Mjn Blog</title>
<link href="/atom.xml" rel="self"/>
<link href="https://github.com/mjnhmd/"/>
<updated>2016-04-21T02:21:22.641Z</updated>
<id>https://github.com/mjnhmd/</id>
<author>
<name>MJN</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>MarkDown简化贴图流程</title>
<link href="https://github.com/mjnhmd/2016/04/14/MarkDown%E7%AE%80%E5%8C%96%E8%B4%B4%E5%9B%BE%E6%B5%81%E7%A8%8B/"/>
<id>https://github.com/mjnhmd/2016/04/14/MarkDown简化贴图流程/</id>
<published>2016-04-14T11:32:50.000Z</published>
<updated>2016-04-21T02:21:22.641Z</updated>
<content type="html"><h2 id="动机"><a href="#动机" class="headerlink" title="动机"></a>动机</h2><p>这个原因还用说吗,完全是刚需啊!!!以前写个markdown想要贴个图简直让人崩溃啊!!!</p>
<ul>
<li>你可能先要截个图</li>
<li>然后打开浏览器</li>
<li>打开你的图床网站</li>
<li>点击上传图片</li>
<li>等待上传完成</li>
<li>复制外链</li>
<li>返回markdown</li>
<li>当然这是只是一个url,你还得写成markdown的格式,才算完成</li>
</ul>
<p>执行完这些操作之后,思路早就跑到九霄云外了有木有,写文章的激情早就耗尽了!!!</p>
<p>MarkDown作为一个逼格满满新潮写作方式,竟然还存在这种反人类的操作,实在是让人不能忍啊。</p>
<h2 id="尝试"><a href="#尝试" class="headerlink" title="尝试"></a>尝试</h2><p>于是就去google了一下,果然有很多人一样忍不了,并且找出了解决办法。搜到了三位大神的文章:</p>
<ul>
<li><a href="http://tianweishu.com/2015/10/16/simplify-the-img-upload-in-markdown/" target="_blank" rel="external">简化markdown写作中的贴图流程</a> 这个是写的很好,可是是mac的</li>
<li><a href="http://mickir.xyz/blog/2015/12/simplify-the-img-upload-in-markdown.html" target="_blank" rel="external">简化markdown中的贴图流程</a> 这个是借鉴上问的windows版,作者是大神,写的比较简略。这里边的利用微信dll截图的功能非常值得借鉴</li>
<li><a href="https://github.com/xzop/markdown-img-upload-windows/blob/master/ReadMe.md">markdown-img-upload-windows</a>这个是github项目,可能是dll版本不对,我这里不成功</li>
</ul>
<p>需要的东西:</p>
<ol>
<li>python环境</li>
<li>AutoHotKey</li>
<li>windows系统</li>
<li>七牛云存储账户</li>
<li>没了</li>
</ol>
<p>七牛肯定是有了,python也装过,所差的就是一个软件而已嘛,于是装了个破解版,按照操作做。果然,并不能成功。。</p>
<p>照着做都不行,于是就只能自己修改了。</p>
<h2 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h2><p>相比起来第三篇比较完整,有源码有解释,只是某个功能需要修改。于是就尝试着对脚本进行修改。</p>
<p>一共就两个脚本,一个是AutoHotKey自己的脚本,一个是Python脚本。很凑巧的是,对这两个都一窍不通。</p>
<p>只能硬着头皮上,看了一下AutoHotKey自带的介绍,并不是很难,大概就是把某个快捷键绑定相应的功能,代码是这样的:</p>
<pre><code>^!c:: #^代表ctrl,!代表alt,也就是说是让ctrl+alt+c执行相应的功能
send, ^c #首先执行Ctrl+c,也就是复制。也就是把选中的图片放到剪切板中
clipwait #然后做一个延迟,等待复制操作完成
Run %comspec% /c &quot;Python D:\qiniu\upload_qiniu.py&quot; %Clipboard% /p
/#执行python脚本,并从剪贴板中获取值,这个值就是刚才执行的复制操作的值,也就是选中的图片
return #返回
</code></pre><p>这里这个脚本逻辑很清楚并且能够正常执行,是不需要修改的。</p>
<p>再看一下python脚本,虽然不懂python语言,但是勉强还是能看懂一些大概逻辑:</p>
<pre><code># -*- coding: utf-8 -*-
import os
import sys
reload(sys)
sys.setdefaultencoding(&apos;utf-8&apos;)
from qiniu import Auth, put_file
import qiniu.config
from ctypes import *
import time
import ConfigParser
from datetime import datetime
#这部分显然是从config中获取到我们设置好的一些参数
cf = ConfigParser.ConfigParser()
cf.read(&apos;config.ini&apos;)
access_key = cf.get(&apos;qiniu&apos;, &apos;ak&apos;) # AK
secret_key = cf.get(&apos;qiniu&apos;, &apos;sk&apos;) # SK
bucket_name = cf.get(&apos;qiniu&apos;, &apos;bucket&apos;) # 七牛空间名
url = cf.get(&apos;qiniu&apos;, &apos;url&apos;) # url
q = Auth(access_key, secret_key)
mime_type = &quot;image/jpeg&quot;
params = {&apos;x:a&apos;: &apos;a&apos;}
prefix = datetime.now().strftime(&apos;%Y_%m_%d&apos;)
def upload_qiniu(path, prefix): #这是具体的上传图片方法,主要是调用七牛的sdk
&apos;&apos;&apos; upload file to qiniu &apos;&apos;&apos;
dirname, filename = os.path.split(path)
key = &apos;%s_%s&apos; % (prefix, filename) # upload to qiniu&apos;s dir
key = key.decode(&apos;gbk&apos;).encode(&apos;utf8&apos;)
token = q.upload_token(bucket_name, key)
progress_handler = lambda progress, total: progress
ret, info = put_file(token, key, path, params, mime_type, progress_handler=progress_handler)
return ret != None and ret[&apos;key&apos;] == key
if __name__ == &apos;__main__&apos;:
path = sys.argv[1] #这里显然是获取图片的路径,也就是我们从剪切板里获取的
ret = upload_qiniu(path, prefix) 这是执行上面的上传方法
if ret:
# upload success 如果成功则把url拼成markdown格式
name = os.path.split(path)[1]
alt = name.split(&apos;.&apos;, 1)
markdown_url = &quot;![%s](%s/%s_%s \&quot;%s\&quot;)&quot; % (alt[0], url, prefix, name, alt[0])
# make it to clipboard 然后调用ahk的dll库吧它放进剪贴板,这样我们直接复制就可以了。 我就是这里没有执行成功。
ahk = cdll.AutoHotkey #load AutoHotkey
ahk.ahktextdll(&quot;&quot;) #start script in persistent mode (wait for action)
while not ahk.ahkReady(): #Wait for AutoHotkey.dll to start
time.sleep(0.01)
ahk.ahkExec(u&quot;clipboard = %s&quot; % markdown_url.decode(&apos;gbk&apos;))
else: print &quot;upload_failed&quot;
</code></pre><p>整个脚本也简单,就一个主函数,一个上传函数,经过运行我发现上传没问题,能够成功上传,只是把它放入剪贴板中存在问题,为此我还安装了个pycharm,输出的log显示ahk.dll调用失败。</p>
<p>但是把一个字符串放入剪贴板显然不只有这一种方法。于是我又查了一下直接用python自己能不能实现这个功能,果然没什么是强大的python做不到的。于是我把主函数改成了这样:</p>
<pre><code>def setText(aString):
w.OpenClipboard()
w.EmptyClipboard()
w.SetClipboardText(aString)
w.CloseClipboard()
if __name__ == &apos;__main__&apos;:
path = sys.argv[1]
ret = upload_qiniu(path, prefix)
if ret:
# upload success
name = os.path.split(path)[1]
alt = name.split(&apos;.&apos;, 1)
markdown_url = &quot;![%s](%s/%s_%s \&quot;%s\&quot;)&quot; % (alt[0], url, prefix, name, alt[0])
# make it to clipboard
setText(markdown_url)
else:
print &quot;upload_failed&quot;
</code></pre><p>这里又加了个方法,简单的四行代码就可以实现剪切板的操作。这样,终于成功了。</p>
<h2 id="使用:"><a href="#使用:" class="headerlink" title="使用:"></a>使用:</h2><ul>
<li><p>首先配置config.ini:</p>
<ul>
<li>[qiniu]</li>
<li>ak = # 填入你的AK</li>
<li>sk = # 填入你的SK</li>
<li>url = # 填入你的域名地址</li>
<li>bucket = # 填入你的七牛空间名称</li>
<li>注意!!!注释一定都要删掉, 写值时不要加引号。ak,sk不是用户名和密码,是在账户管理中的密钥管理那里找到。填完是这样婶儿的:<br><img src="http://7xry4c.com1.z0.glb.clouddn.com//2016_04_15_qiniuconfig.png" alt="qiniuconfig" title="qiniuconfig"></li>
</ul>
</li>
<li><p>然后是把config.ini和upload_qiniu.py放到同一个目录。</p>
</li>
<li>然后修改upload_qiniu.ahk中的upload_qiniu.py路径</li>
<li>然后双击upload_qiniu.ahk就可以了。</li>
<li>用鼠标左键点击一张图片,然后点击ctrl+alt+c,然后窗口消失后再markdown里ctrl+v 神奇的事就发生了!</li>
</ul>
<p>现在图片完全就是一键上传,一键贴到markdown了。</p>
<h3 id="源码在这"><a href="#源码在这" class="headerlink" title="源码在这"></a><a href="https://github.com/mjnhmd/markdown-upload-qiniu">源码在这</a></h3></content>
<summary type="html">
<h2 id="动机"><a href="#动机" class="headerlink" title="动机"></a>动机</h2><p>这个原因还用说吗,完全是刚需啊!!!以前写个markdown想要贴个图简直让人崩溃啊!!!</p>
<ul>
<li>你可能先要截个图</l
</summary>
<category term="Hexo" scheme="https://github.com/mjnhmd/categories/Hexo/"/>
<category term="Hexo" scheme="https://github.com/mjnhmd/tags/Hexo/"/>
<category term="MarkDown" scheme="https://github.com/mjnhmd/tags/MarkDown/"/>
<category term="python" scheme="https://github.com/mjnhmd/tags/python/"/>
</entry>
<entry>
<title>鹿的消逝:对电影《犴达罕》的解读</title>
<link href="https://github.com/mjnhmd/2016/04/13/%E9%B9%BF%E7%9A%84%E6%B6%88%E9%80%9D%EF%BC%9A%E5%AF%B9%E7%94%B5%E5%BD%B1%E3%80%8A%E7%8A%B4%E8%BE%BE%E7%BD%95%E3%80%8B%E7%9A%84%E8%A7%A3%E8%AF%BB/"/>
<id>https://github.com/mjnhmd/2016/04/13/鹿的消逝:对电影《犴达罕》的解读/</id>
<published>2016-04-13T09:45:11.000Z</published>
<updated>2016-04-13T10:19:56.025Z</updated>
<content type="html"><p>&emsp;&emsp;首先,这当然是一部以鄂温克人维加为主切入视角的记录片,但导演机敏地选择了“犴达罕”作为这部纪录片的名字,那么一切都变得比“民族志”式的叙述更为深刻。</p>
<p>&emsp;&emsp;犴达罕是驼鹿在满语中的叫法,纪录片中出现过的只是驼鹿被偷猎者射杀后残留下的尸骨。这是一种世界上最大的鹿科动物,典型的亚寒带针叶林食草动物,喜欢单独或小群生活。它们和鄂温克族的关系其实并没有影片中出现的另一种鹿——驯鹿来的密切,但却是驼鹿而非驯鹿成为了这部纪录片的名称。通常而言,驯鹿是鄂温克族的民族标志物,因为驯鹿作为鄂温克族的交通工具的历史由来已久,早在新中国成立以前,居住在额尔古纳左旗的极少数鄂温克族人尚处于原始社会末期父系家族公社阶段,生活在原始森林中,住在简陋的帐幕—-撮罗子中,往往漂泊不定。因为他们饲养驯鹿,常被称为“使用驯鹿的鄂温克人”,驯鹿甚至就是鄂温克民族的吉祥物。从某种程度上,两种鹿在影片中的存在都具有一种映射性的象征意义,鄂温克族,这是一个鹿的民族,但残忍的事实却是他们不得不与鹿分离,并亲眼见证着鹿的消逝。</p>
<p>&emsp;&emsp;纪录片中采取的一个确定性的线索是鄂温克民族在新时代的号召下被有组织地现代化,语言、服饰以及居住方式的改变是显而易见的,语言被汉化,服装的整洁化以及从山上的简陋的帷帐中搬到山下的鄂温克新村定居点,一个独立自足的民族的面貌发生了全然的改变。如果只从这些宏观的粗放镜头中表现鄂温克民族的生存现状,那么将与一部民族志式的纪实片没有分别,导演的眼光在于将他对于文明的思考穿插进一些微观的细节处。</p>
<p>&emsp;&emsp;外在的东西是容易被塑造的,然而一些属于文化内部的坚硬的内容是无法轻易消除的。包括维加在内的鄂温克人都喜欢喝酒、打架、与鹿为伍。片中最先切入的镜头就是醉酒疯癫的维加,以及在后续无数次族人群聚饮酒的场面,这与汉族的所谓“文明”的文化是决然不同的,汉族饮酒讲究的是微醺,是酒醉后的适度美感,这一强一弱的喝酒文化的对立性其实暗含了对强势文化殖民弱势文化的可能性的反思,并进一步达成了对“文明”这一概念的讽刺,何为“文明”呢?难道“文明”就是要以牺牲少数文化和独特文化为代价吗?又或者是否真的存在一个可以称之为“文明”的概念,并赋予了人类用自以为高姿态的优秀文化去同化所谓的卑劣文化的权力?所以,尽管作为老一辈的维加们受到了文明政策的号召搬到了山下居住,但他们仍不能摒除用打架解决矛盾的“野蛮行为”。在这里,有一个与老维加们形成对比的新生鄂温克人形象,纪录片中的一个场面是年轻的鄂温克人已放下了手里的猎枪,离开了他们的好伙伴——驯鹿,转而在定居点进行着现代的足球游戏,而他们游戏的地点的背景则是一间鹿产品专卖店,这是一张极其接近现代城市生活方式的图景,导演似乎在用这个画面告诉我们,老维加们已经输了,传统鄂温克人的坚守、传统鄂温克文化中的坚硬最终在现代文明的攻势下一败涂地。而导演非常清楚地知道,鄂温克族的没落会伴随着很多东西的消失,但最为关键也是最能摧毁一个民族的点在于剥夺他们的“文明”,对于鄂温克族来说,狩猎文化就是他们最核心的“文明”。就像他们给自己的民族起名为“鄂温克”,寓意就是“住在大山林中的人们”,靠山吃山,靠水吃水,住在兴安岭常年积雪的大山林里的人们以驯鹿为交通工具,以驼鹿为捕猎对象,与鹿为伍,这就是他们的基本生活,也是他们的全部生活。影片中维加酒后的苦闷之言说,驯鹿发展不起来,政府缴收了他们的猎枪,他们无事可做,所以很多年轻人宁愿喝酒喝死。这是整部影片最具力量性的一句话,不成佛便成魔。</p>
<p>&emsp;&emsp;更具讽刺的是,影片中透露出另外一种身份的存在——偷猎者。维加称其为偷猎者是因为这些人违背国家法律还是因为这些人违背狩猎原则呢?我想我更倾向于后者,传统的狩猎人打猎并不是为了获取商业利益,而是出于生存的需要,因此其与大自然与万物生灵间是一种和谐融洽的关系,他们不会肆意破坏生物规律地进行疯狂屠杀,并且会对猎物抱有神灵般的感恩与尊重,这些和偷猎者的暴力行径是截然不同的。那么,这里就存在一个强烈的讽刺手法,现代文明的优越感迫使处于劣势的少数民族文明被同化,这样的一个前提基础就是现代文明能更好地建设,然而,事实却好像是,现代文明总在建设的同时破坏着什么,并在破坏后再重新去建设。打破鄂温克人与鹿的和谐生活状态,将现代文明,商业文明带入深山树林,造成了鹿的消逝,然后再来发明一个法律保护鹿。这样的“文明”的循坏应该并不是一个陌生的话语吧。</p>
<p>&emsp;&emsp;导演在记录鄂温克人与鹿的关系变化的进程中其实实现了对“文明”这一概念的解构,文明只应该作为一种中性的概念性词汇而存在于平等的各民族国家之间,而决不能被赋予任何的修饰性色彩用于不同地域间的比较之上,更不应该成为文化殖民的借口。:鄂温克族已与鹿渐行渐远,他们既不能为伴,也没有能力守护鹿不被偷猎者所屠杀。在见证着鹿的消逝的同时,他们何尝不是在见证着自己的陨灭,驼鹿,他们更习惯称其为“犴达罕”,这一独栖的孤独的大山的物种,终要在历史前往现代的路上被偷猎者终结了生命,它体型庞大,死得沉重而悲壮,却又在那不为人知的某一处丛林处静悄悄。正如昌耀的一首诗,《鹿的角枝》:</p>
<center>在雄鹿的颅骨,<br><br>有两株<br>被精血所滋养的小树。<br><br>雾光里<br><br>这些挺拔的枝状体<br><br>明丽而珍重,<br><br>遁越于危崖、沼泽,<br><br>与猎人相周旋。<br><br><br>若干个世纪以后。<br><br>在我的书架,<br><br>在我新得收藏品之上,<br><br>我才听到来自高原腹地的那一声<br><br>火枪。——<br><br>那样的夕阳<br><br>倾照着那样呼唤的荒野,<br><br>从高岩。飞动的鹿角<br><br>猝然倒仆……<br><br><br>……是悲壮的。</center>
</content>
<summary type="html">
<p>&emsp;&emsp;首先,这当然是一部以鄂温克人维加为主切入视角的记录片,但导演机敏地选择了“犴达罕”作为这部纪录片的名字,那么一切都变得比“民族志”式的叙述更为深刻。</p>
<p>&emsp;&emsp;犴达罕是驼鹿在满语中的叫法,纪录片中出现过的只是驼鹿被偷猎者射
</summary>
<category term="影评" scheme="https://github.com/mjnhmd/categories/%E5%BD%B1%E8%AF%84/"/>
<category term="影评" scheme="https://github.com/mjnhmd/tags/%E5%BD%B1%E8%AF%84/"/>
</entry>
<entry>
<title>CoordinatorLayout调用原理源码解析</title>
<link href="https://github.com/mjnhmd/2016/04/05/CoordinatorLayout%E8%B0%83%E7%94%A8%E5%8E%9F%E7%90%86%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
<id>https://github.com/mjnhmd/2016/04/05/CoordinatorLayout调用原理源码解析/</id>
<published>2016-04-05T10:15:54.000Z</published>
<updated>2016-04-13T11:00:53.887Z</updated>
<content type="html"><h2 id="获取Behavior"><a href="#获取Behavior" class="headerlink" title="获取Behavior"></a>获取Behavior</h2><blockquote>
<p>CoordinatorLayout中子view关联的动作主要由Behavior实现。<br>系统控件中往往把behavior作为内部类,自己实现,比如AppbarLayout。<br>当然我们也可以自己实现自定义的Behavior来完成我们需要的动作。<br>自定义Behavior时,我们需要在xml代码中实现相应属性,例如:</p>
</blockquote>
<pre><code>app:layout_behavior=&quot;com.wuba.views.CustomAppbarBehavior&quot;
</code></pre><p>这个就是我们首页动画实现的自定义的behavior。<br>在xml中实现之后,必然会在代码中获取这个behavior:</p>
<pre><code>public static class LayoutParams extends MarginLayoutParams {
CoordinatorLayout.Behavior mBehavior;
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, styleable.CoordinatorLayout_LayoutParams);
this.mBehaviorResolved = a.hasValue(styleable.CoordinatorLayout_LayoutParams_layout_behavior);
if(this.mBehaviorResolved) {
this.mBehavior = CoordinatorLayout.parseBehavior(context, attrs, a.getString(styleable.CoordinatorLayout_LayoutParams_layout_behavior));//这个方法获取到behavior的实例
}
a.recycle();
}
</code></pre><p>可以看到,获取behavior的方式和很多自定义view的方式是一样的,通过自定义属性来获取。我们具体看一下parseBehavior()方法是怎么实现的:</p>
<pre><code>static CoordinatorLayout.Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if(TextUtils.isEmpty(name)) {
return null;
} else {
String fullName;
if(name.startsWith(&quot;.&quot;)) {//如果以.开始,就拼接上包名
fullName = context.getPackageName() + name;
} else if(name.indexOf(46) &gt;= 0) {//这里我猜46是类名的最大长度,大于46由没有以.开始,说明它本身就是完整包名
fullName = name;
} else {
fullName = WIDGET_PACKAGE_NAME + &apos;.&apos; + name;//否则就是包名.类名
}
try {
Object e = (Map)sConstructors.get();
if(e == null) {
e = new HashMap();
sConstructors.set(e);
}
Constructor c = (Constructor)((Map)e).get(fullName);
if(c == null) {
Class clazz = Class.forName(fullName, true, context.getClassLoader());//用反射获取类
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);//通过构造方法获取实例
((Map)e).put(fullName, c);
}
return (CoordinatorLayout.Behavior)c.newInstance(new Object[]{context, attrs});
} catch (Exception var7) {
throw new RuntimeException(&quot;Could not inflate Behavior subclass &quot; + fullName, var7);
}
}
</code></pre><p>可以看到这个方法,通过拼接路径,然后用反射的方式获取到了behavior的实例。这样View就成功获取到了behavior。</p>
<h2 id="通用动作的实现"><a href="#通用动作的实现" class="headerlink" title="通用动作的实现"></a>通用动作的实现</h2><blockquote>
<p>Behavior中有很多可以实现的接口。在我看来关于动作的方法大概可以分成两类。先说第一类;</p>
</blockquote>
<pre><code>public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)
</code></pre><p>这两个方法主要实现通用的的关联动作,各种view的改变都可以使用。<br>下面看一下他们的作用以及调用关系。<br>前两个方法和主要是在dispatchOnDependentViewChanged方法中调用,我们先看看在哪里调用了这个方法。</p>
<pre><code>class OnPreDrawListener implements android.view.ViewTreeObserver.OnPreDrawListener {
OnPreDrawListener() {
}
public boolean onPreDraw() {
CoordinatorLayout.this.dispatchOnDependentViewChanged(false);
return true;
}
}
</code></pre><p>主要是在这个listener中,再看一下哪里注册了这个监听:</p>
<pre><code>public void onAttachedToWindow() {
super.onAttachedToWindow();
this.resetTouchBehaviors();
if(this.mNeedsPreDrawListener) {
if(this.mOnPreDrawListener == null) {
this.mOnPreDrawListener = new CoordinatorLayout.OnPreDrawListener();
}
ViewTreeObserver vto = this.getViewTreeObserver();
vto.addOnPreDrawListener(this.mOnPreDrawListener);
}
this.mIsAttachedToWindow = true;
}
</code></pre><p>主要是在onAttachedToWindow里。也就是说在coordinatorlayout绑定到窗口是,就会直接注册这个监听,每次要绘制view之前都会i调用dispatchOnDependentViewChanged方法。现在再来看一下刚才提到的dispatchOnDependentViewChanged的具体实现:</p>
<pre><code>void dispatchOnDependentViewChanged(boolean fromNestedScroll) {
int layoutDirection = ViewCompat.getLayoutDirection(this);
int childCount = this.mDependencySortedChildren.size();
for(int i = 0; i &lt; childCount; ++i) {
View child = (View)this.mDependencySortedChildren.get(i);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)child.getLayoutParams();
for(int oldRect = 0; oldRect &lt; i; ++oldRect) {
View newRect = (View)this.mDependencySortedChildren.get(oldRect);
if(lp.mAnchorDirectChild == newRect) {
this.offsetChildToAnchor(child, layoutDirection);
}
}
Rect var14 = this.mTempRect1;
Rect var15 = this.mTempRect2;
this.getLastChildRect(child, var14);
this.getChildRect(child, true, var15);
if(!var14.equals(var15)) {
this.recordLastChildRect(child, var15);
for(int j = i + 1; j &lt; childCount; ++j) {
View checkChild = (View)this.mDependencySortedChildren.get(j);
CoordinatorLayout.LayoutParams checkLp = (CoordinatorLayout.LayoutParams)checkChild.getLayoutParams();
CoordinatorLayout.Behavior b = checkLp.getBehavior();
if(b != null &amp;&amp; b.layoutDependsOn(this, checkChild, child)) {
if(!fromNestedScroll &amp;&amp; checkLp.getChangedAfterNestedScroll()) {
checkLp.resetChangedAfterNestedScroll();
} else {
boolean handled = b.onDependentViewChanged(this, checkChild, child);
if(fromNestedScroll) {
checkLp.setChangedAfterNestedScroll(handled);
}
}
}
}
}
}
}
</code></pre><p>只看我们关心的部分,也就是最下边的for循环,可以看出coordinatorlayout会循环获取每个子view的behavior。然后在if中看到了b.layoutDependsOn,if里面有b.onDependentViewChanged。<br>也就是说,当view改变时,如果通过符合我们自己实现的某些条件(这个条件在layoutDependsOn中定义),就会调用onDependentViewChanged方法,实现我们需要的动作。</p>
<h2 id="滚动动作的实现"><a href="#滚动动作的实现" class="headerlink" title="滚动动作的实现"></a>滚动动作的实现</h2><p>下面就是第二类,专门针对滚动动作的接口</p>
<pre><code>public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed)
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed)
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY)
</code></pre><p>可以看到有很多方法,针对各种不同的滚动以及不同的阶段。<br>这个单在coordinatorlayout中很难看清楚如何调用,因为它涉及到了,接口,以及其他的view。就从事件的源头看,也就是NestedScrollView。<br>首先看NestedScrollView,它实现了NestedScrollingChild接口,这个接口有很多方法,我们就找startNestedScroll这个方法,这个从名称来看,应该跟上边的onStartNestedScroll方法对应。</p>
<pre><code>@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
</code></pre><p>先看看哪里调用了这个方法,果然是touch事件触发的:</p>
<pre><code>@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action &amp; MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
}
}
return mIsBeingDragged;
}
</code></pre><p>再跟进这个方法,可以看到是在NestedScrollingChildHelper中实现的</p>
<pre><code>public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
</code></pre><p>这个方法主要是循环网上找parent,直到符合条件,条件就是ViewParentCompat.onStartNestedScroll(p, child, mView, axes),具体看一下这个方法:</p>
<pre><code>public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}
</code></pre><p>再往下点:</p>
<pre><code>static final ViewParentCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version &gt;= 21) {
IMPL = new ViewParentCompatLollipopImpl();
} else if (version &gt;= 19) {
IMPL = new ViewParentCompatKitKatImpl();
} else if (version &gt;= 14) {
IMPL = new ViewParentCompatICSImpl();
} else {
IMPL = new ViewParentCompatStubImpl();
}
}
</code></pre><p>这里是一个版本判断。我们app支持的最小版本是16,所以肯定看第三个:</p>
<pre><code>@Override
public boolean onStartNestedScroll(ViewParent parent, View child, View target,int nestedScrollAxes) {
if (parent instanceof NestedScrollingParent) {
return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,nestedScrollAxes);
}
return false;
}
</code></pre><p>所这里可以看到,如果想要实现滚动的关联,要求父view必须实现NestedScrollingParent接口,然后调用这个接口的onStartNestedScroll方法。<br>好了,现在再返回头来看:</p>
<pre><code>public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent
</code></pre><p>CoordinatorLayout 刚好实现了这个接口,所以如果它的子view如果实现了NestedScrollingChild接口就可以实现滚动动作的联动了<br>再来看下这个方法的具体实现:</p>
<pre><code>public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
int childCount = this.getChildCount();
for(int i = 0; i &lt; childCount; ++i) {
View view = this.getChildAt(i);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)view.getLayoutParams();
CoordinatorLayout.Behavior viewBehavior = lp.getBehavior();
if(viewBehavior != null) {
boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target, nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
</code></pre><p>可以看到,最终还是通过调用其behavior中的onStartNestedScroll实现了具体的动作。</p>
</content>
<summary type="html">
<h2 id="获取Behavior"><a href="#获取Behavior" class="headerlink" title="获取Behavior"></a>获取Behavior</h2><blockquote>
<p>CoordinatorLayout中子view关联
</summary>
<category term="Android" scheme="https://github.com/mjnhmd/categories/Android/"/>
<category term="Material Designed" scheme="https://github.com/mjnhmd/tags/Material-Designed/"/>
<category term="behavior" scheme="https://github.com/mjnhmd/tags/behavior/"/>
</entry>
<entry>
<title>Handler实现原理</title>
<link href="https://github.com/mjnhmd/2016/03/23/Handler%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/"/>
<id>https://github.com/mjnhmd/2016/03/23/Handler实现原理/</id>
<published>2016-03-23T12:52:31.000Z</published>
<updated>2016-04-13T11:01:26.848Z</updated>
<content type="html"><p>今天在尝试把百度定位sdk的初始化转移到子线程时,遇到了一个问题:</p>
<pre><code>java.lang.RuntimeException: Can&apos;t create handler inside thread that has not called Looper.prepare()
</code></pre><p>这个问题并不少见,网上一搜有很多。<br>解决方法就是在创建handler时传入Looper.getMainLooper()。</p>
<p>但是我发现我在子线程执行的代码里并没有创建handler,只是创建了一个百度sdk的定位类。<br>显然是在这个类里边创建了handler,导致了这个问题。显然这个方法解决不了我的问题,由于handler并不是我创建的,我并没有办法给它传进去一个looper。</p>
<p>只能看一下后台的实现原理,决定怎么解</p>
<p>首先看下源码,在哪里报的错:</p>
<pre><code>public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class&lt;? extends Handler&gt; klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &amp;&amp;
(klass.getModifiers() &amp; Modifier.STATIC) == 0) {
Log.w(TAG, &quot;The following Handler class should be static or leaks might occur: &quot; +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
&quot;Can&apos;t create handler inside thread that has not called Looper.prepare()&quot;);
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
</code></pre><p>可以看到是在handler的构造函数中抛出的异常,从代码和错误内容都不难看出是获取looper失败。那么我们再看一下这个Looper.myLooper()方法:</p>
<pre><code>public static Looper myLooper() {
return sThreadLocal.get();
}
</code></pre><p>它只是返回了一个线程里的looper,显然是这个线程里没有looper造成的。线程里的looper是在哪里设置的呢?我们通过错误的内容“has not called Looper.prepare()”可以推断出应该就是prepare()方法了:</p>
<pre><code>public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException(&quot;Only one Looper may be created per thread&quot;);
}
sThreadLocal.set(new Looper(quitAllowed));
}
</code></pre><p>这里可以看到一个线程里只能有一个looper。</p>
<p>那么Looper到底是干嘛的呢?<br>首先我们都知道收到消息都是在handleMessage()方法处理的。我们就从最熟悉的地方找起,handleMessage方法是在哪调的呢:</p>
<pre><code>public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
</code></pre><p>可以看到是在dispatchMessage(Message msg)里调用的。这里先判断有没有callback,也就是Runnable,如果自己写了runnable,那么就交由runnable处理</p>
<pre><code>private static void handleCallback(Message message) {
message.callback.run();
}
</code></pre><p>如果没有,则调用handleMessage()方法。</p>
<p>继续往上找, dispatchMessage(Message msg)是在这里调用的:</p>
<pre><code>public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(&quot;No Looper; Looper.prepare() wasn&apos;t called on this thread.&quot;);
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
</code></pre><p>这里有很多代码,我们只捡关心的看,首先他拿到了当前的looper,然后从中获取了一个队列,也就是消息队列。可以知道一个looper里会有一个消息队列。然后是一个死循环,循环获取队列中的message,然后调用之前的dispatchMessage(msg)。这样就把整个过程连起来了:</p>
<p>Looper维持一个消息队列,然后不断的从队列里边拿出消息,然后交给handelr处理。还差一点,就是队列里的消息是从哪来的呢。这个相信用过的人都知道,自然就是sendMessage:<br> public final boolean sendMessage(Message msg)<br> {<br> return sendMessageDelayed(msg, 0);<br> }<br>可以看到这是一个包装,经过一系列的调用链,最终调用到了<br> queue.enqueueMessage(msg, uptimeMillis)<br>这个queue就是从looper里获取到的那个消息队列,这个方法里就是一个典型的队列链表操作:</p>
<pre><code> boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when &lt; p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked &amp;&amp; p.target == null &amp;&amp; msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when &lt; p.when) {
break;
}
if (needWake &amp;&amp; p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}
</code></pre><p>好了现在就全明白了,handler拿着一个looper,不断地往looper手里的消息队列里放消息,looper拿着这个消息队列又不断地往出取,让handler来处理消息。</p>
<p>至于最开始的问题,已经很简单了。只需要在线程中调用Looper.prepare()在线程中放一个looper,然后在最后执行Looper.loop(),让它不断的往出取消息就可以了。即使不直接往handler里传入looper,handler也会在构建时自动从线程中拿到looper。</p>
</content>
<summary type="html">
<p>今天在尝试把百度定位sdk的初始化转移到子线程时,遇到了一个问题:</p>
<pre><code>java.lang.RuntimeException: Can&apos;t create handler inside thread that has not called
</summary>
<category term="Android" scheme="https://github.com/mjnhmd/categories/Android/"/>
<category term="java" scheme="https://github.com/mjnhmd/tags/java/"/>
</entry>
</feed>