-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
1117 lines (808 loc) · 48 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>A Mjn Blog</title>
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<meta name="author" content="MJN">
<meta name="description">
<meta property="og:type" content="website">
<meta property="og:title" content="A Mjn Blog">
<meta property="og:url" content="https://github.com/mjnhmd/index.html">
<meta property="og:site_name" content="A Mjn Blog">
<meta property="og:description">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="A Mjn Blog">
<meta name="twitter:description">
<link rel="alternative" href="/atom.xml" title="A Mjn Blog" type="application/atom+xml">
<link rel="icon" href="/img/favicon.ico">
<link rel="apple-touch-icon" href="/img/jacman.jpg">
<link rel="apple-touch-icon-precomposed" href="/img/jacman.jpg">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<div>
<div id="imglogo">
<a href="/"><img src="/img/logo.png" alt="A Mjn Blog" title="A Mjn Blog"/></a>
</div>
<div id="textlogo">
<h1 class="site-name"><a href="/" title="A Mjn Blog">A Mjn Blog</a></h1>
<h2 class="blog-motto"></h2>
</div>
<div class="navbar"><a class="navbutton navmobile" href="#" title="菜单">
</a></div>
<nav class="animated">
<ul>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/archives">Archives</a></li>
<li>
<form class="search" action="//google.com/search" method="get" accept-charset="utf-8">
<label>Search</label>
<input type="search" id="search" name="q" autocomplete="off" maxlength="20" placeholder="搜索" />
<input type="hidden" name="q" value="site:github.com/mjnhmd">
</form>
</li>
</ul>
</nav>
</div>
</header>
<div id="container">
<div id="main">
<article class="post-expand post" itemprop="articleBody">
<header class="article-info clearfix">
<h1 itemprop="name">
<a href="/2016/04/14/MarkDown简化贴图流程/" title="MarkDown简化贴图流程" itemprop="url">MarkDown简化贴图流程</a>
</h1>
<p class="article-time">
<time datetime="2016-04-14T11:32:50.000Z" itemprop="datePublished"> 发表于 2016-04-14</time>
</p>
</header>
<div class="article-content">
<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 "Python D:\qiniu\upload_qiniu.py" %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('utf-8')
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('config.ini')
access_key = cf.get('qiniu', 'ak') # AK
secret_key = cf.get('qiniu', 'sk') # SK
bucket_name = cf.get('qiniu', 'bucket') # 七牛空间名
url = cf.get('qiniu', 'url') # url
q = Auth(access_key, secret_key)
mime_type = "image/jpeg"
params = {'x:a': 'a'}
prefix = datetime.now().strftime('%Y_%m_%d')
def upload_qiniu(path, prefix): #这是具体的上传图片方法,主要是调用七牛的sdk
''' upload file to qiniu '''
dirname, filename = os.path.split(path)
key = '%s_%s' % (prefix, filename) # upload to qiniu's dir
key = key.decode('gbk').encode('utf8')
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['key'] == key
if __name__ == '__main__':
path = sys.argv[1] #这里显然是获取图片的路径,也就是我们从剪切板里获取的
ret = upload_qiniu(path, prefix) 这是执行上面的上传方法
if ret:
# upload success 如果成功则把url拼成markdown格式
name = os.path.split(path)[1]
alt = name.split('.', 1)
markdown_url = "![%s](%s/%s_%s \"%s\")" % (alt[0], url, prefix, name, alt[0])
# make it to clipboard 然后调用ahk的dll库吧它放进剪贴板,这样我们直接复制就可以了。 我就是这里没有执行成功。
ahk = cdll.AutoHotkey #load AutoHotkey
ahk.ahktextdll("") #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"clipboard = %s" % markdown_url.decode('gbk'))
else: print "upload_failed"
</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__ == '__main__':
path = sys.argv[1]
ret = upload_qiniu(path, prefix)
if ret:
# upload success
name = os.path.split(path)[1]
alt = name.split('.', 1)
markdown_url = "![%s](%s/%s_%s \"%s\")" % (alt[0], url, prefix, name, alt[0])
# make it to clipboard
setText(markdown_url)
else:
print "upload_failed"
</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>
<p class="article-more-link">
</p>
</div>
<footer class="article-footer clearfix">
<div class="article-catetags">
<div class="article-categories">
<span></span>
<a class="article-category-link" href="/categories/Hexo/">Hexo</a>
</div>
<div class="article-tags">
<span></span> <a href="/tags/Hexo/">Hexo</a><a href="/tags/MarkDown/">MarkDown</a><a href="/tags/python/">python</a>
</div>
</div>
<div class="comments-count">
<span></span>
<a href="/2016/04/14/MarkDown简化贴图流程/#comments" class="ds-thread-count comments-count-link" data-thread-key="2016/04/14/MarkDown简化贴图流程/" data-count-type="comments"> </a>
</div>
</footer>
</article>
<article class="post-expand photo" itemprop="articleBody">
<header class="article-info clearfix">
<h1 itemprop="name">
<a href="/2016/04/13/鹿的消逝:对电影《犴达罕》的解读/" title="鹿的消逝:对电影《犴达罕》的解读" itemprop="url">鹿的消逝:对电影《犴达罕》的解读</a>
</h1>
<p class="article-time">
<time datetime="2016-04-13T09:45:11.000Z" itemprop="datePublished"> 发表于 2016-04-13</time>
</p>
</header>
<div class="article-content">
<p>  首先,这当然是一部以鄂温克人维加为主切入视角的记录片,但导演机敏地选择了“犴达罕”作为这部纪录片的名字,那么一切都变得比“民族志”式的叙述更为深刻。</p>
<p>  犴达罕是驼鹿在满语中的叫法,纪录片中出现过的只是驼鹿被偷猎者射杀后残留下的尸骨。这是一种世界上最大的鹿科动物,典型的亚寒带针叶林食草动物,喜欢单独或小群生活。它们和鄂温克族的关系其实并没有影片中出现的另一种鹿——驯鹿来的密切,但却是驼鹿而非驯鹿成为了这部纪录片的名称。通常而言,驯鹿是鄂温克族的民族标志物,因为驯鹿作为鄂温克族的交通工具的历史由来已久,早在新中国成立以前,居住在额尔古纳左旗的极少数鄂温克族人尚处于原始社会末期父系家族公社阶段,生活在原始森林中,住在简陋的帐幕—-撮罗子中,往往漂泊不定。因为他们饲养驯鹿,常被称为“使用驯鹿的鄂温克人”,驯鹿甚至就是鄂温克民族的吉祥物。从某种程度上,两种鹿在影片中的存在都具有一种映射性的象征意义,鄂温克族,这是一个鹿的民族,但残忍的事实却是他们不得不与鹿分离,并亲眼见证着鹿的消逝。</p>
<p>  纪录片中采取的一个确定性的线索是鄂温克民族在新时代的号召下被有组织地现代化,语言、服饰以及居住方式的改变是显而易见的,语言被汉化,服装的整洁化以及从山上的简陋的帷帐中搬到山下的鄂温克新村定居点,一个独立自足的民族的面貌发生了全然的改变。如果只从这些宏观的粗放镜头中表现鄂温克民族的生存现状,那么将与一部民族志式的纪实片没有分别,导演的眼光在于将他对于文明的思考穿插进一些微观的细节处。</p>
<p>  外在的东西是容易被塑造的,然而一些属于文化内部的坚硬的内容是无法轻易消除的。包括维加在内的鄂温克人都喜欢喝酒、打架、与鹿为伍。片中最先切入的镜头就是醉酒疯癫的维加,以及在后续无数次族人群聚饮酒的场面,这与汉族的所谓“文明”的文化是决然不同的,汉族饮酒讲究的是微醺,是酒醉后的适度美感,这一强一弱的喝酒文化的对立性其实暗含了对强势文化殖民弱势文化的可能性的反思,并进一步达成了对“文明”这一概念的讽刺,何为“文明”呢?难道“文明”就是要以牺牲少数文化和独特文化为代价吗?又或者是否真的存在一个可以称之为“文明”的概念,并赋予了人类用自以为高姿态的优秀文化去同化所谓的卑劣文化的权力?所以,尽管作为老一辈的维加们受到了文明政策的号召搬到了山下居住,但他们仍不能摒除用打架解决矛盾的“野蛮行为”。在这里,有一个与老维加们形成对比的新生鄂温克人形象,纪录片中的一个场面是年轻的鄂温克人已放下了手里的猎枪,离开了他们的好伙伴——驯鹿,转而在定居点进行着现代的足球游戏,而他们游戏的地点的背景则是一间鹿产品专卖店,这是一张极其接近现代城市生活方式的图景,导演似乎在用这个画面告诉我们,老维加们已经输了,传统鄂温克人的坚守、传统鄂温克文化中的坚硬最终在现代文明的攻势下一败涂地。而导演非常清楚地知道,鄂温克族的没落会伴随着很多东西的消失,但最为关键也是最能摧毁一个民族的点在于剥夺他们的“文明”,对于鄂温克族来说,狩猎文化就是他们最核心的“文明”。就像他们给自己的民族起名为“鄂温克”,寓意就是“住在大山林中的人们”,靠山吃山,靠水吃水,住在兴安岭常年积雪的大山林里的人们以驯鹿为交通工具,以驼鹿为捕猎对象,与鹿为伍,这就是他们的基本生活,也是他们的全部生活。影片中维加酒后的苦闷之言说,驯鹿发展不起来,政府缴收了他们的猎枪,他们无事可做,所以很多年轻人宁愿喝酒喝死。这是整部影片最具力量性的一句话,不成佛便成魔。</p>
<p>  更具讽刺的是,影片中透露出另外一种身份的存在——偷猎者。维加称其为偷猎者是因为这些人违背国家法律还是因为这些人违背狩猎原则呢?我想我更倾向于后者,传统的狩猎人打猎并不是为了获取商业利益,而是出于生存的需要,因此其与大自然与万物生灵间是一种和谐融洽的关系,他们不会肆意破坏生物规律地进行疯狂屠杀,并且会对猎物抱有神灵般的感恩与尊重,这些和偷猎者的暴力行径是截然不同的。那么,这里就存在一个强烈的讽刺手法,现代文明的优越感迫使处于劣势的少数民族文明被同化,这样的一个前提基础就是现代文明能更好地建设,然而,事实却好像是,现代文明总在建设的同时破坏着什么,并在破坏后再重新去建设。打破鄂温克人与鹿的和谐生活状态,将现代文明,商业文明带入深山树林,造成了鹿的消逝,然后再来发明一个法律保护鹿。这样的“文明”的循坏应该并不是一个陌生的话语吧。</p>
<p>  导演在记录鄂温克人与鹿的关系变化的进程中其实实现了对“文明”这一概念的解构,文明只应该作为一种中性的概念性词汇而存在于平等的各民族国家之间,而决不能被赋予任何的修饰性色彩用于不同地域间的比较之上,更不应该成为文化殖民的借口。:鄂温克族已与鹿渐行渐远,他们既不能为伴,也没有能力守护鹿不被偷猎者所屠杀。在见证着鹿的消逝的同时,他们何尝不是在见证着自己的陨灭,驼鹿,他们更习惯称其为“犴达罕”,这一独栖的孤独的大山的物种,终要在历史前往现代的路上被偷猎者终结了生命,它体型庞大,死得沉重而悲壮,却又在那不为人知的某一处丛林处静悄悄。正如昌耀的一首诗,《鹿的角枝》:</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>
<p class="article-more-link">
</p>
</div>
<footer class="article-footer clearfix">
<div class="article-catetags">
<div class="article-categories">
<span></span>
<a class="article-category-link" href="/categories/影评/">影评</a>
</div>
<div class="article-tags">
<span></span> <a href="/tags/影评/">影评</a>
</div>
</div>
<div class="comments-count">
<span></span>
<a href="/2016/04/13/鹿的消逝:对电影《犴达罕》的解读/#comments" class="ds-thread-count comments-count-link" data-thread-key="2016/04/13/鹿的消逝:对电影《犴达罕》的解读/" data-count-type="comments"> </a>
</div>
</footer>
</article>
<article class="post-expand post" itemprop="articleBody">
<header class="article-info clearfix">
<h1 itemprop="name">
<a href="/2016/04/05/CoordinatorLayout调用原理源码解析/" title="CoordinatorLayout调用原理源码解析" itemprop="url">CoordinatorLayout调用原理源码解析</a>
</h1>
<p class="article-time">
<time datetime="2016-04-05T10:15:54.000Z" itemprop="datePublished"> 发表于 2016-04-05</time>
</p>
</header>
<div class="article-content">
<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="com.wuba.views.CustomAppbarBehavior"
</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(".")) {//如果以.开始,就拼接上包名
fullName = context.getPackageName() + name;
} else if(name.indexOf(46) >= 0) {//这里我猜46是类名的最大长度,大于46由没有以.开始,说明它本身就是完整包名
fullName = name;
} else {
fullName = WIDGET_PACKAGE_NAME + '.' + 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("Could not inflate Behavior subclass " + 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 < childCount; ++i) {
View child = (View)this.mDependencySortedChildren.get(i);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)child.getLayoutParams();
for(int oldRect = 0; oldRect < 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 < childCount; ++j) {
View checkChild = (View)this.mDependencySortedChildren.get(j);
CoordinatorLayout.LayoutParams checkLp = (CoordinatorLayout.LayoutParams)checkChild.getLayoutParams();
CoordinatorLayout.Behavior b = checkLp.getBehavior();
if(b != null && b.layoutDependsOn(this, checkChild, child)) {
if(!fromNestedScroll && 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 & 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 >= 21) {
IMPL = new ViewParentCompatLollipopImpl();
} else if (version >= 19) {
IMPL = new ViewParentCompatKitKatImpl();
} else if (version >= 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 < 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>
<p class="article-more-link">
</p>
</div>
<footer class="article-footer clearfix">
<div class="article-catetags">
<div class="article-categories">
<span></span>
<a class="article-category-link" href="/categories/Android/">Android</a>
</div>
<div class="article-tags">
<span></span> <a href="/tags/Material-Designed/">Material Designed</a><a href="/tags/behavior/">behavior</a>
</div>
</div>
<div class="comments-count">
<span></span>
<a href="/2016/04/05/CoordinatorLayout调用原理源码解析/#comments" class="ds-thread-count comments-count-link" data-thread-key="2016/04/05/CoordinatorLayout调用原理源码解析/" data-count-type="comments"> </a>
</div>
</footer>
</article>
<article class="post-expand post" itemprop="articleBody">
<header class="article-info clearfix">
<h1 itemprop="name">
<a href="/2016/03/23/Handler实现原理/" title="Handler实现原理" itemprop="url">Handler实现原理</a>
</h1>
<p class="article-time">
<time datetime="2016-03-23T12:52:31.000Z" itemprop="datePublished"> 发表于 2016-03-23</time>
</p>
</header>
<div class="article-content">
<p>今天在尝试把百度定位sdk的初始化转移到子线程时,遇到了一个问题:</p>
<pre><code>java.lang.RuntimeException: Can'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<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
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("Only one Looper may be created per thread");
}
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("No Looper; Looper.prepare() wasn't called on this thread.");
}
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 < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && 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>
<p class="article-more-link">
</p>
</div>
<footer class="article-footer clearfix">
<div class="article-catetags">
<div class="article-categories">
<span></span>
<a class="article-category-link" href="/categories/Android/">Android</a>
</div>
<div class="article-tags">
<span></span> <a href="/tags/java/">java</a>
</div>
</div>
<div class="comments-count">
<span></span>
<a href="/2016/03/23/Handler实现原理/#comments" class="ds-thread-count comments-count-link" data-thread-key="2016/03/23/Handler实现原理/" data-count-type="comments"> </a>
</div>
</footer>
</article>
</div>
<div class="openaside"><a class="navbutton" href="#" title="显示侧边栏"></a></div>
<div id="asidepart">
<div class="closeaside"><a class="closebutton" href="#" title="隐藏侧边栏"></a></div>
<aside class="clearfix">
<div class="github-card">
<p class="asidetitle">Github 名片</p>
<div class="github-card" data-github="mjnhmd" data-width="220" data-height="119" data-theme="medium">
<script type="text/javascript" src="//cdn.jsdelivr.net/github-cards/latest/widget.js" ></script>
</div>
</div>
<div class="categorieslist">
<p class="asidetitle">分类</p>
<ul>
<li><a href="/categories/Android/" title="Android">Android<sup>2</sup></a></li>
<li><a href="/categories/Hexo/" title="Hexo">Hexo<sup>1</sup></a></li>
<li><a href="/categories/影评/" title="影评">影评<sup>1</sup></a></li>
</ul>
</div>
<div class="tagslist">
<p class="asidetitle">标签</p>
<ul class="clearfix">
<li><a href="/tags/Hexo/" title="Hexo">Hexo<sup>1</sup></a></li>
<li><a href="/tags/MarkDown/" title="MarkDown">MarkDown<sup>1</sup></a></li>
<li><a href="/tags/python/" title="python">python<sup>1</sup></a></li>
<li><a href="/tags/Material-Designed/" title="Material Designed">Material Designed<sup>1</sup></a></li>
<li><a href="/tags/behavior/" title="behavior">behavior<sup>1</sup></a></li>
<li><a href="/tags/java/" title="java">java<sup>1</sup></a></li>
<li><a href="/tags/影评/" title="影评">影评<sup>1</sup></a></li>
</ul>
</div>
<div class="linkslist">
<p class="asidetitle">友情链接</p>
<ul>
<li>
<a href="http://www.cnblogs.com/puff" target="_blank" title="赵岘大神">赵岘大神</a>
</li>
<li>
<a href="http://123.57.236.114:3000/" target="_blank" title="曲彦桥大神">曲彦桥大神</a>
</li>
<li>
<a href="http://blog.csdn.net/guxiao1201" target="_blank" title="徐萌阳大神">徐萌阳大神</a>
</li>
<li>
<a href="http://blog.csdn.net/lihappyangel" target="_blank" title="李晓梅大神">李晓梅大神</a>
</li>
<li>
<a href="http://xdmaolei.github.io/" target="_blank" title="毛磊大神">毛磊大神</a>
</li>
<li>
<a href="http://blog.csdn.net/u010032372" target="_blank" title="王永川大神">王永川大神</a>
</li>
<li>
<a href="http://peive.coding.me/" target="_blank" title="张雪峰大神">张雪峰大神</a>
</li>
<li>
<a href="http://nohere.github.io/" target="_blank" title="闫涛大神">闫涛大神</a>
</li>
<li>
<a href="http://mozzieblog.sinaapp.com/" target="_blank" title="胡昊大神">胡昊大神</a>
</li>
<li>
<a href="http://zhangzhixin2199.github.io/" target="_blank" title="张志新大神">张志新大神</a>
</li>
<li>
<a href="http://coofee.github.io/" target="_blank" title="赵聪颖大神">赵聪颖大神</a>
</li>
</ul>
</div>
<div class="rsspart">
<a href="/atom.xml" target="_blank" title="rss">RSS 订阅</a>
</div>
<div class="weiboshow">
<p class="asidetitle">新浪微博</p>
<iframe width="100%" height="119" class="share_self" frameborder="0" scrolling="no" src="http://widget.weibo.com/weiboshow/index.php?language=&width=0&height=119&fansRow=2&ptype=1&speed=0&skin=9&isTitle=1&noborder=1&isWeibo=0&isFans=0&uid=null&verifier=&dpc=1"></iframe>
</div>
</aside>
</div>
</div>
<footer><div id="footer" >
<div class="line">
<span></span>
<div class="author"></div>
</div>
<section class="info">
<p> Hello ,I'm mjn in 58ganji. <br/>
This is my blog, believe it or not.</p>
</section>
<div class="social-font" class="clearfix">
<a href="https://github.com/mjnhmd" target="_blank" class="icon-github" title="github"></a>
</div>
<p class="copyright">
Powered by <a href="http://hexo.io" target="_blank" title="hexo">hexo</a> and Theme by <a href="https://github.com/wuchong/jacman" target="_blank" title="Jacman">Jacman</a> © 2016
<a href="/about" target="_blank" title="MJN">MJN</a>
<span id="busuanzi_container_site_pv">
total visit:<span id="busuanzi_value_site_pv"></span>
</p>
<script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js">
</script>
</span>
</div>
</footer>
<script src="/js/jquery-2.0.3.min.js"></script>
<script src="/js/jquery.imagesloaded.min.js"></script>
<script src="/js/gallery.js"></script>
<script src="/js/jquery.qrcode-0.12.0.min.js"></script>