隔了两个月未更新本地文章到 Github,今天突然想去发布两篇,结果流程命令全然忘记了。为防止以后出现类似情况,这里描述一下文章发布的工作流,仅作记录使用。
Posts
Blogging is baked into Jekyll. You write blog posts as text files and Jekyll provides everything you need to turn it into a blog.
The Posts Folder
The _posts folder is where your blog posts live. You typically write posts in Markdown, HTML is also supported.
Creating Posts
To create a post, add a file to your _posts directory with the following format:
1
YEAR-MONTH-DAY-title.MARKUP
Where YEAR is a four-digit number, MONTH and DAY are both two-digit numbers, and MARKUP is the file extension representing the format used in the file. For example, the following are examples of valid post filenames:
1
2
2011-12-31-new-years-eve-is-awesome.md
2012-09-12-how-to-write-a-blog.md
All blog post files must begin with front matter which is typically used to set a layout or other meta data. For a simple example this can just be empty:
1
2
3
4
5
6
7
8
9
10
---
layout: post
title: "Welcome to Jekyll!"
---
# Welcome
**Hello world**, this is my first Jekyll blog post.
I hope you like it!
ProTip™: Link to other posts
Use the post_url tag to link to other posts without having to worry about the URLs breaking when the site permalink style changes.
Be aware of character sets
Content processors can modify certain characters to make them look nicer. For example, the smart extension in Redcarpet converts standard, ASCII quotation characters to curly, Unicode ones. In order for the browser to display those characters properly, define the charset meta value by including <meta charset="utf-8"> in the <head> of your layout.
Including images and resources
At some point, you’ll want to include images, downloads, or other digital assets along with your text content. One common solution is to create a folder in the root of the project directory called something like assets, into which any images, files or other resources are placed. Then, from within any post, they can be linked to using the site’s root as the path for the asset to include. The best way to do this depends on the way your site’s (sub)domain and path are configured, but here are some simple examples in Markdown:
Including an image asset in a post:
1
2
... which is shown in the screenshot below:

Linking to a PDF for readers to download:
1
... you can [get the PDF](/assets/mydoc.pdf) directly.
Displaying an index of posts
Creating an index of posts on another page should be easy thanks to Liquid and its tags. Here’s a simple example of how to create a list of links to your blog posts:
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
<ul>
<li>
<a href="/skill/2026/06/03/juejin-skill/">把文章发布到掘金,做成一个可复用的 juejin-skill</a>
</li>
<li>
<a href="/skill/2026/05/24/blog-post-skill-implementation/">Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的</a>
</li>
<li>
<a href="/android/2026/04/12/android-ink-api-compose/">Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力</a>
</li>
<li>
<a href="/android/2023/09/16/deep-link/">Android上的Deep-Link技术调研</a>
</li>
<li>
<a href="/2023/05/04/app-init-process/">Launcher进程启动过程剖析</a>
</li>
<li>
<a href="/2023/04/29/java-producer-consumer-model/">回顾Java中经典的生产/消费者模型</a>
</li>
<li>
<a href="/2023/04/22/android-initialization/">Android系统启动流程剖析</a>
</li>
<li>
<a href="/2023/04/21/aosp-download/">AOSP在Mac上的编译实践(上)</a>
</li>
<li>
<a href="/skill/2023/04/16/code-shortcut/">效率编程之快捷键篇</a>
</li>
<li>
<a href="/android/2023/04/07/android-thread-creation/">Android系统中线程的创建过程</a>
</li>
<li>
<a href="/skill/2023/03/29/chatgpt-talk/">简单聊聊 ChatGPT</a>
</li>
<li>
<a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a>
</li>
<li>
<a href="/2023/03/19/min-number/">把数组排成最小的数</a>
</li>
<li>
<a href="/gradle/2022/09/21/gradle-property/">Gradle手札之Properties配置</a>
</li>
<li>
<a href="/skill/2022/09/13/git-user-guide/">Git用户手札</a>
</li>
<li>
<a href="/skill/2022/09/06/github-actions-auto-publish/">GitHub Actions实践之自动发布组件</a>
</li>
<li>
<a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a>
</li>
<li>
<a href="/kotlin/2022/08/27/kotlin-dsl-bad-feeling/">记一次Kotlin DSL的糟糕体验</a>
</li>
<li>
<a href="/gradle/2022/08/20/component-publication-plugin/">Gradle手札之组件发布迅疾如风</a>
</li>
<li>
<a href="/android/2022/08/10/android-lint/">Android代码检查之自定义Lint</a>
</li>
<li>
<a href="/gradle/2022/07/26/publish-library-to-github-repo/">发布组件到GitHub Packages</a>
</li>
<li>
<a href="/other/2022/07/16/change-article-comments/">关于博客更换评论系统的一场厮杀</a>
</li>
<li>
<a href="/gradle/2022/06/30/gradle-command-line-interface/">Gradle手札之命令行接口一览</a>
</li>
<li>
<a href="/kotlin/2022/05/05/kotlin-companion-object/">揭开 Kotlin 中的 companion object 的奥秘</a>
</li>
<li>
<a href="/pattern-design/2022/04/24/pattern-design-note/">行为型设计模式一览</a>
</li>
<li>
<a href="/skill/2022/04/17/sublime-auto-launch/">Sublime Text在Mac上开机自启动问题</a>
</li>
<li>
<a href="/gradle/2022/04/17/gradle-dep-management/">Gradle 组件依赖版本管理</a>
</li>
<li>
<a href="/android/2022/04/10/context-instance/">如何维护一个全局 Context</a>
</li>
<li>
<a href="/2022/03/19/sort-collections/">常见排序算法整理</a>
</li>
<li>
<a href="/computer/2022/03/13/memory-allocation/">操作系统地址空间与内存分配</a>
</li>
<li>
<a href="/computer/2022/03/12/os-boot-process/">操作系统的启动过程</a>
</li>
<li>
<a href="/computer/2022/03/12/know-what-os/">操作系统原理的基本概念和组成</a>
</li>
<li>
<a href="/skill/2022/02/27/git-emoji-commit/">Git emoji 提交规约</a>
</li>
<li>
<a href="/indie/2022/02/27/indie-developer-skills/">独立开发者需奉行的原则</a>
</li>
<li>
<a href="/skill/2021/11/12/git-default-editor/">Git 默认编辑器替换</a>
</li>
<li>
<a href="/skill/2021/11/12/article-publications-flow/">文章发布流程记录</a>
</li>
<li>
<a href="/android/2021/08/08/collection-copy/">关于 StateFlow 使用的一次车祸现场</a>
</li>
<li>
<a href="/tools/2021/08/07/markdown-drawing/">Markdown 流程图绘制的二三事儿</a>
</li>
<li>
<a href="/other/2021/08/05/about-avoid-useless-learning/">关于如何避免低效学习的所思</a>
</li>
<li>
<a href="/android/2020/08/12/MotionLayout-part2/">MotionLayout:打开动画新世界大门(partII)</a>
</li>
<li>
<a href="/android/2020/06/29/android-proguard/">一篇文章带你领略Android混淆的魅力</a>
</li>
<li>
<a href="/flutter/2020/04/19/flutter-tips/">Flutter 开发小结 | Tips</a>
</li>
<li>
<a href="/flutter/2020/02/18/flutter-timer/">Flutter 中“倒计时”的那些事儿</a>
</li>
<li>
<a href="/android/2019/10/19/motionlayout-part1/">MotionLayout:打开动画新世界大门(partI)</a>
</li>
<li>
<a href="/android/2019/08/11/android-tools-attribute/">是时候让 Android Tools 属性拯救你了</a>
</li>
<li>
<a href="/kotlin/2019/05/21/kotlin-when/">带你领略 Kotlin 中的 “when”魔法</a>
</li>
<li>
<a href="/android/architecture/2018/12/24/mvp-presenter/">Android中的 MVP:如何使 Presenter 层系统化?</a>
</li>
<li>
<a href="/kotlin/2018/12/22/kotlin-functions/">当 Kotlin 中的监听器包含多个方法时,如何让它 “巧夺天工”?</a>
</li>
<li>
<a href="/kotlin/2018/12/11/kotlin-anko/">Android Kotlin 快速开发之 Anko 魔法</a>
</li>
<li>
<a href="/2018/05/20/constraintlayout/">带你领略 ConstraintLayout 1.1 的新功能</a>
</li>
</ul>
You have full control over how (and where) you display your posts, and how you structure your site. You should read more about how templates work with Jekyll if you want to know more.
Note that the post variable only exists inside the for loop above. If you wish to access the currently-rendering page/posts’s variables (the variables of the post/page that has the for loop in it), use the page variable instead.
Tags and Categories
Jekyll has first class support for tags and categories in blog posts.
Tags
Tags for a post are defined in the post’s front matter using either the key tag for a single entry or tags for multiple entries.
Since Jekyll expects multiple items mapped to the key tags, it will automatically split a string entry if it contains whitespace. For example, while front matter tag: classic hollywood will be processed into a singular entity "classic hollywood", front matter tags: classic hollywood will be processed into an array of entries ["classic", "hollywood"].
Irrespective of the front matter key chosen, Jekyll stores the metadata mapped to the plural key which is exposed to Liquid templates.
All tags registered in the current site are exposed to Liquid templates via site.tags. Iterating over site.tags on a page will yield another array with two items, where the first item is the name of the tag and the second item being an array of posts with that tag.
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
<h3>ConstraintLayout</h3>
<ul>
<li><a href="/2018/05/20/constraintlayout/">带你领略 ConstraintLayout 1.1 的新功能</a></li>
</ul>
<h3>Kotlin</h3>
<ul>
<li><a href="/kotlin/2022/05/05/kotlin-companion-object/">揭开 Kotlin 中的 companion object 的奥秘</a></li>
<li><a href="/android/2021/08/08/collection-copy/">关于 StateFlow 使用的一次车祸现场</a></li>
<li><a href="/kotlin/2019/05/21/kotlin-when/">带你领略 Kotlin 中的 “when”魔法</a></li>
<li><a href="/kotlin/2018/12/22/kotlin-functions/">当 Kotlin 中的监听器包含多个方法时,如何让它 “巧夺天工”?</a></li>
<li><a href="/kotlin/2018/12/11/kotlin-anko/">Android Kotlin 快速开发之 Anko 魔法</a></li>
</ul>
<h3>Anko</h3>
<ul>
<li><a href="/kotlin/2018/12/11/kotlin-anko/">Android Kotlin 快速开发之 Anko 魔法</a></li>
</ul>
<h3>翻译</h3>
<ul>
<li><a href="/kotlin/2018/12/22/kotlin-functions/">当 Kotlin 中的监听器包含多个方法时,如何让它 “巧夺天工”?</a></li>
</ul>
<h3>MVP</h3>
<ul>
<li><a href="/android/architecture/2018/12/24/mvp-presenter/">Android中的 MVP:如何使 Presenter 层系统化?</a></li>
</ul>
<h3>Android architecture</h3>
<ul>
<li><a href="/pattern-design/2022/04/24/pattern-design-note/">行为型设计模式一览</a></li>
<li><a href="/android/architecture/2018/12/24/mvp-presenter/">Android中的 MVP:如何使 Presenter 层系统化?</a></li>
</ul>
<h3>Android</h3>
<ul>
<li><a href="/android/2026/04/12/android-ink-api-compose/">Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力</a></li>
<li><a href="/skill/2022/09/06/github-actions-auto-publish/">GitHub Actions实践之自动发布组件</a></li>
<li><a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a></li>
<li><a href="/gradle/2022/06/30/gradle-command-line-interface/">Gradle手札之命令行接口一览</a></li>
<li><a href="/gradle/2022/04/17/gradle-dep-management/">Gradle 组件依赖版本管理</a></li>
<li><a href="/android/2022/04/10/context-instance/">如何维护一个全局 Context</a></li>
<li><a href="/android/2020/06/29/android-proguard/">一篇文章带你领略Android混淆的魅力</a></li>
<li><a href="/android/2019/08/11/android-tools-attribute/">是时候让 Android Tools 属性拯救你了</a></li>
</ul>
<h3>tools</h3>
<ul>
<li><a href="/android/2019/08/11/android-tools-attribute/">是时候让 Android Tools 属性拯救你了</a></li>
</ul>
<h3>MotionLayout</h3>
<ul>
<li><a href="/android/2020/08/12/MotionLayout-part2/">MotionLayout:打开动画新世界大门(partII)</a></li>
<li><a href="/android/2019/10/19/motionlayout-part1/">MotionLayout:打开动画新世界大门(partI)</a></li>
</ul>
<h3>Android 动画</h3>
<ul>
<li><a href="/android/2020/08/12/MotionLayout-part2/">MotionLayout:打开动画新世界大门(partII)</a></li>
<li><a href="/android/2019/10/19/motionlayout-part1/">MotionLayout:打开动画新世界大门(partI)</a></li>
</ul>
<h3>Flutter</h3>
<ul>
<li><a href="/flutter/2020/04/19/flutter-tips/">Flutter 开发小结 | Tips</a></li>
<li><a href="/flutter/2020/02/18/flutter-timer/">Flutter 中“倒计时”的那些事儿</a></li>
</ul>
<h3>Proguard</h3>
<ul>
<li><a href="/android/2020/06/29/android-proguard/">一篇文章带你领略Android混淆的魅力</a></li>
</ul>
<h3>杂谈</h3>
<ul>
<li><a href="/skill/2021/11/12/article-publications-flow/">文章发布流程记录</a></li>
<li><a href="/other/2021/08/05/about-avoid-useless-learning/">关于如何避免低效学习的所思</a></li>
</ul>
<h3>学习方法</h3>
<ul>
<li><a href="/tools/2021/08/07/markdown-drawing/">Markdown 流程图绘制的二三事儿</a></li>
<li><a href="/other/2021/08/05/about-avoid-useless-learning/">关于如何避免低效学习的所思</a></li>
</ul>
<h3>工具</h3>
<ul>
<li><a href="/skill/2022/04/17/sublime-auto-launch/">Sublime Text在Mac上开机自启动问题</a></li>
<li><a href="/tools/2021/08/07/markdown-drawing/">Markdown 流程图绘制的二三事儿</a></li>
</ul>
<h3>图表绘制</h3>
<ul>
<li><a href="/tools/2021/08/07/markdown-drawing/">Markdown 流程图绘制的二三事儿</a></li>
</ul>
<h3>协程</h3>
<ul>
<li><a href="/android/2021/08/08/collection-copy/">关于 StateFlow 使用的一次车祸现场</a></li>
</ul>
<h3>集合</h3>
<ul>
<li><a href="/android/2021/08/08/collection-copy/">关于 StateFlow 使用的一次车祸现场</a></li>
</ul>
<h3>Java</h3>
<ul>
<li><a href="/android/2021/08/08/collection-copy/">关于 StateFlow 使用的一次车祸现场</a></li>
</ul>
<h3>文章发布</h3>
<ul>
<li><a href="/skill/2021/11/12/article-publications-flow/">文章发布流程记录</a></li>
</ul>
<h3>开发技巧</h3>
<ul>
<li><a href="/gradle/2022/04/17/gradle-dep-management/">Gradle 组件依赖版本管理</a></li>
<li><a href="/skill/2021/11/12/git-default-editor/">Git 默认编辑器替换</a></li>
</ul>
<h3>效率工具</h3>
<ul>
<li><a href="/skill/2023/04/16/code-shortcut/">效率编程之快捷键篇</a></li>
<li><a href="/skill/2021/11/12/git-default-editor/">Git 默认编辑器替换</a></li>
</ul>
<h3>Git</h3>
<ul>
<li><a href="/skill/2022/09/13/git-user-guide/">Git用户手札</a></li>
<li><a href="/skill/2022/02/27/git-emoji-commit/">Git emoji 提交规约</a></li>
<li><a href="/skill/2021/11/12/git-default-editor/">Git 默认编辑器替换</a></li>
</ul>
<h3>Indie</h3>
<ul>
<li><a href="/indie/2022/02/27/indie-developer-skills/">独立开发者需奉行的原则</a></li>
</ul>
<h3>开发规范</h3>
<ul>
<li><a href="/skill/2022/02/27/git-emoji-commit/">Git emoji 提交规约</a></li>
</ul>
<h3>操作系统</h3>
<ul>
<li><a href="/computer/2022/03/13/memory-allocation/">操作系统地址空间与内存分配</a></li>
<li><a href="/computer/2022/03/12/os-boot-process/">操作系统的启动过程</a></li>
<li><a href="/computer/2022/03/12/know-what-os/">操作系统原理的基本概念和组成</a></li>
</ul>
<h3>Operation system</h3>
<ul>
<li><a href="/computer/2022/03/13/memory-allocation/">操作系统地址空间与内存分配</a></li>
<li><a href="/computer/2022/03/12/os-boot-process/">操作系统的启动过程</a></li>
<li><a href="/computer/2022/03/12/know-what-os/">操作系统原理的基本概念和组成</a></li>
</ul>
<h3>计算机基础</h3>
<ul>
<li><a href="/computer/2022/03/13/memory-allocation/">操作系统地址空间与内存分配</a></li>
<li><a href="/computer/2022/03/12/os-boot-process/">操作系统的启动过程</a></li>
<li><a href="/computer/2022/03/12/know-what-os/">操作系统原理的基本概念和组成</a></li>
</ul>
<h3>数据结构</h3>
<ul>
<li><a href="/2023/03/19/min-number/">把数组排成最小的数</a></li>
<li><a href="/2022/03/19/sort-collections/">常见排序算法整理</a></li>
</ul>
<h3>排序</h3>
<ul>
<li><a href="/2023/03/19/min-number/">把数组排成最小的数</a></li>
<li><a href="/2022/03/19/sort-collections/">常见排序算法整理</a></li>
</ul>
<h3>Context</h3>
<ul>
<li><a href="/android/2022/04/10/context-instance/">如何维护一个全局 Context</a></li>
</ul>
<h3>Gradle</h3>
<ul>
<li><a href="/skill/2022/09/06/github-actions-auto-publish/">GitHub Actions实践之自动发布组件</a></li>
<li><a href="/kotlin/2022/08/27/kotlin-dsl-bad-feeling/">记一次Kotlin DSL的糟糕体验</a></li>
<li><a href="/android/2022/08/10/android-lint/">Android代码检查之自定义Lint</a></li>
<li><a href="/gradle/2022/06/30/gradle-command-line-interface/">Gradle手札之命令行接口一览</a></li>
<li><a href="/gradle/2022/04/17/gradle-dep-management/">Gradle 组件依赖版本管理</a></li>
</ul>
<h3>依赖管理</h3>
<ul>
<li><a href="/gradle/2022/04/17/gradle-dep-management/">Gradle 组件依赖版本管理</a></li>
</ul>
<h3>奇技淫巧</h3>
<ul>
<li><a href="/skill/2022/04/17/sublime-auto-launch/">Sublime Text在Mac上开机自启动问题</a></li>
</ul>
<h3>设计模式</h3>
<ul>
<li><a href="/pattern-design/2022/04/24/pattern-design-note/">行为型设计模式一览</a></li>
</ul>
<h3>评论系统</h3>
<ul>
<li><a href="/other/2022/07/16/change-article-comments/">关于博客更换评论系统的一场厮杀</a></li>
</ul>
<h3>Jekyll</h3>
<ul>
<li><a href="/skill/2026/05/24/blog-post-skill-implementation/">Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的</a></li>
<li><a href="/other/2022/07/16/change-article-comments/">关于博客更换评论系统的一场厮杀</a></li>
</ul>
<h3>Gitalk</h3>
<ul>
<li><a href="/other/2022/07/16/change-article-comments/">关于博客更换评论系统的一场厮杀</a></li>
</ul>
<h3>Giscus</h3>
<ul>
<li><a href="/other/2022/07/16/change-article-comments/">关于博客更换评论系统的一场厮杀</a></li>
</ul>
<h3>Github</h3>
<ul>
<li><a href="/gradle/2022/07/26/publish-library-to-github-repo/">发布组件到GitHub Packages</a></li>
</ul>
<h3>Github Packages</h3>
<ul>
<li><a href="/gradle/2022/07/26/publish-library-to-github-repo/">发布组件到GitHub Packages</a></li>
</ul>
<h3>Maven publish</h3>
<ul>
<li><a href="/gradle/2022/07/26/publish-library-to-github-repo/">发布组件到GitHub Packages</a></li>
</ul>
<h3>Lint</h3>
<ul>
<li><a href="/android/2022/08/10/android-lint/">Android代码检查之自定义Lint</a></li>
</ul>
<h3>静态代码检查</h3>
<ul>
<li><a href="/android/2022/08/10/android-lint/">Android代码检查之自定义Lint</a></li>
</ul>
<h3>Android Library</h3>
<ul>
<li><a href="/gradle/2022/08/20/component-publication-plugin/">Gradle手札之组件发布迅疾如风</a></li>
</ul>
<h3>Gradle Plugin</h3>
<ul>
<li><a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a></li>
<li><a href="/gradle/2022/08/20/component-publication-plugin/">Gradle手札之组件发布迅疾如风</a></li>
</ul>
<h3>Maven</h3>
<ul>
<li><a href="/gradle/2022/08/20/component-publication-plugin/">Gradle手札之组件发布迅疾如风</a></li>
</ul>
<h3>GitHub</h3>
<ul>
<li><a href="/gradle/2022/08/20/component-publication-plugin/">Gradle手札之组件发布迅疾如风</a></li>
</ul>
<h3>Kotlin DSL</h3>
<ul>
<li><a href="/kotlin/2022/08/27/kotlin-dsl-bad-feeling/">记一次Kotlin DSL的糟糕体验</a></li>
</ul>
<h3>Maven Publishing</h3>
<ul>
<li><a href="/skill/2022/09/06/github-actions-auto-publish/">GitHub Actions实践之自动发布组件</a></li>
<li><a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a></li>
</ul>
<h3>Maven Plugin Portal</h3>
<ul>
<li><a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a></li>
</ul>
<h3>Maven Central</h3>
<ul>
<li><a href="/skill/2022/09/06/github-actions-auto-publish/">GitHub Actions实践之自动发布组件</a></li>
<li><a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a></li>
</ul>
<h3>工作技巧</h3>
<ul>
<li><a href="/skill/2023/03/29/chatgpt-talk/">简单聊聊 ChatGPT</a></li>
<li><a href="/skill/2022/09/13/git-user-guide/">Git用户手札</a></li>
</ul>
<h3>版本控制</h3>
<ul>
<li><a href="/skill/2022/09/13/git-user-guide/">Git用户手札</a></li>
</ul>
<h3>Project Properties</h3>
<ul>
<li><a href="/gradle/2022/09/21/gradle-property/">Gradle手札之Properties配置</a></li>
</ul>
<h3>System Properties</h3>
<ul>
<li><a href="/gradle/2022/09/21/gradle-property/">Gradle手札之Properties配置</a></li>
</ul>
<h3>Build Environment</h3>
<ul>
<li><a href="/gradle/2022/09/21/gradle-property/">Gradle手札之Properties配置</a></li>
</ul>
<h3>算法</h3>
<ul>
<li><a href="/2023/03/19/min-number/">把数组排成最小的数</a></li>
</ul>
<h3>Linux</h3>
<ul>
<li><a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a></li>
</ul>
<h3>APM</h3>
<ul>
<li><a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a></li>
</ul>
<h3>性能分析</h3>
<ul>
<li><a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a></li>
</ul>
<h3>ANR</h3>
<ul>
<li><a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a></li>
</ul>
<h3>proc</h3>
<ul>
<li><a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a></li>
</ul>
<h3>ChatGPT</h3>
<ul>
<li><a href="/skill/2023/03/29/chatgpt-talk/">简单聊聊 ChatGPT</a></li>
</ul>
<h3>线程</h3>
<ul>
<li><a href="/android/2023/04/07/android-thread-creation/">Android系统中线程的创建过程</a></li>
</ul>
<h3>源码分析</h3>
<ul>
<li><a href="/2023/05/04/app-init-process/">Launcher进程启动过程剖析</a></li>
<li><a href="/2023/04/22/android-initialization/">Android系统启动流程剖析</a></li>
<li><a href="/android/2023/04/07/android-thread-creation/">Android系统中线程的创建过程</a></li>
</ul>
<h3>native</h3>
<ul>
<li><a href="/android/2023/04/07/android-thread-creation/">Android系统中线程的创建过程</a></li>
</ul>
<h3>Android studio</h3>
<ul>
<li><a href="/skill/2023/04/16/code-shortcut/">效率编程之快捷键篇</a></li>
</ul>
<h3>Coding</h3>
<ul>
<li><a href="/skill/2023/04/16/code-shortcut/">效率编程之快捷键篇</a></li>
</ul>
<h3>Mac</h3>
<ul>
<li><a href="/skill/2023/04/16/code-shortcut/">效率编程之快捷键篇</a></li>
</ul>
<h3>repo</h3>
<ul>
<li><a href="/2023/04/21/aosp-download/">AOSP在Mac上的编译实践(上)</a></li>
</ul>
<h3>framework</h3>
<ul>
<li><a href="/2023/04/21/aosp-download/">AOSP在Mac上的编译实践(上)</a></li>
</ul>
<h3>AOSP</h3>
<ul>
<li><a href="/2023/04/21/aosp-download/">AOSP在Mac上的编译实践(上)</a></li>
</ul>
<h3>APP启动</h3>
<ul>
<li><a href="/2023/04/22/android-initialization/">Android系统启动流程剖析</a></li>
</ul>
<h3>多线程</h3>
<ul>
<li><a href="/2023/04/29/java-producer-consumer-model/">回顾Java中经典的生产/消费者模型</a></li>
</ul>
<h3>BlockingQueue</h3>
<ul>
<li><a href="/2023/04/29/java-producer-consumer-model/">回顾Java中经典的生产/消费者模型</a></li>
</ul>
<h3>APP启动过程</h3>
<ul>
<li><a href="/2023/05/04/app-init-process/">Launcher进程启动过程剖析</a></li>
</ul>
<h3>Launcher</h3>
<ul>
<li><a href="/2023/05/04/app-init-process/">Launcher进程启动过程剖析</a></li>
</ul>
<h3>App Link</h3>
<ul>
<li><a href="/android/2023/09/16/deep-link/">Android上的Deep-Link技术调研</a></li>
</ul>
<h3>Deep Link</h3>
<ul>
<li><a href="/android/2023/09/16/deep-link/">Android上的Deep-Link技术调研</a></li>
</ul>
<h3>手写渲染</h3>
<ul>
<li><a href="/android/2026/04/12/android-ink-api-compose/">Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力</a></li>
</ul>
<h3>Ink API</h3>
<ul>
<li><a href="/android/2026/04/12/android-ink-api-compose/">Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力</a></li>
</ul>
<h3>Compose</h3>
<ul>
<li><a href="/android/2026/04/12/android-ink-api-compose/">Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力</a></li>
</ul>
<h3>自动化</h3>
<ul>
<li><a href="/skill/2026/06/03/juejin-skill/">把文章发布到掘金,做成一个可复用的 juejin-skill</a></li>
<li><a href="/skill/2026/05/24/blog-post-skill-implementation/">Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的</a></li>
</ul>
<h3>Claude Code</h3>
<ul>
<li><a href="/skill/2026/06/03/juejin-skill/">把文章发布到掘金,做成一个可复用的 juejin-skill</a></li>
<li><a href="/skill/2026/05/24/blog-post-skill-implementation/">Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的</a></li>
</ul>
<h3>Codex</h3>
<ul>
<li><a href="/skill/2026/06/03/juejin-skill/">把文章发布到掘金,做成一个可复用的 juejin-skill</a></li>
<li><a href="/skill/2026/05/24/blog-post-skill-implementation/">Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的</a></li>
</ul>
<h3>掘金</h3>
<ul>
<li><a href="/skill/2026/06/03/juejin-skill/">把文章发布到掘金,做成一个可复用的 juejin-skill</a></li>
</ul>
Categories
Categories of a post work similar to the tags above:
- They can be defined via the front matter using keys
categoryorcategories(that follow the same logic as for tags) - All categories registered in the site are exposed to Liquid templates via
site.categorieswhich can be iterated over (similar to the loop for tags above.)
The similarity between categories and tags however, ends there.
Unlike tags, categories for posts can also be defined by a post’s file path. Any directory above _post will be read-in as a category. For example, if a post is at path movies/horror/_posts/2019-05-21-bride-of-chucky.markdown, then movies and horror are automatically registered as categories for that post.
When the post also has front matter defining categories, they just get added to the existing list if not present already.
The hallmark difference between categories and tags is that categories of a post may be incorporated into the generated URL for the post, while tags cannot be.
Therefore, depending on whether front matter has category: classic hollywood, or categories: classic hollywood, the example post above would have the URL as either movies/horror/classic%20hollywood/2019/05/21/bride-of-chucky.html or movies/horror/classic/hollywood/2019/05/21/bride-of-chucky.html respectively.
Post excerpts
You can access a snippet of a posts’s content by using excerpt variable on a post. By default this is the first paragraph of content in the post, however it can be customized by setting a excerpt_separator variable in front matter or _config.yml.
1
2
3
4
5
6
7
8
9
---
excerpt_separator: <!--more-->
---
Excerpt with multiple paragraphs
Here's another paragraph in the excerpt.
<!--more-->
Out-of-excerpt
Here’s an example of outputting a list of blog posts with an excerpt:
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
<ul>
<li>
<a href="/skill/2026/06/03/juejin-skill/">把文章发布到掘金,做成一个可复用的 juejin-skill</a>
<p>很多工具的起点都不宏大,往往只是为了去掉一段反复出现、但没有创造性的劳动。</p>
<p><a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 也是这样长出来的。我平时习惯在本地写 Markdown,配图和草稿都按项目目录管理。文章完成后,再回到掘金编辑器里补分类、挑标签、上传图片、创建草稿,偶尔做一次问题不大,但只要写作频率稍微稳定一点,这条链路就会迅速变成额外负担。更麻烦的是,文章一旦修改,还得回到网页端重新同步一遍,本地明明已经有完整原稿,却还要重复操作。</p>
</li>
<li>
<a href="/skill/2026/05/24/blog-post-skill-implementation/">Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的</a>
<p>为个人博客补一套自动化发文能力,看似只是“少敲几次 front matter”,真正落地时却会迅速演变成一个工程问题:如何适配现有博客结构、如何控制发布风险、如何让脚本保持确定性、又如何让不同 AI 编程环境复用同一套能力。本文就围绕这几个问题,复盘 <code class="language-plaintext highlighter-rouge">blog-post skill</code> 的完整实现过程。</p>
</li>
<li>
<a href="/android/2026/04/12/android-ink-api-compose/">Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力</a>
<p>在 Android 手写、批注与白板类应用中,决定体验上限的通常不是单一的绘制 API,而是整条输入与渲染链路的协同效率。过去,开发者往往需要自行处理 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 采样、预测点插值、压感映射、误触回滚以及低延迟渲染。近两年,Google 逐步将这套能力沉淀为可复用的官方组件:底层由 <code class="language-plaintext highlighter-rouge">androidx.graphics:graphics-core</code> 提供低延迟渲染基础设施,上层由 <code class="language-plaintext highlighter-rouge">androidx.ink:ink-*</code> 提供笔迹建模、笔刷系统与几何操作能力;同时,Jetpack Compose 文档也补充了高级触控笔输入的工程化处理方式。</p>
<p>本文基于截至 <code class="language-plaintext highlighter-rouge">2026-05-24</code> 的官方资料,对 Android 手写技术栈的最新状态进行梳理,并讨论前缓冲、Ink API 与 Compose 高级触控笔能力在实际项目中的组合方式。</p>
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr-fQK660FgiUsJF64wNgAAxkWgA-Ul68stuzOKasoAIpQddUhACLL1nEmwSu7oRJriJh_B1vlKftd2VfQ2kDAnDNQNhQ8SGd0y6-U5Zatg9xAKe0EFW7Q13YtB1owvG2vMbC72XsBzsJOhBrGegpCnn4PEEfyFksSkSdAqDviGhDbtm_SyH89Uy4QmQo/s1600/v2-header-Android-type-safe-navigation-for-compose.png" alt="Ink API 官方封面图" /></p>
<p>图源:Google Android Developers Blog,Ink API 官方文章</p>
<h2 id="1-官方技术栈的最新状态">1. 官方技术栈的最新状态</h2>
<p>截至 <code class="language-plaintext highlighter-rouge">2026-05-24</code>,与 Android 手写能力直接相关的官方组件处于以下状态:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">androidx.graphics:graphics-core:1.0.0</code> 稳定版发布于 <code class="language-plaintext highlighter-rouge">2024-05-29</code></li>
<li><code class="language-plaintext highlighter-rouge">androidx.graphics:graphics-core:1.0.4</code> 发布于 <code class="language-plaintext highlighter-rouge">2025-12-03</code></li>
<li><code class="language-plaintext highlighter-rouge">androidx.ink:ink-*:1.0.0</code> 稳定版发布于 <code class="language-plaintext highlighter-rouge">2025-12-17</code></li>
<li><code class="language-plaintext highlighter-rouge">androidx.ink:ink-*:1.1.0-alpha03</code> 发布于 <code class="language-plaintext highlighter-rouge">2026-05-19</code></li>
<li>Compose 文档《高级触控笔功能》最后更新时间为 <code class="language-plaintext highlighter-rouge">2026-05-23</code></li>
</ul>
<p>这组时间线说明两点。</p>
<p>其一,低延迟渲染不再只是依赖内部实现细节的试验性方案,而已经演化为稳定的 AndroidX 能力。其二,笔迹生成、笔刷配置、输入序列化、几何运算与在途笔迹渲染,已经形成了较完整的上层抽象。这意味着当前 Android 手写应用的技术重点,正在从“如何从零实现一套书写系统”转移到“如何基于官方组件搭建更稳定的工程架构”。</p>
<h2 id="2-前缓冲的技术意义">2. 前缓冲的技术意义</h2>
<p>手写场景与普通界面渲染的主要差异在于延迟容忍度极低。列表、卡片或页面切换通常可以接受几十毫秒级的视觉延迟,但在书写过程中,用户会直接感知笔尖与墨迹之间的空间偏移。所谓“跟手性”,本质上是输入到显示的端到端延迟是否足够小。</p>
<p>前缓冲(front-buffered rendering)的价值正在于此。与只依赖双缓冲或多缓冲的完整帧提交流程相比,前缓冲允许当前正在变化的局部内容更早进入显示链路,从而缩短笔迹首次可见的时间。这种优化并不直接提高最终画质,但可以显著改善书写阶段的主观响应速度。</p>
<p>Google 在 <code class="language-plaintext highlighter-rouge">graphics-core</code> 中围绕低延迟渲染提供了几类关键能力:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">GLFrontBufferedRenderer</code></li>
<li><code class="language-plaintext highlighter-rouge">CanvasFrontBufferedRenderer</code></li>
<li><code class="language-plaintext highlighter-rouge">LowLatencyCanvasView</code></li>
<li><code class="language-plaintext highlighter-rouge">CanvasBufferedRenderer</code></li>
</ul>
<p>其中,<code class="language-plaintext highlighter-rouge">CanvasFrontBufferedRenderer</code> 与 <code class="language-plaintext highlighter-rouge">LowLatencyCanvasView</code> 使基于 <code class="language-plaintext highlighter-rouge">Canvas</code> 的低延迟渲染路径更加直接,适合触控笔输入类场景;<code class="language-plaintext highlighter-rouge">GLFrontBufferedRenderer</code> 则更适合已有 OpenGL 渲染栈的应用。</p>
<p>从 release notes 可以看到,这套能力的成熟过程高度依赖设备适配与系统兼容修复:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">graphics-core:1.0.2</code> 修复了部分 Android 14+ 设备在不支持 front buffer usage flag 时的闪烁问题</li>
<li><code class="language-plaintext highlighter-rouge">graphics-core:1.0.3</code> 修复了部分 <code class="language-plaintext highlighter-rouge">API < 33</code> 设备上的全屏闪烁</li>
<li><code class="language-plaintext highlighter-rouge">graphics-core:1.0.4</code> 继续改进了特定设备上的兼容性与性能</li>
</ul>
<p>这类修复说明,前缓冲的价值已经得到验证,但它依然是接近平台层的能力。对于大多数业务团队而言,直接从 <code class="language-plaintext highlighter-rouge">graphics-core</code> 最底层开始搭建书写系统,意味着需要自己承担更多的设备差异、资源释放与回退逻辑。因此,更常见也更稳妥的做法,是优先站在 Ink API 这一层向下借力。</p>
<h2 id="3-ink-api-的定位从笔迹绘制到书写系统">3. Ink API 的定位:从笔迹绘制到书写系统</h2>
<p>如果只将 Ink API 理解为“官方画笔库”,会低估它的工程价值。更准确的说法是:Ink API 试图为 Android 提供一套结构化的手写系统抽象。</p>
<p>官方将其拆分为多个模块:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Authoring</code>:处理输入接收、在途笔迹管理与交互状态</li>
<li><code class="language-plaintext highlighter-rouge">Rendering</code>:负责实时笔迹与完成态笔迹的绘制</li>
<li><code class="language-plaintext highlighter-rouge">Strokes</code>:定义笔迹数据模型</li>
<li><code class="language-plaintext highlighter-rouge">Brush</code>:定义笔刷的外观、材质与行为</li>
<li><code class="language-plaintext highlighter-rouge">Geometry</code>:提供选区、相交、覆盖等几何能力</li>
</ul>
<p>这一分层的关键价值,在于将“实时反馈”与“最终结果”从概念上分离。</p>
<p>在手写系统中,用户落笔后的第一诉求是尽快看到墨迹;而笔迹结束之后,系统又必须支持持久化、撤销、回放、重绘、选中与同步。这两类需求关注点不同:前者以延迟最小化为目标,后者以稳定性和可操作性为目标。Google 因而在 Ink API 中明确区分:</p>
<ul>
<li>使用 <code class="language-plaintext highlighter-rouge">InProgressStrokesView</code> 处理在途笔迹</li>
<li>使用 <code class="language-plaintext highlighter-rouge">CanvasStrokeRenderer</code> 或 <code class="language-plaintext highlighter-rouge">ViewStrokeRenderer</code> 处理完成态笔迹</li>
</ul>
<p>这种分层并不是实现细节,而是一种值得直接采纳的架构原则:在书写进行时优先追求显示时延,在笔迹结束后再转入质量与结构化表示优先的渲染路径。</p>
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiE_VC7aiMgrPRq-rBG-c1LdauGYqi2U8alx80PoxZI4G9kNoURFNHh6RM21KO5SQL8RGRNDxIn_KuYjqA2sFXy_KWjorI1w16wN8HWaXEus2JlEjRtTWfInJGyJb9LpexvxzneqsVL2_Uw214MHlUhi4R7nw1O6QsiJyMEZ4N3hQ-nGQw5PzsNBk6kQe0/w640-h468/image5.gif" alt="Ink API 低延迟书写演示 1" /></p>
<p>图源:Google Android Developers Blog,Samsung Tab S8 上基于 Ink API 的低延迟书写演示</p>
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNLDZmNs7BwcQsuCn4orht8-OGhsaW4xLs-SEiGOsmu5UdPhBfs787uUFsX9zc25Fd2OEjootN6_U_FhifgLW_3855RdPWlFBZ2kKMLX0pV-ZaR5HrzZt2EevpaQghcnJbDhScQYKke3o3WmBhMvvoyQ9Dt1bqbj6N9vsevjdKa47YzhJqBv_vQQt06u8/s1600/image6.gif" alt="Ink API 低延迟书写演示 2" /></p>
<p>图源:Google Android Developers Blog,Ink API 官方演示动图</p>
<h2 id="4-笔刷能力的近期演进">4. 笔刷能力的近期演进</h2>
<p>截至 <code class="language-plaintext highlighter-rouge">2026-05-24</code>,Ink API 在笔刷能力上的演进,主要集中在三个方向。</p>
<h3 id="41-从预置笔刷走向可编程笔刷">4.1 从预置笔刷走向可编程笔刷</h3>
<p><code class="language-plaintext highlighter-rouge">1.0.0-alpha04</code> 于 <code class="language-plaintext highlighter-rouge">2025-04-09</code> 引入实验性的自定义 <code class="language-plaintext highlighter-rouge">BrushFamily</code> 能力,官方明确提到可支持类似 <code class="language-plaintext highlighter-rouge">Pencil</code>、<code class="language-plaintext highlighter-rouge">Laser Pointer</code> 的新笔刷。到 <code class="language-plaintext highlighter-rouge">1.1.0-alpha03</code>,程序化自定义笔刷 API 进一步公开为 public。</p>
<p>这一变化的意义在于,笔刷系统开始从“参数调节型配置”转向“可编排的笔刷模型”。对于笔记、教育、批注、设计和创作类产品,这意味着笔刷不再只是颜色与线宽的组合,而可以逐渐成为产品差异化的一部分。</p>
<h3 id="42-笔刷配置的可序列化与版本兼容">4.2 笔刷配置的可序列化与版本兼容</h3>
<p><code class="language-plaintext highlighter-rouge">1.1.0-alpha02</code> 对 <code class="language-plaintext highlighter-rouge">BrushFamily.decode</code> 的兼容性控制进行了增强,同时将 <code class="language-plaintext highlighter-rouge">BrushFamily</code> 的序列化 API 从 experimental 状态提升出来。</p>
<p>这类更新对于线上产品尤其重要。笔刷系统一旦参与文档存储、跨端同步或长期版本演进,就必须回答三个问题:</p>
<ul>
<li>历史数据是否可以被新版本正确解析</li>
<li>多设备间是否能得到一致的笔刷表现</li>
<li>笔刷协议是否支持平滑迁移</li>
</ul>
<p>序列化与兼容性 API 的完善,意味着 Ink API 正在从“本地渲染工具”向“可持久化的文档能力”演进。</p>
<h3 id="43-输入建模与数值稳定性">4.3 输入建模与数值稳定性</h3>
<p>从 <code class="language-plaintext highlighter-rouge">1.0.0-alpha07</code> 及后续 beta、stable 版本的变更记录来看,Google 持续在优化以下内容:</p>
<ul>
<li>stroke input smoothing</li>
<li><code class="language-plaintext highlighter-rouge">StrokeInputBatch</code> 序列化</li>
<li>浮点精度</li>
<li>某些场景下的渲染伪影</li>
</ul>
<p>这些更新虽然不如新增笔刷直观,却直接决定笔迹系统的稳定性。手写体验中的“抖动感”“回放变形”“跨设备表现不一致”,往往都与输入建模和数值处理有关,而不仅仅是渲染器本身。</p>
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsX5BcJ2qvBaVRKJXf2T-1hxCl9E_VuSZqUDgJnAMhmUGEiOhaC8XreGD9y_I9nRi-Ibz4IQfrWA0tVHmuCzVRmg3oMiJBpsqr-S5m3UHnMKogPfFf8M6Dk9MJv5qxhOE8H5JGypK0ZGpOyRvOhrnNR-z6H-8t8epx76z1jBI2oDlNP211Z1WSP9glBC0/s1600/v2-card-Android-type-safe-navigation-for-compose.png" alt="Ink API 官方卡片图" /></p>
<p>图源:Google Android Developers Blog,Ink API 官方文章配图</p>
<h2 id="5-compose-高级触控笔能力的工程意义">5. Compose 高级触控笔能力的工程意义</h2>
<p>Google 在 Compose 文档《高级触控笔功能》中给出的一个重要信号是:即便应用的 UI 主体采用 Compose,实现高质量手写能力时仍然需要回到 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 级别获取完整输入信息。官方示例使用 <code class="language-plaintext highlighter-rouge">pointerInteropFilter</code> 作为桥接入口:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="nd">@Composable</span>
<span class="nd">@OptIn</span><span class="p">(</span><span class="nc">ExperimentalComposeUiApi</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="k">fun</span> <span class="nf">DrawArea</span><span class="p">(</span><span class="n">modifier</span><span class="p">:</span> <span class="nc">Modifier</span> <span class="p">=</span> <span class="nc">Modifier</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">Canvas</span><span class="p">(</span>
<span class="n">modifier</span> <span class="p">=</span> <span class="n">modifier</span>
<span class="p">.</span><span class="nf">clipToBounds</span><span class="p">()</span>
<span class="p">.</span><span class="nf">pointerInteropFilter</span> <span class="p">{</span>
<span class="n">viewModel</span><span class="p">.</span><span class="nf">processMotionEvent</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">)</span> <span class="p">{</span>
<span class="c1">// Drawing code here.</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>这一写法的意义不在于“Compose 也能处理触控笔”,而在于它明确了职责划分:Compose 适合承担界面组织与交互壳层,而笔输入细节仍然应由 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 驱动的专门逻辑处理。对多数项目而言,这意味着更合理的结构通常是“Compose UI + MotionEvent 互操作 + 独立的笔迹层”。</p>
<h2 id="6-高级触控笔特性与笔刷设计">6. 高级触控笔特性与笔刷设计</h2>
<p>Compose 文档中最值得纳入手写系统设计的,不是单一 API,而是一组输入能力及其工程含义。</p>
<h3 id="61-工具类型识别">6.1 工具类型识别</h3>
<p>官方建议通过 <code class="language-plaintext highlighter-rouge">getToolType(pointerIndex)</code> 区分输入工具,例如:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">TOOL_TYPE_STYLUS</code></li>
<li><code class="language-plaintext highlighter-rouge">TOOL_TYPE_ERASER</code></li>
</ul>
<p>这使系统能够在同一交互链路中区分书写与擦除,而无需依赖额外的模式切换。对于笔记和批注类应用,这种“翻转触控笔即可擦除”的行为接近原生书写习惯,应优先纳入设计。</p>
<h3 id="62-压力方向倾斜与距离">6.2 压力、方向、倾斜与距离</h3>
<p>高级触控笔输入的核心价值,在于提供比二维坐标更丰富的轴数据。官方文档重点涉及:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">AXIS_PRESSURE</code></li>
<li><code class="language-plaintext highlighter-rouge">AXIS_ORIENTATION</code></li>
<li><code class="language-plaintext highlighter-rouge">AXIS_TILT</code></li>
<li><code class="language-plaintext highlighter-rouge">AXIS_DISTANCE</code></li>
</ul>
<p>这些轴数据应直接参与笔刷建模,而不应仅用于简单线宽映射。较合理的使用方式包括:</p>
<ul>
<li>使用 <code class="language-plaintext highlighter-rouge">AXIS_PRESSURE</code> 调整粗细、透明度或墨量感</li>
<li>使用 <code class="language-plaintext highlighter-rouge">AXIS_ORIENTATION</code> 构建扁头笔、马克笔或书法笔的方向性</li>
<li>使用 <code class="language-plaintext highlighter-rouge">AXIS_TILT</code> 实现铅笔侧锋、阴影与扫刷效果</li>
<li>使用 <code class="language-plaintext highlighter-rouge">AXIS_DISTANCE</code> 支持悬停预览或落笔前的目标提示</li>
</ul>
<p>官方同时提醒,压力值虽然通常位于 <code class="language-plaintext highlighter-rouge">0..1</code> 区间,但部分设备可能返回更高数值。因此,在进入笔刷计算前,应先做归一化与边界处理,避免不同硬件上的表现漂移。</p>
<h3 id="63-悬停反馈">6.3 悬停反馈</h3>
<p>悬停(hover)在手机场景中未必显著,但在平板、折叠屏与 ChromeOS 设备上具有明确价值。其作用不在于“多一个特效”,而在于提升输入前的可预期性。例如:</p>
<ul>
<li>显示当前笔刷的实际落笔范围</li>
<li>高亮即将操作的对象或区域</li>
<li>在复杂工具栏或文档页面中给出更细粒度的定位提示</li>
</ul>
<p>在 Compose 中,文档建议配合 <code class="language-plaintext highlighter-rouge">hoverable</code> 与 <code class="language-plaintext highlighter-rouge">indication</code> 使用。这类反馈虽然不改变笔迹本身,却能够显著提高交互确定性。</p>
<h2 id="7-误触取消与输入回滚">7. 误触、取消与输入回滚</h2>
<p>高质量手写系统必须将误触处理视为数据一致性问题,而不仅仅是事件过滤问题。官方文档明确指出,手掌误触相关处理涉及:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ACTION_CANCEL</code></li>
<li><code class="language-plaintext highlighter-rouge">FLAG_CANCELED</code></li>
</ul>
<p>其中,<code class="language-plaintext highlighter-rouge">ACTION_CANCEL</code> 可能在导航手势或防手掌误触触发时出现;<code class="language-plaintext highlighter-rouge">FLAG_CANCELED</code> 自 Android 13(API 33)起可用于标识一次抬起事件很可能源于非预期接触。</p>
<p>正确的处理方式不是简单忽略后续输入,而是支持对已有笔迹进行回滚。具体而言,系统应保留结构化输入历史,在检测到取消语义后定位对应 pointer 所生成的笔迹,并将其从当前状态中移除,再触发重新渲染。只有采用结构化 stroke 数据而非一次性 Path 累积,误触撤销才具备可实现性。</p>
<p>这一点也解释了为什么 Ink API 对 <code class="language-plaintext highlighter-rouge">StrokeInputBatch</code>、几何模型和在途/完成态分层如此重视:它们并非仅服务于渲染,而是服务于后续的状态修正与编辑操作。</p>
<h2 id="8-动作预测与低延迟输入分发">8. 动作预测与低延迟输入分发</h2>
<p>Compose 文档推荐使用 <code class="language-plaintext highlighter-rouge">androidx.input:input-motionprediction:1.0.0-beta01</code> 中的 <code class="language-plaintext highlighter-rouge">MotionEventPredictor</code> 进行动作预测。其基本思路是:</p>
<ol>
<li>对真实 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 调用 <code class="language-plaintext highlighter-rouge">record()</code></li>
<li>在 <code class="language-plaintext highlighter-rouge">ACTION_MOVE</code> 阶段调用 <code class="language-plaintext highlighter-rouge">predict()</code></li>
<li>将预测点仅作为临时可视化结果参与绘制</li>
</ol>
<p>官方同时明确指出,预测点不能作为最终渲染与持久化数据使用。原因在于预测本质上是估算值,随着真实事件到来,旧预测必须被替换。也就是说,预测机制适合改善主观跟手性,但不应污染最终的笔迹数据模型。</p>
<p>与动作预测配套的另一项能力,是 <code class="language-plaintext highlighter-rouge">requestUnbufferedDispatch()</code>。它的作用不是直接提高渲染性能,而是缩短输入在系统队列中被批量缓存的时间。对于手写系统而言,端到端延迟并不只由绘制决定;如果输入事件本身到达应用过晚,再快的前缓冲路径也只能更快地显示“已经过时的输入”。因此,更完整的低延迟链路应同时包含:</p>
<ul>
<li>低延迟输入分发</li>
<li>在途笔迹的快速显示</li>
<li>完成态笔迹的稳定提交</li>
</ul>
<h2 id="9-推荐的工程组合方式">9. 推荐的工程组合方式</h2>
<p>综合 <code class="language-plaintext highlighter-rouge">graphics-core</code>、Ink API 与 Compose 文档中的能力,可以得到一套较稳健的工程分层。</p>
<h3 id="91-界面层">9.1 界面层</h3>
<p>使用 Compose 构建页面结构、工具栏、颜色与笔刷选择器、文档容器以及悬停反馈。这一层的目标是组织交互,而不是承担全部输入解析逻辑。</p>
<h3 id="92-输入层">9.2 输入层</h3>
<p>通过 <code class="language-plaintext highlighter-rouge">pointerInteropFilter</code> 接入 <code class="language-plaintext highlighter-rouge">MotionEvent</code>,完成以下工作:</p>
<ul>
<li>区分 <code class="language-plaintext highlighter-rouge">TOOL_TYPE_STYLUS</code> 与 <code class="language-plaintext highlighter-rouge">TOOL_TYPE_ERASER</code></li>
<li>读取压力、方向、倾斜、距离等轴数据</li>
<li>处理 <code class="language-plaintext highlighter-rouge">ACTION_DOWN / MOVE / UP / CANCEL</code></li>
<li>根据需要接入 <code class="language-plaintext highlighter-rouge">MotionEventPredictor</code></li>
<li>在落笔阶段调用 <code class="language-plaintext highlighter-rouge">requestUnbufferedDispatch()</code></li>
</ul>
<h3 id="93-笔迹层">9.3 笔迹层</h3>
<p>使用 Ink API 承接实时与完成态笔迹:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">InProgressStrokesView</code> 用于在途笔迹显示</li>
<li><code class="language-plaintext highlighter-rouge">CanvasStrokeRenderer</code> 或 <code class="language-plaintext highlighter-rouge">ViewStrokeRenderer</code> 用于完成态绘制</li>
<li><code class="language-plaintext highlighter-rouge">BrushFamily</code> 用于笔刷建模</li>
<li><code class="language-plaintext highlighter-rouge">StrokeInputBatch</code> 用于输入数据存储、回放与同步</li>
</ul>
<h3 id="94-渲染优化层">9.4 渲染优化层</h3>
<p>仅在确有必要时继续下沉到 <code class="language-plaintext highlighter-rouge">graphics-core</code> 层,针对设备性能、显示时延或现有渲染栈进行深度优化,例如:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">CanvasFrontBufferedRenderer</code></li>
<li><code class="language-plaintext highlighter-rouge">LowLatencyCanvasView</code></li>
<li><code class="language-plaintext highlighter-rouge">GLFrontBufferedRenderer</code></li>
</ul>
<p>这种分层的优点在于,既能充分利用官方已验证的上层抽象,又保留了继续向底层优化的空间。</p>
<h2 id="10-结论">10. 结论</h2>
<p>Android 手写技术栈当前的变化,不在于单一新 API 的出现,而在于官方能力开始形成清晰的层次结构:<code class="language-plaintext highlighter-rouge">graphics-core</code> 负责低延迟渲染基础设施,Ink API 负责笔迹与笔刷语义,Compose 高级触控笔文档则补足了原始输入处理与工程实践。</p>
<p>从实现角度看,前缓冲解决的是“墨迹何时被尽快看到”,Ink API 解决的是“笔迹如何被建模、绘制、存储与编辑”,而高级触控笔能力解决的是“输入信号如何被完整、准确且可回滚地纳入系统”。只有将这三层能力组合起来,Android 手写应用才能同时获得可接受的时延、稳定的数据模型以及接近原生纸笔的交互质量。</p>
<p>对于当前准备建设书写、批注或圈选能力的团队而言,这套官方技术栈已经具备较高的采用价值。真正需要关注的重点,已经不是是否“应该上官方方案”,而是如何根据业务场景,在实时显示、笔刷表现、状态管理与设备兼容之间完成合理取舍。</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://developer.android.com/jetpack/androidx/releases/graphics">AndroidX Graphics Release Notes</a></li>
<li><a href="https://developer.android.com/jetpack/androidx/releases/ink">AndroidX Ink Release Notes</a></li>
<li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/about-ink-api">About Ink API</a></li>
<li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/low-latency-graphics">Low latency graphics</a></li>
<li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/ink-api-brush-apis-views">Ink API brush APIs for Views</a></li>
<li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/ink-api-draw-stroke">Draw a stroke with Ink API</a></li>
<li><a href="https://developer.android.com/develop/ui/compose/touch-input/stylus-input/advanced-stylus-features?hl=zh-cn">高级触控笔功能(Jetpack Compose)</a></li>
<li><a href="https://android-developers.googleblog.com/2024/10/introducing-ink-api-jetpack-library.html">Introducing Ink API, a new Jetpack library for stylus apps</a></li>
</ul>
</li>
<li>
<a href="/android/2023/09/16/deep-link/">Android上的Deep-Link技术调研</a>
<blockquote>
<p><em>本文是两年前输出的文章,可能与现在的成熟方案存在部分细节上“代沟”,请诸位选择性阅读,适当参考即可。</em></p>
</blockquote>
</li>
<li>
<a href="/2023/05/04/app-init-process/">Launcher进程启动过程剖析</a>
<p>上一篇<a href="https://dorck.cn/2023/04/22/android-initialization/">文章</a>我们对 Android 系统启动过程做了一定了解,本文将继续分析 Launcher 进程的启动流程。</p>
</li>
<li>
<a href="/2023/04/29/java-producer-consumer-model/">回顾Java中经典的生产/消费者模型</a>
<h3 id="概述">概述</h3>
</li>
<li>
<a href="/2023/04/22/android-initialization/">Android系统启动流程剖析</a>
<p>Android 手机从长按开机到应用的启动阶段都经历了些什么呢?Android 启动过程涉及一系列操作,首先是启动 ROM,接着是引导加载程序、内核启动、init、Zygote 和 SystemServer 创建等过程。整个过程涉及到虚拟机的启动、Binder 线程池的创建以及各项系统服务启动等过程。熟悉 APP 启动过程还可以帮助我们打破性能优化瓶颈,助力于启动性能的提升。下面是启动过程涉及到的关键进程和服务:</p>
</li>
<li>
<a href="/2023/04/21/aosp-download/">AOSP在Mac上的编译实践(上)</a>
<p>AOSP,即 Android Open Source Project,对于 Android 开发者来说再熟悉不过的项目,本文将着重介绍如何在 MacOS Monterey 环境上下载完整的 AOSP 源码。</p>
</li>
<li>
<a href="/skill/2023/04/16/code-shortcut/">效率编程之快捷键篇</a>
<p>这是一个关于如何<strong>效率编程</strong>的系列专题,未来将会涉及诸如工具、脚本、插件、开源框架等各方面,只要能够帮助我们提升开发效率。</p>
</li>
<li>
<a href="/android/2023/04/07/android-thread-creation/">Android系统中线程的创建过程</a>
<p>我们都知道,Android 中线程创建过程需要追溯到 Native 层面,最终是委托给一个 Linux 标准线程 pthread 来执行的,所以 Android 中线程状态本质上是 Native 线程的一种映射。Android 中运行的线程可以分为两种:一种是 attach 到虚拟机的,即虚拟机线程;另一种是没有 attach 到虚拟机的。今天我们就分别从源码层面来看看 Android 系统中 Java 和 Native 层线程的创建过程。</p>
</li>
<li>
<a href="/skill/2023/03/29/chatgpt-talk/">简单聊聊 ChatGPT</a>
<p>关于国内如何注册和使用 ChatGPT 可以参考这篇文章:<a href="https://chatgpt-plus.github.io/chatgpt/">国内注册一个属于自己的免费ChatGPT账号</a>。本文不是扫盲贴,只是通过这两天对 ChatGPT 的使用体验来随便聊聊。</p>
</li>
<li>
<a href="/android/2023/03/27/about-prof-dir/">Android系统中关于/proc目录的点滴</a>
<p>Android 系统有着这么一个神奇的“文件“目录存放着 CPU 及设备所运行进程的相关数据,这对于我们从事 APM 应用性能监控有着莫大的帮助。当然这种监控应用及手机系统信息的方式并非无中生有,早在 Android Framework 中就已经涉及它的踪迹,比如 ANR 发生时,系统会去 dump 及上报打印相关 CPU、进程、线程等信息到 log 中。而这些信息是借助 <a href="https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r36/core/java/com/android/internal/os/ProcessCpuTracker.java">ProcessCpuTracker</a> 这个类来实现获取的,该类内部正是通过读取系统 /proc/ 目录的相关“文件”来提取和转化的。本文的重点当然不是去分析 ANR 如何产生,主要来看下 <strong>proc/</strong> 的庐山真面目。</p>
</li>
<li>
<a href="/2023/03/19/min-number/">把数组排成最小的数</a>
<p>本文是对于 <a href="https://dorck.cn/2022/03/19/sort-collections/">常用排序算法合集</a> 的扩展应用。</p>
</li>
<li>
<a href="/gradle/2022/09/21/gradle-property/">Gradle手札之Properties配置</a>
<p>Gradle 很多构建属性可以通过 Properties 来设置。<strong>Properties</strong> 文件格式可由 <code class="language-plaintext highlighter-rouge">java.util.Properties</code> 解析,其中包含若干键值对,类似 <code class="language-plaintext highlighter-rouge">Map<String,String></code> 格式数据来存储。</p>
</li>
<li>
<a href="/skill/2022/09/13/git-user-guide/">Git用户手札</a>
<h2 id="git-的由来">Git 的由来</h2>
</li>
<li>
<a href="/skill/2022/09/06/github-actions-auto-publish/">GitHub Actions实践之自动发布组件</a>
<p>在<a href="https://dorck.cn/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a>一文中简单介绍了如何发布一款 Gradle 插件到 Maven Central。虽说最终发布只需要本地执行 <code class="language-plaintext highlighter-rouge">./gradlew publishPlugins</code> 命令即可快速发布完成,但每次发布都需要区分本地和远程仓库的环境差异,然后手动配置。举个最简单的例子:我不希望本地敏感信息上传到远程仓库中,所以,每次执行完发布操作还需要手动擦除敏感信息,还是比较麻烦的。另外,有没有一种方式可以完全解放双手,甚至不需要执行上述命令然后等待其漫长地发布过程呢?自然是存在的,就是本文要介绍的 GitHub Actions。</p>
</li>
<li>
<a href="/gradle/2022/08/28/how-to-publish-gradle-plugin/">关于发布Gradle插件到Maven开放仓库的碎碎念</a>
<p>早几年前发布过几个开源库到 Jcenter 上,有一段时间没关注 GitHub 也就逐渐淡忘了。最近几天刚好想起此事,也就打算迁移一下历史组件,顺便将前阵子搞的一个 Gradle 插件也发布一下,于是便有了这篇文章。以下仅记录发布涉及的主要步骤以及遇到的一些坑,前车之鉴,后车之师而已矣。</p>
</li>
<li>
<a href="/kotlin/2022/08/27/kotlin-dsl-bad-feeling/">记一次Kotlin DSL的糟糕体验</a>
<p>昨天在用 Kotlin 写 Gradle 插件的时候遇到个奇怪的问题,折磨良久,最后才发现是 Kotlin Dsl 的坑。我们先来看下这段代码:</p>
</li>
<li>
<a href="/gradle/2022/08/20/component-publication-plugin/">Gradle手札之组件发布迅疾如风</a>
<p>大浪淘沙,随着 jcenter 的落幕,组件发布逐渐转向了 maven central 仓库。各种平台层出不穷,不论组件存储在何处,作为开发更加关注的是组件发布的效率,这也正是本文接下来想要阐述的主题。</p>
</li>
<li>
<a href="/android/2022/08/10/android-lint/">Android代码检查之自定义Lint</a>
<h3 id="概述">概述</h3>
</li>
<li>
<a href="/gradle/2022/07/26/publish-library-to-github-repo/">发布组件到GitHub Packages</a>
<p>使用过 Maven Central 的朋友应该都体会过组件发布流程的繁琐,于是乎心里萌生了一个想法:是否可以将组件发布到 GitHub 仓库呢?果断去 Google 了一下,果不其然,GitHub 提供了 <a href="https://docs.github.com/cn/actions/publishing-packages">GitHub Packages</a> 可以让作为开发者组件发布的分发仓库。下面来看下如何具体实现本地 Library 发布到 GitHub Packages 以及在项目中下载依赖库。</p>
</li>
<li>
<a href="/other/2022/07/16/change-article-comments/">关于博客更换评论系统的一场厮杀</a>
<p>从去年建站 <strong><em>dorck.cn</em></strong> 开始到现在,我已经陆续更换过三四次评论系统了,以至于最近半年甚至都未开启这个功能,只因为它们太不稳定或者体验太差。有一阵子没更新博客了,重拾起来一段时间之后发现评论功能还是要有的,毕竟要客观了解到他人对于文章内容质量的看法,沟通的平台少不了的,更重要的一点是:以后遇到相关知识点还能应急性地自我评论,补充一二思路。</p>
</li>
<li>
<a href="/gradle/2022/06/30/gradle-command-line-interface/">Gradle手札之命令行接口一览</a>
<h3 id="概述">概述</h3>
</li>
<li>
<a href="/kotlin/2022/05/05/kotlin-companion-object/">揭开 Kotlin 中的 companion object 的奥秘</a>
<p>Kotlin 中有个所谓的伴生对象(companion object),一般使用过程中我们会将它作为 Java 静态成员使用方式的替代品:</p>
</li>
<li>
<a href="/pattern-design/2022/04/24/pattern-design-note/">行为型设计模式一览</a>
<blockquote>
<p>设计模式要干的事情就是解耦,创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚低耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。</p>
</blockquote>
</li>
<li>
<a href="/skill/2022/04/17/sublime-auto-launch/">Sublime Text在Mac上开机自启动问题</a>
<p>去年自从重新下载了 Sublime Text 来作为主要的代码及文本阅读工具后就一直“苦开机自启动问题久矣”,每次开机Sublime Text 都会自动打开一个空的文件,极度影响使用体验(我又不是每次打开电脑都需要用 sublime🤮)。于是乎,稍微 Google 搜索了下如何关闭 Sublime Text 自启动,其实也很简单,此处记录下以防忘记。</p>
</li>
<li>
<a href="/gradle/2022/04/17/gradle-dep-management/">Gradle 组件依赖版本管理</a>
<p>日常 Android 开发过程中,我们总需要在各个 module 中依赖各种第三方远程组件,像下面这样:</p>
</li>
<li>
<a href="/android/2022/04/10/context-instance/">如何维护一个全局 Context</a>
<p>一般来说,App 在运行的时候,势必存在一个 Application 对象,而日常开发中我们离不开 Context,获取资源、启动组件等等都需要这位“管家”的帮助。那么,平时我们是怎么获取 Context 的?</p>
</li>
<li>
<a href="/2022/03/19/sort-collections/">常见排序算法整理</a>
<blockquote>
<p><strong><em>今日寄语:靡不有初,鲜克有终。</em></strong></p>
</blockquote>
</li>
<li>
<a href="/computer/2022/03/13/memory-allocation/">操作系统地址空间与内存分配</a>
<h2 id="内存分层体系">内存分层体系</h2>
</li>
<li>
<a href="/computer/2022/03/12/os-boot-process/">操作系统的启动过程</a>
<h3 id="操作系统启动过程">操作系统启动过程</h3>
</li>
<li>
<a href="/computer/2022/03/12/know-what-os/">操作系统原理的基本概念和组成</a>
<h3 id="操作系统概念">操作系统概念</h3>
</li>
<li>
<a href="/skill/2022/02/27/git-emoji-commit/">Git emoji 提交规约</a>
<p>执行 <code class="language-plaintext highlighter-rouge">git commit</code> 时使用 emoji 为本次提交打上一个 “标签”, 使得此次 commit 的主要工作得以凸现,也能够使得其在整个提交历史中易于区分与查找。</p>
</li>
<li>
<a href="/indie/2022/02/27/indie-developer-skills/">独立开发者需奉行的原则</a>
<p><img src="/img/in-post/post-other/indie_hackers_post.jpg" alt="indie_hackers_post" /></p>
</li>
<li>
<a href="/skill/2021/11/12/git-default-editor/">Git 默认编辑器替换</a>
<p>一般情况下,Git 的默认编辑器是 vim,对于新手来说上手可能比较困难。例如常见地我们输入 <code class="language-plaintext highlighter-rouge">git commit</code> 会看到如下场景,终端自动通过 vim 打开了本地 git 文件:</p>
</li>
<li>
<a href="/skill/2021/11/12/article-publications-flow/">文章发布流程记录</a>
<p>隔了两个月未更新本地文章到 Github,今天突然想去发布两篇,结果流程命令全然忘记了。为防止以后出现类似情况,这里描述一下文章发布的工作流,仅作记录使用。</p>
</li>
<li>
<a href="/android/2021/08/08/collection-copy/">关于 StateFlow 使用的一次车祸现场</a>
<p>最近了解了一下 Kotlin 中的协程,这两天得闲就想着把 Flow 拿来练练手,没想到车祸现场立马就来了。</p>
</li>
<li>
<a href="/tools/2021/08/07/markdown-drawing/">Markdown 流程图绘制的二三事儿</a>
<p>作为一位经常出入各大博客站点的取经人来说,<strong>Markdown</strong> 显然已经成为一项必备技能。很多人平时写文档或者博客都会用常见的 Markdown 编辑器,如:有道、Atom、Typora等等。我们经常会有绘图需求,常见的有流程图、类图、序列图和甘特图等等,然而,当你还在苦苦寻找一些免费易用的画图软件时,殊不知 Markdown 早就已经具备了此项功能。</p>
</li>
<li>
<a href="/other/2021/08/05/about-avoid-useless-learning/">关于如何避免低效学习的所思</a>
<p>医者,讲究对症下药,何谓“<strong>低效</strong>”?</p>
</li>
<li>
<a href="/android/2020/08/12/MotionLayout-part2/">MotionLayout:打开动画新世界大门(partII)</a>
<p>距离上一篇文章「 <a href="https://juejin.im/post/5d595328f265da03c34bfa59">MotionLayout:打开动画新世界大门(partI)</a>」已经过去了很久,由于个人原因,<strong>MotionLayout</strong> 系列文章姗姗来迟。在之前的文章中,我们领略到了 MotionLayout 的魅力,了解到它继承自 <em>ConstraintLayout</em>,并具有它“约束布局”的特性。同时,关于如何创建和使用 <code class="language-plaintext highlighter-rouge">MotionScene</code> 及其内部的 <code class="language-plaintext highlighter-rouge">KeyFrameSet</code> 也都做了一些简单介绍。那么,本文来带大家进一步探索 <strong><code class="language-plaintext highlighter-rouge">KeyFrameSet</code></strong> 这个大家族中的“神秘宝藏”,并针对上文中留下的一些<strong>彩蛋</strong>进行讲解,来看看<strong>如何实现 MotionLayout 与其他控件的联动</strong>。<!-- more --></p>
</li>
<li>
<a href="/android/2020/06/29/android-proguard/">一篇文章带你领略Android混淆的魅力</a>
<p>在 Android 日常开发过程中,<strong>混淆</strong>是我们开发 App 的一项必不可少的技能。只要是我们亲身经历过 App 打包上线的过程,或多或少都需要了解一些代码混淆的基本操作。那么,混淆到底是什么?它的好处有哪些?具体效果如何?别急,下面我们来一一探索它的”独特”魅力🐳。</p>
</li>
<li>
<a href="/flutter/2020/04/19/flutter-tips/">Flutter 开发小结 | Tips</a>
<p>接触 Flutter 已经有一阵子了,期间记录了很多开发小问题,苦于忙碌没时间整理,最近项目进度步上正轨,借此机会抽出点时间来统一记录这些问题,并分享项目开发中的一点心得以及多平台打包的一些注意事项,希望能对大家有所帮助😁。</p>
</li>
<li>
<a href="/flutter/2020/02/18/flutter-timer/">Flutter 中“倒计时”的那些事儿</a>
<p>好久不见了,文章有一段时间没有更新了,最近一直在沉迷工作无法自拨😂。上周,应公司号召以及上次Google大会中Flutter宣讲的感染,计划将公司新项目采用Flutter技术实现。大概花了几天熟悉了一下Flutter基础语法和结构组成,便着手开始项目的搭建和基础模块功能开发,毕竟只有通过实战才能加快新技术的熟悉和“消化”。</p>
</li>
<li>
<a href="/android/2019/10/19/motionlayout-part1/">MotionLayout:打开动画新世界大门(partI)</a>
<p>最初接触到 <strong>MotionLayout</strong> 是在国外知名博客的 <a href="https://medium.com/google-developers">Android 专栏</a>上。第一眼见到 <code class="language-plaintext highlighter-rouge">MotionLayout</code> 时无疑是兴奋的,在经过使用和熟悉了这个布局组件之后,我就想将这份喜悦传递给国内开发者,从此“拳打”设计,“脚踢”产品😁。当然,由于关于 <code class="language-plaintext highlighter-rouge">MotionLayout</code> 的外文专栏相关介绍已足够详细,所以本文仅对其进行总结和简单应用。老规矩,正文开始前先上一张图:</p>
</li>
<li>
<a href="/android/2019/08/11/android-tools-attribute/">是时候让 Android Tools 属性拯救你了</a>
<p>日常开发过程中,我们都会遇到这样一种场景:我们写出的 UI 效果在对接数据之前需要提前进行预览,进而调整 UI 细节和排版问题。我们一般的做法是什么样的?如果存在像 TextView 或者 ImageView 这种基础控件,你是不是还在通过诸如 <em><code class="language-plaintext highlighter-rouge">android:text="xxx"</code></em> 和 <em><code class="language-plaintext highlighter-rouge">android:src="@drawable/xxx"</code></em> 的方式来测试和预览UI效果?当然你肯定也会遇到这些“脏数据”给你带来的困扰:测试的时候某些地方出现了本不该出现的数据,事后可能一拍脑门才发现,原来是布局中控件预览数据没有清除导致的。</p>
</li>
<li>
<a href="/kotlin/2019/05/21/kotlin-when/">带你领略 Kotlin 中的 “when”魔法</a>
<p>提到 <strong><code class="language-plaintext highlighter-rouge">when</code></strong>,大家都会联想到 Java 中的 <strong><code class="language-plaintext highlighter-rouge">switch</code></strong>,然而在 kotlin 中,<code class="language-plaintext highlighter-rouge">when</code> 显然比 Java 中的 <code class="language-plaintext highlighter-rouge">switch</code> 要强大得多。首先,我们先来看看 when 的特点:</p>
</li>
<li>
<a href="/android/architecture/2018/12/24/mvp-presenter/">Android中的 MVP:如何使 Presenter 层系统化?</a>
<blockquote>
<ul>
<li>原文地址:<a href="https://antonioleiva.com/mvp-android/">MVP for Android: how to organize the presentation layer</a></li>
<li>原文作者:<a href="https://antonioleiva.com">Antonio Leiva</a></li>
</ul>
</blockquote>
</li>
<li>
<a href="/kotlin/2018/12/22/kotlin-functions/">当 Kotlin 中的监听器包含多个方法时,如何让它 “巧夺天工”?</a>
<p>我经常遇到的一个问题是在使用 Kotlin 时如何简化具有多个方法的监听器的交互。对于具有只具有一个方法的监听器(或任何接口)很简单:Kotlin 会自动让您用 lambda 替换它。但对于具有多个方法的监听器来说,情况并非如此。</p>
</li>
<li>
<a href="/kotlin/2018/12/11/kotlin-anko/">Android Kotlin 快速开发之 Anko 魔法</a>
<p>众所周知,目前 kotlin 已经作为 Google 官方推荐的 Android 开发语言,目前 GitHub 上面关于 kotlin 的项目已然呈现一片势不可挡的热度。作为一名 Android 开发者,学好 koltin 已经成为我们必须 get 的技能,而想要在工作中使用 kotlin 快速开发项目,Anko 无疑成为首选利器!</p>
</li>
<li>
<a href="/2018/05/20/constraintlayout/">带你领略 ConstraintLayout 1.1 的新功能</a>
<blockquote>
<ul>
<li>原文地址:<a href="https://medium.com/google-developers/introducing-constraint-layout-1-1-d07fc02406bc">Introducing Constraint Layout 1.1</a></li>
<li>原文作者:<a href="https://medium.com/@objcode?source=post_header_lockup">Sean McQuillan</a></li>
</ul>
</blockquote>
</li>
</ul>
Drafts
Drafts are posts without a date in the filename. They’re posts you’re still working on and don’t want to publish yet. To get up and running with drafts, create a _drafts folder in your site’s root and create your first draft:
1
2
3
4
.
├── _drafts
│ └── a-draft-post.md
...
To preview your site with drafts, run jekyll serve or jekyll build with the --drafts switch. Each will be assigned the value modification time of the draft file for its date, and thus you will see currently edited drafts as the latest posts.
Post command line
The Jekyll gem makes a jekyll executable available to you in your terminal.
The jekyll program has several commands but the structure is always:
1
2
3
4
5
jekyll command [argument] [option] [argument_to_option]
Examples:
jekyll new site/ --blank
jekyll serve --config _alternative_config.yml
Typically you’ll use jekyll serve while developing locally and jekyll build when you need to generate the site for production.
For a full list of options and their argument, see Build Command Options.
Here are some of the most common commands:
jekyll new PATH- Creates a new Jekyll site with default gem-based theme at specified path. The directories will be created as necessary.jekyll new PATH --blank- Creates a new blank Jekyll site scaffold at specified path.jekyll buildorjekyll b- Performs a one off build your site to./_site(by default).jekyll serveorjekyll s- Builds your site any time a source file changes and serves it locally.jekyll clean- Removes all generated files: destination folder, metadata file, Sass and Jekyll caches.jekyll help- Shows help, optionally for a given subcommand, e.g.jekyll help build.jekyll new-theme- Creates a new Jekyll theme scaffold.jekyll doctor- Outputs any deprecation or configuration issues.
To change Jekyll’s default build behavior have a look through the configuration options.
以上内容皆来自 Jekyll 官网,更多内容可前往:http://jekyllrb.com/docs/
中文可参考:Jekyll中的配置和模板语法
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。