├── README.md
├── appreciation.md
├── art
├── alipay.jpg
├── mvi_1_01.webp
├── mvi_1_02.gif
├── mvi_1_03.png
├── mvi_1_04.gif
├── mvi_2_01.png
├── mvi_2_02.gif
├── mvi_2_03.gif
├── mvi_2_04.gif
├── mvi_2_05.png
├── mvi_3_01.gif
├── mvi_3_02.gif
├── mvi_4_01.jpg
├── mvi_4_02.png
├── mvi_4_03.png
├── mvi_4_04.gif
├── mvi_4_05.png
├── mvi_5_01.png
├── mvi_7_01.gif
├── mvi_7_02.gif
├── mvi_7_03.gif
├── mvi_7_04.gif
└── wechat.jpg
├── error_collection.md
└── src
├── Algorithm
├── Week12_曾被反转链表支配的恐惧.md
├── Week13.md
├── 二叉树的递归与迭代遍历.md
├── 哈希映射用法及算法例题.md
├── 哈希表原理及简单设计.md
├── 哈希集合用法及算法例题.md
├── 弗洛伊德的乌龟与兔子.md
├── 循环与循环双端队列.md
├── 栈和深度优先搜索(DFS).md
├── 栈的设计与算法例题.md
├── 运用递归解决二叉树相关问题.md
└── 队列和广度优先搜索.md
├── Android-Core
├── Camera
│ ├── Android-Camera-系列(一)拍照和录制视频.md
│ ├── Android-Camera-系列(三)Camera-API-详解.md
│ └── Android-Camera-系列(二)控制Camera.md
├── Handler
│ ├── Handler.xmind
│ └── Handler原理分析.md
└── ThreadLocal
│ ├── ThreadLocal.xmind
│ └── ThreadLocal源码解析.md
├── Android-DI
├── Android-神兵利器Dagger2使用详解(一)基础使用.md
├── Android-神兵利器Dagger2使用详解(三)MVP架构下的使用.md
├── Android-神兵利器Dagger2使用详解(二)Module&Component源码分析.md
├── Android-神兵利器Dagger2使用详解(四)Scope注解的使用及源码分析.md
├── [译]Android开发从Dagger2迁移至Kodein的感受.md
├── 告别Dagger2模板代码:DaggerAndroid使用详解.md
├── 告别Dagger2模板代码:DaggerAndroid原理解析.md
└── 告别Dagger2,Android的Kotlin项目中使用Kodein进行依赖注入.md
├── Android-Jetpack
├── Android官方架构组件DataBinding双向绑定篇_观察者模式的殊途同归.md
├── Android官方架构组件Lifecycle-生命周期组件详解&原理分析.md
├── Android官方架构组件LiveData_观察者模式领域二三事.md
├── Android官方架构组件Navigation:大巧不工的Fragment管理框架.md
├── Android官方架构组件Paging:分页库的设计美学.md
├── Android官方架构组件ViewModel-从前世今生到追本溯源.md
├── DataBinding
│ └── 一行Java代码实现RecyclerView的Adapter-一行都不需要!.md
├── paging_ex
│ ├── ex1_Paging1.jpg
│ ├── ex1_gif1.gif
│ ├── ex1_gif2.gif
│ ├── ex1_paging2.jpg
│ ├── ex2_image1.png
│ ├── ex2_image2.png
│ ├── ex2_image3.png
│ ├── ex2_image4.png
│ ├── ex2_image5.png
│ ├── ex2_image6.png
│ ├── ex2_image7.png
│ ├── ex2_star.gif
│ ├── paging_ex_header_footer.md
│ └── paging_ex_state.md
└── 使用MVVM尝试开发Github客户端及对编程的一些思考.md
├── Android-MVI
├── MVI_1.md
├── MVI_2.md
├── MVI_3.md
├── MVI_4.md
├── MVI_5.md
├── MVI_6.md
├── MVI_7.md
└── MVI_8.md
├── Flutter
├── Flutter与Android混合编码配置笔记.md
└── 使用Flutter开发Github客户端及学习过程中的感悟.md
├── Groovy
├── Android-用Groovy实现扇贝阅读APP的自动阅读功能.md
├── Gradle-Permission-denied解决方案.md
├── Gradle学习笔记(一)基本配置.md
├── Gradle学习笔记(三)管理依赖.md
├── Gradle学习笔记(二)自定义构建基础.md
├── Gradle学习笔记(四)构建Variant.md
├── Groovy学习笔记1:下载及配置环境.md
├── Groovy学习笔记2:Groovy的基本语法.md
├── Groovy学习笔记3:接口,布尔判断,操作符重载.md
├── Groovy学习笔记4:特殊注解.md
└── JakeWharton说我代码写的像是在打地鼠?.md
├── IDE-Plugin
├── trans_idea_plugin_1.md
├── trans_idea_plugin_2.md
├── trans_idea_plugin_3.md
├── trans_idea_plugin_4.md
└── trans_idea_plugin_5.md
├── Java
└── Java代理模式分析总结.md
├── Kotlin
├── Android用DSL实现复杂RecyclerView的思路分析.md
└── [译]Kotlin中用DSL代替建造者模式.md
├── Linux
├── Linux配置AndroidSDK&Jenkins远程部署.md
└── Linux配置JDK和Tomcat.md
├── Media
└── trans_alternative_android_visualizer
│ ├── image1.gif
│ ├── image2.gif
│ ├── image3.gif
│ ├── image4.gif
│ ├── image5.gif
│ ├── image6.jpeg
│ └── trans_an_alternative_android_visualizer.md
├── Others
├── 2018-Google-IO干货摘要及对国内Android开发者的影响.md
├── year_2018.md
├── year_2019.md
└── 如何通俗理解设计模式及其思想.md
├── UnitTest
├── -Android-Kotlin使用Mockito进行单元测试.md
└── [译]Java将Powermock和Mockito搭配进行单元测试.md
├── android-RxJava
├── Android-RxActivityResult-优雅的方式实现startActivityForResult.md
├── Android-RxCache使用详解.md
├── Android单元测试:测试RxJava的同步及异步操作.md
├── Android架构中添加AutoDispose解决RxJava内存泄漏.md
├── RxImagePicker-从零实现灵活且可高度定制的Android图片选择架构.md
├── RxJava+Retrofit2缓存库:RxCache中文文档.md
├── RxJava+Retrofit2缓存库:RxCache原理解析.md
├── 不要打破链式调用!一个极低成本的RxJava全局Error处理方案.md
├── 全副武装!AndroidUI自动化测试在RxImagePicker中的实践历程.md
├── 理解RxJava(一)基本流程原理分析.md
├── 理解RxJava(三)线程调度原理分析.md
├── 理解RxJava(二)操作符流程原理分析.md
├── 理解RxJava(四)Subject用法及原理分析.md
├── 解决RxJava内存泄漏(前篇):RxLifecycle详解及原理分析.md
└── 解放双手,Android开发应该尝试的UI自动化测试.md
└── 反思系列
├── ANR
├── thinking_in_android_anr1.md
├── thinking_in_android_anr2.md
└── 反思|Android 输入系统 & ANR机制的设计与实现.xmind
├── Activity
└── LayoutInflater
│ ├── LayoutInflater.xmind
│ └── thinking_in_android_layoutinflater.md
├── ArchitectureComponents
├── thinking_in_android_router.md
└── 组件化通信库的设计与实现.xmind
├── Git
├── Android源码模块化项目管理工具Repo分析.xmind
└── thinking_in_android_repo.md
├── Jetpack
├── jetpack_workmanager1.jpeg
├── thinking_in_android_paging_1a.md
├── thinking_in_android_paging_1b.md
├── thinking_in_android_paging_2.md
├── thinking_in_android_workmanager.md
├── 分页组件Paging的设计与实现.xmind
└── 分页组件Paging的设计与实现2.xmind
├── SharedPreferences
├── shared_preferences.md
└── shared_preferences.xmind
├── Skin
├── bilibili.png
├── hot_update_wechat.gif
├── skin_juejin.drawio
├── skin_juejin_design.png
├── skin_layout_inflater.drawio
├── skin_layout_inflater.png
├── skin_skin_layout_inflater.drawio
├── skin_skin_layout_inflater.png
├── taobao.png
└── thinking_in_android_skin.md
├── View
├── View布局流程.xmind
├── View测量流程.xmind
├── surfaceview
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.jpg
│ └── 5.png
├── thinking_in_android_surface_view.md
├── thinking_in_android_view_dispatch.md
├── thinking_in_android_view_intercept.md
├── thinking_in_android_view_layout.md
├── thinking_in_android_view_measure.md
├── 事件分发流程.xmind
└── 事件拦截机制.xmind
└── thinking_in_android_index.md
/README.md:
--------------------------------------------------------------------------------
1 | # qingmei2-blogs
2 |
3 | 我和我的编程总结.
4 |
5 | ## 简介
6 |
7 | 我是 [清梅](https://github.com/qingmei2), `Android`开发者,爱好 **写作**,曾多次作为嘉宾受邀参加`GDG (Google Developer Groups)`线下活动进行分享。
8 |
9 | 在过去的时间里,我持续归纳总结了若干技术文章,并将它们统一总结到这里,以后所有的文章也会第一时间在这里进行更新。
10 |
11 | ## 博客大纲
12 |
13 | |专题 |文章 |备注|
14 | | ---- | ----- | ------- |
15 | | [Android Jetpack](https://github.com/qingmei2/android-programming-profile/issues?q=is%3Aopen+is%3Aissue+label%3A%22Android+Jetpack%22) | [Android官方架构组件Lifecycle:生命周期组件详解&原理分析](https://juejin.im/post/5c53beaf51882562e27e5ad9) | |
16 | | | [Android官方架构组件ViewModel:从前世今生到追本溯源](https://juejin.im/post/5c047fd3e51d45666017ff86) | |
17 | | | [Android官方架构组件LiveData:观察者模式领域二三事](https://juejin.im/post/5c25753af265da61561f5335) | |
18 | | | [Android官方架构组件Paging:分页库的设计美学](https://juejin.im/post/5c53ad9e6fb9a049eb3c5cfd) | |
19 | | | [Android官方架构组件Paging-Ex:为分页列表添加Header和Footer](https://juejin.im/post/5caa0052f265da24ea7d3c2c) | |
20 | | | [Android官方架构组件Paging-Ex:列表状态的响应式管理](https://juejin.im/post/5ce6ba09e51d4555e372a562) | |
21 | | | [Android官方架构组件Navigation:大巧不工的Fragment管理框架](https://juejin.im/post/5c53be3951882562d27416c6) | |
22 | | | [Android官方架构组件DataBinding-Ex:双向绑定篇](https://juejin.im/post/5c3e04b7f265da611b589574) | |
23 | | | [【开源项目】MVVM+Jetpack实现的Github客户端](https://github.com/qingmei2/MVVM-Rhine) | |
24 | | | [【开源项目】MVI+Jetpack实现的Github客户端](https://github.com/qingmei2/MVI-Rhine) | |
25 | | |[总结:使用MVVM尝试开发Github客户端及对编程的一些思考](https://juejin.im/post/5be7bbd9f265da61797458cf)| |
26 | |依赖注入|||
27 | ||[ Android 神兵利器Dagger2使用详解(一)基础使用 ](http://www.jianshu.com/p/b40bcd1a9ec9)||
28 | ||[ Android 神兵利器Dagger2使用详解(二)Module&Component源码分析](http://www.jianshu.com/p/30d48ddefd30)||
29 | ||[ Android 神兵利器Dagger2使用详解(三)MVP架构下的使用](http://www.jianshu.com/p/c46acc3f21ab)||
30 | ||[ Android 神兵利器Dagger2使用详解(四)Scope注解的使用及源码分析 ](http://www.jianshu.com/p/caaac320c785)||
31 | ||[ 告别Dagger2模板代码:dagger.android使用详解 ](http://www.jianshu.com/p/917bf39cae0d)||
32 | ||[ 告别Dagger2模板代码:dagger.android原理解析 ](http://www.jianshu.com/p/d4d62945d9c8)||
33 | ||[ 告别Dagger2,在Kotlin项目中使用Kodein进行依赖注入 ](https://www.jianshu.com/p/b0da805f7534)||
34 | ||[ 译:Android开发从Dagger2迁移至Kodein的感受 ](https://www.jianshu.com/p/e5eef49570b9)||
35 | |RxJava|||
36 | ||[ 理解RxJava(一):基本流程源码分析 ](https://www.jianshu.com/p/7fce2955f2db)||
37 | ||[ 理解RxJava(二):操作符流程原理分析 ](https://www.jianshu.com/p/0a28428e734d)||
38 | ||[ 理解RxJava(三):线程调度原理分析 ](https://www.jianshu.com/p/9e3930fbcb26)||
39 | ||[ 理解RxJava(四):Subject用法及原理分析 ](https://www.jianshu.com/p/d7efc29ec9d3)||
40 | ||[ 解决RxJava内存泄漏(前篇):RxLifecycle详解及原理分析 ](https://www.jianshu.com/p/8311410de676)||
41 | ||[ 解决RxJava内存泄漏(后篇):Android架构中添加AutoDispose解决RxJava内存泄漏 ](https://www.jianshu.com/p/8490d9383ba5)||
42 | ||[ RxImagePicker:从零实现灵活且可高度定制的Android图片选择架构](https://www.jianshu.com/p/fecf3a13e615)||
43 | ||[ 不要打破链式调用!一个极低成本的RxJava全局Error处理方案 ](https://www.jianshu.com/p/eb10d6e40c4b)||
44 | |MVI Archtecture|||
45 | ||[[译]使用MVI打造响应式APP(一):Model到底是什么](https://juejin.im/post/5c7c0471e51d455ff14bae0c)||
46 | ||[[译]使用MVI打造响应式APP[二]:View层和Intent层](https://juejin.im/post/5c8520eb6fb9a04a0441d804) ||
47 | ||[[译]使用MVI打造响应式APP[三]:状态折叠器](https://juejin.im/post/5c8904015188251251356945) ||
48 | ||[[译]使用MVI打造响应式APP[四]:独立性UI组件](https://juejin.im/post/5c8b38476fb9a049b222c365) ||
49 | ||[[译]使用MVI打造响应式APP[五]:轻而易举地Debug](https://juejin.im/post/5c8e55eaf265da68126b1d9d)||
50 | ||[[译]使用MVI打造响应式APP[六]:恢复状态](https://juejin.im/post/5c92cce0e51d451b893ff7b6)||
51 | ||[[译]使用MVI打造响应式APP[七]:掌握时机(SingleLiveEvent问题)](https://juejin.im/post/5c95f2145188252d7a5c5864)||
52 | ||[[译]使用MVI打造响应式APP[八]:导航](https://juejin.im/post/5c9713285188252dab3ec273) ||
53 | ||[实战:使用MVI打造响应式&函数式的Github客户端](https://github.com/qingmei2/MVI-Rhine)||
54 | |反思系列|||
55 | ||[Android View机制设计与实现:测量流程](https://github.com/qingmei2/blogs/issues/12) ||
56 | ||[Android View机制设计与实现:布局流程](https://github.com/qingmei2/blogs/issues/13)||
57 | ||[Android LayoutInflater机制的设计与实现](https://github.com/qingmei2/blogs/issues/25)||
58 | ||[Android 事件分发机制的设计与实现](https://github.com/qingmei2/blogs/issues/27)||
59 | ||[Android 事件拦截机制的设计与实现](https://github.com/qingmei2/blogs/issues/44)||
60 | ||[Android 列表分页组件Paging的设计与实现:系统概述](https://github.com/qingmei2/blogs/issues/30)||
61 | ||[Android 列表分页组件Paging的设计与实现:架构设计与原理解析](https://github.com/qingmei2/blogs/issues/31)||
62 | ||[Android 源码模块化管理工具Repo分析](https://github.com/qingmei2/blogs/issues/45)||
63 | ||[Android 输入系统&ANR机制的设计与实现](https://github.com/qingmei2/blogs/issues/46)||
64 |
65 |
66 | 首页我只放出了部分博文,所有文章你都可以在 **[这里](https://github.com/qingmei2/Programming-life/tree/master/src)** 找到,**更多文章持续更新中......**
67 |
68 | ## 食用方式
69 |
70 | 建议读者通过 [Issues](https://github.com/qingmei2/android-programming-profile/issues) 中的自定义筛选条件查找指定的博客。
71 |
72 | ## 关于我
73 |
74 | 你可以在这里找到我:
75 |
76 | * **Wechat** mq2553299
77 | * **GitHub:** https://github.com/qingmei2
78 | * **CSDN:** https://blog.csdn.net/mq2553299
79 | * **掘金:** https://juejin.im/user/588555ff1b69e600591e8462
80 | * **Email:** mq2553299@qq.com
81 |
82 | ## 其它
83 |
84 | #### [关于纠错(issues、PR)](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
85 |
86 | #### [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
87 |
88 | ## License
89 |
90 | The android-programming-profile: Apache License
91 |
92 | Copyright (c) 2019 qingmei2
93 |
94 | Licensed under the Apache License, Version 2.0 (the "License");
95 | you may not use this file except in compliance with the License.
96 | You may obtain a copy of the License at
97 |
98 | http://www.apache.org/licenses/LICENSE-2.0
99 |
100 | Unless required by applicable law or agreed to in writing, software
101 | distributed under the License is distributed on an "AS IS" BASIS,
102 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
103 | See the License for the specific language governing permissions and
104 | limitations under the License.
105 |
--------------------------------------------------------------------------------
/appreciation.md:
--------------------------------------------------------------------------------
1 | ## 关于知识付费
2 |
3 | 我是 [却把清梅嗅](https://github.com/qingmei2) ,热爱 **技术分享** 和 **开源**,如果您因为我的文章而受益,您可以选择对您的所得进行付费,这样我 **花费的时间可以得到一些回报** ,就像[王垠](http://www.yinwang.org/)说的:
4 |
5 | > 我一直很高尚的样子,不愿意为此收费。然而,根据经济学的原理,这是有害社会的 :P 经济的原理是这样,有价值的事物,应该在经济上受到相应的支持,这样好的东西才能受到鼓励,发扬光大,不好的东西才可能被人忘记。
6 |
7 | **开源并不意味着免费**——我对于 **主张知识付费** 的行为十分认同,所以我决定,在文章的最下方加一个赞赏的入口,这样喜欢文章的人可以点进来,并根据所得选择是否自愿付费(我的定价是**2¥**),当然 **也可以不付费**。
8 |
9 | > 将赞赏功能统一放在这个独立的页面,其好处在于,我无需将 **知识付费** 的相关文字在每篇文章的末尾都啰嗦一次,因为这样会干扰读者的阅读,也降低了我个人文章的质量;其次,增加赞赏的入口本身也能够提升写作者对自我的约束,尽力保证每篇文章的输出有 **足够的篇幅** 和 **深度**。
10 |
11 | 我会 **用自己最大的努力写好每一篇文章**,也欢迎通过 **评论和建议** 一起进行技术讨论。
12 |
13 | [返回上一页](https://github.com/qingmei2/Programming-life)
14 |
15 | ### 赞赏码
16 |
17 |
18 |
--------------------------------------------------------------------------------
/art/alipay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/alipay.jpg
--------------------------------------------------------------------------------
/art/mvi_1_01.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_1_01.webp
--------------------------------------------------------------------------------
/art/mvi_1_02.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_1_02.gif
--------------------------------------------------------------------------------
/art/mvi_1_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_1_03.png
--------------------------------------------------------------------------------
/art/mvi_1_04.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_1_04.gif
--------------------------------------------------------------------------------
/art/mvi_2_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_2_01.png
--------------------------------------------------------------------------------
/art/mvi_2_02.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_2_02.gif
--------------------------------------------------------------------------------
/art/mvi_2_03.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_2_03.gif
--------------------------------------------------------------------------------
/art/mvi_2_04.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_2_04.gif
--------------------------------------------------------------------------------
/art/mvi_2_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_2_05.png
--------------------------------------------------------------------------------
/art/mvi_3_01.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_3_01.gif
--------------------------------------------------------------------------------
/art/mvi_3_02.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_3_02.gif
--------------------------------------------------------------------------------
/art/mvi_4_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_4_01.jpg
--------------------------------------------------------------------------------
/art/mvi_4_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_4_02.png
--------------------------------------------------------------------------------
/art/mvi_4_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_4_03.png
--------------------------------------------------------------------------------
/art/mvi_4_04.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_4_04.gif
--------------------------------------------------------------------------------
/art/mvi_4_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_4_05.png
--------------------------------------------------------------------------------
/art/mvi_5_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_5_01.png
--------------------------------------------------------------------------------
/art/mvi_7_01.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_7_01.gif
--------------------------------------------------------------------------------
/art/mvi_7_02.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_7_02.gif
--------------------------------------------------------------------------------
/art/mvi_7_03.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_7_03.gif
--------------------------------------------------------------------------------
/art/mvi_7_04.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/mvi_7_04.gif
--------------------------------------------------------------------------------
/art/wechat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/art/wechat.jpg
--------------------------------------------------------------------------------
/error_collection.md:
--------------------------------------------------------------------------------
1 | ## 关于纠错
2 |
3 | 在写作的过程中(尤其是个人博客),错误是在所难免的,小到 **错别字** 或者 **不严谨的措辞**,大到对 **知识点理解的偏差**。
4 |
5 | 我个人对博客的约束是,在发布之前,**至少认真检查三遍**,这样很多小问题都能够被扼杀在摇篮之中,但是依然会有很多问题,我很重视这些问题,因为微小的错误都有可能会令读者走入歧途。
6 |
7 | 在此我真挚的希望,在发现文章的问题时,您能够力所能及对我进行警醒,通过 **评论** 、 **Email** 或者是 **提issue** ,这都将是对开源精神本身很大的臂助。
8 |
9 | [返回上一页](https://github.com/qingmei2/Programming-life)
10 |
--------------------------------------------------------------------------------
/src/Algorithm/Week13.md:
--------------------------------------------------------------------------------
1 | # 每日一题 |
2 |
3 | > Date: 2021-03-22 Week13
4 |
5 | ### 1、位1的个数
6 |
7 | * 难度:`Easy`
8 |
9 | #### 题目描述
10 |
11 | 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。
12 |
13 | 提示:
14 |
15 | 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
16 | 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
17 |
18 |
19 | > 示例 1:
20 | > 输入:00000000000000000000000000001011
21 | > 输出:3
22 | > 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
23 |
24 | #### 解题思路及实现
25 |
26 | 标准的位运算。
27 |
28 | ```java
29 | public class Solution {
30 | public int hammingWeight(int n) {
31 | int count = 0;
32 | int num = 1;
33 |
34 | while (num <= 32) {
35 | if ((n & (1 << num)) != 0) {
36 | count++;
37 | }
38 | num ++;
39 | }
40 | return count;
41 | }
42 | }
43 | ```
44 |
45 | 因为位运算的算法接触的比较少,官方还提供了一个优化的解法更有意思一些:
46 |
47 | 观察这个运算:`n & (n−1)`,其预算结果恰为把 `n` 的二进制位中的最低位的 1 变为 0 之后的结果。
48 |
49 | 这样我们可以利用这个位运算的性质加速我们的检查过程,在实际代码中,我们不断让当前的 `n 与 n - 1` 做与运算,直到 `n` 变为 `0` 即可。因为每次运算会使得 `n` 的最低位的 `1` 被翻转,因此运算次数就等于 `n` 的二进制位中 `1` 的个数。
50 |
51 | ```java
52 | public class Solution {
53 | public int hammingWeight(int n) {
54 | int count = 0;
55 |
56 | while (n != 0) {
57 | count++;
58 | n = n & (n - 1);
59 | }
60 | return count;
61 | }
62 | }
63 | ```
64 |
65 | 个人觉得这种解法比较巧妙,但实战中价值不高,同时这种解法需要注意是`(n != 0)`而非`(n > 0)`, 因为`n`可能是负值(即32位为1)。
66 |
67 | ### 1.1 环形链表
68 |
69 | * 难度:`Easy`
70 |
71 | #### 题目描述
72 |
73 | 给定一个链表,判断链表中是否有环。
74 |
75 | 如果链表中有某个节点,可以通过连续跟踪 `next` 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 `0` 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。注意:`pos` 不作为参数进行传递,仅仅是为了标识链表的实际情况。
76 |
77 | 如果链表中存在环,则返回 `true` 。 否则,返回 `false` 。
78 |
79 | #### 解题思路及实现
80 |
81 | 经典的环形链表题目,作为回顾链表类题型的开篇,这里不使用常规的`HashMap`进行解答,直接使用弗洛伊德判断算法。
82 |
83 | ```java
84 | public class Solution {
85 | public boolean hasCycle(ListNode head) {
86 | if (head == null || head.next == null) {
87 | return false;
88 | }
89 | ListNode slow = head;
90 | ListNode fast = head.next;
91 |
92 | while (fast != null && fast.next != null) {
93 | if (slow == fast) {
94 | return true;
95 | }
96 | slow = slow.next;
97 | fast = fast.next.next;
98 | }
99 |
100 | return false;
101 | }
102 | }
103 | ```
104 |
105 | 最麻烦的还是边界条件的判断,写了5遍才过,心累。
106 |
107 | ## 参考 & 感谢
108 |
109 | 文章绝大部分内容节选自`LeetCode`,例题:
110 |
111 | * https://leetcode-cn.com/problems/number-of-1-bits/
112 | * https://leetcode-cn.com/problems/linked-list-cycle/
113 |
114 | ## 关于我
115 |
116 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://blog.csdn.net/mq2553299) 或者 [GitHub](https://github.com/qingmei2)。
117 |
118 | 如果您觉得文章还差了那么点东西,也请通过 **关注** 督促我写出更好的文章——万一哪天我进步了呢?
119 |
120 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
121 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
122 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
123 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/thinking_in_android_index.md)
124 |
--------------------------------------------------------------------------------
/src/Algorithm/二叉树的递归与迭代遍历.md:
--------------------------------------------------------------------------------
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 | 递归实现二叉树的遍历是非常简单的,其核心就是 **深度优先搜索(DFS)** 算法。
30 |
31 | 由于比较简单,三种遍历方式的实现代码只是 **深度优先搜索** 过程中执行顺序的区别,故模版如下:
32 |
33 | ```java
34 | public class Solution {
35 |
36 | // 递归遍历二叉树
37 | public List preorderTraversal(TreeNode root) {
38 | ArrayList list = new ArrayList<>();
39 | if (root == null) return list;
40 |
41 | dfs(root, list);
42 | return list;
43 | }
44 |
45 | private void dfs(TreeNode root, ArrayList list) {
46 | if (root == null) return;
47 |
48 | // 前序遍历 根 -> 左 -> 右
49 | list.add(root.val); // 根
50 | dfs(root.left, list); // 左
51 | dfs(root.right, list); // 右
52 |
53 | // 中序遍历 右 -> 根 -> 右
54 | // dfs(root.left, list);
55 | // list.add(root.val);
56 | // dfs(root.right, list);
57 |
58 | // 后序遍历 左 -> 右 -> 根
59 | // dfs(node.left, list);
60 | // dfs(node.right, list);
61 | // list.add(node.val);
62 | }
63 | }
64 | ```
65 |
66 | ## 迭代实现
67 |
68 | ### 前序遍历
69 |
70 | 通过迭代对前序遍历需要一个栈进行辅助,其负责对不同层级父子节点进行迭代存储。
71 |
72 | ```java
73 | class Solution {
74 | public List preorderTraversal(TreeNode root) {
75 | ArrayList list = new ArrayList<>();
76 | Stack stack = new Stack<>();
77 |
78 | TreeNode curr = root;
79 |
80 | // 1.退出最外层迭代的条件是,指针指向null,且栈为空
81 | while (curr != null || !stack.isEmpty()) {
82 | // 2.内层循环按顺序入栈,同时更新当前指针
83 | // 4.这时候也可能是开始遍历右节点
84 | while (curr != null) {
85 | stack.push(curr);
86 | list.add(curr.val);
87 | curr = curr.left;
88 | }
89 | // 3.返回父节点,并将指针指向右节点
90 | curr = stack.pop();
91 | curr = curr.right;
92 | }
93 |
94 | return list;
95 | }
96 | }
97 | ```
98 |
99 | ### 中序遍历
100 |
101 | 中序遍历和前序遍历思想是一致的,区别仅仅在于根节点在左叶子节点添加之后添加:
102 |
103 | ```java
104 | class Solution {
105 | public List preorderTraversal(TreeNode root) {
106 | List list = new ArrayList<>();
107 | Stack stack = new Stack<>();
108 |
109 | TreeNode curr = root;
110 |
111 | // 1.退出最外层迭代的条件是,指针指向null,且栈为空
112 | while (!stack.isEmpty() || curr != null) {
113 | // 2.内层循环按顺序入栈,同时更新当前指针
114 | // 5.这时候也可能是开始遍历右节点
115 | while (curr != null) {
116 | stack.push(curr.left);
117 | curr = curr.left;
118 | }
119 | // 3.返回父节点,并加入数组中
120 | curr = stack.pop();
121 | list.add(curr.val);
122 | // 4.将指针指向右节点
123 | curr = curr.right;
124 | }
125 | return list;
126 | }
127 | }
128 | ```
129 |
130 | ### 后序遍历
131 |
132 | 后序遍历 `LeetCode` 官方题解给出了一个额外的思路,对于树的 **后序遍历** 而言,其遍历顺序与 **广度优先搜索(BFS)** 恰恰是相反的:
133 |
134 | 
135 |
136 | 如图所示,`BFS`的遍历顺序是 `1->2->3->4->5`,而相同的树后序遍历顺序则是 `4->5->2->3->1`。
137 |
138 | 因此,后序遍历的思路如下:
139 |
140 | > 从根节点开始依次迭代,弹出栈顶元素输出到输出列表中,然后依次压入它的所有孩子节点,按照从上到下、从左至右的顺序依次压入栈中。
141 |
142 | > 因为深度优先搜索后序遍历的顺序是从下到上、从左至右,所以需要将输出列表逆序输出。
143 |
144 | ```java
145 | /**
146 | * Definition for a binary tree node.
147 | * public class TreeNode {
148 | * int val;
149 | * TreeNode left;
150 | * TreeNode right;
151 | * TreeNode(int x) { val = x; }
152 | * }
153 | */
154 | class Solution {
155 | public List postorderTraversal(TreeNode root) {
156 | LinkedList output = new LinkedList<>();
157 | // 和传统的bfs不同,这里并没有用 Queue
158 | // 因为顺序是相反的,这里并不是取第一个元素,而是取栈顶的元素(即同层级节点从右->左遍历)
159 | Stack stack = new Stack<>();
160 |
161 | if (root == null) return output;
162 |
163 | stack.push(root);
164 |
165 | while(!stack.isEmpty()) {
166 | TreeNode node = stack.pop();
167 | output.addFirst(node.val);
168 |
169 | if (node.left != null) {
170 | stack.push(node.left);
171 | }
172 | if (node.right != null) {
173 | stack.push(node.right);
174 | }
175 | }
176 | return output;
177 | }
178 | }
179 | ```
180 |
181 | ## 参考 & 感谢
182 |
183 | 文章绝大部分内容节选自`LeetCode`,概述:
184 |
185 | * https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/2/traverse-a-tree/7/
186 |
187 | 例题:
188 |
189 | * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
190 | * https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
191 | * https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
192 |
193 |
194 | ## 关于我
195 |
196 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
197 |
198 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
199 |
200 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
201 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
202 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
203 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
204 |
--------------------------------------------------------------------------------
/src/Algorithm/哈希表原理及简单设计.md:
--------------------------------------------------------------------------------
1 | # 哈希表原理及简单设计
2 |
3 | 
4 |
5 | > 本文为博主算法学习过程中的学习笔记,主要内容来源于其他平台或书籍,出处请参考下方 **参考&感谢** 一节。
6 |
7 | ## 介绍
8 |
9 | **哈希表** 是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。
10 |
11 | 有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。
12 |
13 | * 哈希集合是 **集合** 数据结构的实现之一,用于存储 **非重复值**。
14 | * 哈希映射是 **映射** 数据结构的实现之一,用于存储`(key, value)`键值对。
15 |
16 | 通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现出色的性能。
17 |
18 | ## 原理及设计关键
19 |
20 | 哈希表的关键思想是使用哈希函数 **将键映射到存储桶**。更确切地说,
21 |
22 | * 1、当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
23 | * 2、当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。
24 |
25 | ### 1、哈希函数
26 |
27 | 哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。
28 |
29 | 散列函数将取决于 **键值的范围** 和 **桶的数量**。
30 |
31 | 哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。
32 |
33 | ### 2、解决冲突
34 |
35 | 理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。
36 |
37 | 冲突解决算法应该解决以下几个问题:
38 |
39 | 1、如何组织在同一个桶中的值?
40 | 2、如果为同一个桶分配了太多的值,该怎么办?
41 | 3、如何在特定的桶中搜索目标值?
42 |
43 | 根据我们的哈希函数,这些问题与 **桶的容量** 和可能映射到 **同一个桶的键的数目** 有关。
44 |
45 | 让我们假设存储最大键数的桶有 `N` 个键。
46 |
47 | 通常,如果 `N` 是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 N 是可变的或很大,我们可能需要使用 **高度平衡的二叉树** 来代替。
48 |
49 | ## 设计哈希集合
50 |
51 | 不使用任何内建的哈希表库设计一个哈希集合。
52 |
53 | 在本文中,我们使用单独链接法,来看看它是如何工作的。
54 |
55 | * 1、从本质上讲,`HashSet`的存储空间相当于连续内存数组,这个数组中的每个元素相当于一个桶。
56 | * 2、给定一个值,我们首先通过哈希函数生成对应的散列值来定位桶的位置。
57 | * 3、一旦找到桶的位置,则在该桶上做相对应的操作,如`add,remove,contains`。
58 |
59 | 对于桶的设计,我们有几种选择,可以使用数组来存储桶的所有值。然而数组的一个缺点是需要`O(N)`的时间复杂度进行插入和删除,而不是`O(1)`。
60 |
61 | 因为任何的更新操作,我们首先是需要扫描整个桶为了避免重复。选择链表来存储桶的所有值是更好的选择,插入和删除具有常数的时间复杂度。
62 |
63 | ```java
64 | public class MyHashSet {
65 |
66 | private int keyRange;
67 | private Bucket[] buckets;
68 |
69 | /**
70 | * Initialize your data structure here.
71 | */
72 | public MyHashSet() {
73 | this.keyRange = 793;
74 | this.buckets = new Bucket[this.keyRange];
75 |
76 | for (int i = 0; i < keyRange; i++) {
77 | buckets[i] = new Bucket();
78 | }
79 | }
80 |
81 | protected int _hash(int key) {
82 | return key % this.keyRange;
83 | }
84 |
85 | public void add(int key) {
86 | int index = this._hash(key);
87 | buckets[index].insert(key);
88 | }
89 |
90 | public void remove(int key) {
91 | int index = this._hash(key);
92 | buckets[index].delete(key);
93 | }
94 |
95 | public boolean contains(int key) {
96 | int index = this._hash(key);
97 | return buckets[index].contain(key);
98 | }
99 |
100 | class Bucket {
101 |
102 | private LinkedList container;
103 |
104 | Bucket() {
105 | this.container = new LinkedList<>();
106 | }
107 |
108 | void insert(Integer key) {
109 | int index = container.indexOf(key);
110 |
111 | if (index == -1)
112 | container.addFirst(key);
113 | }
114 |
115 | void delete(Integer key) {
116 | container.remove(key);
117 | }
118 |
119 | boolean contain(Integer key) {
120 | return container.indexOf(key) != -1;
121 | }
122 | }
123 | }
124 | ```
125 |
126 | ## 设计哈希映射
127 |
128 | 不使用任何内建的哈希表库设计一个哈希映射。
129 |
130 | ```java
131 | class MyHashMap {
132 |
133 | private int keyRange;
134 | private Node[] nodes;
135 |
136 | public MyHashMap() {
137 | this.keyRange = 793;
138 | this.nodes = new Node[this.keyRange];
139 | }
140 |
141 | protected int _hash(int key) {
142 | return key % this.keyRange;
143 | }
144 |
145 | public void put(int key, int value) {
146 | int index = this._hash(key);
147 | Node curr = nodes[index];
148 |
149 | if (curr == null) {
150 | Node node = new Node(key, value);
151 | nodes[index] = node;
152 | return;
153 | }
154 |
155 | while (curr != null) {
156 | if (curr.key == key) {
157 | curr.value = value;
158 | return;
159 | }
160 | if (curr.next == null) {
161 | Node node = new Node(key, value);
162 | node.prev = curr;
163 | node.next = curr.next; // curr.next = null
164 | curr.next = node;
165 | return;
166 | } else {
167 | curr = curr.next;
168 | }
169 | }
170 | }
171 |
172 | public int get(int key) {
173 | int index = this._hash(key);
174 | Node curr = nodes[index];
175 |
176 | while (curr != null) {
177 | if (curr.key == key) {
178 | return curr.value;
179 | } else {
180 | curr = curr.next;
181 | }
182 | }
183 | return -1;
184 | }
185 |
186 | public void remove(int key) {
187 | int index = this._hash(key);
188 | Node curr = nodes[index];
189 |
190 | if (curr != null && curr.key == key) {
191 | Node next = curr.next;
192 | if (next != null)
193 | next.prev = null;
194 | nodes[index] = next;
195 | return;
196 | }
197 |
198 | while (curr != null) {
199 | if (curr.key == key) {
200 | Node prev = curr.prev;
201 | Node next = curr.next;
202 |
203 | if (prev != null)
204 | prev.next = next;
205 | if (next != null)
206 | next.prev = prev;
207 | return;
208 | } else {
209 | curr = curr.next;
210 | }
211 | }
212 | }
213 |
214 | class Node {
215 |
216 | Integer key;
217 | Integer value;
218 |
219 | Node next;
220 | Node prev;
221 |
222 | Node(Integer key, Integer value) {
223 | this.key = key;
224 | this.value = value;
225 | }
226 | }
227 | }
228 | ```
229 |
230 | ## 参考 & 感谢
231 |
232 | 文章绝大部分内容节选自`LeetCode`,概述:
233 |
234 | * https://leetcode-cn.com/explore/learn/card/hash-table/203/design-a-hash-table/797/
235 |
236 | 例题:
237 |
238 | * https://leetcode-cn.com/problems/design-hashset/
239 | * https://leetcode-cn.com/problems/design-hashmap/
240 |
241 | ## 关于我
242 |
243 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
244 |
245 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
246 |
247 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
248 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
249 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
250 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
251 |
--------------------------------------------------------------------------------
/src/Algorithm/哈希集合用法及算法例题.md:
--------------------------------------------------------------------------------
1 | # 哈希集合用法及算法例题
2 |
3 | 
4 |
5 | > 本文为博主算法学习过程中的学习笔记,主要内容来源于其他平台或书籍,出处请参考下方 **参考&感谢** 一节。
6 |
7 | ## 用法
8 |
9 | **哈希集** 是集合的实现之一,它是一种存储 **不重复值** 的数据结构。
10 |
11 | 因此,通常,使用哈希集来检查该值是否已经出现过。
12 |
13 | 让我们来看一个例子:
14 |
15 | > 给定一个整数数组,查找数组是否包含任何重复项。
16 |
17 | 这是一个典型的问题,可以通过哈希集来解决。
18 |
19 | 你可以简单地迭代每个值并将值插入集合中。 如果值已经在哈希集中,则存在重复。
20 |
21 | ## 例题
22 |
23 | ### 1、存在重复元素
24 |
25 | #### 题目描述
26 |
27 | 给定一个整数数组,判断是否存在重复元素。
28 |
29 | 如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
30 |
31 | 示例:
32 |
33 | > 输入: [1,2,3,1]
34 | > 输出: true
35 |
36 | #### 解题思路及实现
37 |
38 | `HashSet`的入门题,最基本模版的实现。
39 |
40 | ```java
41 | class Solution {
42 | public boolean containsDuplicate(int[] nums) {
43 | HashSet set = new HashSet<>();
44 | for (int i = 0; i < nums.length; i++) {
45 | if (set.contains(nums[i])) {
46 | return true;
47 | } else {
48 | set.add(nums[i]);
49 | }
50 | }
51 | return false;
52 | }
53 | }
54 | ```
55 |
56 | ### 2、只出现一次的数字
57 |
58 | #### 题目描述
59 |
60 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
61 |
62 | 示例:
63 |
64 | >输入: [2,2,1]
65 | >输出: 1
66 |
67 | #### 解题思路及实现
68 |
69 | 这道题解决方案有很多种,`HashSet`是其中的一种解法,也比较基础。
70 |
71 | ```java
72 | class Solution {
73 | public int singleNumber(int[] nums) {
74 | HashSet set = new HashSet<>();
75 | for (int i = 0; i < nums.length; i++) {
76 | if (!set.contains(nums[i])) {
77 | set.add(nums[i]);
78 | } else {
79 | set.remove(nums[i]);
80 | }
81 | }
82 | return set.iterator().next();
83 | }
84 | }
85 | ```
86 |
87 | ### 3、两个数组的交集
88 |
89 | #### 题目描述
90 |
91 | 给定两个数组,编写一个函数来计算它们的交集。
92 |
93 | 示例:
94 |
95 | > 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
96 | > 输出: [9,4]
97 |
98 | 说明:
99 |
100 | * 输出结果中的每个元素一定是唯一的。
101 | * 我们可以不考虑输出结果的顺序。
102 |
103 | #### 解题思路及实现
104 |
105 | 有点繁琐,`HashSet`的暴力破解法:
106 |
107 | ```java
108 | class Solution {
109 | public int[] intersection(int[] nums1, int[] nums2) {
110 | // 暴力破解之
111 | int len1 = nums1.length;
112 | int len2 = nums2.length;
113 | if (len1 == 0 || len2 == 0) return new int[]{};
114 |
115 | HashSet set1 = new HashSet<>();
116 | for (int i = 0; i < len1; i++)
117 | set1.add(nums1[i]);
118 | HashSet set2 = new HashSet<>();
119 | for (int i = 0; i < len2; i++)
120 | set2.add(nums2[i]);
121 |
122 | int[] result = new int[set1.size()];
123 | int index = 0;
124 | for (int value : set1) {
125 | if (set2.contains(value)) {
126 | result[index] = value;
127 | index++;
128 | }
129 | }
130 | return Arrays.copyOf(result,index);
131 | }
132 | }
133 | ```
134 |
135 | ### 4、快乐数
136 |
137 | #### 题目描述
138 |
139 | 编写一个算法来判断一个数是不是“快乐数”。
140 |
141 | 一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
142 |
143 | 示例:
144 |
145 | > 输入: 19
146 | 输出: true
147 | 解释:
148 | 12 + 92 = 82
149 | 82 + 22 = 68
150 | 62 + 82 = 100
151 | 12 + 02 + 02 = 1
152 |
153 | #### 解题思路及实现
154 |
155 | 这道题是非常有意思的一道题,可以思考不难得出,这种一个正整数的循环计算,每位上数字的平方和,计算结果最多也就3、4位数——也就是说,最终经过有限的步骤,最终都会进入循环。
156 |
157 | 因此我们每次计算都将结果存入`HashSet`,出现`1`时自然就是快乐数;而当出现循环时若结果还没有出现`1`,意味着接下来再也不会出现`1`,因此这个数字也就不是快乐数了:
158 |
159 | ```java
160 | class Solution {
161 | public boolean isHappy(int n) {
162 | HashSet set = new HashSet<>();
163 | set.add(n);
164 |
165 | while (true) {
166 | n = change(n);
167 | if (n == 1)
168 | return true;
169 | if (set.contains(n)) {
170 | return false;
171 | } else {
172 | set.add(n);
173 | }
174 | }
175 | }
176 |
177 | private int change(int n) {
178 | int sum = 0;
179 | while (n > 0) {
180 | int bit = n % 10;
181 | sum += bit * bit;
182 | n = n / 10;
183 | }
184 | return sum;
185 | }
186 | }
187 | ```
188 |
189 | 之所以说这道题有意思的地方在于,另外一种视角来看,我们可以将每次计算的结果串联起来,这样问题就转换为了 [弗洛伊德的乌龟与兔子](https://github.com/qingmei2/blogs/issues/33) 问题,通过 **快慢指针** 进行解决。
190 |
191 | ## 参考 & 感谢
192 |
193 | 文章绝大部分内容节选自`LeetCode`,概述:
194 |
195 | * https://leetcode-cn.com/explore/learn/card/hash-table/204/practical-application-hash-set/803/
196 |
197 | 例题:
198 |
199 | * https://leetcode-cn.com/problems/contains-duplicate/
200 | * https://leetcode-cn.com/problems/single-number/
201 | * https://leetcode-cn.com/problems/intersection-of-two-arrays/
202 | * https://leetcode-cn.com/problems/happy-number/
203 |
204 | ## 关于我
205 |
206 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
207 |
208 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
209 |
210 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
211 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
212 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
213 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
214 |
--------------------------------------------------------------------------------
/src/Algorithm/弗洛伊德的乌龟与兔子.md:
--------------------------------------------------------------------------------
1 | # 弗洛伊德的乌龟与兔子
2 |
3 | 
4 |
5 | `Floyd` 判圈算法(`Floyd Cycle Detection Algorithm`),又称龟兔赛跑算法(`Tortoise and Hare Algorithm`),是一个可以在有限状态机、迭代函数或者链表上判断是否存在环,以及判断环的起点与长度的算法。
6 |
7 | ## 结论
8 |
9 | * 1、如果链表上存在环,那么在某个环上以不同速度前进的2个指针必定会在某个时刻相遇;
10 | * 2、根据结论1找到的相遇点可找到环的入口,初始化额外的两个指针: `ptr1` ,指向链表的头, `ptr2` 指向相遇点。然后,每次将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口。
11 |
12 | 结论1是很显然的,结论2似乎有点匪夷所思,下面将针对以上结论分别进行证明。
13 |
14 | ## 证明
15 |
16 | ### 1.龟兔相遇
17 |
18 | > 一个跑得快的人和一个跑得慢的人在一个圆形的赛道上赛跑,会发生什么?在某一个时刻,跑得快的人一定会从后面赶上跑得慢的人。
19 |
20 | 下图说明了这个算法的工作方式。
21 |
22 | 
23 |
24 | 初始状态下,假设已知某个起点节点为节点F。现设两个指针 `fast` 和 `slow`,将它们均指向F。
25 |
26 | 同时让 `fast` 和 `slow` 往前推进,`fast `的速度为 `slow` 的2倍),直到 `fast` 无法前进,即到达某个没有后继的节点时,就可以确定从F出发不会遇到环。反之当 `fast` 和 `slow` 再次相遇时,就可以确定从F出发一定会进入某个环,设其为环C( `fast` 和 `slow` 推进的步数差是环长的倍数)。
27 |
28 | ### 2.计算环的入口
29 |
30 | 如何找到环的入口?
31 |
32 | > 根据结论1找到的相遇点可找到环的入口,初始化额外的两个指针: `ptr1` ,指向链表的头, `ptr2` 指向相遇点。然后,每次将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口。
33 |
34 | 下图对结论2进行证明。
35 |
36 | 
37 |
38 | 我们利用已知的条件:慢指针移动 1 步,快指针移动 2 步,来说明它们相遇在环的入口处:(下面证明中的 tortoise 表示慢指针,hare 表示快指针)
39 |
40 | 
41 |
42 | 因为 `F = b` ,指针从 `h` 点出发和从链表的头出发,最后会遍历相同数目的节点后在环的入口处相遇。
43 |
44 | ## 算法描述
45 |
46 | ```Java
47 | public class Solution {
48 | private ListNode getIntersect(ListNode head) {
49 | ListNode tortoise = head;
50 | ListNode hare = head;
51 |
52 | while (hare != null && hare.next != null) {
53 | tortoise = tortoise.next;
54 | hare = hare.next.next;
55 | if (tortoise == hare) {
56 | return tortoise;
57 | }
58 | }
59 |
60 | return null;
61 | }
62 |
63 | public ListNode detectCycle(ListNode head) {
64 | if (head == null) {
65 | return null;
66 | }
67 |
68 | // 通过结论1,找到相遇点
69 | ListNode intersect = getIntersect(head);
70 | // 相遇点为空,则链表为非循环链表
71 | if (intersect == null) {
72 | return null;
73 | }
74 |
75 | // 通过结论2,找到环的入口
76 | // 分别定义两个指针,从head和相遇点开始前进,相遇点即为环的入口
77 | ListNode ptr1 = head;
78 | ListNode ptr2 = intersect;
79 | while (ptr1 != ptr2) {
80 | ptr1 = ptr1.next;
81 | ptr2 = ptr2.next;
82 | }
83 |
84 | return ptr1;
85 | }
86 | }
87 | ```
88 |
89 | * 时间复杂度:`O(n)`
90 | * 空间复杂度:`O(1)`
91 |
92 |
93 | ## 例题
94 |
95 | ### 142. 环形链表 II
96 |
97 | #### 题目描述
98 |
99 | 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。
100 |
101 | 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从0开始)。 如果 `pos` 是`-1`,则在该链表中没有环。
102 |
103 | 说明:不允许修改给定的链表。
104 |
105 | * 难度:`Medium`
106 |
107 | #### 解题思路
108 |
109 | 经典的 `Floyd` 算法的应用场景。
110 |
111 | ```Java
112 | public class Solution {
113 | public ListNode detectCycle(ListNode head) {
114 | if (head == null || head.next == null) return null;
115 |
116 | ListNode slow = head;
117 | ListNode fast = head;
118 |
119 | while (true) {
120 | if (fast == null || fast.next == null) {
121 | return null;
122 | }
123 | fast = fast.next.next;
124 | slow = slow.next;
125 | if (fast == slow) break;
126 | }
127 |
128 | fast = head;
129 | while (fast != slow) {
130 | fast = fast.next;
131 | slow = slow.next;
132 | }
133 | return fast;
134 | }
135 | }
136 | ```
137 |
138 | ### 287. 寻找重复数
139 |
140 | #### 题目描述
141 |
142 | 给定一个包含 `n + 1` 个整数的数组 `nums`,其数字都在 `1` 到 `n` 之间( 包括 `1` 和 `n` ),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
143 |
144 | * 示例 1:
145 |
146 | >输入: [1,3,4,2,2]
147 | 输出: 2
148 |
149 | * 示例 2:
150 |
151 | >输入: [3,1,3,4,2]
152 | 输出: 3
153 |
154 | * 难度:`Medium`
155 |
156 | #### 解题思路
157 |
158 | 正常的思路是通过 `HashSet` , 或者通过排序以迅速找到重复数。
159 |
160 | 前者的时间和空间复杂度为 `O(N)`,后者排序解决方案的时间复杂度为 `O(NlogN)` 空间复杂度为 `O(1)`。
161 |
162 | 可以取巧的是,这道题因为题目的关系,可以将题目中数组视为 **索引** 与 **对应值** 的关系视为一个 **链表**,因为重复数的关系,它还是一个 **循环链表**,因此依然可以通过 `Floyd` 算法解决:
163 |
164 | ```Java
165 | class Solution {
166 | public int findDuplicate(int[] nums) {
167 | int fast = nums[0];
168 | int slow = nums[0];
169 |
170 | // 找到相遇节点
171 | while (true) {
172 | slow = nums[slow];
173 | fast = nums[nums[fast]];
174 |
175 | if (slow == fast) {}
176 | }
177 |
178 | // 找到重复数
179 | int ptr1 = nums[0];
180 | int ptr2 = fast;
181 |
182 | while (ptr1 != ptr2) {
183 | ptr1 = nums[ptr1];
184 | ptr2 = nums[ptr2];
185 | }
186 |
187 | return ptr1;
188 | }
189 | }
190 | ```
191 |
192 | ## 参考
193 |
194 | * [LeetCode-142:环形链表](https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode/)
195 | * [Wiki百科:Floyd判圈算法](https://zh.wikipedia.org/wiki/Floyd%E5%88%A4%E5%9C%88%E7%AE%97%E6%B3%95)
196 |
197 | ## 关于我
198 |
199 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
200 |
201 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
202 |
203 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
204 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
205 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
206 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
207 |
--------------------------------------------------------------------------------
/src/Algorithm/循环与循环双端队列.md:
--------------------------------------------------------------------------------
1 | # 循环与循环双端队列
2 |
3 | 
4 |
5 | **循环队列** 是一种线性数据结构,其操作表现基于 `FIFO`(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为 **环形缓冲器**。
6 |
7 | 循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
8 |
9 | ## 为什么使用循环队列?
10 |
11 | 这里我们先对 **队列** 的简单实现进行简单展示,队列应支持两种操作:**入队** 和 **出队** 。
12 |
13 | ```Java
14 | class MyQueue {
15 | // store elements
16 | private List data;
17 | // a pointer to indicate the start position
18 | private int p_start;
19 | public MyQueue() {
20 | data = new ArrayList();
21 | p_start = 0;
22 | }
23 | /** Insert an element into the queue. Return true if the operation is successful. */
24 | public boolean enQueue(int x) {
25 | data.add(x);
26 | return true;
27 | };
28 | /** Delete an element from the queue. Return true if the operation is successful. */
29 | public boolean deQueue() {
30 | if (isEmpty() == true) {
31 | return false;
32 | }
33 | p_start++;
34 | return true;
35 | }
36 | /** Get the front item from the queue. */
37 | public int Front() {
38 | return data.get(p_start);
39 | }
40 | /** Checks whether the queue is empty or not. */
41 | public boolean isEmpty() {
42 | return p_start >= data.size();
43 | }
44 | }
45 | ```
46 |
47 | ### 缺点
48 |
49 | 上面的实现很简单,但在某些情况下效率很低。 随着起始指针的移动,浪费了越来越多的空间。 当我们有空间限制时,这将是难以接受的。
50 |
51 | 
52 |
53 | 让我们考虑一种情况,即我们只能分配一个最大长度为 5 的数组。当我们只添加少于 5 个元素时,我们的解决方案很有效。 例如,如果我们只调用入队函数四次后还想要将元素 10 入队,那么我们可以成功。
54 |
55 | 但是我们不能接受更多的入队请求,这是合理的,因为现在队列已经满了。但是如果我们将一个元素出队呢?
56 |
57 | 
58 |
59 | 实际上,在这种情况下,我们应该能够再接受一个元素。
60 |
61 | **循环队列** 的需求迫在眉睫。
62 |
63 | ## 设计思想
64 |
65 | 这里直接复制了 [liweiwei1419](https://leetcode-cn.com/u/liweiwei1419/) 精彩的 [题解](https://leetcode-cn.com/problems/design-circular-queue/solution/shu-zu-shi-xian-de-xun-huan-dui-lie-by-liweiwei141/) :
66 |
67 | **1、定义循环变量 front 和 rear 。一直保持这个定义,到底是先赋值还是先移动指针就很容易想清楚了。**
68 |
69 | `front`:指向队列头部第 `1` 个有效数据的位置;
70 | `rear`:指向队列尾部(即最后 `1` 个有效数据)的下一个位置,即下一个从队尾入队元素的位置。
71 |
72 | (说明:这个定义是依据“动态数组”的定义模仿而来。)
73 |
74 | **2、为了避免“队列为空”和“队列为满”的判别条件冲突,我们有意浪费了一个位置。**
75 |
76 | 浪费一个位置是指:循环数组中任何时刻一定至少有一个位置不存放有效元素。
77 |
78 | 判别队列为空的条件是:`front == rear`;
79 |
80 | 判别队列为满的条件是:`(rear + 1) % capacity == front`;。可以这样理解,当 `rear` 循环到数组的前面,要从后面追上 `front`,还差一格的时候,判定队列为满。
81 |
82 | **3、因为有循环的出现,要特别注意处理数组下标可能越界的情况。指针后移的时候,索引 + 1,并且要注意取模。**
83 |
84 | ## 例题
85 |
86 | 本文的写作目的就是为了 **再次手写一边算法练手**。
87 |
88 | ### 622.设计循环队列
89 |
90 | * 难度:`Medium`
91 |
92 | 参考上述设计思路,很容易编写代码,需要注意的是取模代码的编写相对容易出问题。
93 |
94 | 实现代码:
95 |
96 | ```Java
97 | class MyCircularQueue {
98 |
99 | private int front;
100 | private int tail;
101 | private int capacity;
102 | private int[] arr;
103 |
104 | public MyCircularQueue(int k) {
105 | this.capacity = k + 1;
106 | this.arr = new int[capacity];
107 |
108 | this.front = 0;
109 | this.tail = 0;
110 | }
111 |
112 | public boolean enQueue(int value) {
113 | if (isFull()) return false;
114 |
115 | arr[tail] = value;
116 | tail = (tail + 1) % capacity;
117 | return true;
118 | }
119 |
120 | public boolean deQueue() {
121 | if (isEmpty()) return false;
122 |
123 | front = (front + 1) % capacity;
124 | return true;
125 | }
126 |
127 | public int Front() {
128 | if (isEmpty()) return -1;
129 | return arr[front];
130 | }
131 |
132 | public int Rear() {
133 | if (isEmpty()) return -1;
134 | return arr[(tail - 1 + capacity) % capacity];
135 | }
136 |
137 | public boolean isEmpty() {
138 | return front == tail;
139 | }
140 |
141 | public boolean isFull() {
142 | return (tail + 1) % capacity == front;
143 | }
144 | }
145 | ```
146 |
147 | ### 641. 设计循环双端队列
148 |
149 | * 难度:`Medium`
150 |
151 | 和上题基本相似,除了多几个取模运算,几乎没什么变化。
152 |
153 | ```Java
154 | class MyCircularDeque {
155 |
156 | private int front;
157 | private int tail;
158 | private int capacity;
159 | private int[] arr;
160 |
161 | public MyCircularDeque(int k) {
162 | this.capacity = k + 1;
163 | this.arr = new int[capacity];
164 |
165 | this.front = 0;
166 | this.tail = 0;
167 | }
168 |
169 | public boolean insertFront(int value) {
170 | if (isFull()) return false;
171 |
172 | front = (front - 1 + capacity) % capacity;
173 | arr[front] = value;
174 | return true;
175 | }
176 |
177 | public boolean insertLast(int value) {
178 | if (isFull()) return false;
179 |
180 | arr[tail] = value;
181 | tail = (tail + 1) % capacity;
182 | return true;
183 | }
184 |
185 | public boolean deleteFront() {
186 | if (isEmpty()) return false;
187 |
188 | front = (front + 1) % capacity;
189 | return true;
190 | }
191 |
192 | public boolean deleteLast() {
193 | if (isEmpty()) return false;
194 |
195 | tail = (tail - 1 + capacity) % capacity;
196 | return true;
197 | }
198 |
199 | public int getFront() {
200 | if (isEmpty()) return -1;
201 | return arr[front];
202 | }
203 |
204 | public int getRear() {
205 | if (isEmpty()) return -1;
206 | return arr[(tail - 1 + capacity) % capacity];
207 | }
208 |
209 | public boolean isEmpty() {
210 | return front == tail;
211 | }
212 |
213 | public boolean isFull() {
214 | return (tail + 1) % capacity == front;
215 | }
216 | }
217 | ```
218 |
219 | ## 参考 & 感谢
220 |
221 | 文章绝大部分内容节选自`LeetCode`:
222 |
223 | https://leetcode-cn.com/problems/design-circular-queue
224 |
225 | https://leetcode-cn.com/problems/design-circular-deque
226 |
227 | 感谢 [liweiwei1419](https://leetcode-cn.com/u/liweiwei1419/) 提供的精彩的 [题解](https://leetcode-cn.com/problems/design-circular-queue/solution/shu-zu-shi-xian-de-xun-huan-dui-lie-by-liweiwei141/) 。
228 |
229 | ## 关于我
230 |
231 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
232 |
233 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
234 |
235 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
236 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
237 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
238 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
239 |
--------------------------------------------------------------------------------
/src/Algorithm/运用递归解决二叉树相关问题.md:
--------------------------------------------------------------------------------
1 | # 运用递归解决二叉树相关问题
2 |
3 | 
4 |
5 | 在之前的章节中,我们已经介绍了如何解决树的遍历问题。我们也已经尝试过使用递归解决树的为 **前序遍历** 、 **中序遍历** 和 **后序遍历** 问题。
6 |
7 | 事实上,**递归** 是解决树相关问题的最有效和最常用的方法之一。本节中,我们将会介绍两种典型的递归方法。
8 |
9 | ## 解决方案
10 |
11 | > 本小节内容节选自 [LeetCode:运用递归解决树的问题](https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/3/solve-problems-recursively/11/) .
12 |
13 | 递归是解决树的相关问题最有效和最常用的方法之一。
14 |
15 | 我们知道,树可以以递归的方式定义为一个节点(根节点),它包括一个值和一个指向其他节点指针的列表。 递归是树的特性之一。 因此,许多树问题可以通过递归的方式来解决。 对于每个递归层级,我们只能关注单个节点内的问题,并通过递归调用函数来解决其子节点问题。
16 |
17 | 通常,我们可以通过 **自顶向下** 或 **自底向上** 的递归来解决树问题。
18 |
19 | ### “自顶向下” 的解决方案
20 |
21 | **自顶向下** 意味着在每个递归层级,我们将首先访问节点来计算一些值,并在递归调用函数时将这些值传递到子节点。 所以 **自顶向下** 的解决方案可以被认为是一种 **前序遍历**。 具体来说,递归函数 `top_down(root, params)` 的原理是这样的:
22 |
23 | * 1、在`null`值的情况下返回指定的值;
24 | * 2、如果有必要,更新`answer`;
25 | * 3、`left_ans = top_down(root.left, left_params)`;
26 | * 4、`right_ans = top_down(root.right, right_params)`;
27 | * 5、如果有必要返回`answer`。
28 |
29 | ### “自底向上” 的解决方案
30 |
31 | **自底向上** 是另一种递归方法。 在每个递归层次上,我们首先对所有子节点递归地调用函数,然后根据返回值和根节点本身的值得到答案。 这个过程可以看作是 **后序遍历** 的一种。 通常, **自底向上** 的递归函数 `bottom_up(root)` 为如下所示:
32 |
33 | * 1、在`null`值的情况下返回指定的值;
34 | * 2、`left_ans = top_down(root.left, left_params)`;
35 | * 3、`right_ans = top_down(root.right, right_params)`;
36 | * 4、返回`answer`。
37 |
38 | ## 例题
39 |
40 | ### 1、二叉树的最大深度
41 |
42 | #### 题目描述
43 |
44 | 
45 |
46 | #### 解题思路及实现
47 |
48 | #### 1、自顶向下
49 |
50 | ```java
51 | class Solution {
52 | private int answer = 0;
53 |
54 | public int maxDepth(TreeNode root) {
55 | max_depth(root, 0);
56 | return answer;
57 | }
58 |
59 | private void max_depth(TreeNode root, int depth) {
60 | if (root == null) return;
61 |
62 | if (root.left == null && root.right == null) {
63 | answer = Math.max(depth, answer);
64 | return;
65 | }
66 |
67 | max_depth(root.left, depth + 1);
68 | max_depth(root.right, depth + 1);
69 | }
70 | }
71 | ```
72 |
73 | #### 2、自底向上
74 |
75 | ```java
76 | class Solution {
77 |
78 | public int maxDepth(TreeNode root) {
79 | return max_depth2(root);
80 | }
81 |
82 | private int max_depth2(TreeNode root) {
83 | if (root == null)
84 | return 0;
85 |
86 | int leftDepth = max_depth2(root.left) + 1;
87 | int rightDepth = max_depth2(root.right) + 1;
88 | return Math.max(leftDepth, rightDepth);
89 | }
90 | }
91 | ```
92 |
93 | ### 2、对称二叉树
94 |
95 | #### 题目描述
96 |
97 | 
98 |
99 | #### 解题思路及实现
100 |
101 | 这道题笔者的思路是迭代,后来发现非常困难,看了题解才发现,将同一个树作为2次参数分别放入递归函数进行递归,确实是一个很棒的思路。
102 |
103 | ```java
104 | class Solution {
105 |
106 | public boolean isSymmetric(TreeNode root) {
107 | return isMirror(root, root);
108 | }
109 |
110 | private boolean isMirror(TreeNode t1, TreeNode t2) {
111 | if (t1 == null && t2 == null) return true;
112 | if (t1 == null || t2 == null) return false;
113 |
114 | return t1.val == t2.val && isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left);
115 | }
116 | }
117 | ```
118 |
119 | ### 3、路径总和
120 |
121 | #### 题目描述
122 |
123 | 
124 |
125 | #### 解题思路及实现
126 |
127 | 比较简单,标准的`dfs`进行递归:
128 |
129 | ```java
130 | class Solution {
131 |
132 | public boolean hasPathSum(TreeNode root, int sum) {
133 | return dfs(root, 0, sum);
134 | }
135 |
136 | private boolean dfs(TreeNode root, int currentSum, int target) {
137 | if (root == null) {
138 | return false;
139 | }
140 | int sum = currentSum + root.val;
141 |
142 | if (root.left != null || root.right != null) {
143 | return dfs(root.left, sum, target) || dfs(root.right, sum, target);
144 | } else {
145 | return sum == target;
146 | }
147 | }
148 | }
149 | ```
150 |
151 | ## 参考 & 感谢
152 |
153 | 文章绝大部分内容节选自`LeetCode`,概述:
154 |
155 | * https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/3/solve-problems-recursively/11/
156 |
157 | 例题:
158 |
159 | * https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
160 | * https://leetcode-cn.com/problems/symmetric-tree/
161 | * https://leetcode-cn.com/problems/path-sum/
162 |
163 | ## 关于我
164 |
165 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
166 |
167 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
168 |
169 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
170 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
171 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
172 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
173 |
--------------------------------------------------------------------------------
/src/Android-Core/Camera/Android-Camera-系列(二)控制Camera.md:
--------------------------------------------------------------------------------
1 | > Camera系列文章首发于 [我的慕课网](https://www.imooc.com/u/6936865),欢迎关注。
2 |
3 | ## 概述
4 | **Camera** 可能是接下来个人想深入学习的课题,准备新起一个系列,从个人的角度总结阐述自己对于 **Android Camera** 的研究过程,希望也能够对其他想学习 **Camera** 的同学一些帮助。
5 |
6 | > 本小节内容为 **Android Camera 官方文档** 的精要翻译,原文请参考:
7 | > * [Android Camera: Control the camera](https://developer.android.com/training/camera/cameradirect)
8 |
9 | ## 一、打开相机对象
10 |
11 | 获取`Camera`对象的实例是自定义相机的第一步。 作为Android的 **自定义相机应用**,更推荐是启动一个单独的线程并在`onCreate()`生命周期的回调打开`Camera`。 打开摄像机的操作延迟到`onResume()`方法中处理是一个不错的注意,因为`Camera`的初始化可能需要一段时间,而在UI线程这样做则有可能让应用变得卡顿。
12 |
13 | 如果摄像头已被其他应用程序使用,则调用`Camera.open()`会抛出异常,因此我们将其包装在`try{}`中。
14 |
15 | ```kotlin
16 | private fun safeCameraOpen(id: Int): Boolean {
17 | return try {
18 | releaseCameraAndPreview()
19 | mCamera = Camera.open(id)
20 | true
21 | } catch (e: Exception) {
22 | Log.e(getString(R.string.app_name), "failed to open Camera")
23 | e.printStackTrace()
24 | false
25 | }
26 | }
27 |
28 | private fun releaseCameraAndPreview() {
29 | mPreview?.setCamera(null)
30 | mCamera?.also { camera ->
31 | camera.release()
32 | mCamera = null
33 | }
34 | }
35 | ```
36 |
37 | ## 二、创建相机预览
38 |
39 | ### 预览类
40 | 要开始显示相机的预览,您需要预览类。 预览需要实现`android.view.SurfaceHolder.Callback`接口,该接口用于将图像数据从相机硬件传递到应用程序。
41 |
42 | ```kotlin
43 | class Preview(
44 | context: Context,
45 | val mSurfaceView: SurfaceView = SurfaceView(context)
46 | ) : ViewGroup(context), SurfaceHolder.Callback {
47 |
48 | var mHolder: SurfaceHolder = mSurfaceView.holder.apply {
49 | addCallback(this@Preview)
50 | setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
51 | }
52 | ...
53 | }
54 | ```
55 |
56 | 必须先将预览类传递给`Camera`对象,然后才能启动实时图像预览,如下一节所示。
57 |
58 | ### 配置并开始预览
59 |
60 | `Camera`的配置和预览等相关操作都需要**严格地控制顺序**,`Camera`对象首当其冲, 在下面的代码片段中,`Camera`的初始化过程被封装,以便每当用户做某事来更改`Camera`都会调用`setCamera()`方法,即调用`Camera.startPreview()`。 还必须在预览类的`surfaceChanged()`回调方法中重新启动预览。
61 |
62 | ```kotlin
63 | fun setCamera(camera: Camera?) {
64 | if (mCamera == camera) {
65 | return
66 | }
67 |
68 | stopPreviewAndFreeCamera()
69 |
70 | mCamera = camera
71 |
72 | mCamera?.apply {
73 | mSupportedPreviewSizes = parameters.supportedPreviewSizes
74 | requestLayout()
75 |
76 | try {
77 | setPreviewDisplay(mHolder)
78 | } catch (e: IOException) {
79 | e.printStackTrace()
80 | }
81 |
82 | // Important: Call startPreview() to start updating the preview
83 | // surface. Preview must be started before you can take a picture.
84 | startPreview()
85 | }
86 | }
87 | ```
88 |
89 | ## 三、修改Camera的配置
90 |
91 | 相机设置会改变相机拍摄照片的方式,从**缩放级别**到**曝光补偿**。 下述代码示例仅展示了如何更改预览大小:
92 |
93 | ```kotlin
94 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
95 | mCamera?.apply {
96 | // Now that the size is known, set up the camera parameters and begin
97 | // the preview.
98 | parameters?.also { params ->
99 | params.setPreviewSize(mPreviewSize.width, mPreviewSize.height)
100 | requestLayout()
101 | parameters = params
102 | }
103 |
104 | // Important: Call startPreview() to start updating the preview surface.
105 | // Preview must be started before you can take a picture.
106 | startPreview()
107 | }
108 | }
109 | ```
110 |
111 | ## 四、设置预览方向
112 |
113 | 大多数相机应用程序将显示器锁定为**横向模式**,因为这是**相机传感器的物理方向**。 此设置不会阻止您拍摄纵向模式照片,因为设备的方向记录在**EXIF Header**中。 [setCameraDisplayOrientation()](https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)) 方法允许您更改预览的显示方式,而不会影响图片的记录方式。
114 |
115 | ```kotlin
116 | // kotlin版本
117 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
118 | fun setCameraDisplayOrientation(activity: Activity,
119 | cameraId: Int = 0,
120 | camera: Camera) {
121 | val info = Camera.CameraInfo()
122 | Camera.getCameraInfo(cameraId, info)
123 | val rotation = activity.windowManager.defaultDisplay.rotation
124 | var degrees = 0
125 | when (rotation) {
126 | Surface.ROTATION_0 -> degrees = 0
127 | Surface.ROTATION_90 -> degrees = 90
128 | Surface.ROTATION_180 -> degrees = 180
129 | Surface.ROTATION_270 -> degrees = 270
130 | }
131 | var result: Int
132 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
133 | result = (info.orientation + degrees) % 360
134 | result = (360 - result) % 360 // compensate the mirror
135 | } else { // back-facing
136 | result = (info.orientation - degrees + 360) % 360
137 | }
138 | camera.setDisplayOrientation(result)
139 | }
140 | ```
141 | ## 五、拍照
142 |
143 | 预览开始展示后,可以使用`Camera.takePicture()`方法拍摄照片。 您可以创建`Camera.PictureCallback`和`Camera.ShutterCallback`对象,并将它们传递给`Camera.takePicture()`。
144 |
145 | 如果要连续抓取图像,可以创建一个实现`onPreviewFrame()`的`Camera.PreviewCallback`。 对于介于两者之间的内容,您可以仅捕获选定的预览帧,或设置延迟操作以调用`takePicture()`。
146 |
147 | ## 六、重新启动预览
148 |
149 | 拍摄照片后,必须重新开始预览,然后用户才能拍摄另一张照片。 下述代码展示了如何通过重载快门按钮完成重启:
150 |
151 | ```kotlin
152 | fun onClick(v: View) {
153 | mPreviewState = if (mPreviewState == K_STATE_FROZEN) {
154 | mCamera?.startPreview()
155 | K_STATE_PREVIEW
156 | } else {
157 | mCamera?.takePicture(null, rawCallback, null)
158 | K_STATE_BUSY
159 | }
160 | shutterBtnConfig()
161 | }
162 | ```
163 |
164 | ## 七、停止预览并释放相机
165 |
166 | 当`Camera`功能使用完毕后,释放对应的资源保证相机能被其它应用所再次使用,至于释放时机,在预览View的`surfaceDestroyed()`处理是个不错的选择。
167 |
168 | ```kotlin
169 | override fun surfaceDestroyed(holder: SurfaceHolder) {
170 | // Surface will be destroyed when we return, so stop the preview.
171 | // Call stopPreview() to stop updating the preview surface.
172 | mCamera?.stopPreview()
173 | }
174 |
175 | /**
176 | * When this function returns, mCamera will be null.
177 | */
178 | private fun stopPreviewAndFreeCamera() {
179 | mCamera?.apply {
180 | // Call stopPreview() to stop updating the preview surface.
181 | stopPreview()
182 |
183 | // Important: Call release() to release the camera for use by other
184 | // applications. Applications should release the camera immediately
185 | // during onPause() and re-open() it during onResume()).
186 | release()
187 |
188 | mCamera = null
189 | }
190 | }
191 | ```
192 |
193 | > **笔者注**:官方文档关于《控制Camera》小节的课程文档,归纳得实在是无力吐槽,关于`Camera`各项参数的配置方式请参考笔者接下来的文章,本小节的作用请将其视为Camera相关知识的**系统化了解**。
194 |
--------------------------------------------------------------------------------
/src/Android-Core/Handler/Handler.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Core/Handler/Handler.xmind
--------------------------------------------------------------------------------
/src/Android-Core/ThreadLocal/ThreadLocal.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Core/ThreadLocal/ThreadLocal.xmind
--------------------------------------------------------------------------------
/src/Android-Core/ThreadLocal/ThreadLocal源码解析.md:
--------------------------------------------------------------------------------
1 | # ThreadLocal原理分析
2 |
3 | > 接下来笔者的文章方向偏向于 Android & Java 面试相关知识点系统性的总结,欢迎关注。
4 |
5 | `ThreadLocal`类是`java.lang`包下的一个类,用于线程内部的数据存储,通过它可以在指定的线程中存储数据,本文针对该类进行原理分析。
6 |
7 | 通过思维导图对其进行简单的总结:
8 |
9 | 
10 |
11 | ## 一.ThreadLocal源码分析
12 |
13 | `ThreadLocal`类最重要的几个方法如下:
14 |
15 | * get():T 获取当前线程下存储的变量副本
16 | * set(T):void 存储该线程下的某个变量副本
17 | * remove():void 移除该线程下的某个变量副本
18 |
19 | ### 1.get()方法分析
20 |
21 | `ThreadLocal`类比较简单,其最重要的就是`get()`和`set()`方法,顾名思义,起作用就是取值和设置值:
22 |
23 | ```java
24 | // 获取当前线程中的变量副本
25 | public T get() {
26 | // 获取当前线程
27 | Thread t = Thread.currentThread();
28 | // 获取线程中的ThreadLocalMap对象
29 | ThreadLocalMap map = getMap(t);
30 | if (map != null) {
31 | ThreadLocalMap.Entry e = map.getEntry(this);
32 | if (e != null) {
33 | @SuppressWarnings("unchecked")
34 | // 获取变量副本并返回
35 | T result = (T)e.value;
36 | return result;
37 | }
38 | }
39 | // 若没有该变量副本,返回setInitialValue()
40 | return setInitialValue();
41 | }
42 | ```
43 |
44 | 这里先将`ThreadLocalMap`暂时理解为一个`Map`结构的容器,内部存储着该线程作用域下的的所有变量副本,我们从`ThreadLocal`类中取值的时候,实际上是从`ThreadLocalMap`中取值。
45 |
46 | 如果`Map`中没有该变量的副本,会从`setInitialValue()`中取值:
47 |
48 | ```Java
49 | private T setInitialValue() {
50 | T value = initialValue();
51 | Thread t = Thread.currentThread();
52 | ThreadLocalMap map = getMap(t);
53 | if (map != null)
54 | map.set(this, value);
55 | else
56 | createMap(t, value);
57 | return value;
58 | }
59 | ```
60 |
61 | 可以看到,`setInitialValue()`中也非常的简单,依然是从当前线程中获取到`ThreadLocalMap`,略微不同的是,`setInitialValue()`会对变量进行初始化,存入`ThreadLocalMap`中并返回。
62 |
63 | 这个初始化的方法的执行,需要开发者自己重写`initialValue()`方法,否则返回值依然为`null`。
64 |
65 | ```java
66 | public class ThreadLocal {
67 | // ...
68 | protected T initialValue() {
69 | return null;
70 | }
71 | }
72 | ```
73 |
74 | ### 2.set()方法分析
75 |
76 | 和`setInitialValue()`方法类似,`set()`方法也非常简单:
77 | ```java
78 | public void set(T value) {
79 | Thread t = Thread.currentThread();
80 | ThreadLocalMap map = getMap(t);
81 | if (map != null)
82 | // map不为空,直接将ThreadLocal对象作为key
83 | // 变量本身的值为value,存入map
84 | map.set(this, value);
85 | else
86 | // 否则,创建ThreadLocalMap
87 | createMap(t, value);
88 | }
89 | ```
90 |
91 | 可以看到,这个方法的作用就是将变量副本作为`value`存入`Map`,需要注意的是,`key`并非是我们下意识认为的`Thread`对象,而是`ThreadLocal`本身(`Thread`和`Value`本身是一对一的,我们更容易将其映射为`key-value`的关系)。
92 |
93 | ### 3.remove()方法分析
94 |
95 | ```Java
96 | public void remove() {
97 | ThreadLocalMap m = getMap(Thread.currentThread());
98 | if (m != null)
99 | m.remove(this);
100 | }
101 | ```
102 |
103 | 对于变量副本的移除,也是通过`map`进行处理的,和`set()`和`get()`相同,`Entry`的键值对中,`ThreadLocal`本身作为`key`,对变量副本进行检索。
104 |
105 | ### 4.小结
106 |
107 | 可以看出,`ThreadLocal`本身内部的逻辑都是围绕着`ThreadLocalMap`在运作,其本身更像是一个空壳,仅作为`API`供开发者调用,内部逻辑都委托给了`ThreadLocalMap`。
108 |
109 | 接下来我们来探究一下`ThreadLocalMap`和`Thread`以及`ThreadLocal`之间的关系。
110 |
111 | ## 二、ThreadLocalMap分析
112 |
113 | `ThreadLocalMap`内部代码和算法相对复杂,个人亦是一知半解,因此就不逐行代码进行分析,仅系统性进行概述。
114 |
115 | 首先来看一下`ThreadLocalMap`的定义:
116 |
117 | ```Java
118 | public class ThreadLocal {
119 |
120 | // ThreadLocalMap是ThreadLocal的内部类
121 | static class ThreadLocalMap {
122 |
123 | // Entry类,内部key对应的是ThreadLocal的弱引用
124 | static class Entry extends WeakReference> {
125 | // 变量的副本,强引用
126 | Object value;
127 |
128 | Entry(ThreadLocal> k, Object v) {
129 | super(k);
130 | value = v;
131 | }
132 | }
133 | }
134 | }
135 | ```
136 |
137 | `ThreadLocal`中的嵌套内部类`ThreadLocalMap`本质上是一个`map`,依然是`key-value`的形式,其中有一个内部类`Entry`,其中`key`可以看做是`ThreadLocal`实例的弱引用。
138 |
139 | 和最初的设想不同的是,`ThreadLocalMap`中`key`并非是线程的实例`Thread`,而是`ThreadLocal`,那么`ThreadLocalMap`是如何保证同一个`Thread`中,`ThreadLocal`的指定变量唯一呢?
140 |
141 | ```Java
142 | // 1.ThreadLocal的set()方法
143 | public void set(T value) {
144 | Thread t = Thread.currentThread();
145 | ThreadLocalMap map = getMap(t);
146 | // ...
147 | }
148 |
149 | // 2.getMap()实际上是从Thread中获取threadLocals成员
150 | ThreadLocalMap getMap(Thread t) {
151 | return t.threadLocals;
152 | }
153 |
154 | public class Thread implements Runnable {
155 | // 3.每个Thread实例都持有一个ThreadLocalMap的属性
156 | ThreadLocal.ThreadLocalMap threadLocals = null;
157 | }
158 | ```
159 |
160 | `Thread`本身持有`ThreadLocal.ThreadLocalMap`的属性,每个线程在向`ThreadLocal`里`setValue`的时候,其实都是向自己的`ThreadLocalMap`成员中加入数据;`get()`同理。
161 |
162 | ## 三、内存泄漏的风险?
163 |
164 | 在上一小节中,我们看到`ThreadLocalMap`中的`Entry`中,其`ThreadLocal`作为`key`,是作为弱引用进行存储的。
165 |
166 | 当`ThreadLocal`不再被作为强引用持有时,会被GC回收,这时`ThreadLocalMap`对应的`ThreadLocal`就变成了`null`。而根据文档所叙述的,当`key == null`时,这时就可以默认该键不再被引用,该`Entry`就可以被直接清除,该清除行为会在`Entry`本身的`set()/get()/remove()`中被调用,这样就能 **一定情况下避免内存泄漏**。
167 |
168 | 这时就有一个问题出现了,作为`key`的`ThreadLocal`变成了`null`,那么作为`value`的变量可是强引用呀,这不就导致内存泄漏了吗?
169 |
170 | 其实一般情况下也不会,因为即使再不济,线程在执行结束时,自然也会消除其对`value`的引用,使得`Value`能够被GC回收。
171 |
172 | 当然,在某种情况下(比如使用了 **线程池**),线程再次被使用,`Value`这时依然可以被获取到,自然也就发生了内存泄漏,因此此时,我们还是需要通过手动将`value`的值设置为`null`(即调用`ThreadLocal.remove()`方法)以规避内存泄漏的风险。
173 |
174 | ## 参考&感谢
175 |
176 | * 《Android开发艺术探索》
177 | * [深入理解ThreadLocal的"内存溢出"](https://mahl1990.iteye.com/blog/2347932)
178 | * [关于ThreadLocal内存泄露的备忘](https://www.jianshu.com/p/250798f9ff76)
179 | * [ThreadLocal源码分析](https://www.jianshu.com/p/80866ca6c424)
180 |
181 | ---
182 |
183 | ## 关于我
184 |
185 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[博客](https://www.jianshu.com/u/df76f81fe3ff)或者[Github](https://github.com/qingmei2)。
186 |
187 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
188 |
189 | * [我的Android学习体系](https://github.com/qingmei2/android-programming-profile)
190 | * [关于文章纠错](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
191 | * [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
192 |
--------------------------------------------------------------------------------
/src/Android-DI/[译]Android开发从Dagger2迁移至Kodein的感受.md:
--------------------------------------------------------------------------------
1 | > 原文:[From Dagger2 to Kodein: A small experiment](https://medium.com/@AllanHasegawa/from-dagger2-to-kodein-a-small-experiment-9800f8959eb4)
2 | 作者:[Allan Yoshio Hasegawa](https://medium.com/@AllanHasegawa "Go to the profile of Allan Yoshio Hasegawa")
3 | 译者:[却把清梅嗅](https://github.com/qingmei2)
4 |
5 | ## 译者说
6 | ---
7 | 我是[却把清梅嗅](https://github.com/qingmei2),一个普通的Android开发者。去年,我写了一系列关于Android开发者依赖注入框架 **Dagger2** 及 **dagger.android** 的博客:
8 |
9 | > [Android 神兵利器Dagger2使用详解(一)基础使用](http://blog.csdn.net/mq2553299/article/details/73065745)
10 | > [Android 神兵利器Dagger2使用详解(二)Module&Component源码分析](http://blog.csdn.net/mq2553299/article/details/73136396)
11 | > [Android 神兵利器Dagger2使用详解(三)MVP下的使用](http://blog.csdn.net/mq2553299/article/details/73251405)
12 | > [Android 神兵利器Dagger2使用详解(四)Scope注解使用及源码分析](http://blog.csdn.net/mq2553299/article/details/73414710)
13 | > [告别Dagger2模板代码:dagger.android使用详解](http://blog.csdn.net/mq2553299/article/details/77485800)
14 | > [告别Dagger2模板代码:dagger.android原理分析](http://blog.csdn.net/mq2553299/article/details/77725912)
15 |
16 | 最近个人在尝试构建 **Kotlin版本** 的Android MVVM开发框架,在依赖注入框架的选型上,我最终选择了 **[Kodein](https://github.com/Kodein-Framework/Kodein-DI)** 。
17 |
18 | 这是一个非常轻量级的DI框架,相比于配置繁琐的Dagger(繁琐的配置也是导致Dagger学习成本一直居高不下的原因!),它的配置过程更清晰且简单,并且,这个库的源码也是 **Kotlin** 的。
19 |
20 | 有同学说,虽然 **Dagger2** 配置很繁琐,但 **dagger.android** 已经大大减少了模板代码的配置。确实如此,但它终究是通过编译器自动生成 **Java** 代码的库,我已经厌倦使用它了......请不要再劝我将它掺入 **Kotlin** 的项目中了。
21 |
22 | 扯了这么多,请阅读正文吧, 作者简单阐述了 **[Kodein](https://github.com/Kodein-Framework/Kodein-DI)** 的使用感受,通过和 **Dagger2** 对比,阐述库本身的优缺点,希望能给同行一些参考。
23 |
24 | > 不久之后,我会专门写一篇文章剖析**[Kodein](https://github.com/Kodein-Framework/Kodein-DI)**的 **入门教程** 和 **项目中的应用** ,敬请期待。
25 |
26 | ### 2018.9追加
27 |
28 | `Kodein`的中文博客详解已更新:
29 |
30 | > **[告别Dagger2,Android的Kotlin项目中使用Kodein进行依赖注入](https://www.jianshu.com/p/b0da805f7534)**
31 |
32 | # 正文
33 | ---
34 | ## 从Dagger2迁移至Kodein的感受
35 |
36 | 有些时候,Dagger2可能会有点过于复杂。 例如,当每个 Activity 都有一个 Scope(作用域) 时,每个屏幕都必须实现一个Scope,一个Module和一个Component。
37 |
38 | 对于Kotlin的开发者来讲,Kodein将是你的救星。
39 |
40 | ### 1.Kodein并不是一个依赖注入(DI)框架
41 |
42 | 虽然 **Kodein** 全名为 **KO**tlin **DE**pendency **IN**jection,但Kodein并不是一个真正的依赖注入框架。 他们的官方文档将其称作 **依赖检索容器** 。
43 |
44 | Kodein使用容器来传递依赖关系,这与Dagger2有什么不同? 让我们看一下从文档的Kodein示例代码:
45 |
46 | ```kotlin
47 | val kodein = Kodein {
48 | bind() with provider { RandomDice(0, 5) }
49 | bind() with singleton { SqliteDS.open("path/to/file") }
50 | }
51 | class Controller(private val kodein: Kodein) {
52 | private val ds: DataSource = kodein.instance()
53 | }
54 | ```
55 |
56 |
57 | 第一个表达式创建了一个容器,然后将容器传递给Controller。这之后,将检索依赖项的工作 **委托** 给了Controller。
58 |
59 | 而使用Dagger2,该示例将如下所示:
60 |
61 | 
62 |
63 | Dagger2中没有 **容器** 的概念。 依赖关系通过构造函数,属性或方法注入。 Controller不知道dataSource的来源——这是Dagger2和Kodein之间的根本区别。
64 |
65 | ## 2.Kodein中可以使用“非常简单易读的声明性DSL”
66 |
67 | 官方文档是这样描述的, 上面的代码示例也正是这样做的。 它的好处是使代码更加紧凑,嗯,仅此而已 :)
68 |
69 | ## 3.使用了Kodein的开源项目?
70 |
71 | 网上有大量资料可供学习Dagger2:教程,博客文章,开源项目; 它们非常有用。
72 |
73 | 对于Kodein? 并不多😔我仍然有很多关于如何正确使用Kodein的疑问。
74 |
75 | ## 4 Kodein有很多方式可以实现依赖注入
76 |
77 | 以上文的代码为例,如下所示:
78 |
79 | ```kotlin
80 | val kodein = Kodein { ... }
81 | class Controller(val dataSource: DataSource)
82 |
83 | val controller = kodein.newInstance { Controller(instance()) }
84 | ```
85 |
86 | 现在Controller不依赖于一个容器,这更类似Dagger2通过构造函数传递依赖关系的的实现方式。
87 |
88 | 或者我们可以这样做:
89 |
90 | ```kotlin
91 | val kodein = Kodein { ... }
92 | class Controller {
93 | private val injector = KodeinInjector()
94 | private val dataSource: DataSource by injector.instance()
95 |
96 | init {
97 | injector.inject(kodein)
98 | // use dataSource
99 | }
100 | }
101 | ```
102 |
103 | 这类似于Dagger2注入属性的实现方式。
104 |
105 | 而且,如果你“勇敢”地在你的应用程序中滥用反射,你甚至可以将JSR330与Kodein一起使用。
106 |
107 | 关键是,Kodein提供了许多**注入**依赖关系的方法, 您不必总是将**容器**传递给对象。
108 |
109 | ## 5.你的类可能依赖于Kodein
110 | 在使用Dagger2的项目中,您的类将取决于JSR330标准的注解(译者注,@Inject等注解)。 这些类并不依赖Dagger2。
111 |
112 | 在使用Kodein的项目中,您的类可能需要依赖于Kodein; 这一切都取决于使用哪种实现方式。 在第一个示例中,Controller依赖于Kodein容器。 在**注入器**示例中,Controller依赖于KodeinInjector。
113 |
114 | ## 6.Kodein在运行时判断依赖关系
115 | 如果您忘记将实例绑定到容器,则只会在运行时收到错误消息。 而使用Dagger2,您在编译时会收到错误。
116 |
117 | ## 7.Kodein容器太通用了
118 | 在Dagger2中,我们在定义好的方法中声明模块和组件。 它们可以让Contract了解到谁被谁依赖,依赖被注入在哪里。
119 |
120 | 但是,Kodein容器没有提供或保存的任何类型信息。 这意味着,对于类型系统,这两个容器是相同的:
121 |
122 | ```kotlin
123 | val kodein = Kodein {
124 | bind with provider { RandomDice(0, 5) }
125 | }
126 | val foo = Kodein {
127 | bind with singleton { UserRepo() }
128 | }
129 | ```
130 |
131 | 如果你很幸运,不小心将foo而不是kodein传递给一个对象。 该项目将成功编译,但它将在运行时发生崩溃。
132 |
133 | ## 还有?
134 |
135 | 我喜欢在这个小项目中使用Kodein。 我认为它的DSL非常简单,功能强大且简洁。 但我确实遇到了只在运行时捕获的问题。
136 |
137 | 我可能会在小项目上再次使用Kodein。 对于更大的项目,我可能还是需要保持观望的态度。
138 |
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex1_Paging1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex1_Paging1.jpg
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex1_gif1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex1_gif1.gif
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex1_gif2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex1_gif2.gif
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex1_paging2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex1_paging2.jpg
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image1.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image2.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image3.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image4.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image5.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image6.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_image7.png
--------------------------------------------------------------------------------
/src/Android-Jetpack/paging_ex/ex2_star.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Android-Jetpack/paging_ex/ex2_star.gif
--------------------------------------------------------------------------------
/src/Android-MVI/MVI_6.md:
--------------------------------------------------------------------------------
1 | # [译]使用MVI打造响应式APP(六):恢复状态
2 |
3 | > 原文:[REACTIVE APPS WITH MODEL-VIEW-INTENT - PART6 - RESTORING STATE](http://hannesdorfmann.com/android/mosby3-mvi-6)
4 | 作者:[Hannes Dorfmann](http://hannesdorfmann.com)
5 | 译者:[却把清梅嗅](https://github.com/qingmei2)
6 |
7 | 在前几篇文章中,我们讨论了`Model-View-Intent(MVI)`和单向数据流的重要性,这极大简化了状态的恢复,那么其过程和原理是什么呢,本文我们针对这个问题进行探讨。
8 |
9 | 我们将针对2个场景进行探讨:
10 |
11 | * 在内存中恢复状态(比如当屏幕方向发生改变)
12 | * 持久化恢复状态(比如从`Bundle`中获取之前在`Activity.onSaveInstanceState()`保存的状态)
13 |
14 | ## 内存中
15 |
16 | 这种情况处理起来非常简单。我们只需要保持我们的`RxJava`流随着时间的推移从`Android`生命周期组件(即`Activity`,`Fragment`甚至`ViewGroups`)种发射新的状态。
17 |
18 | 比如`Mosby`的 **`MviBasePresenter`** 类在内部就使用了类似这样的`RxJava`的流:使用 **PublishSubject** 发射`intent`,以及使用 **BehaviorSubject** 对`View`进行渲染。对此,在 [第二部分](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%BA%8C%5D%3AView%E5%B1%82%E5%92%8CIntent%E5%B1%82.md) 中我已经阐述了是如何实现的。其主要思想是`MviBasePresenter`是一个和`View`生命周期隔离的组件,因此它能够被`View`脱离和附着。在`Mosby`中,当`View`被永久销毁时,`Presenter`被`destroyed`(垃圾收集)。同样,这只是`Mosby`的一个实现细节,您的MVI实现可能完全不同。
19 |
20 | 重要的是,像`Presenter`这样的组件存活在`View`的生命周期之外,因为这样很容易处理`View`脱离和附着的事件。每当`View`(重新)依附到`Presenter`时,我们只需调用`view.render(previousState)`(因此Mosby内部使用了`BehaviorSubject`)。
21 |
22 | 这只是处理屏幕方向改变的一种处理方案,它同样适用于返回栈导航中。例如,`Fragment`在返回栈中,我们如果从返回栈中返回,我们可以简单的再次调用`view.render(previousState)`,并且,`view`也会显示正确的状态。
23 |
24 | 事实上,即使没有`View`对其进行依附,状态也依然会被更新,因为`Presenter`存活在`View`的生命周期之外,并被保存在`RxJava`流中。设想如果没有`View`附着,则会收到一个更改数据(部分状态)的推送通知,同样,每当`View`重新附着时,最新状态(包含来自推送通知的更新数据)将被移交给`View`进行渲染。
25 |
26 | ## 持久化状态
27 |
28 | 这种场景在`MVI`这种单向数据流模式下也很简单。现在我们希望`View`层的状态不仅仅存在于内存中,即使进程终止也能够对其持有。`Android`中通常的一种解决方案是通过调用`Activity.onSaveInstanceState(Bundle)`去保存状态。
29 |
30 | 与`MVP`、`MVVM`不同的是,在`MVI`中你持有了代表状态的`Model`,`View`有一个`render(state)`方法来记录最新的状态,这让持有最后一个状态变得简单。因此,显然易见的是打包和存储状态到一个`bundle`下面,并且之后恢复它:
31 |
32 | ```Java
33 | class MyActivity extends Activity implements MyView {
34 |
35 | private final static KEY_STATE = "MyStateKey";
36 | private MyViewState lastState;
37 |
38 | @Override
39 | public void render(MyState state) {
40 | lastState = state;
41 | ... // 更新UI控件
42 | }
43 |
44 | @Override
45 | public void onSaveInstanceState(Bundle out){
46 | out.putParcelable(KEY_STATE, lastState);
47 | }
48 |
49 | @Override
50 | public void onCreate(Bundle saved){
51 | super.onCreate(saved);
52 | MyViewState initialState = null;
53 | if (saved != null){
54 | initialState = saved.getParcelable(KEY_STATE);
55 | }
56 |
57 | presenter = new MyPresenter( new MyStateReducer(initialState) ); // With dagger: new MyDaggerModule(initialState)
58 | }
59 | ...
60 | }
61 | ```
62 |
63 | 我想你已得要领,请注意,在`onCreate()`中我们并不直接调用`view.render(initialState)`, 我们让初始状态的逻辑下沉到状态管理的地方: **状态折叠器**(请参考[第三部分](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%89%5D%3AStateReducer.md)),我们将它与`.scan(initialState,reducerFunction)`搭配使用。
64 |
65 | ## 结语
66 |
67 | 与其他模式相比,使用单向数据流和表示状态的`Model`,许多与状态相关的东西更容易实现。但是,我通常不会在我的`App`中将状态持久化,两个原因:首先,`Bundle`有大小限制,因此你不能将任意大的状态放入`bundle`中(或者你可以将状态保存到文件或像`Realm`这样的对象存储中);其次,我们只讨论了如何序列化和反序列化状态,但这不一定与恢复状态相同。
68 |
69 | 例如:假设我们有一个`LCE`(加载内容错误)视图,它会在加载数据时显示一个指示器,并在完成加载后显示条目列表,因此状态就类似`MyViewState.LOADING`。让我们假设加载需要一些时间,而就在此时进程刚好被终止了(比如突然一个电话打了进来,导致电话应用占据了前台)。
70 |
71 | 如果我们仅仅将`MyViewState.LOADING`进行序列化并在之后进行反序列化操作对状态进行恢复,我们的**状态折叠器**会调用`view.render(MyViewState.LOADING)`,目前为止这是正确的,但实际上我们 **永远不会通过这个状态对网络进行请求加载数据**。
72 |
73 | 如您所见,序列化和反序列化状态与状态恢复不同,这可能需要一些额外的步骤来增加复杂性(当然对于`MVI`来说这实现起来同样比其它模式更简单),当重新创建`View`时,包含某些数据的反序列化状态可能会过时,因此您可能必须刷新(加载数据)。
74 |
75 | 在我研究过的大多数应用程序中,我发现相比之下这种方案更简单且友好:即,将状态仅仅保存在内存中,并且在进程死亡后以空的初始状态启动,好像应用程序将首次启动一样。理想情况下,`App`具有对缓存和离线的支持,因此在进程终止后加载数据的速度也会很快。
76 |
77 | 这最终导致了我和其它`Android`开发者针对一个问题进行了激烈的辩论:
78 |
79 | > 如果我使用缓存或存储,我已经拥有了一个存活于`Android`组件生命周期之外的组件,而且我不再需要去处理相关的状态存储问题,并且`MVI`毫无意义,对吗?
80 |
81 | 这其中大多数`Android`开发者推荐 `Mike Nakhimovich` 发表的 [《Presenter 不是为了持久化》](https://hackernoon.com/presenters-are-not-for-persisting-f537a2cc7962)这篇文章介绍的 [NyTimes Store](https://github.com/NYTimes/Store),这是一个数据加载和缓存库。遗憾的是,那些开发人员不明白 **加载数据和缓存不是状态管理**。例如,如果我必须从缓存或存储中加载数据呢?
82 |
83 | 最后,类似[NyTimes Store](https://github.com/NYTimes/Store)的库帮助我们处理进程终止了吗?显然没有,因为进程随时都有可能被终止。我们能做的仅仅是祈祷`Android`操作系统不要杀死我们的进程,因为我们还有一些需要通过`Service`做的事(这也是一个能够不生存于其它android组件生命周期的组件),或者我们可以通过使用`RxJava`而不再需要`Android Service`了,这可行吗?
84 |
85 | 我们将在下一章节探讨关于`android services`、`RxJava`以及`MVI`,敬请期待。
86 |
87 | ##### 剧透:我认为我们确实需要服务。
88 |
89 | ---
90 |
91 | ## 系列目录
92 |
93 | **《使用MVI打造响应式APP》原文**
94 |
95 | > * [Part 1: Model
96 | ](http://hannesdorfmann.com/android/mosby3-mvi-1)
97 | > * [Part 2: View and Intent](http://hannesdorfmann.com/android/mosby3-mvi-2)
98 | > * [Part 3: State Reducer](http://hannesdorfmann.com/android/mosby3-mvi-3)
99 | > * [Part 4: Independent UI Components
100 | ](http://hannesdorfmann.com/android/mosby3-mvi-4)
101 | > * [Part 5: Debugging with ease
102 | ](http://hannesdorfmann.com/android/mosby3-mvi-5)
103 | > * [Part 6: Restoring State
104 | ](http://hannesdorfmann.com/android/mosby3-mvi-6)
105 | > * [Part 7: Timing (SingleLiveEvent problem)
106 | ](http://hannesdorfmann.com/android/mosby3-mvi-7)
107 | > * [Part 8: In-App Navigation
108 | ](http://hannesdorfmann.com/android/mosby3-mvi-8)
109 |
110 | **《使用MVI打造响应式APP》译文**
111 | > * [[译]使用MVI打造响应式APP(一):Model到底是什么](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%80%5D%3AModel%E5%B1%82%E5%88%B0%E5%BA%95%E4%BB%A3%E8%A1%A8%E4%BB%80%E4%B9%88.md)
112 | > * [[译]使用MVI打造响应式APP[二]:View层和Intent层](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%BA%8C%5D%3AView%E5%B1%82%E5%92%8CIntent%E5%B1%82.md)
113 | > * [[译]使用MVI打造响应式APP[三]:状态折叠器](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%89%5D%3AStateReducer.md)
114 | > * [[译]使用MVI打造响应式APP[四]:独立性UI组件](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E5%9B%9B%5D%3AIndependentUIComponents.md)
115 | > * [[译]使用MVI打造响应式APP[五]:轻而易举地Debug](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%BA%94%5D%3ADebuggingWithEase.md)
116 | > * [[译]使用MVI打造响应式APP[六]:恢复状态](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E5%85%AD%5D%3ARestoringState.md)
117 | > * [[译]使用MVI打造响应式APP[七]:掌握时机(SingleLiveEvent问题)](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%83%5D%3ATiming%2CSingleLiveEventProblem.md)
118 | > * [[译]使用MVI打造响应式APP[八]:导航](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E5%85%AB%5D%3ANavigation.md)
119 |
120 | **《使用MVI打造响应式APP》实战**
121 | > * [实战:使用MVI打造响应式&函数式的Github客户端](https://github.com/qingmei2/MVI-Rhine)
122 |
123 | ---
124 |
125 | ## 关于我
126 |
127 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[博客](https://www.jianshu.com/u/df76f81fe3ff)或者[Github](https://github.com/qingmei2)。
128 |
129 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
130 |
131 | * [我的Android学习体系](https://github.com/qingmei2/android-programming-profile)
132 | * [关于文章纠错](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
133 | * [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
134 |
--------------------------------------------------------------------------------
/src/Android-MVI/MVI_8.md:
--------------------------------------------------------------------------------
1 | # [译]使用MVI打造响应式APP(八):导航
2 |
3 | > 原文:[REACTIVE APPS WITH MODEL-VIEW-INTENT - PART 8 - NAVIGATION](http://hannesdorfmann.com/android/mosby3-mvi-8)
4 | 作者:[Hannes Dorfmann](http://hannesdorfmann.com)
5 | 译者:[却把清梅嗅](https://github.com/qingmei2)
6 |
7 |
8 | 在[上一篇博客](http://hannesdorfmann.com/android/coordinators-android)中,我们探讨了协调模式是如何在`Android`中应用的。这次我想展示如何在`Model-View-Intent`中使用它。
9 |
10 | 如果您还不知道协调器模式是什么,我强烈建议您回过头来阅读[上文内容](http://hannesdorfmann.com/android/coordinators-android)。
11 |
12 | 在`MVI`中应用此模式与`MVVM`或`MVP`没有太大区别:我们将lambda作为导航的回调传递给我们的`MviBasePresenter`。有趣的是我们如何在状态驱动的架构中触发这些回调?我们来看一个具体的例子:
13 |
14 | ```Kotlin
15 | class FooPresenter(
16 | private var navigationCallback: ( () -> Unit )?
17 | ) : MviBasePresenter {
18 | lateinit var disposable : Disposable
19 | override fun bindIntents(){
20 | val intent1 = ...
21 | val intent2 = ...
22 | val intents = Observable.merge(intent1, intent2)
23 |
24 | val state = intents.switchMap { ... }
25 |
26 | // 这里就是有趣的部分
27 | val sharedState = state.share()
28 | disposable = sharedState.filter{ state ->
29 | state is State.Foo
30 | }.subscribe { navigationCallback!!() }
31 |
32 | subscribeViewState(sharedState, FooView::render)
33 | }
34 |
35 | override fun unbindIntents(){
36 | disposable.dispose() // disposable 导航
37 | navigationCallback = null // 避免内存泄漏
38 | }
39 | }
40 | ```
41 |
42 | 其思想是:通过`RxJava`的 **share()** 操作符,我们对通常用来对`View`层渲染状态的`Observable`进行复用,再加上通过与 **.filter()** 操作符的组合使用,达到能够监听到确定的状态,这之后,当我们观察到该状态时,触发对应的导航操作,然后协调器模式就像我之前的博客文章中描述的那样进行工作。
43 |
44 | ---
45 |
46 | ## 系列目录
47 |
48 | **《使用MVI打造响应式APP》原文**
49 |
50 | > * [Part 1: Model
51 | ](http://hannesdorfmann.com/android/mosby3-mvi-1)
52 | > * [Part 2: View and Intent](http://hannesdorfmann.com/android/mosby3-mvi-2)
53 | > * [Part 3: State Reducer](http://hannesdorfmann.com/android/mosby3-mvi-3)
54 | > * [Part 4: Independent UI Components
55 | ](http://hannesdorfmann.com/android/mosby3-mvi-4)
56 | > * [Part 5: Debugging with ease
57 | ](http://hannesdorfmann.com/android/mosby3-mvi-5)
58 | > * [Part 6: Restoring State
59 | ](http://hannesdorfmann.com/android/mosby3-mvi-6)
60 | > * [Part 7: Timing (SingleLiveEvent problem)
61 | ](http://hannesdorfmann.com/android/mosby3-mvi-7)
62 | > * [Part 8: In-App Navigation
63 | ](http://hannesdorfmann.com/android/mosby3-mvi-8)
64 |
65 | **《使用MVI打造响应式APP》译文**
66 | > * [[译]使用MVI打造响应式APP(一):Model到底是什么](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%80%5D%3AModel%E5%B1%82%E5%88%B0%E5%BA%95%E4%BB%A3%E8%A1%A8%E4%BB%80%E4%B9%88.md)
67 | > * [[译]使用MVI打造响应式APP[二]:View层和Intent层](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%BA%8C%5D%3AView%E5%B1%82%E5%92%8CIntent%E5%B1%82.md)
68 | > * [[译]使用MVI打造响应式APP[三]:状态折叠器](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%89%5D%3AStateReducer.md)
69 | > * [[译]使用MVI打造响应式APP[四]:独立性UI组件](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E5%9B%9B%5D%3AIndependentUIComponents.md)
70 | > * [[译]使用MVI打造响应式APP[五]:轻而易举地Debug](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%BA%94%5D%3ADebuggingWithEase.md)
71 | > * [[译]使用MVI打造响应式APP[六]:恢复状态](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E5%85%AD%5D%3ARestoringState.md)
72 | > * [[译]使用MVI打造响应式APP[七]:掌握时机(SingleLiveEvent问题)](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E4%B8%83%5D%3ATiming%2CSingleLiveEventProblem.md)
73 | > * [[译]使用MVI打造响应式APP[八]:导航](https://github.com/qingmei2/android-programming-profile/blob/master/src/Android-MVI/%5B%E8%AF%91%5D%E4%BD%BF%E7%94%A8MVI%E6%89%93%E9%80%A0%E5%93%8D%E5%BA%94%E5%BC%8FAPP%5B%E5%85%AB%5D%3ANavigation.md)
74 |
75 | **《使用MVI打造响应式APP》实战**
76 | > * [实战:使用MVI打造响应式&函数式的Github客户端](https://github.com/qingmei2/MVI-Rhine)
77 |
78 | ---
79 |
80 | ## 关于我
81 |
82 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[博客](https://www.jianshu.com/u/df76f81fe3ff)或者[Github](https://github.com/qingmei2)。
83 |
84 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
85 |
86 | * [我的Android学习体系](https://github.com/qingmei2/android-programming-profile)
87 | * [关于文章纠错](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
88 | * [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
89 |
--------------------------------------------------------------------------------
/src/Flutter/Flutter与Android混合编码配置笔记.md:
--------------------------------------------------------------------------------
1 | 学习`Flutter`一小段时间,对纯`Flutter`项目有了一些基本的了解,但更趋近实际开发的应该是将`Flutter`模块作为一个依赖库添加到原生的`Android`项目中。
2 |
3 | **本文笔者将尝试分享个人针对`Flutter`与`Android`混编时的配置步骤,以及踩坑过程。**
4 |
5 | ## 一、初始化Flutter-Module
6 |
7 | 参考 [官方文档](https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps) ,首先需要确认`Flutter-Module`依赖库文件夹的位置,简单来说,这里有两种方式:
8 |
9 | * 1.创建在项目的根目录下(**内部**);
10 | * 2.创建和项目文件夹的同一层级(**外部**),这也是官方推荐的方式。
11 |
12 | 其实这些方式没什么区别,但是个人更倾向于第二种,我们在项目文件夹的目录层级下对`Flutter-Module`文件夹进行 **创建** 并 **初始化**:
13 |
14 | ```shell
15 | $ flutter create -t module module_flutter
16 | ```
17 |
18 | 成功后,`Flutter-Module`和`Android`项目本身应该是这样的(红框内的两个项目):
19 |
20 | 
21 |
22 | ## 二、配置Android项目
23 |
24 | 接下来我们需要将这个项目和刚刚创建的`module-flutter`进行依赖,我们先打开`Android`原生项目,并为项目根目录下的`settings.gradle`文件中添加如下配置:
25 |
26 | ```groovy
27 | setBinding(new Binding([gradle: this]))
28 | evaluate(new File(
29 | settingsDir.parentFile,
30 | 'module_flutter/.android/include_flutter.groovy'
31 | ))
32 | ```
33 |
34 | 如果`module-flutter`模块是创建在项目内部,那么需要稍微改一改:
35 |
36 | ```groovy
37 | setBinding(new Binding([gradle: this]))
38 | evaluate(new File(
39 | settingsDir.path,
40 | 'module_flutter/.android/include_flutter.groovy'
41 | ))
42 | ```
43 |
44 | 然后,我们需要打开`app`的`build.gradle`文件,添加对`flutter`的依赖:
45 |
46 | ```groovy
47 | dependencies {
48 | implementation fileTree(dir: 'libs', include: ['*.jar'])
49 | implementation 'androidx.appcompat:appcompat:1.0.0'
50 | implementation 'androidx.annotation:annotation:1.0.0'
51 |
52 | ......
53 |
54 | implementation project(':flutter')
55 | }
56 | ```
57 |
58 | 这样,对于简单的`Android`原生项目而言,`Flutter`已经配置成功了。
59 |
60 | ## 三、AndroidX的迁移
61 |
62 | 由于笔者的项目迁移了`AndroidX`, 但是低版本的`Flutter`命令生成的`module`默认依赖的是`support`包, 因此我们需要将默认`support`的依赖手动迁移到`AndroidX`。
63 |
64 | > 截止笔者发文前,`Flutter`V1.7已经提供了对`AndroidX`的支持,当创建 `Flutter` 项目的时候,你可以通过添加 `--androidx` 来确保生成的项目文件支持`AndroidX`,详情参考[这里](https://juejin.im/post/5d26bf1f51882536124052c2)。
65 |
66 | 手动迁移的方式有两种:
67 |
68 | * 1.通过`Android Studio` **自动迁移** 过去。
69 |
70 | 首先通过`Android Studio`打开`flutter-module`,这时候是不能直接迁移`AndroidX`的,需要通过`flutter` - `Open Android module in AS` 方式新打开一个窗口。
71 |
72 | 
73 |
74 | 这样编译成功后,就可以点击`Refactor` - `Migrate to AndroidX`进行迁移了,后续步骤网上有很多,不赘述。
75 |
76 | 
77 |
78 | * 2.手动配置过去,这个方式也很简单,打开`Flutter`-`build.gradle`文件,对依赖进行更新:
79 |
80 | ```groovy
81 | android {
82 | //...
83 | defaultConfig {
84 | // ...
85 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
86 | }
87 | // ...
88 | }
89 |
90 | dependencies {
91 | testImplementation 'junit:junit:4.12'
92 | implementation 'androidx.appcompat:appcompat:1.0.0'
93 | implementation 'androidx.annotation:annotation:1.0.0'
94 |
95 | // ...
96 | }
97 | ```
98 |
99 | 手动配置网上有很多博客,不赘述。
100 |
101 | 需要注意的是,一定要保证`Flutter`模块中对`AndroidX`相关依赖的版本和实际原生项目中相关依赖的版本是一致的,否则可能会导致依赖冲突。
102 |
103 | ## 四、多模块项目的配置
104 |
105 | 上文说到,简单的项目已经配置完毕了,但是多模块的项目来说则稍显复杂,比如笔者的项目:
106 |
107 | 
108 |
109 | 首先,需要在底层`library`(本文中是`library-core`)的`build.gradle`文件中添加对`flutter`的依赖:
110 |
111 | ```groovy
112 | dependencies {
113 | // ...
114 | api project(':flutter')
115 | }
116 | ```
117 |
118 | 添加之后并进行同步,原生的项目就会对`settings.gradle`文件中指向的`module-flutter`文件夹进行依赖。
119 |
120 | 同步、编译成功后,我运行了项目,但我很快遇到了问题:
121 |
122 | ```
123 | [ERROR:flutter/runtime/dart_vm_data.cc(19)] VM snapshot invalid and could not be inferred from settings.
124 | [ERROR:flutter/runtime/dart_vm.cc(241)] Could not setup VM data to bootstrap the VM from.
125 | [ERROR:flutter/runtime/dart_vm_lifecycle.cc(89)] Could not create Dart VM instance.
126 | [FATAL:flutter/shell/common/shell.cc(218)] Check failed: vm. Must be able to initialize the VM.
127 | ```
128 |
129 | ## 五、ProductFlavors的坑
130 |
131 | 这个问题纠结了很久,最后在 [这个issue中](https://github.com/flutter/flutter/issues/19818) 找到了答案:
132 |
133 | 
134 |
135 | 经 **@yk3372** 大佬提示,原来是以为项目中配置了`ProductFlavors`, 因此,`Flutter`模块中对应的`build.gradle`文件也需要进行对应的配置,比如这样:
136 |
137 | ```groovy
138 | buildTypes {
139 | release {}
140 | debug {}
141 | }
142 | flavorDimensions "environment"
143 | productFlavors {
144 | dev {}
145 | qa {}
146 | prod {}
147 | }
148 | ```
149 |
150 | 配置好之后,还需要手动将相关`module`的`ProductFlavors`配置相同,否则会提示一堆错误,比如我的一个原生的`module`依赖了`flutter`的`module`,它们就必须都保持同一个状态:
151 |
152 | 
153 |
154 | ???这是不是意味着所有的`module`的`build.gradle`都配置相同的`productFlavors`信息?
155 |
156 | 实践给予我答案,是的。
157 |
158 | 
159 |
160 | 虽然折腾了很久,还好前人栽树,后人乘凉,解决了问题还是`happy ending`, `Github`大法好。
161 |
162 | ## 六、更多Flutter混编姿势
163 |
164 | 本文提供了 **官方文档** 提供混合开发的集成方式,实际上,国内很多大厂都分享过相关的技术文章,这里也一并放出来:
165 |
166 | * [闲鱼Flutter混合工程持续集成的最佳实践](https://yq.aliyun.com/articles/618599?spm=a2c4e.11153959.0.0.4f29616b9f6OWs)
167 |
168 | * [头条Flutter混合工程实践](https://mp.weixin.qq.com/s/wdbVVzZJFseX2GmEbuAdfA)
169 |
170 | * [2019 最前沿的几个 Flutter 实践:微信、咸鱼、美团](https://mp.weixin.qq.com/s/TyjwBASNvxnQNXtC3zCG1w)
171 |
172 | ---
173 |
174 | ## 关于我
175 |
176 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[博客](https://www.jianshu.com/u/df76f81fe3ff)或者[Github](https://github.com/qingmei2)。
177 |
178 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
179 |
180 | * [我的Android学习体系](https://github.com/qingmei2/android-programming-profile)
181 | * [关于文章纠错](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
182 | * [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
183 |
--------------------------------------------------------------------------------
/src/Flutter/使用Flutter开发Github客户端及学习过程中的感悟.md:
--------------------------------------------------------------------------------
1 | # 使用Flutter开发Github客户端及学习历程的小结
2 |
3 | 本文笔者将尝试分享个人针对`Flutter`的 **学习** 并 **搭建一个Flutter应用** 的过程。
4 |
5 | 在这一个月学习`Flutter`的过程中,**我不可避免的走了很多弯路**,也许这并非坏事,但是还是希望将这些经历表述出来,有两个目的:
6 |
7 | * 1.为自己做一个周期性的总结;
8 | * 2.也希望能给想学习`Flutter`的读者一定实质性的参考。
9 |
10 | > 关于笔者总结的`Flutter`入门学习计划,可直接跳转文末的 **Flutter入门学习计划** 小节进行查看。
11 |
12 | ## 契机
13 |
14 | 上个月25号,[任玉刚](http://renyugang.io/)老师联系我,问我有没有兴趣翻译一篇`Flutter`的技术博客。
15 |
16 | 当时我还没有接触`Flutter`,觉得这是一个督促自己学习的机会,就尝试接下了这个任务。截止今天为止(6月25日)刚好一个月,在第一周保证翻译任务 [完成](https://mp.weixin.qq.com/s/fh6lvYONrWmldMEnEdw5yg) 之后,三周之后的今天,我基本实现了自己的另外一个目标——搭建一个 **Github客户端**。
17 |
18 | 这个项目运行之后,App整体效果是这样的:
19 |
20 | 
21 |
22 | 我将代码托管在了自己的`Github`上:
23 |
24 | [FlutterGitHubApp: Flutter开发的跨平台Github客户端.](https://github.com/qingmei2/FlutterGitHubApp)
25 |
26 | 因为这是一个入门的项目,所以接下来也会从各方面深入学习`Flutter`,并反过来继续完善和优化它。
27 |
28 | ## 第一周:初识Flutter
29 |
30 | 最初学习`Flutter`的方式是通过学习 [wendux](https://github.com/wendux) 老师的 [《Flutter实战》](https://book.flutterchina.club/)。
31 |
32 | 这是一本非常优秀的中文`Flutter`教程,对个人学习`Flutter`入门有非常大的帮助。
33 |
34 | 我根据这本小册中的内容完成了第一个 **计数器** 的入门案例,并对最常用的一些控件进行了熟悉和了解:
35 |
36 | 
37 |
38 | 正如读者所见,我跟着[《Flutter实战》](https://book.flutterchina.club/) 写了若干的demo代码,遗憾的是,效果并没有想象的那么好,原因也很明显,那就是我还没有完全熟悉`Dart`的语法。
39 |
40 | ### 磨刀不误砍柴工
41 |
42 | 学习`Flutter`的最开始,语法并非是最大的阻碍因素,因为对于熟悉`Java`语法的我们来说,`Dart`有很多相似之处,但随着`Flutter`学习的不断深入,有时一些`Dart`独有的语法特性会给我带来困惑,比如 **级联操作符** 、 **var和dynamic关键字的区别** 等等。
43 |
44 | 正如标题所言,我发现我走入了一个误区,`Dart`语法的学习势在必行。
45 |
46 | 我学习语法的方式是通过翻阅`Dart`中文网:
47 |
48 | [Dart中文社区:http://dart.goodev.org/](http://dart.goodev.org/)
49 |
50 | ### 第一周的感受
51 |
52 | 因为是空闲时间学习,因此严格来说学习时间并没有那么多,最初的第一周,笔者花了几个晚上,每天9点下班之后学2~3个小时,熟悉了`Dart`基本的语法和`Flutter`的最常用的基础组件。
53 |
54 | 严格来说,此时个人依然处于小白水平,勉强摸到了入门的门槛。
55 |
56 | 私下里也会偷偷吐槽一下`Dart`和`Flutter`,布局写着写着下面连续十数行的 `),),),);),),},),},);),),),;),},);` 真的令人不寒而栗......
57 |
58 | ## 第二周:状态管理
59 |
60 | 因为当初接翻译任务时,自己给自己设定了10天的期限(也是为了督促自己学习),因此第二周我需要在前3天内翻译完这篇博客:
61 |
62 | * [Widget-Async-Bloc-Service: A Practical Architecture for Flutter Apps](https://medium.com/coding-with-flutter/widget-async-bloc-service-a-practical-architecture-for-flutter-apps-250a28f9251b)
63 |
64 | 坦白来说,第二周的开始,这篇文章我看不懂,因此我需要学习`Flutter`开发过程中的架构思想。
65 |
66 | 正所谓窥一斑而知全豹,虽然还没有真正着手`Flutter`项目的开发,但是通过学习`Flutter`的核心——**状态管理**,以及将 **业务逻辑** 和 **UI的渲染** 分开学习,再加上作为一个`Android`开发者,理解这些概念本身就有很大的优势,学习效率自然非常的高。
67 |
68 | 学习`Flutter`中状态管理的资料,我强烈推荐 [Vadaski](https://juejin.im/user/5b5d45f4e51d453526175c06) 的系列文章。
69 |
70 | * [Flutter | 状态管理探索篇——Scoped Model(一)](https://juejin.im/post/5b97fa0d5188255c5546dcf8)
71 | * [Flutter | 状态管理探索篇——Redux(二)](https://juejin.im/post/5ba26c086fb9a05ce57697da)
72 | * [Flutter | 状态管理探索篇——BLoC(三)](https://juejin.im/post/5bb6f344f265da0aa664d68a)
73 | * [Flutter | 状态管理拓展篇——RxDart(四)](https://juejin.im/post/5bcea438e51d4536c65d2232)
74 | * [Flutter | 状态管理指南篇——Provider](https://juejin.im/post/5d00a84fe51d455a2f22023f)
75 |
76 | 冒昧推荐这几篇关于状态管理的文章,实际上 [Vadaski](https://juejin.im/user/5b5d45f4e51d453526175c06) 老师关于`Flutter`还有很多优秀的博客,这里不一一列举了,有兴趣的朋友可以去拜读一下。
77 |
78 | 如果读者之前学习或者了解过`Redux`和`ReactiveX`相关的思想,状态管理并不是非常难理解的概念。
79 |
80 | 熟悉了一系列`Flutter`状态管理的实现方式之后,翻译文章时就顺畅很多了,幸不辱命,最终在第十天的凌晨将文章翻译完毕:
81 |
82 | * [Flutter 移动端架构实践:Widget-Async-Bloc-Service](https://mp.weixin.qq.com/s/fh6lvYONrWmldMEnEdw5yg)
83 |
84 | 完成之后,因为工作和私人的原因,第二周接下来几天就没有什么时间学习`Flutter`了。
85 |
86 | ### 第二周小结
87 |
88 | 第二周的学习成果实际上和第一周差不多,因为前三天全神贯注,同时每天晚上多学了一会,再加上吃了之前的老本(之前对于`Redux`的状态管理和`RxJava`有一定的储备),学习效率还是比较高的。
89 |
90 | 这周的感觉就是,虽然自己没怎么上手项目,但是看了一些文章,对`Flutter`有了一些初步的认识,总结如下:
91 |
92 | * 1.因为`Flutter`本身采用的是`React`的思路,和我们认知的 **过程式开发** 是不一样的, **状态管理** 和 **响应式编程** 是非常重要的概念,如果之前有相关的知识储备,这个关键的知识点基本不会有什么难度,只需要关注`API`的使用就好了;当然,没了解过也没关系,本小节上方的几篇关于状态管理优秀的博客,也能够帮助开发者非常快的进入`Flutter`的节奏中去。
93 | * 2.类比是一个非常好的学习方式,对于`Flutter`中的一些概念或者库而言:
94 | > 2.1 `RxDart`和`Stream`相关的`API`和`RxJava`很相似;
95 | > 2.2 `Future`相关的API可以参考`Kotlin`的协程,通过同步的方式编写异步的代码;
96 | > 2.3 `Provider`其实也就是另一种方式的依赖注入.
97 | > 2.4 `Redux`就是参考前端的`Redux`引进的,没有什么变化......
98 |
99 | ## 第三周:学习Widget
100 |
101 | 从结果来看,第三周我走了不少弯路。
102 |
103 | 第三周的最初,我认为我需要开始深入学习`Flutter`中的`Widget`,因此我选择`fork`了著名的 [flutter-go](https://github.com/alibaba/flutter-go), 并且开始尝试跟着这个项目敲代码。
104 |
105 | 在敲了几天之后,我发现一个严重的问题,那就是这个学习过程中非常枯燥无聊,知识点之间没有关联性,感觉自己学了一个新的`Widget`,就忘了上一个`Widget`,没坚持多久,我就`hold`不住了......
106 |
107 | 
108 |
109 | 这也难怪,这个项目本身的目的就是 `常用组件的demo演示与中文文档`, 我一个`Widget`一个`Widget`的用法跟着敲,这给了我一种 **学习碎片没有组织起来** 的感觉,说白了就是不成系统,效果并不明显。
110 |
111 | 因此我将 [flutter-go](https://github.com/alibaba/flutter-go) 这个项目的定位变成了 **工具书** ,接下来的学习过程中,每当我对一个`Widget`的使用有了疑问,就随手打开这个APP进行查阅这个`Widget`的用法,效果还不错。
112 |
113 | ## 第四周:在实战中学习
114 |
115 | 第四周我选择了实战开发,了解我的朋友应该知道,我曾经通过不同的开发模式(`MVVM`和`MVI`)开发过两次`Github`的客户端,这次我也不例外。
116 |
117 | 选择以`Github`客户端作为实战的练手项目还有一个原因,那就是 [恋猫de小郭](https://juejin.im/user/582aca2ba22b9d006b59ae68) 老师已经开源了一个更强大的`Github`客户端可以作为参考:
118 |
119 | [GSYGithubAppFlutter: 超完整的Flutter项目](https://github.com/CarGuo/GSYGithubAppFlutter)
120 |
121 | 同时,[恋猫de小郭](https://juejin.im/user/582aca2ba22b9d006b59ae68) 老师也有非常优秀的`Flutter`系列博客,因为该系列文章太多了,就不一一列出了,强烈建议收藏阅读。
122 |
123 | 所谓前人栽树后人乘凉,[GSYGithubAppFlutter](https://github.com/CarGuo/GSYGithubAppFlutter) 确实在我实践过程中提供了很大的帮助,同时,因为第四周工作阶段性告一段落,我有更多时间去学习`Flutter`,因此很快就把一个简单的`Github`客户端敲了出来:
124 |
125 | https://github.com/qingmei2/FlutterGitHubApp
126 |
127 | ## 阶段性总结
128 |
129 | 在一个月的学习过程中,我学习到了很多东西,也感觉很多地方需要慢慢改进,也感觉到有很多知识点需要去补。
130 |
131 | 但是令我振奋的一点是,我成功从舒适区跳了出来,并且度过了学习新知识过程中最痛苦的一段时间(畏难情绪+新领域的陌生感);
132 |
133 | 现在面对诸如 **`Kotlin`和`Flutter`我学哪个比较好?** 的问题,我也可以这样回答了:
134 |
135 | > **小孩子才做选择,成年人当然是全都要啦!**
136 |
137 | 最后,衷心感谢文中提到的各位老师对个人的帮助,其实在学习过程中,我还参考了更多`Flutter`先驱者们优秀的博客和代码,实在难以一一列举,在此深表感谢。
138 |
139 | ### Flutter入门学习计划?
140 |
141 | 如何入门`Flutter`? 以个人经验来看,入门学习`Flutter`可以参考下面步骤:
142 |
143 | * 1.通过[《Flutter实战》](https://book.flutterchina.club/)电子书完成一个简单的计时器示例;
144 | * 2.通过 [Dart中文社区](http://dart.goodev.org/) 学习语法;
145 | * 3.继续学习[《Flutter实战》](https://book.flutterchina.club/),了解`Flutter`基本概念;
146 | * 4.下载 [`flutter-go`](https://github.com/alibaba/flutter-go),将`App`下载到手机中作为工具书随时随地查阅;
147 | * 5.1.学习一些优秀的`Flutter`博客系列,比如上文中 [Vadaski](https://juejin.im/user/5b5d45f4e51d453526175c06) 和 [恋猫de小郭](https://juejin.im/user/582aca2ba22b9d006b59ae68) 两位老师的文章;
148 | * 5.2 同时,下载优秀的`Flutter`开源项目学习源码;
149 | * 6. 选择一个感兴趣的项目或者方向进行实战练习。
150 |
151 | > 这个学习计划 **一定是有改进空间** 的,也诚挚的希望您能在评论区留下宝贵的想法和建议,这也能够为读者提供更多参考性的建议,感谢!
152 |
153 | ---
154 |
155 | ## 关于我
156 |
157 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[博客](https://www.jianshu.com/u/df76f81fe3ff)或者[Github](https://github.com/qingmei2)。
158 |
159 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
160 |
161 | * [我的Android学习体系](https://github.com/qingmei2/android-programming-profile)
162 | * [关于文章纠错](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
163 | * [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
164 |
--------------------------------------------------------------------------------
/src/Groovy/Gradle-Permission-denied解决方案.md:
--------------------------------------------------------------------------------
1 | 今天在查看Android项目的依赖关系时,发现蜜汁好用的gradle命令权限被限制了:
2 |
3 | ```
4 | qingmeideMac-mini:FireProtectionClient_Android qing.mei$ ./gradlew -q app:dependencies
5 | //注意这行,被提示没有权限
6 | -bash: ./gradlew: Permission denied
7 | ```
8 | 最后在 [stackoverflow-gradlew: Permission Denied](https://stackoverflow.com/questions/17668265/gradlew-permission-denied)找到了答案:
9 |
10 | > 输入 chmod +x gradlew
11 |
12 | 该命令的作用是是Linux下去除执行权限。
13 | ```
14 | //输入该命令
15 | qingmeideMac-mini:FireProtectionClient_Android qing.mei$ chmod +x gradlew
16 |
17 | //检查权限,发现该命令可以用了
18 | qingmeideMac-mini:FireProtectionClient_Android qing.mei$ ./gradlew
19 |
20 | > Configure project :app
21 | Configuration 'provided' in project ':app' is deprecated. Use 'compileOnly' instead.
22 | app: 'androidProcessor' dependencies won't be recognized as kapt annotation processors. Please change the configuration name to 'kapt' for these artifacts: 'com.google.dagger:dagger-compiler:2.11', 'com.google.dagger:dagger-android-processor:2.11', 'com.github.bumptech.glide:compiler:4.2.0', 'org.projectlombok:lombok:1.16.18', 'com.android.databinding:compiler:3.0.1' and apply the kapt plugin: "apply plugin: 'kotlin-kapt'".
23 |
24 | > Task :help
25 |
26 | Welcome to Gradle 4.1.
27 |
28 | ...
29 |
30 | BUILD SUCCESSFUL in 0s
31 | 1 actionable task: 1 executed
32 | qingmeideMac-mini:FireProtectionClient_Android qing.mei$
33 |
34 | ```
35 |
36 | 通过这个问题,深深感觉到,只是单纯的懂得配置gradle是不够的,接下来更需要深入学习这门脚本语言。
37 |
--------------------------------------------------------------------------------
/src/Groovy/Gradle学习笔记(一)基本配置.md:
--------------------------------------------------------------------------------
1 | ## 简介
2 |
3 | Gradle构建脚本的书写没有基于传统的XML文件,而是基于Groovy的领域专用语言(DSL)。Groovy是一种基于Java虚拟机的动态语言。Gradle团队认为,基于动态语言的DSL语言与Ant或者任何基于XML的构建系统相比,优势都十分显著。
4 |
5 | ## 一、Gradle基础
6 |
7 | ### 1、构建生命周期
8 | 一个Gradle的构建通常有如下三个阶段。
9 |
10 | * 初始化:项目实例会在该阶段被创建。如果一个项目有多个模块,并且每一个模块都有其对应的build.gradle文件,那么就会创建多个项目实例。
11 | * 配置:在该阶段,构建脚本会被执行,并为每个项目实例创建和配置任务。
12 | * 执行:在该阶段,Gradle将决定哪个任务会被执行。哪些任务被执行取决于开始该次构建的参数配置和该Gradle文件的当前目录。
13 |
14 | ### 2、构建配置文件
15 | Android的构建文件中,有一些元素是必需的:
16 | ```
17 | buildscript {
18 | repositories {
19 | jcenter()
20 | }
21 | dependencies {
22 | classpath'com.android.tools.build:gradle:3.0.1'
23 | }
24 | }
25 | ```
26 |
27 | 构建脚本代码块在Android构建工具上定义了一个依赖,就像Maven的artifact。这就是Android插件的来源,Android插件提供了构建和测试应用所需要的一切。每一个Android项目都应该申请该插件:
28 |
29 | ```
30 | apply plugin: 'com.android.application'
31 | ```
32 | > 如果你正在构建一个依赖库,那么你需要声明'com.android.library',而不是‘com.android.application’。你不能在一个模块中同时使用它们,因为这会导致构建错误。一个模块可以是一个Android应用模块,或者是一个Android依赖模块,但不能二者都是。
33 |
34 | ### 3、运行基本构建任务
35 |
36 | 使用terminal或命令提示符,可以导航到项目根目录,运行带有tasks的GradleWrapper命令:
37 |
38 | > $ gradlew tasks
39 |
40 | 这将打印出所有可用的任务列表。如果你添加了--all参数,那么你将获得每个任务对应依赖的详细介绍。
41 |
42 | > $ gradlew assembleDebug
43 |
44 | 这个任务会为这个应用创建一个debug版本的APK。
45 |
46 | 除了assemble外,还有其他三个基本任务。
47 |
48 | * Check:运行所有的检查,这通常意味着在一个连接的设备或模拟器上运行测试。
49 | * Build:触发assemble和check。
50 | * Clean:清除项目的输出。
51 |
52 | ## 二、认识Gradle文件
53 | ### 1、Settings.gradle文件
54 | 对于一个只包含一个Android应用的新项目来说,settings.gradle应该是这样的:
55 |
56 | > Include ':app'
57 |
58 | settings文件在初始化阶段被执行,并且定义了哪些模块应该包含在构建内。在本例中,app模块被包含在内。单模块项目并不一定需要setting文件,但是多模块项目必须要有setting文件,否则,Gradle不知道哪个模块应包含在构建内。
59 |
60 | ### 2、Project级别的build.gradle文件
61 |
62 | 默认情况下其包含如下两个代码块:
63 | ```
64 | buildscript { //实际构建配置代码块
65 | ext.kotlin_version = '1.2.10'
66 | repositories { //依赖仓库,每个仓库意味着一系列的依赖包
67 | jcenter() //JCenter是一个很有名的Maven库
68 | mavenCentral()
69 | maven { url 'https://maven.google.com' }
70 | google()
71 | }
72 | dependencies { //配置构建过程中的依赖包
73 | classpath 'com.android.tools.build:gradle:3.0.1'
74 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
75 | // NOTE: Do not place your application dependencies here; they belong
76 | // in the individual module build.gradle files
77 | }
78 | }
79 |
80 | allprojects { //声明那些需要被用于所有模块的属性,甚至可以在allprojects中创建task,这些任务最终被运用到所有Module
81 | repositories {
82 | jcenter()
83 | maven { url "https://jitpack.io" }
84 | google()
85 | }
86 | }
87 | ```
88 | 请注意,只要你使用了allprojects,模块就会被耦合到项目。这意味着,其很可能在没有主项目构建文件的情况下,无法独立构建模块。最初,这看起来可能不是一个问题,但是如果你后面想要分离一个内部依赖库到自己的项目,那么你将需要重构你的构建文件。
89 |
90 | ### 3、Module级别的build.gradle文件
91 |
92 | > Module层的build.gradle文件的属性只能应用在Androidapp模块,它可以覆盖Project层build.gradle文件的任何属性。
93 |
94 | ```
95 | apply plugin: 'com.android.application'
96 | android {
97 | compileSdkVersion 25
98 | buildToolsVersion '25.0.3'
99 |
100 | defaultConfig {
101 | applicationId "com.qingmei2.gradle.demo"
102 | minSdkVersion 14
103 | targetSdkVersion 22
104 | multiDexEnabled true
105 | }
106 |
107 | buildTypes {
108 | release {
109 | minifyEnabled false
110 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
111 | }
112 | }
113 | }
114 |
115 | dependencies {
116 | compile fileTree(dir: 'libs', include: '*.jar')
117 | compile 'com.android.support:multidex:1.0.1'
118 | }
119 | ```
120 | 记录以下三点:
121 |
122 | #### 插件
123 |
124 | 第一行用到了Android应用插件,谷歌的Android工具团队负责Android插件的编写和维护,并提供构建、测试和打包Android应用以及依赖项目的所有任务。
125 |
126 | #### Android
127 |
128 | * compileSdkVersion(必须):编译应用Android API的版本
129 | * buildToolsVersion(必须):构建工具及编译器的版本号
130 |
131 | * applicationId
132 | > 该属性覆盖了manifest文件中的packagename,但applicationId和packagename有一些不同。在Gradle被用作默认的Android构建系统之前,AndroidManifest.xml中的packagename有两个用途:作为一个应用的唯一标志,以及在R资源类中被用作包名。使用构建variants,Gradle可更容易地创建不同版本的应用。
133 | * minSdkVersion 应用最小API级别
134 | * targetSdkVersion
135 | > targetSdkVersion用于通知系统,该应用已经在某特定Android版本通过测试,从而操作系统不必启用任何向前兼容的行为。
136 |
137 | #### Dependencies
138 |
139 | 定义依赖包
140 |
141 |
142 |
--------------------------------------------------------------------------------
/src/Groovy/Gradle学习笔记(三)管理依赖.md:
--------------------------------------------------------------------------------
1 | ## 概述
2 |
3 | 依赖管理是Gradle最耀眼的特点之一。最佳情况下,你需要做的仅仅是在构建文件中添加一行代码,Gradle将会从远程仓库下载依赖,确保你的项目能够使用依赖中的类。
4 |
5 | Gradle甚至可以做得更多。如果你的项目中有一个依赖,并且其有自己的依赖,那么Gradle将会处理并解决这些问题。这些依赖中的依赖,被称之为**传递依赖**。
6 |
7 | ## 一、依赖仓库
8 |
9 | 一个依赖仓库可以被看作是文件的集合。Gradle默认情况下没有为你的项目定义任何依赖仓库,所以你需要在repositories代码块中添加它们。如果使用AndroidStudio,那么它会为你自动完成。如下所示:
10 |
11 | ```
12 | repositories {
13 | jcenter()
14 | maven { url "https://jitpack.io" }
15 | google()
16 | }
17 | ```
18 |
19 | 一个依赖通常是由三种元素定义的:group、name和version。group指定了创建该依赖库的组织,通常是反向域名。name是依赖库的唯一标识。version指定了需要使用依赖库的版本号。使用这三个元素,就可以在dependencies代码块中声明一个依赖了:
20 |
21 | ```
22 | compile 'io.reactivex.rxjava2:rxjava:2.1.8,
23 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
24 | ```
25 |
26 | ## 二、本地依赖
27 |
28 | ### 1、文件依赖
29 |
30 | 你可以使用Gradle提供的file方法来添加JAR文件作为一个依赖,如下所示:
31 |
32 | ```
33 | dependencies {
34 | compile files('libs/ domoarigato.jar ')
35 | }
36 | ```
37 | 当你有很多JAR文件时,这种方式会变得异常烦琐,一次添加一个完整的文件夹可能会更容易些。
38 |
39 | 默认情况下,新建的Android项目会有一个libs文件夹,其会被声明为依赖使用的文件夹。一个过滤器可以保证只有JAR文件会被依赖,而不是简单地依赖文件夹中的所有文件:
40 |
41 | ```
42 | dependencies {
43 | compile fileTree(include: ['*.jar'], dir: 'libs')
44 | }
45 | ```
46 |
47 | ### 2、NDK依赖
48 |
49 | Android插件默认支持原生依赖库,你所需要做的就是在模块层创建一个jniLibs文件夹,然后为每个平台创建子文件夹,将.so文件放在适当的文件夹中。
50 |
51 | ```
52 | android {
53 | sourceSets.main {
54 | jniLibs.srcDir 'src/main/libs'
55 | }
56 | }
57 | ```
58 |
59 | ### 3、依赖Library
60 |
61 | 构建Library需要Androidy Library插件:
62 |
63 | ```
64 | apply plugin: 'com.android.library'
65 | ```
66 |
67 | 在应用中包含依赖项目的方式有两种。一种是在项目中当作一个模块,另一种是创建一个可在多个应用中复用的.aar文件。
68 |
69 | 如果在项目中创建了一个模块作为依赖项目,那么你需要在settings.gradle中添加该模块,在应用模块中将它作为依赖:
70 |
71 | ```
72 | include ':sample', ':rximagepicker'
73 | ```
74 |
75 | 同时,你还需要在你的module的dependences模块中添加对应依赖:
76 |
77 | ```
78 | dependencies {
79 | implementation project(':rximagepicker')
80 | }
81 | ```
82 |
83 | ## 三、依赖的概念(Compile)
84 |
85 | Gradle将多个依赖添加至配置,并将其命名为集文件。下面是一个Android应用或依赖库的标准配置:
86 |
87 | * compile
88 | * apk
89 | * provided
90 | * testCompile
91 | * androidTestCompile
92 |
93 | compile是默认的配置,在编译主应用时包含所有的依赖。该配置不仅会将依赖添加至类路径,还会生成对应的APK。
94 |
95 | 如果依赖使用apk配置,则该依赖只会被打包到APK,而不会添加到编译类路径。
96 |
97 | provided配置则完全相反,其依赖不会被打包进APK。这两个配置只适用于JAR依赖。如果试图在依赖项目中添加它们,那么将会导致错误。
98 |
99 | 最后,testCompile和androidTestCompile配置会添加用于测试的额外依赖库。在运行测试相关的任务时,这些配置会被使用,并且在添加如JUnit或Espresso测试框架时,特别有用。如果你只希望在测试APK时使用这些框架,那么就不会生产APK。
100 |
101 |
102 | ## 参考
103 |
104 | 《Gradle For Android 中文版》
105 |
--------------------------------------------------------------------------------
/src/Groovy/Gradle学习笔记(二)自定义构建基础.md:
--------------------------------------------------------------------------------
1 | ## 一、配置Manifest文件
2 |
3 | 我们可以直接通过构建文件而不是manifest文件来配置applicationId、minSdkVersion、targetSdkVersion、versionCode和versionName。另外,下面一些属性也是我们可以操控的:
4 |
5 | * testApplicationId: 针对 instrument测试APK的applicationId
6 | * testInstrumentationRunner: JUnit测试运行器的名称,被用来运行测试
7 | * signingConfig:
8 | * proguardFiles
9 |
10 | ## 二、BuildConfig类
11 |
12 | 自SDK工具版本升级到17之后,构建工具都会生成一个叫作BuildConfig的类,该类包含一个按照构建类型设置值的DEBUG常量。如果有一部分代码你只想在debugging时期运行,比如logging,那么DEBUG会非常有用。你可以通过Gradle来扩展该文件,这样在debug和release时,就可以拥有不同的常量:
13 |
14 | ```groovy
15 | buildTypes {
16 | debug {
17 | buildConfigField "String", "API_ URL", "\"http:// test. example. com/ api\""
18 | buildConfigField "boolean", "LOG_ HTTP_ CALLS", "true"
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | release {
23 | buildConfigField "String", "API_ URL", "\"http:// test. example. com/ api\""
24 | buildConfigField "boolean", "LOG_ HTTP_ CALLS", "false"
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | ```
30 |
31 | 字符串值必须用转义双引号括起来,这样才会生成实际意义上的字符串。在添加buildConfigField行后,你可以在你的Java代码中使用BuildConfig.API_URL和BuildConfig.LOG_HTTP_ CALLS。
32 |
33 | 最近,Android工具开发小组还增加了类似的方式来配置资源值:
34 |
35 |
36 | ```groovy
37 | buildTypes {
38 | debug {
39 | resValue "string", "app_name", "ExampleDEBUG"
40 | }
41 | release {
42 | resValue "string", "app_name", "Example"
43 | }
44 | }
45 | ```
46 | 这里可以不加转义双引号,因为资源值通常默认被包装成value=""。
47 |
48 | ## 三、项目配置的范围
49 |
50 | 看这段代码:
51 |
52 | ```groovy
53 | allprojects {
54 | apply plugin:'com.android.application'
55 | android {
56 | compileSdkVersion 27
57 | buildToolsVersion "27.0.1"
58 | }
59 | }
60 | ```
61 |
62 | 该段代码只有在你的所有模块都是Androidapp项目的时候才有效,因为你需要运用Android插件来获取Android特有的设置。
63 |
64 | 实现这种行为的更好方式是**在顶层构建文件中定义值,然后将它们应用到模块中**。Gradle允许在Project对象上添加额外属性。这意味着任何build.gradle文件都能定义额外的属性,添加额外属性需要通过ext代码块。
65 |
66 | 你可以给顶层构建文件添加一个含有自定义属性的ext代码块:
67 |
68 | ```
69 | project.ext {
70 | android = [
71 | compileSdkVersion : 27,
72 | buildToolsVersion : "27.0.1",
73 |
74 | minSdkVersion : 16,
75 | targetSdkVersion : 27,
76 | versionCode : 1,
77 | versionName : "1.0.0"
78 | ]
79 | }
80 | ```
81 |
82 | 该段代码使得模块层的构建文件可以使用rootProject来获取属性:
83 | ```
84 | android {
85 | compileSdkVersion rootProject.ext.android["compileSdkVersion"]
86 | buildToolsVersion rootProject.ext.android["buildToolsVersion"]
87 |
88 | defaultConfig {
89 | minSdkVersion rootProject.ext.android["minSdkVersion"]
90 | targetSdkVersion rootProject.ext.android["targetSdkVersion"]
91 | versionCode rootProject.ext.android["versionCode"]
92 | versionName rootProject.ext.android["versionName"]
93 | }
94 | }
95 | ```
96 |
97 | ## 四、项目属性
98 |
99 | 前面例子的ext代码块是定义额外属性的一种方式。你可以使用属性来动态定制构建过程。定义属性的方式有很多种,这里我们只介绍三种常用的:
100 |
101 | * ext代码块
102 | * gradle.properties文件
103 | * -P命令行参数
104 |
105 | ```
106 | ext {
107 | local='Hello from build.gradle'
108 | }
109 |
110 | task printProperties {
111 | println local // Local extra property
112 | println propertiesFile // Property from file
113 | if (project. hasProperty(' cmd')) {
114 | println cmd // Command line property
115 | }
116 | }
117 | ```
118 |
119 | gradle.properties文件的实现(相同文件夹下):
120 | ```groovy
121 | propertiesFile = Hello from gradle.properties
122 | ```
123 |
124 | > 我们可以同时在顶层构建文件和模块构建文件中定义属性。如果一个模块定义了一个在顶层文件中早已存在的属性时,那么新属性将会直接覆盖原来的属性。
125 |
126 | ## 五、默认任务
127 |
128 | 如果没有指定任何任务而运行Gradle的话,其会运行help任务,它会打印一些如何使用Gradle工作的信息。help任务被设置为默认的任务,在每次运行没有明确指定任务的Gradle时,可以覆写默认的任务,添加一个常用的任务,甚至是多个任务。
129 |
130 | 在顶层build.gradle文件中加入一行,可用来指定默认的任务:
131 |
132 | ```
133 | defaultTasks 'clean', 'assembleDebug'
134 | ```
135 | 现在,当你运行没有任何参数的GradleWrapper时,它会运行clean和assembleDebug。通过运行tasks任务和格式化输出,可以清晰地看到哪些任务被设置为默认任务。
136 |
137 | ```
138 | $ gradlew tasks | grep "Default tasks"
139 |
140 | Default tasks: clean, assembleDebug
141 | ```
142 |
--------------------------------------------------------------------------------
/src/Groovy/Gradle学习笔记(四)构建Variant.md:
--------------------------------------------------------------------------------
1 | ## 一、构建类型
2 |
3 | 你可以在buildTypes代码块中定义构建类型。下面是AndroidStudio创建的构建文件的标准buildTypes代码块:
4 |
5 | ```groovy
6 | buildTypes {
7 | release {
8 | minifyEnabled false
9 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
10 | }
11 | }
12 | ```
13 |
14 | 在你的项目中,release构建类型不是被创建的唯一构建类型。默认情况下,每个模块都有一个debug构建类型。其被设置为默认构建类型,但是你可以通过将其包含至buildTypes代码块,然后覆写任何你想改变的属性配置。
15 |
16 | ### 1、创建构建类型
17 |
18 | 当默认的设置不够用时,我们可以很容易地创建自定义构建类型。新的构建类型只需在buildTypes代码块中新增一个对象即可。
19 |
20 | ```groovy
21 | android {
22 | buildTypes {
23 | staging {
24 | versionNameSuffix "-staging"
25 | buildConfigFiled "String","API_URL","https://xxx.xxx.xx"
26 | }
27 | }
28 | }
29 | ```
30 | staging构建类型针对applicationID定义了一个新的后缀,使其和debug以及release版本的applicationID不一样。假设你的构建类型是默认的,并且添加了staging构建类型,那么不同构建类型的applicationID应该像下面这样:
31 |
32 | * debug com.package
33 | * release com.package
34 | * staging com.package.staging
35 |
36 | 这意味着你将能够在相同色设备上同时安装staging版本和release版本,而不发生任何冲突。staging构建类型也有版本名后缀,其在相同设备上区分多个应用版本时非常重要。
37 |
38 | 在创建一个新的构建类型时,还可以用另一个构建类型的属性来初始化该构建类型:
39 |
40 | ```groovy
41 | buildTypes {
42 | release {
43 | minifyEnabled false
44 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
45 | }
46 | staging.initWith(buildTypes.debug)
47 | staging {
48 | debuggable = false
49 | }
50 | }
51 | ```
52 | initWith()方法创建了一个新的构建类型,并且复制了一个已经存在的构建类型的所有属性到新的构建类型中。我们可以通过在新的构建类型对象中简单地定义它们来覆写属性或定义额外的属性。
53 |
54 | ### 2、依赖
55 |
56 | Gradle自动为每个构建类型创建新的依赖配置。如果你只想在debug构建中添加一个appcompat-v7。那么,你可以这么做:
57 |
58 | ```
59 | dependencies {
60 | compile fileTree(include: ['*.jar'], dir: 'libs')
61 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
62 | exclude group: 'com.android.support', module: 'support-annotations'
63 | })
64 | debugCompile 'com.android.support:appcompat-v7:25.3.1'
65 | }
66 | ```
67 | 你可以以这种方式结合任何构建配置的构建类型,来让依赖变得更加具体。
68 |
69 | ## 二、 product flavor
70 |
71 | 与被用来配置相同App或library的不同构建类型相反,productflavor被用来创建不同的版本。典型的例子是一个应用有免费版和付费版。
72 |
73 | 如果你不确定是否需要一个新的构建类型或新的productflavor,那么你应该问自己,是否想要创建一个供内部使用的应用,或一个会发布到GooglePlay的新APK。如果你需要一个全新的App,独立于你已有的应用发布,那么productflavor就是你需要的,否则,你应该坚持使用构建类型。
74 |
75 | ### 1、创建product flavor
76 |
77 | ```
78 | productFlavors {
79 |
80 | red {
81 | manifestPlaceholders = [
82 | APP_NAME : "@string/app_name"]
83 | }
84 | blue {
85 | manifestPlaceholders = [
86 | APP_NAME : "@string/app_name_dev"]
87 | }
88 | }
89 | ```
90 | ### 2、源集
91 |
92 | 和构建类型类似,productflavor也可以拥有它们自己的源集目录。为一个特殊的flavor创建一个文件夹就和创建一个有flavor名称的文件夹一样简单。你甚至可以为一个特定构建类型和flavor的结合体创建一个文件夹。该文件夹的名称将是flavor名称+构建类型的名称。例如,如果你想让blueflavor的release版本有一个不同的应用图标,那么文件夹将会被叫作blueRelease。
93 |
94 | 合并文件夹的组成将比构建类型文件夹和productflavor文件夹拥有更高优先级。
95 |
96 | ### 3、多维度
97 |
98 | 在某些情况下,你可能想要更进一步,创建productflavor的结合体。例如,客户A和客户B在他们的App中都想要免费版和付费版,并且是基于相同的代码、不同的品牌。创建四种不同的flavor意味着需要像这样设置多个拷贝,所以这不是最佳做法。而使用flavor维度是结合flavor的有效方式,如下所示:
99 |
100 | ```
101 | android {
102 | flavorDimensions "color", "price"
103 | productFlavors {
104 | red { flavorDimension "color" }
105 | blue { flavorDimension "color" }
106 | free { flavorDimension "price" }
107 | paid { flavorDimension "price" }
108 | }
109 | }
110 | ```
111 |
112 | 当你为flavor添加了维度后,Gradle会希望你能够为每个flavor都添加维度。如果你忘了,那么你会收到一个带有错误接受的构建错误。
113 |
114 | 维度的顺序非常重要。当结合两个flavor时,它们可能定义了相同的属性或资源。在这种情况下,flavor维度数组的顺序就决定了哪个flavor配置将被覆盖。在上一个例子中,color维度覆盖了price维度。该顺序也决定了构建variant的名称。
115 |
116 | 假设默认的构建配置是debug和release构建类型,那么前面例子中定义的flavor将会生成下面这些构建variant:
117 |
118 | * blueFreeDebug and blueFreeRelease
119 | * bluePaidDebug and bluePaidRelease
120 | * redFreeDebug and redFreeRelease
121 | * redPaidDebug and redPaidRelease
122 |
123 | ### 3、构建 variant
124 |
125 | 如果没有productflavor,variant将只会包含构建类型。没有任何构建类型是不可能的,即便你自己不定义任何构建类型,Gradle的Android插件也会为你的App或library创建一个debug构建类型。
126 |
127 | #### 3.1 Task
128 |
129 | Gradle的Android插件将会为你配置的每一个构建variant创建任务。一个新的Android应用默认有debug和release两种构建类型,所以你可以用assembleDebug和assembleRelease来分别构建两个APKs,即用单个命令assemble来创建两个APKs。
130 |
131 | 一旦你开始添加flavor,那么整个全新的任务系列将会被创建,因为每个构建类型的任务会和每个productflavor相结合。
132 |
133 | 仅仅一个BuildType + Product Flavor。你可以得到:
134 |
135 | * assembleBlue:使用blue flavor配置和组装BlueRelease及BlueDebug。
136 | * assembleDebug:使用debug构建配置类型,并为每个productflavor组装一个debug版本。
137 | * assembleBlueDebug:用构建配置类型结合flavor配置,并且flavor设置将会覆盖构建类型设置。
138 |
139 | #### 3.2 源集
140 |
141 | 构建variant,是一个构建类型和一个或多个productflavor的结合体,其可以有自己的源集文件夹。例如,由debug构建类型blueflavor和freeflavor创建的variant可以有其自己的源集src/blueFreeDebug/java/。其可以通过使用sourceSets代码块来覆盖文件夹的位置。
142 |
143 | 源集的引入额外增加了构建进程的复杂性。Gradle的Android插件在打包应用之前将main源集和构建类型源集合并在一起。此外,library项目也可以提供额外的资源,这些也需要被合并。这同样适用于manifest文件。
144 |
145 | 资源 和 manifest 的 优先顺序:
146 |
147 | > BuildType >> Flavor >> Main >> Dependencies
148 |
149 | 如果资源在flavor和main源集中都有申明,那么flavor中的资源将被赋予更高的优先级。在这种情况下,flavor源集中的资源将会被打包。在library项目中申明的资源通常具有最低优先级。
150 |
151 | > 这也就说明了,即使appModule和Library中同样拥有同名的资源,appModule会覆盖Library的资源
152 |
153 |
154 | 想了解更多,请阅读官方文档:
155 |
156 | http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger
157 |
158 | #### 3.3 variant过滤器
159 |
160 | 在你的构建中,可以完全忽略某些variant。通过这种方式,你就可以通过assemble命令来加快构建所有variant的进程,并且你的任务列表不会被任何无须执行的任务污染。
161 |
162 | variant过滤器也可确保在AndroidStudio的构建variant切换器中不会出现过滤的构建variant。
163 |
164 | 你可以在App或library根目录下的build.gradle文件使用以下代码来过滤variants:
165 |
166 |
167 | ```
168 | android.variantFilter { variant ->
169 | if(variant.buildType.name.equals('release')) {
170 | variant.getFlavors().each() { flavor ->
171 | if(flavor.name.equals('blue')) {
172 | variant.setIgnore(true);
173 | }
174 | }
175 | }
176 | }
177 | ```
178 |
179 | 在本例中,我们首先检查了variant的构建类型中是否含有release,然后我们去除了所有含有该名称的Productflavor。当不使用flavor维度时,在flavor数组中将只含有一个Productflavor。一旦你开始使用flavor维度,那么flavor数组就将会持有和维度一样多的flavor。在示例脚本中,我们检查了blueproductflavor,并且告诉构建脚本忽略这一variant。
180 |
181 | #### 3.4 签名配置
182 |
183 | Android插件使用了一个通用keystore和一个已知密码自动创建了debug配置,所以就没有必要为该构建类型再创建一个签名配置了:
184 |
185 | ```
186 | android {
187 | signingConfigs {
188 | staging.initWith(signingConfigs.debug)
189 | release {
190 | storeFilefile("release.keystore")
191 | storePassword "secretpassword"
192 | keyAlias "gradleforandroid"
193 | keyPassword "secretpassword"
194 | }
195 | }
196 | }
197 | ```
198 |
199 | release配置使用storeFile来指定keystore文件的路径,之后定义了密钥别名和两个密码。
200 |
201 | 在定义签名配置之后,你需要将它们应用到你的构建类型或flavor中。构建类型和flavor都有一个叫作signingConfi的属性,如下所示:
202 |
203 | ```
204 | buildTypes {
205 | release {
206 | signingConfigsigningConfigs.release
207 | }
208 | }
209 | ```
210 |
211 | 该例使用了构建类型,但如果你想为每个你所创建的flavor使用不同的凭证,那么你就需要创建不同的签名配置,不过你可以以相同的方式来定义它们:
212 |
213 |
214 | ```
215 | productFlavors {
216 | blue {
217 | signingConfigsigningConfigs.release
218 | }
219 | }
220 | ```
221 |
222 | 使用签名配置这种方式会造成很多问题。当为flavor分配一个配置时,实际上它是覆盖了构建类型的签名配置。如果你不想这样,那么在使用flavor时,就应该为每个构建类型的每个flavor分配不同的密钥.
223 |
224 | ## 参考
225 |
226 | 本文大部分内容节选自 凯文·贝利格里姆斯(KevinPelgrims).
227 |
228 | 《GradleforAndroid中文版》电子工业出版社.Kindle版本.
229 |
230 | 仅供自己作为笔记参考,未经允许严禁转载及相关商业性用途!
231 |
--------------------------------------------------------------------------------
/src/Groovy/Groovy学习笔记1:下载及配置环境.md:
--------------------------------------------------------------------------------
1 | ## 本文简单讲述了
2 | * Mac/Linux系统Groovy的环境配置
3 | * Windows系统Groovy的环境配置
4 |
5 | 我们首先进入Groovy的官网:
6 |
7 | http://www.groovy-lang.org/download.html
8 |
9 | ## Mac下配置Groovy
10 |
11 | 看到官网提示,我们可以直接下载groovy sdk的压缩包,但是对于Mac或者Linux用户,官方更推荐我们使用
12 | SDKMAN(这是什么奇怪的名字)开发者安装工具。
13 |
14 | 
15 |
16 | [SDKMAN的Github链接](https://github.com/sdkman/sdkman-cli)
17 |
18 | > Install Software Development Kits for the JVM such as Java, Groovy, Scala, Kotlin and Ceylon. Ant, Gradle, Grails, Maven, SBT, Spark, Spring Boot, Vert.x and many others also supported.
19 |
20 | SDKMan还是提供了很强大的功能,为Java,Groovy,Scala,Kotlin和Ceylon等JVM安装软件开发工具包。 Ant,Gradle,Grails,Maven,SBT,Spark,Spring Boot,Vert.x和许多其他支持。
21 |
22 | 然后我们往下拉,看到提供给Mac或者Linux开发者的步骤:
23 |
24 | 
25 |
26 | 我们直接按照他给的命令行,进行安装即可,首先输入先下载sdkman:
27 |
28 | > $ curl -s get.sdkman.io | bash
29 |
30 | > $ source "$HOME/.sdkman/bin/sdkman-init.sh"
31 |
32 | 很快我们安装成功,个人原因忘记截图,sdkman下载好后,我们安装groovy:
33 |
34 | > $ sdk install groovy
35 |
36 | 
37 |
38 | 安装好后,我们直接运行以下命令确认Groovy安装成功:
39 |
40 | > $ groovy -version
41 |
42 | 这之后,我们可以直接通过命令行打开Groovy的console,开启我们的Groovy之旅:
43 |
44 | > $ groovyConsole
45 |
46 | 
47 |
48 | ## Windows配置Groovy
49 |
50 | 直接进入官网,点击【Windows installer】,下载对应的exe可执行文件,下载完毕后,一键傻瓜式安装即可
51 |
52 | 
53 |
54 | WindowsInstaller 会自动帮你配置好环境变量,因此我们只需要确认环境变量是否配置好后,打开cmd命令行,进行测试即可:
55 |
56 | 
57 |
58 |
59 | 
60 |
61 |
62 | ## Mac系统配置Groovy到Idea
63 |
64 | 在Idea中使用groovy很简单,直接新建一个groovy后缀的文件即可,但是必须先把Groovy的SDK配置给Idea:
65 |
66 | 
67 |
68 | 点击Configure Groovy SDK,找到Groovy的根目录并进行配置即可:
69 |
70 |
71 | 
72 |
73 | SDKMan虽然下载安装Groovy比较简单,但是可能导致Groovy的根目录不太好找,实际目录在
74 | >**Path: /Users/(用户名)/.sdkman/candidates/gradle/[(版本号)|current]**
75 |
76 | 隐藏目录需要 Cmd+Shift+. 控制显示。
77 |
78 | 配置好后,运行groovy文件即可:
79 |
80 | 
81 |
--------------------------------------------------------------------------------
/src/Groovy/Groovy学习笔记2:Groovy的基本语法.md:
--------------------------------------------------------------------------------
1 | ## 1、Java和Groovy对比
2 | ### 1.1、Hello Groovy!
3 | 从一个简单的案例开始:
4 | ```Groovy
5 | class A01_GroovyApp {
6 |
7 | public static void hello() {
8 | println('Hello Groovy!')
9 | }
10 |
11 | public static void main(String[] args) {
12 | hello()
13 | }
14 | }
15 | ```
16 | 运行结果
17 | ```
18 | Hello Groovy!
19 | ```
20 |
21 | ### 1.2、如何实现循环
22 | 简单的,可以类似Java一样这样写:
23 |
24 | ```groovy
25 | Stream.of(1, 2, 3, 4)
26 | .forEach(new Consumer() {
27 | @Override
28 | void accept(Integer integer) {
29 | println("Java print" + integer)
30 | }
31 | })
32 | ```
33 |
34 | 运行结果
35 | > Java print1
36 | Java print2
37 | Java print3
38 | Java print4
39 |
40 | 也可以这样写
41 | ```groovy
42 | for (i in 0..4) { //方式1
43 | print "$i"
44 | }
45 |
46 | println()
47 |
48 | 0.upto(4) { print "$it" } //方式2
49 |
50 | println()
51 |
52 | 3.times { print("$it") } //方式3
53 |
54 | println()
55 |
56 | 0.step(10, 2) { print("$it") } //方式4
57 | ```
58 | 运行结果:
59 | >01234
60 | 01234
61 | 012
62 | 02468
63 |
64 | ### 1.3、初识GDK
65 |
66 | Java中,假如我们想要在代码中调用SVN的help,我们应该这样实现Java代码:
67 |
68 | 
69 |
70 | 在groovy中,我们只需要这样实现:
71 |
72 | ```groovy
73 | println "svn help".execute().text
74 |
75 | //或者执行 groovy -v命令查询groovy的版本
76 | println "groovy -v".execute().text
77 |
78 | //或者执行 ls -l命令
79 | println "ls -l".execute().text
80 | ```
81 | 结果:
82 | > java.lang.UNIXProcess
83 |
84 | > Groovy Version: 2.4.15 JVM: 1.8.0_131 Vendor: Oracle Corporation OS: Mac OS X
85 |
86 | > total 128
87 | -rw-r--r-- 1 qing.mei staff 23 Mar 29 20:11 A00_Helloworld.groovy
88 | -rw-r--r-- 1 qing.mei staff 232 Apr 4 20:04 A01_GroovyApp.groovy
89 | -rw-r--r-- 1 qing.mei staff 50 Mar 29 20:29 A02_GroovyScript.groovy
90 | -rw-r--r-- 1 qing.mei staff 383 Apr 4 20:09 A03_foreach.groovy
91 | -rw-r--r-- 1 qing.mei staff 114 Mar 30 17:33 A04_GDK.groovy
92 | -rw-r--r-- 1 qing.mei staff 93 Mar 30 19:27 A05_Exception.groovy
93 | -rw-r--r-- 1 qing.mei staff 250 Mar 30 19:29 A06_Wizard.groovy
94 | -rw-r--r-- 1 qing.mei staff 29 Mar 30 19:29 A06_Wizard_Script.groovy
95 | -rw-r--r-- 1 qing.mei staff 125 Mar 30 19:36 A07_GroovyBean.groovy
96 | -rw-r--r-- 1 qing.mei staff 188 Mar 30 19:36 A07_GroovyBean_Script.groovy
97 | -rw-r--r-- 1 qing.mei staff 387 Mar 30 19:36 A07_JavaBean.java
98 | -rw-r--r-- 1 qing.mei staff 299 Apr 4 19:25 A08Calendar.groovy
99 | -rw-r--r-- 1 qing.mei staff 188 Apr 4 19:33 A09Robot.groovy
100 | -rw-r--r-- 1 qing.mei staff 200 Apr 4 19:31 A09RobotScript.groovy
101 | -rw-r--r-- 1 qing.mei staff 524 Apr 4 19:44 A10Params.groovy
102 | -rw-r--r-- 1 qing.mei staff 1540 Apr 4 20:01 A11MultipleAssignments.groovy
103 |
104 | ## 2、JavaBean
105 | 相比Java的get和set方法,我们来看Groovy的JavaBean:
106 | ```groovy
107 | class A07_GroovyBean {
108 |
109 | def name = 'qingmei2'
110 | final year
111 |
112 | A07_GroovyBean(year) {
113 | this.year = year
114 | }
115 | }
116 | ```
117 |
118 | 其操作方式:
119 | ```groovy
120 | def groovyBean = new A07_GroovyBean(10)
121 |
122 | println "year: $groovyBean.year"
123 | println "name: $groovyBean.name"
124 | println 'Setting...'
125 | groovyBean.name = "LiHua"
126 | println "name: $groovyBean.name"
127 | ```
128 | 执行结果:
129 | > year: 10
130 | name: qingmei2
131 | Setting...
132 | name: LiHua
133 |
134 | 同时,还可以这样节省代码:
135 | ```groovy
136 | //我们可以这样代替Java的Calendar.getInstance()
137 | Calendar.instance
138 |
139 | str = 'hello'
140 |
141 | //谨慎使用class属性,类似Map/生成器等一些类对该属性有特殊处理,因此为了避免意外,一般使用getClass()
142 | def name = str.class.name
143 |
144 | println "the string = $str"
145 | println "the string class name = $name"
146 | ```
147 |
148 | ## 3、灵活初始化和具名参数
149 | Groovy中可以灵活地初始化一个JavaBean类。在构造对象时,可以简单地以逗号分隔的名值对来给出属性值。如果类有一个无参构造器,该操作会在构造器之后执行。1也可以设计自己的方法,使其接受具名参数。要利用这一特性,需要把第一个形参定义为Map。下面通过代码来实际地看一下。
150 | ```groovy
151 | class A09Robot {
152 |
153 | def type, height, width
154 |
155 | def access(Map location, weight, fragile) {
156 | println "received fragile ? $fragile, weight: $weight, location: $location"
157 | }
158 | }
159 | ```
160 |
161 | ```groovy
162 | def robot = new A09Robot(type: 'arm', width: 10, height: 40)
163 | println "$robot.type, $robot.width, $robot.height"
164 |
165 | robot.access(x: 30, y: 20, z: 10, 50, true)
166 | robot.access(50, true, x: 30, y: 20, z: 10)
167 | ```
168 | 我们来看结果:
169 | > arm, 10, 40
170 | received fragile ? true, weight: 50, location: [x:30, y:20, z:10]
171 | received fragile ? true, weight: 50, location: [x:30, y:20, z:10]
172 |
173 | ## 4.可选形参
174 | 参考Kotlin,我们可以给函数的参数设置默认的值:
175 | ```groovy
176 | //groovy 可以把方法和构造器形参设置为可选择的
177 | //但是这些形参必须位于形参列表的末尾
178 | def log(x, base = 10) {
179 | Math.log(x) / Math.log(base)
180 | }
181 |
182 | println log(1024)
183 | println log(1024, 10)
184 | println log(1024, 2)
185 |
186 | //同时,groovy还会把末尾的数组形参视作可选的
187 | //因此,可以为最后一个形参提供0个或者多个值
188 | def task(name, String[] details) {
189 | println "$name - $details"
190 | }
191 |
192 | task 'call', '123-456-7890'
193 | task 'call', '123-456-7890', '234,567,890'
194 | task 'checkmail'
195 | ```
196 |
197 | 结果:
198 | > 3.0102999566398116
199 | 3.0102999566398116
200 | 10.0
201 | call - [123-456-7890]
202 | call - [123-456-7890, 234,567,890]
203 | checkmail - []
204 |
205 | ## 5.使用多赋值
206 | 向方法传递多个参数,这在很多编程语言中都司空见惯。但是从方法返回多个结果,尽管可能非常实用,却不那么常见。
207 |
208 | 要想从方法返回多个结果,并将它们一次性赋给多个变量,我们可以返回一个数组,然后将多个变量以逗号分隔,放在圆括号中,置于赋值表达式左侧即可。
209 |
210 | 后面的例子中有一个负责将全名分割为名字(FirstName)和姓氏(LastName)的方法。不出所料,split()方法就返回一个数组。可以把splitName()的结果赋给一对变量:firstName和lastName。Groovy会把结果中的两个值分别赋给这两个变量。
211 |
212 | ```groovy
213 | def splitFullName(String fullname) {
214 | fullname.split ' '
215 | }
216 |
217 | def (firstname, lastname) = splitFullName('James Bond')
218 |
219 | println "$lastname, $firstname $lastname"
220 | ```
221 |
222 | 结果:
223 | > Bond, James Bond
224 |
225 | 还可以使用该特性来交换变量,无需创建中间变量来保存被交换的值,只需将欲交换的变量放在圆括号内,置于赋值表达式左侧,同时将它们以相反顺序放于方括号内,置于右侧即可。
226 |
227 | ```groovy
228 | //交换值的方式
229 |
230 | def name1 = 'name1'
231 | def name2 = 'name2'
232 |
233 | println "$name1 and $name2"
234 |
235 | (name1, name2) = [name2, name1]
236 |
237 | println "$name1 and $name2"
238 | ```
239 |
240 | 结果:
241 |
242 | > name1 and name2
243 | name2 and name1
244 |
245 | 此外,当变量与值的数目不匹配时,如果有多余的变量,groovy会把它们设置为null,多余的值则会被丢弃:
246 |
247 | ```groovy
248 | //仍然是上面的名字分割案例
249 | def (names) = splitFullName('James Bond')
250 | println "$names"
251 |
252 | //左侧只有两个变量,因此Spike和Tyke会被丢弃
253 | def (String cat, String mouse) = ['Tom', 'Jerry', 'Spike', 'Tyke']
254 | println "$cat and $mouse"
255 |
256 | def (String first, String second, String third) = ['Tom', 'Jerry']
257 | println "$first and $second and $third"
258 | ```
259 | 结果:
260 |
261 | > James //多余的值则会被丢弃
262 | Tom and Jerry
263 | Tom and Jerry and null //多余的变量,groovy会把它们设置为null
264 |
265 |
266 |
--------------------------------------------------------------------------------
/src/Groovy/Groovy学习笔记3:接口,布尔判断,操作符重载.md:
--------------------------------------------------------------------------------
1 | ## Groovy接口
2 |
3 | Groovy 不需要显示的通过new创建匿名内部类的实例。
4 |
5 | ```groovy
6 | //Button对象
7 | class Button {
8 |
9 | void addOnClickListener(OnClickListener listener) {
10 | listener.onClick()
11 | }
12 |
13 | void addOnLongClickListener(OnLongClickListener listener) {
14 | listener.onLongClick()
15 | }
16 |
17 | }
18 |
19 | //按钮的点击监听
20 | interface OnClickListener {
21 | void onClick()
22 | }
23 | //长按事件监听
24 | interface OnLongClickListener {
25 | void onLongClick()
26 | }
27 | ```
28 |
29 |
30 | 调用了addOnClickListener方法,同时为该方法提供了一个代码块,借助as操作符,相当于实现了OnClickListener接口:
31 |
32 | ```groovy
33 | def button = new Button()
34 |
35 | listener = { println 'addListener' }
36 |
37 | button.addOnClickListener(listener) as OnClickListener
38 | ```
39 |
40 | 输出:
41 | > addListener
42 |
43 | Groovy自会处理剩下的工作。它会拦截对接口中任何方法的调用,然后将调用路由到我们提供的代码块。
44 |
45 | 对于有多个方法的接口,如果打算为其所有方法提供一个相同的实现,和上面一样,不需要特殊的操作:
46 |
47 | ```groovy
48 | def button = new Button()
49 |
50 | listener = { println 'addListener' }
51 |
52 | button.addOnClickListener(listener) as OnClickListener
53 | button.addOnLongClickListener(listener) as OnLongClickListener
54 | ```
55 |
56 | 输出:
57 | > addListener
58 | addListener
59 |
60 | Groovy没有强制实现接口中的所有方法:可以只定义自己关心的,而不考虑其他方法。如果剩下的方法从来不会被调用,那也就没必要去实现这些方法了。当在单元测试中通过实现接口来模拟某些行为时,这项技术非常有用。
61 |
62 | 但是在大多数实际情况下,接口中的每个方法需要不同的实现。不用担心,Groovy可以摆平。只需要创建一个映射,以每个方法的名字作为键,以方法对应的代码体作为键值,同时使用简单的Groovy风格,用冒号(:)分隔方法名和代码块即可。
63 |
64 | 不必实现所有方法,只需实现真正关心的那些即可。如果未予实现的方法从未被调用过,那么也就没有必要浪费精力去实现这些伪存根。当然,如果没提供的方法被调用了,则会出现异常:
65 |
66 | ```groovy
67 | class Button {
68 |
69 | void addOnStateChangeListener(OnStateChangeListener listener) {
70 | listener.onCreate()
71 | listener.onStart()
72 | listener.onStop()
73 | listener.onDestory()
74 | }
75 |
76 | }
77 |
78 | //状态监听
79 | interface OnStateChangeListener {
80 |
81 | void onCreate()
82 |
83 | void onStart()
84 |
85 | void onStop()
86 |
87 | void onDestroy()
88 | }
89 |
90 | ```
91 | ```groovy
92 | def onStateChangelistener = [
93 | onCreate: {
94 | println 'onCreate'
95 | },
96 | onStart : {
97 | println 'onStart'
98 | },
99 | onStop : {
100 | println 'onStop'
101 | } //这里只实现3个状态监听,onDestroy()并未实现
102 | ]
103 | button.addOnStateChangeListener(onStateChangelistener as OnStateChangeListener)
104 | ```
105 |
106 | 结果:
107 |
108 | 
109 |
110 | ## 布尔值判断
111 |
112 | Java要求if语句的条件部分必须是一个布尔表达式,比如前面例子中的if(obj!=null)和if(val>0)。
113 |
114 | Groovy会尝试推断,如果在需要布尔值的地方放了一个对象引用,Groovy会检查该引用是否为null。它将null视作false,将非null的值视作true:
115 |
116 | ```groovy
117 | str = 'hello'
118 |
119 | if (str) {
120 | println str
121 | }
122 | ```
123 | 结果:
124 |
125 | 
126 |
127 | 更准确的说,表达式的结果还与对象的类型有关。例如,如果对象是一个集合(如java.util.ArrayList),那么Groovy会检查该集合是否为空。
128 | 因此,在这种情况下,只有当obj不为null,而且该集合至少包含一个元素时,表达式if(obj)才会被计算为true:
129 |
130 | ```groovy
131 | str1 = null
132 | println str1 ? 'str1 is not null' : 'str1 is null'
133 |
134 | str2 = [1, 2, 3]
135 | println str2 ? 'str2 is not null' : 'str2 is null'
136 |
137 | str3 = []
138 | println str3 ? 'str3 is not null' : 'str3 is null'
139 | ```
140 |
141 | 结果:
142 |
143 | 
144 |
145 | 集合类不是唯一受到特殊对待的。那么有哪些类型将被特殊对待,Groovy又是如何计算它们的呢?
146 |
147 | | 类型 | 为真的条件 |
148 | | - | - |
149 | | Boolean | true |
150 | | Collection | 集合不为空 |
151 | | character | 值不为0 |
152 | |CharSequence|长度不为0|
153 | |Enumration|hasMoreElements()为true|
154 | |Iterator|hasNext()为true|
155 | |Number| Double值部位0|
156 | |Map|映射不为空|
157 | |Matcher|至少有一个匹配|
158 | |Object[]|长度大于0|
159 | |其他引用类型|引用不为null|
160 |
161 | 除了使用Groovy内建的布尔求值约定,在自己的类中,还可以通过实现asBoolean()方法来编写自己的布尔转换。
162 |
163 | ## 操作符重载
164 |
165 | Groovy支持操作符重载,可以巧妙地应用这一点来创建DSL
166 |
167 | 比如我们可以重载++操作符,该示例映射的是String类的next()方法:
168 |
169 | ```groovy
170 | for (ch = 'a'; ch < 'd'; ch++) {
171 | println ch
172 | }
173 | ```
174 |
175 | 结果:
176 | > a
177 | b
178 | c
179 |
180 | Groovy中还可以使用简洁的for-each语法,不过两种实现都用到了String类的next()方法:
181 |
182 | ```groovy
183 | for (ch in 'a'..'c') {
184 | println ch
185 | }
186 | ```
187 | 结果同上,都是输出了abc。
188 |
189 | 要向集合中添加元素,可以使用<<操作符,该操作符会被转换为Groovy在Collection上添加的leftShift()方法:
190 |
191 | ```groovy
192 | list = ['hello']
193 | list << 'groovy!'
194 | println list
195 | ```
196 |
197 | 输出:
198 | > [hello, groovy!]
199 |
200 | 通过添加映射方法,我们可以为自己的类提供操作符,比如为+操作符添加plus()方法:
201 |
202 | ```groovy
203 | class B03ComplexNumber {
204 |
205 | def real, imaginary //实部,虚部
206 |
207 | def plus(other) {
208 | new B03ComplexNumber(real: real + other.real,
209 | imaginary: imaginary + other.imaginary)
210 | }
211 |
212 | @Override
213 | String toString() {
214 | return "$real ${imaginary > 0 ? '+' : ''} ${imaginary}i "
215 | }
216 | }
217 | ```
218 |
219 | 我们执行这段代码:
220 | ```groovy
221 | c1 = new B03ComplexNumber(real: 1, imaginary: 4)
222 | c2 = new B03ComplexNumber(real: 2, imaginary: 3)
223 | println c1 + c2
224 | ```
225 | 输出:
226 |
227 | > 3 + 7i
228 |
229 | ComplexNumber类重载了+操作符。对于计算涉及负数平方根的复杂方程式,复数非常有用: 复数有实部和虚部。
230 | 因为在ComplexNumber类上添加了plus()方法,所以可以使用+操作符把两个复数加到一起,得到又一个作为结果的复数。
231 |
232 |
--------------------------------------------------------------------------------
/src/Groovy/Groovy学习笔记4:特殊注解.md:
--------------------------------------------------------------------------------
1 | ### 1.@Canonical
2 | 如果要编写的toString()方法只是简单地显示以逗号分隔的字段值,则可以使用@Canonical变换让Grooovy编译器帮来干这个活。
3 |
4 | 默认情况下,它生成的代码会包含所有字段。不过可以让它仅包含特定字段,而去掉其他字段。
5 |
6 | ```groovy
7 | @Canonical
8 | class Student {
9 | String firstName
10 | String lastName
11 | int age
12 | String address
13 | }
14 |
15 | def student = new Student(firstName: "Zhang",
16 | lastName: "Sanfeng",
17 | age: 16,
18 | address: "China")
19 |
20 | println student
21 | ```
22 | 输出:
23 | > Student(Zhang, Sanfeng, 16, China)
24 |
25 | 不管如何,这个注解在打印Log等情况下,可堪一用。
26 |
27 | ### 2.@Delegate
28 |
29 | 只有当派生类是真正可替换的,而且可代替基类使用时,继承才显示出其优势。从纯粹的代码复用角度看,对于其他大部分用途,**委托要优于继承**。
30 |
31 | 然而在Java中我们不太愿意使用委托,因为会导致代码冗余,而且需要更多工作。Groovy使委托变得非常容易,所以我们可以做出正确的设计选择。
32 |
33 | ```groovy
34 | class Worker {
35 | def work() { println 'get work done' }
36 |
37 | def analyze() { println 'analysis...'}
38 |
39 | def writeReport() { println 'get report written' }
40 | }
41 |
42 | class Expert {
43 |
44 | def analyze() { println 'expert analysis...' }
45 | }
46 |
47 | class Manager {
48 |
49 | @Delegate
50 | Expert expert = new Expert()
51 | @Delegate
52 | Worker worker = new Worker()
53 | }
54 |
55 | def manager = new Manager()
56 | manager.analyze()
57 | manager.work()
58 | manager.writeReport()
59 | ```
60 |
61 | 在编译时,Groovy会检查Manager类,如果该类中没有被委托类中的方法,就把这些方法从被委托类中引入进来。因此,首先它会引入Expert类中的analyze()方法。
62 |
63 | 而从Worker类中,只会把work()和writeReport()方法因为进来。这时候,因为从Expert类带来的analyze()方法已经出现在Manager类中,所以Worker类中的analyze()方法会被忽略。
64 |
65 | 对于引入的每个方法,Groovy会简单地把对该方法的调用路由给实例上的相应方法,就像这样:publicObjectanalyze(){ expert.analyze() }。委托类会对新获得的方法做出响应,在下面的输出中可以看到:
66 |
67 | > expert analysis...
68 | get work done
69 | get report written
70 |
71 | 因为有了@Delegate注解,Manager类是可扩展的。如果在Worker或Expert类上添加或去掉了方法,不必对Manager类做任何修改,相应的变化就会生效。只需要重新编译代码,剩下的事Groovy会处理。
72 |
73 | ### 3.@Immutable
74 |
75 | 不可变对象天生是线程安全的,将其字段标记为final是很好的实践选择。**如果用@Immutable注解标记一个类,Groovy会将其字段标记为final的**,并且额外为我们创建一些便捷方法,从而使得“做正确的事情”变得更容易了。
76 |
77 | ```groovy
78 | @Immutable
79 | class ImmutableStudent {
80 |
81 | String name
82 | int age
83 | }
84 | println new ImmutableStudent("jack", 24)
85 | ```
86 |
87 | 作为反馈,Groovy给我们提供了一个构造器,其参数以类中字段定义的顺序依次列出。在构造时间过后,字段就无法修改了。此外,Groovy还添加了hashCode()、equals()和toString()方法。
88 |
89 |
90 | 运行所提供的构造器和toString()方法:
91 | > ImmutableStudent(jack, 24)
92 |
93 | **可以使用@Immutable注解轻松地创建轻量级的不可变值对象**。在基于Actor模型的并发应用中,线程安全是个大问题,而这些不可变值对象是作为消息传递的理想实例。
94 |
95 | ### 4.@Lazy
96 |
97 | 我们想把耗时对象的构建推迟到真正需要时。完全可以懒惰与高效并得,编写更少的代码,同时又能获得惰性初始化的所有好处。
98 |
99 | 下面的例子将推迟创建Heavy实例,直到真正需要它时。既可以在声明的地方直接初始化实例,也可以将创建逻辑包在一个闭包中。
100 |
101 | ```groovy
102 | class Heavy {
103 | def size = 10
104 |
105 | Heavy() { println "Creating Heavy : Size = $size" }
106 | }
107 |
108 | class AsNeed {
109 | def value
110 |
111 | @Lazy Heavy heavy1 = new Heavy()
112 | @Lazy Heavy heavy2 = { new Heavy(size: value) }()
113 |
114 | AsNeed() { println 'Created AsNeed' }
115 | }
116 |
117 | def need = new AsNeed(value: 1000)
118 | println need.heavy1.size
119 | println need.heavy1.size
120 | println need.heavy2.size
121 | ```
122 |
123 | **Groovy不仅推迟了创建,还将字段标记为volatile,并确保创建期间是线程安全的**。实例会在第一次访问这些字段的时候被创建,在输出中可以看到:
124 |
125 | > Created AsNeed
126 | Creating Heavy : Size = 10
127 | 10
128 | 10
129 | Creating Heavy : Size = 10
130 | 1000
131 |
132 | 另一个好处是,@Lazy注解提供了一种轻松实现**线程安全**的**虚拟代理模式**(virtual proxy pattern)的方式。
133 |
134 | ### 5.@Newify
135 |
136 | 在Groovy中,经常会按照传统的Java语法,使用new来创建实例。然而,在创建DSL时,去掉这个关键字,表达会更流畅。
137 |
138 | @Newify注解可以帮助创建类似Ruby的构造器,在这里,new是该类的一个方法。该注解还可以用来创建类似Python的构造器(也类似Scala的applicator),这里可以完全去掉new。要创建类似Python的构造器,必须向@Newify注解指明类型列表。
139 |
140 | 只有将auto=false这个值作为一个参数设置给@Newify,Groovy才会创建Ruby风格的构造器。可以在不同的作用域中使用@Newify注解,比如类或方法,如下面例子所示:
141 |
142 | ```groovy
143 | @Newify(value = [Student, ImmutableStudent])
144 | def fluentCreate() {
145 |
146 | println Student.new(firstName: "Zhang", lastName: "Sanfeng",
147 | age: 24, address: "China")
148 | println ImmutableStudent.new("LiHua", 29)
149 | }
150 |
151 | fluentCreate()
152 | ```
153 | 结果:
154 | > Student(Zhang, Sanfeng, 24, China)
155 | ImmutableStudent(LiHua, 29)
156 |
157 | 在创建DSL时,@Newify注解非常有用,它可以使得实例创建更像是一个隐式操作。
158 |
159 | ### 6.@Singleton
160 |
161 | 要实现单件模式,正常来讲,我们会创建一个静态字段,并创建一个静态方法来初始化该字段,然后返回单件实例。我们必须确保该方法是线程安全的,同时还要决定是否要惰性创建该单件。
162 |
163 | 而通过使用@Singleton变换则完全可以避免这种麻烦,如下面例子所示:
164 | ```groovy
165 | @Singleton(lazy = true, strict = false)
166 | class President {
167 |
168 | private President() {
169 | println 'Instance'
170 | }
171 |
172 | def hello() {
173 | println 'hello'
174 | }
175 | }
176 |
177 | President.instance.hello()
178 | President.instance.hello()
179 | ```
180 |
181 | 输出:
182 | > Instance
183 | hello
184 | hello
185 |
186 | 这里使用@Singleton注解标记了TheUnique类,以生成静态的getInstance()方法。因为此处将lazy属性的值设为了true,所以会将实例的创建延迟到请求时。
187 |
188 | Groovy不仅将实例创建延迟到了最后责任时刻,还保证创建部分是线程安全的。
189 |
--------------------------------------------------------------------------------
/src/Groovy/JakeWharton说我代码写的像是在打地鼠?.md:
--------------------------------------------------------------------------------
1 | # JakeWharton评价我的代码像是在打地鼠?
2 |
3 | > 【标题党警告】本文主要内容为 **Gradle依赖替换规则详解**。
4 |
5 | ## RxJava3版本迁移的血泪史
6 |
7 | 不久前`RxJava`正式发布了`3.x`版本,作为`RxJava`的爱好者,笔者第一时间对个人项目进行了`3.x`版本的迁移。
8 |
9 | 迁移过程中遇到了一个小问题,那就是`RxAndroid`因为没有及时升级,因此内部还是依赖`2.x`版本的`RxJava`,这就导致项目的依赖发生了冲突。
10 |
11 | 笔者的解决方式非常简单,既然`RxAndroid`依赖了不合适的`RxJava`版本,我就把它的依赖排除掉就可以了:
12 |
13 | ```groovy
14 | implementation ('io.reactivex.rxjava2:rxandroid:2.1.0') {
15 | exclude group: 'io.reactivex.rxjava2', module: 'rxjava'
16 | }
17 | ```
18 |
19 | 这样做之后,项目成功将`RxJava`迁移到了`3.x`版本,笔者还第一时间在 [这篇文章](https://juejin.im/post/5d1eeffe6fb9a07f0870b4e8#comment) 中进行了如下的评论:
20 |
21 | 
22 |
23 | 评论发出去了一段时间,并没有收到各路大神的批评,笔者便以为这就是 **正确的升级方式**,于是在 [RxAndroid](https://github.com/ReactiveX/RxAndroid) 的这个 [issue](https://github.com/ReactiveX/RxAndroid/issues/538) 中 **沾沾自喜** 地进行了分享:
24 |
25 | 
26 |
27 | 没想到 [JakeWharton](https://github.com/JakeWharton) 竟然看到了我的回复,并且非常直接针对我提供的代码进行了点评:
28 |
29 | 
30 |
31 | 翻译过来的意思就是:
32 |
33 | > 长远来看,制定一个 **替换规则** 远比通过`exclude`这种类似 **打地鼠** 的方式要好得多。
34 |
35 | 收到男神的回复令我受宠若惊,但我更迫切需要了解我的代码问题出在了哪里—— **我一直认为我的代码就是正确的处理方案,但事实却证明了我的无知**。
36 |
37 | 我翻阅了对应的`Gradle`文档,`Gradle`中提供了对应的 **依赖替换规则**,而我之前一直没有了解过它,这也正是本文的主要内容。
38 |
39 | ## 依赖替换规则
40 |
41 | 依赖替换规则的适用场景分为以下几种:
42 |
43 | * 1.根据某些条件对依赖进行替换;
44 | * 2.将本地依赖替换为外部依赖;
45 | * 3.将外部依赖替换为本地依赖;
46 |
47 | 我们先解释一下 **外部依赖** 和 **本地依赖** 是什么。
48 |
49 | ### 外部依赖
50 |
51 | **外部依赖**,顾名思义,就是从远程仓库拉取的依赖,也被称为常用的 **三方库**:
52 |
53 | ```groovy
54 | // 从远程仓库拉取的开源代码库
55 | implementation 'com.facebook.stetho:stetho:1.5.1'
56 | implementation 'io.reactivex.rxjava3:rxjava:3.0.0-RC0'
57 | ```
58 |
59 | ### 本地依赖
60 |
61 | **本地依赖**,也就是我们项目中常见的`module`,按照**《阿里Java开发手册》**中来描述,也叫做 **一方库**:
62 |
63 | ```groovy
64 | implementation project(':library')
65 | ```
66 |
67 | 好的,现在我们了解了这两个基本概念,问题来了:
68 |
69 | ## 知道这些有什么用?
70 |
71 | 有同学肯定会有这个困惑,这些概念我虽然都了解了,但实际开发过程中我并没有用到这些, **项目依然稳定的迭代和运行**,那学习这些东西有什么用呢?
72 |
73 | 这些规则真的很有用,在实际开发过程中,我们肯定会遇到一些问题,这些问题我们通过`baidu`或者`google`的方式绕了过去,但是这真的解决了吗?
74 |
75 | 比如说 **依赖冲突**。
76 |
77 | ### 1.根据某些条件对依赖进行替换
78 |
79 | 举个例子,很多UI三方库都会依赖`RecyclerView`,但这么多的依赖库,我们不可避免遇到版本不同导致依赖冲突的情况,一般情况下,我们是这么解决的:
80 |
81 | ```groovy
82 | // 将RecyclerView的依赖从这个三方库中排除掉
83 | implementation "xxx.xxx:xxx:1.0.0",{
84 | exclude group: 'com.android.support', module: 'recyclerview-v7'
85 | }
86 | ```
87 |
88 | 将`RecyclerView`的依赖从这个三方库中排除掉,令其使用项目本身的`RecyclerView`版本,这样项目就可以正常运行了,看起来并没有什么问题。
89 |
90 | [JakeWharton](https://github.com/JakeWharton) 非常敏锐地点出了问题的所在——试想,如果项目的依赖比较复杂,也许我们要面对的将是这样的依赖配置:
91 |
92 | ```gradle
93 | implementation "libraryA:xxx:1.0.0",{
94 | exclude group: 'com.android.support', module: 'recyclerview-v7'
95 | }
96 | implementation "libraryB:xxx:2.2.0",{
97 | exclude group: 'com.android.support', module: 'recyclerview-v7'
98 | }
99 | implementation "libraryC:xxx:0.0.8",{
100 | exclude group: 'com.android.support', module: 'recyclerview-v7'
101 | }
102 | ```
103 |
104 | 我们需要将每个依赖了`RecyclerView`的三方库都通过`exclude`的方式移除掉本身对应的依赖,这种缝缝补补式地乱堵,不正是在 **打地鼠** 么。
105 |
106 | 针对类似这种情况,我们可以在`gradle`的构建过程中强制指定依赖的版本,以笔者的项目为例,我们针对`RxJava`的版本依赖进行了统一:
107 |
108 | 
109 |
110 | 现在,项目中所有`RxJava`相关的依赖,在构建过程中版本都统一使用了`3.0.0-RC0`,这样就 **避免了依赖冲突**,开发者再也不需要针对每一个有`RxJava`依赖的三方库进行额外的`exclude`了。
111 |
112 | ### 2.本地依赖替换为外部依赖
113 |
114 | 本地依赖替换为外部依赖,最经典的场景就是`SDK`的发布测试,如果您有过开源项目的经历,对此一定不会陌生。
115 |
116 | 以笔者开源的 [RxImagePicker](https://github.com/qingmei2/RxImagePicker) 为例,日常开发过程中,`sample`代码依赖本地的`module`;新版本发布后,笔者的UI测试代码便需要通过依赖`jcenter`远程仓库的最新代码。
117 |
118 | 这种情况下,通过`dependencySubstitution`便可以非常方便对这两种场景进行切换:
119 |
120 | 
121 |
122 | `useRemote`只是定义在`build.gradle`文件中的一个变量,作为切换开发-测试环境的开关:
123 |
124 | ```groovy
125 | final boolean useRemote = true
126 | ```
127 |
128 | 当`useRemote`值为`true`时,`sample`依赖远程仓库,当值为`false`时,`sample`依赖本地`module`。
129 |
130 | 看起来代码量反而增加了,实际上,随着项目复杂度的提升,这种全局的配置优点显而易见。
131 |
132 | ### 3.将外部依赖替换为本地依赖
133 |
134 | 该规则和2非常相似,只不过将依赖替换的双方调换了而已,下面是官方的示例代码:
135 |
136 | ```groovy
137 | configurations.all {
138 | resolutionStrategy.dependencySubstitution {
139 | substitute module("org.utils:api") because "we work with the unreleased development version" with project(":api")
140 | substitute module("org.utils:util:2.5") with project(":util")
141 | }
142 | }
143 | ```
144 |
145 | ## 最终的迁移方案?
146 |
147 | 故事的最后,笔者的解决方案如下:
148 |
149 | 
150 |
151 | * 1.因为`group`不同,所以需要先将`2.x`的`rxjava`全局`exclude`掉;
152 | * 2.将所有`3.x`的`rxjava`的依赖版本都统一(文中是`3.0.0-RC0`);
153 |
154 | 笔者并不知道这种方式是否就是 [JakeWharton](https://github.com/JakeWharton) 描述的解决方案,但相比较之前而言效果确实更好,如果有更好的依赖管理方案,诚挚希望您能在评论区中进行分享。
155 |
156 | ## 感受
157 |
158 | `GitHub`确实是一个神奇的东西,它让我避免固步自封,毕竟世界上最顶尖的开发者们都聚焦于此,在他们眼里,你的代码永远都有着非常广阔的进步空间。
159 |
160 | 发现自己的短板不是坏事,它可以督促我不断去尝试自我超越,就像我常年放在文章末尾的那句话一样,**万一哪天我进步了呢?**
161 |
162 | ## 关于我
163 |
164 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[个人博客](https://juejin.im/user/588555ff1b69e600591e8462)或者[Github](https://github.com/qingmei2)。
165 |
166 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
167 |
168 | * [我的Android学习体系](https://github.com/qingmei2/android-programming-profile)
169 | * [关于文章纠错](https://github.com/qingmei2/Programming-life/blob/master/error_collection.md)
170 | * [关于知识付费](https://github.com/qingmei2/Programming-life/blob/master/appreciation.md)
171 |
--------------------------------------------------------------------------------
/src/IDE-Plugin/trans_idea_plugin_5.md:
--------------------------------------------------------------------------------
1 | # [译] 编写AndroidStudio插件(五):本地化和通知
2 |
3 | > 原文:[Write an Android Studio Plugin Part 5: Localization and Notifications](https://proandroiddev.com/write-an-android-studio-plugin-part-5-localization-and-notifications-cb036d867587)
4 | > 作者:[Marcos Holgado](https://medium.com/@marcosholgado)
5 | > 译者:[却把清梅嗅](https://github.com/qingmei2)
6 | >《编写AndroidStudio插件》系列是 IntelliJ IDEA 官方推荐的学习IDE插件开发的博客专栏,希望对有需要的读者有所帮助。
7 |
8 | 在本系列的[第四部分](https://proandroiddev.com/write-an-android-studio-plugin-part-4-jira-integration-cd54df01cff6)中,我们学习了如何在插件中集成诸如`Jira Cloud Platform`之类的第三方`API`,以及如何使用`MVP`或`MVC`之类的模式开发。本文我将部分重构插件,以便我们可以对插件进行本地化,并以更简单的方式使用通知。
9 |
10 | 
11 |
12 | ## 我们要做什么?
13 |
14 | 今天的目标非常简单,我们将尝试整理插件的代码。为此,我将重点关注两个领域:**通知** 和 **字符串**。
15 |
16 | 我们将探索一种移除所有字符串硬编码的使用方式,并创建将其移到一个位置的方法,就像我们都知道的`strings.xml`一样,这也将使我们免费进行国际化和本地化。
17 |
18 | 此外,不同于往常的样板代码,我们还将探索如何在创建新通知时,将相关代码移至`utils`中,我们还将添加在通知中具有超链接的可选项,我们将在下一篇文章中使用它。
19 |
20 | 与往常一样,本文的所有代码都可以在以下仓库的`Part5`分支中找到。
21 |
22 | > https://github.com/marcosholgado/plugin-medium
23 |
24 | ## 将插件本地化
25 |
26 | 无论您是否已经阅读了之前的文章,您的插件中都可能有很多字符串被硬编码,像这样:
27 |
28 | ```kotlin
29 | val lblPassword = JLabel("Token")
30 | ```
31 |
32 | 或者这样
33 |
34 | ```kotlin
35 | createNotification("Updated","Welcome to the new version", ...)
36 | ```
37 |
38 | 为了摆脱这些硬编码的字符串,我们将利用 **Resource Bundles**,`resource bundle`是一组具有相同基本名称和特定语言的后缀的属性文件。
39 |
40 | 在`Android`中,您可以通过在`values`文件夹中添加特定语言的后缀来本地化您的应用程序,以便英语可以使用`values-en/strings.xml`,西班牙语可以使用`values-es/strings.xml`,您可将 **Resource Bundles**视为与此等效。
41 |
42 | 您的`resource bundle`应存在于`resources`文件夹内,我将把它们放在更深层次的`messages`中,完整路径是`resources/messages/`。在其中,我将首先创建一个名为`strings_en.properties`的新属性文件,在该文件中,我将使用以下格式以英文存储字符串。
43 |
44 | ```
45 | plugin.name = My Jira Plugin
46 | settings.username = Username
47 | settings.token = Token
48 | settings.jiraUrl = Jira URL
49 | settings.regEx = RegEx
50 | ...
51 | ```
52 |
53 | 现在我们可以为另一种语言创建另一个属性文件,我对西班牙语非常流利,所以我将创建一个新的`strings_es.properties`文件,其内容如下:
54 |
55 | ```
56 | plugin.name = Mi Jira Plugin
57 | settings.username = Usuario
58 | settings.token = Token
59 | settings.jiraUrl = Jira URL
60 | settings.regEx = Expresion Regular
61 | ...
62 | ```
63 | 如果我们查看`project`窗口,则可以看到我们的单个`strings_en.properties`文件如何与新的`strings_es.properties`一起捆绑在称为`strings`的 **resource bundle** 中。
64 |
65 | 
66 |
67 | 要使用新的字符串,我将创建一个`helper`类,该类将获取我们创建的字符串资源,并将基于属性键返回一个字符串。
68 |
69 | ```kotlin
70 | object StringsBundle {
71 | @NonNls
72 | private val BUNDLE_NAME = "messages.strings"
73 | private var ourBundle: Reference? = null
74 |
75 | private fun getBundle(): ResourceBundle {
76 | var bundle = SoftReference.dereference(ourBundle)
77 | if (bundle == null) {
78 | bundle = ResourceBundle.getBundle(BUNDLE_NAME)
79 | ourBundle = SoftReference(bundle)
80 | }
81 | return bundle!!
82 | }
83 |
84 | fun message(
85 | @PropertyKey(
86 | resourceBundle = "messages.strings"
87 | ) key: String,
88 | vararg params: Any
89 | ): String {
90 | return CommonBundle.message(getBundle(), key, *params)
91 | }
92 | }
93 | ```
94 |
95 | 如果现在我们想要检索插件的名称,而不是在需要的地方对字符串进行硬编码,我们可以简单地执行以下操作:
96 |
97 | ```kotlin
98 | StringsBundle.message("plugin.name")
99 | ```
100 |
101 | 现在是时候使用帮助或我们的`StringsBundle`类,使用适当的本地化字符串替换插件中的所有硬编码字符串。:)
102 |
103 | ## 通知
104 |
105 | 到目前为止,我们已经在不同的地方创建了一些通知弹窗,我们一遍又一遍地重复相同的代码,所以我希望简化代码,通过拥有一个`utils`类来处理通知的创建。
106 |
107 | 在该`utils`类中,我将创建一个新方法来显示通知。此方法将具有不同的参数,例如标题,消息和我们要在哪个项目中显示通知。`Project`可以为空,因为在某些情况下,我们的通知不会显示在`Project`中,例如`Project`未载入的时候。
108 |
109 | 另一个参数是通知的类型,默认情况下,我们将显示`balloon`类型的通知,这是一种显示类型,而不是通知类型,我们可以显示`INFORMATION`、`WARNING`或`ERROR`类型的通知。
110 |
111 | 最后一个参数是通知的监听类,您可能已经注意到`balloon`类型通知可以具有一个超链接,如下例所示。
112 |
113 | 
114 |
115 | 这些超链接就像我们可以根据需要控制的`Action`,而不是将用户重定向到打开浏览器中对应的`URL`,我们可以使用通知监听类指定对应的行为。
116 |
117 | 最终方法如下所示:
118 |
119 | ```kotlin
120 | fun createNotification(
121 | title: String,
122 | message: String,
123 | project: Project?,
124 | type: NotificationType,
125 | listener: NotificationListener?
126 | ) {
127 | val stickyNotification =
128 | NotificationGroup(
129 | "myplugin$title",
130 | NotificationDisplayType.BALLOON,
131 | true
132 | )
133 | stickyNotification.createNotification(
134 | title, message, type, listener
135 | ).notify(project)
136 | }
137 | ```
138 |
139 | 现在,您可以使用此方法替换旧代码,并使用在在插件的代码中,最终结果应如下所示:
140 |
141 | ```kotlin
142 | Utils.createNotification(
143 | StringsBundle.message("common.success"),
144 | StringsBundle.message("issue.moved"),
145 | project,
146 | NotificationType.INFORMATION,
147 | null
148 | )
149 | ```
150 |
151 | ## 超链接
152 |
153 | 由于我已经讨论过使用超链接,因此我认为我可以再深入一点。
154 |
155 | 我将在我们的`utils`类中添加更多的辅助方法,我们将在下一篇文章中使用。首先,我们将了解如何在通知中集成超链接。
156 |
157 | 要创建超链接,我们需要一些`html`代码。 我们的通知消息不再是简单的字符串,而是`html`字符串。在`html`代码中,我们将必须使用``标签创建一个新的超链接,由于我希望能够复用该代码,因此将整个`html`字符串放入我们先前创建的资源包中。
158 |
159 | ```
160 | utils.hyperlink.code = {0} {1} {2}
161 | ```
162 |
163 | 您可以看到我定义了3个不同的部分,我们可以按需修改,第一部分为普通文本,第二部分为超链接文本,最后是第三部分,为普通文本的场景作准备。
164 |
165 | 通过使用`resource bundle`,我可以快速创建一个新的`utils`方法,该方法将使用给定的超链接字符串前缀,超链接字符串和超链接字符串后缀返回`html`代码。
166 |
167 | ```kotlin
168 | fun createHyperLink(pre:String, link: String, post: String) =
169 | StringsBundle.message(
170 | "utils.hyperlink.code", pre, link, post
171 | )
172 | ```
173 |
174 | 最后,我将创建一个新的监听`Listener`,使用插件时,您可能要做的主要事情之一是允许用户重新启动`Android Studio`,以完成新版本的安装,或者因为您的插件刚刚进行了更改而需要重启。
175 |
176 | 新方法返回一个`Listener`,该`Listener`检测何时触发了超链接事件,并在这种情况下重新启动`IDE`。
177 |
178 | ```kotlin
179 | fun restartListener() =
180 | NotificationListener { _, event ->
181 | if (event.eventType === HyperlinkEvent.EventType.ACTIVATED) {
182 | ApplicationManager.getApplication().restart()
183 | }
184 | }
185 | ```
186 |
187 | 以上就是本文的全部内容!现在,您应该能够将插件配置本地化,同时对一些数据进行持久化。
188 |
189 | 请记住,本文的代码在该系列[GitHub Repo](https://github.com/marcosholgado/plugin-medium/tree/Part4)的`Part5`分支中可用。
190 |
191 | 在下一篇文章中,我们将介绍模板以及如何使它们在插件中可用,保证用户可以通过简单的右键单击来创建新项目、模块或任何您想要的东西。同时,如果您有任何问题,请随时发表评论或在[Twitter](https://www.twitter.com/orbycius)上关注我。
192 |
193 | ---
194 | ## 《编写AndroidStudio插件》译文系列
195 |
196 | * [译: 编写AndroidStudio插件(一):创建一个基本插件](https://github.com/qingmei2/blogs/issues/50)
197 | * [译: 编写AndroidStudio插件(二):持久化数据](https://github.com/qingmei2/blogs/issues/51)
198 | * [译: 编写AndroidStudio插件(三):设置页](https://github.com/qingmei2/blogs/issues/52)
199 | * [译: 编写AndroidStudio插件(四):集成Jira](https://github.com/qingmei2/blogs/issues/53)
200 | * [译: 编写AndroidStudio插件(五):本地化和通知](https://github.com/qingmei2/blogs/issues/54)
201 |
202 | ## 关于译者
203 |
204 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://blog.csdn.net/mq2553299) 或者 [GitHub](https://github.com/qingmei2)。
205 |
206 | 如果您觉得文章还差了那么点东西,也请通过 **关注** 督促我写出更好的文章——万一哪天我进步了呢?
207 |
208 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
209 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/main/error_collection.md)
210 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/main/appreciation.md)
211 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/main/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/thinking_in_android_index.md)
212 |
--------------------------------------------------------------------------------
/src/Java/Java代理模式分析总结.md:
--------------------------------------------------------------------------------
1 | ## 动机
2 |
3 | 学习动机来源于[RxCache](https://github.com/VictorAlbertos/RxCache),在研究这个库的源码时,被这个库的设计思路吸引了,该库的原理就是通过动态代理和Dagger的依赖注入,实现Android移动端Retrofit的缓存功能。
4 |
5 | 既然在项目中尝试使用这个库,当然要从设计的角度思考作者的思路,动态代理必然涉及到Java的反射,既然是反射,性能当然会有所降低,那么是否有更好的思路呢,使用动态代理的优势有哪些?
6 |
7 | 关于动态代理,百度上面的资料数不胜数,今天也借鉴其他前辈的学习总结,自己实践一次代理的实现。
8 |
9 | ## 静态代理
10 |
11 | 代理分为动态代理和静态代理,我们先看静态代理的代码:
12 |
13 | 我们首先定义一个接口:
14 |
15 | ```java
16 | public interface Subject {
17 |
18 | void enjoyMusic();
19 |
20 | }
21 | ```
22 |
23 | 我们接下来实现一个Subject的实现类:
24 |
25 | ```java
26 | public class RealSubject implements Subject {
27 |
28 | @Override
29 | public void enjoyMusic() {
30 | System.out.println("enjoyMusic");
31 | }
32 | }
33 | ```
34 | 在不考虑代理模式的情况下,我们调用Subject的真实对象,我们代码中必然是这样:
35 |
36 | ```
37 | @Test
38 | public void testNoProxy() throws Exception {
39 | Subject subject= new RealSubject();
40 | subject.enjoyMusic();
41 | }
42 | ```
43 |
44 | 上面是我们的业务代码,我们这样使用当然没有问题,但是我们需要考虑的一点是,如果我们的业务代码中多次引用了这个类,并且在之后的版本迭代中,我们需要修改(或者替换)这个类,我们需要在引用这个对象的代码处进行修改——也就是说我们需要修改业务代码。
45 |
46 | 这显然不是良好的设计,我们希望业务代码不需要修改的前提下,进行RealSubject的修改(或者替换),这时我们可以通过代理模式,创建一个代理类,从而达到控制RealSubject对象的引用 :
47 |
48 | ```
49 | public class SubjectProxy implements Subject {
50 | private Subject subject = new RealSubject();
51 | @Override
52 | public void enjoyMusic() {
53 | subject.enjoyMusic();
54 | }
55 | }
56 | ```
57 |
58 | 我们在业务代码中通过代理类,达到调用真实对象RealSubject的对应方法:
59 |
60 | ```
61 | @Test
62 | public void staticProxy() throws Exception {
63 | SubjectProxy proxy = new SubjectProxy();
64 |
65 | proxy.enjoyMusic();
66 | }
67 | ```
68 |
69 | 这就是静态代理,优势是显然的,如果我们需要一个新的对象NewRealSubject代替RealSubject 应用在业务中,我们不需要修改业务代码,而是只需要在代理类中,将代码进行简单的替换:
70 | ```
71 | private Subject subject = new RealSubject();//before
72 |
73 | private Subject subject = new NewRealSubject();//after
74 | ```
75 |
76 | 同理,即使RealSubject类有所修改(比如说构造函数添加新的参数依赖),我们也不需要在每一处业务代码中添加一个新的参数,只需要在代理类中,对代理的真实对象进行简单修改即可。
77 |
78 | ## 瑕疵
79 |
80 | 现在我们看到了静态代理的优势,但是还有一点需要我们去思考,随着项目中业务量的逐渐庞大,真实对象类的功能可能越来越多:
81 |
82 | ```
83 | //接口类
84 | public interface Subject {
85 |
86 | void enjoyMusic();
87 |
88 | void enjoyFood();
89 |
90 | void enjoyBeer();
91 |
92 | //...甚至更多
93 | }
94 |
95 | //实现类
96 | public class RealSubject implements Subject {
97 |
98 | @Override
99 | public void enjoyMusic() {
100 | System.out.println("enjoyMusic");
101 | }
102 |
103 | @Override
104 | public void enjoyFood() {
105 | System.out.println("enjoyFood");
106 | }
107 |
108 | @Override
109 | public void enjoyBeer() {
110 | System.out.println("enjoyBeer");
111 | }
112 |
113 | //...甚至更多
114 | }
115 | ```
116 |
117 | 这样岂不是说明,我们的代理类也要这样:
118 |
119 | ```
120 | public class SubjectProxy implements Subject {
121 |
122 | private Subject subject = new RealSubject();
123 |
124 | @Override
125 | public void enjoyMusic() {
126 | subject.enjoyMusic();
127 | }
128 |
129 | @Override
130 | public void enjoyFood() {
131 | subject.enjoyFood();
132 | }
133 |
134 | @Override
135 | public void enjoyBeer() {
136 | subject.enjoyBeer();
137 | }
138 |
139 | //...甚至更多
140 | }
141 | ```
142 |
143 | 静态代理的话,确实如此,随着真实对象的功能增多,不可避免的,代理对象的代码也会随之臃肿,这是我们不希望看到的,我们更希望的是,即使真实对象的代码量再繁重,我们的代理类也不要有太多的改动和臃肿。
144 |
145 | ## 动态代理
146 |
147 | 直接来看代码,我们首先声明一个接口和实现类:
148 |
149 | ```
150 | public interface Subject {
151 |
152 | void enjoyMusic();
153 |
154 | void enjoyFood();
155 |
156 | void enjoyBeer();
157 | }
158 |
159 | public class RealSubject implements Subject {
160 |
161 | @Override
162 | public void enjoyMusic() {
163 | System.out.println("enjoyMusic");
164 | }
165 |
166 | @Override
167 | public void enjoyFood() {
168 | System.out.println("enjoyFood");
169 | }
170 |
171 | @Override
172 | public void enjoyBeer() {
173 | System.out.println("enjoyBeer");
174 | }
175 | }
176 | ```
177 | 这两位是老朋友了,接下来我们实现一个动态代理类:
178 |
179 | ```
180 | public class DynamicProxy implements InvocationHandler {
181 |
182 | private Object subject;
183 |
184 | public DynamicProxy(Object subject) {
185 | this.subject = subject;
186 | }
187 |
188 | public Object bind() {
189 | return Proxy.newProxyInstance(subject.getClass().getClassLoader(),
190 | subject.getClass().getInterfaces(),
191 | this);
192 | }
193 |
194 | @Override
195 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
196 | method.invoke(subject, args);
197 | return null;
198 | }
199 | }
200 | ```
201 | 我们来看业务代码:
202 |
203 | ```
204 | @Test
205 | public void dynamicProxy() throws Exception {
206 | RealSubject realSubject = new RealSubject();
207 | Subject proxy = (Subject) new DynamicProxy(realSubject).bind();
208 |
209 | System.out.println(proxy.getClass().getName());
210 |
211 | proxy.enjoyMusic();
212 | proxy.enjoyFood();
213 | proxy.enjoyBeer();
214 | }
215 | ```
216 | 动态代理中,我们可以看到,最重要的两个类:
217 | Proxy 和 InvocationHandler
218 | 接下来分别对这两个类的功能进行描述.
219 |
220 | ### InvocationHandler接口
221 | 我们先看一下Java的API文档:
222 |
223 | > InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
224 | Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
225 |
226 | 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
227 |
228 | > Object invoke(Object proxy, Method method, Object[] args) throws Throwable
229 |
230 | > proxy: 指代最终生成的代理对象
231 | > method: 指代调用真实对象的某个方法的Method对象
232 | > args: 指代的是调用真实对象某个方法时接受的参数
233 |
234 | 我们看到,我们的动态代理类中,实现了InvocationHandler接口的invoke方法,这里面三个参数的作用很简单,以proxy.enjoyMusic();为例,参数1表示最终生成的代理对象,参数2表示enjoyMusic()这个方法对象,参数3代表调用enjoyMusic()的参数,此例中是没有调用参数的。
235 |
236 | 有一个疑问是,这个proxy对象是什么呢,是这个new DynamicProxy(realSubject)吗?
237 |
238 | 当然不是,我们可以看到,这个代理对象proxy实际上是调用bind()方法获得的,也就是说是通过这个方法获得的:
239 |
240 | > Proxy.newProxyInstance(subject.getClass().getClassLoader(),
241 | subject.getClass().getInterfaces(),
242 | this);
243 |
244 | ### Proxy类
245 |
246 | 先看JavaAPI:
247 |
248 | > Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
249 |
250 | Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
251 |
252 |
253 | ```
254 | public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException
255 | ```
256 | > Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
257 |
258 | > * loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
259 |
260 | >* interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
261 |
262 | > * h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
263 |
264 |
265 | 可以看到,Proxy这个类才会帮助我们生成相应的代理类,它是如何知道我们需要生成什么类的代理呢?
266 |
267 | 看第二个参数,我们把Subject.class作为参数传进去时,那么我这个代理对象就会实现了这个接口,这个时候我们当然可以将这个代理对象强制类型转化,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。
268 |
269 | 我们看到,我在业务代码中对生成的proxy进行了打印:
270 |
271 | ```
272 | System.out.println(proxy.getClass().getName());
273 |
274 | //结果:
275 | //$Proxy0
276 | ```
277 |
278 | 也就是说,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
279 |
280 | 到此,动态代理的基本知识就告一段落了。
281 |
282 |
283 | ## 参考资料:
284 |
285 | java的动态代理机制详解:(对我帮助很大,衷心感谢!)
286 | http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
287 |
288 | 知乎:Java 动态代理作用是什么?
289 | https://www.zhihu.com/question/20794107
290 |
291 | 本文sample代码已托管github:
292 |
293 | https://github.com/qingmei2/Samples-Android/tree/master/SampleProxy/app/src/test/java/com/qingmei2/sampleproxy
294 |
--------------------------------------------------------------------------------
/src/Linux/Linux配置AndroidSDK&Jenkins远程部署.md:
--------------------------------------------------------------------------------
1 | 最近将公司的项目部署了`Jenkins`持续集成,遇到了几个麻烦的点,其中之一就是将`Android SDK`进行配置在远程服务器(总结下来还是自己对Linux命令还不够熟悉),特此记录。
2 |
3 | * 系统: **Ubuntu Server 16.04.1 LTS 64位**
4 | * 前置:完成`JDK`的环境搭建
5 |
6 | ## 1.下载SDK
7 |
8 | [点击进入下载网址](http://tools.android-studio.org/index.php/sdk) 下载对应的 `android-sdk_r24.4.1-linux.tgz` 文件。
9 |
10 | ## 2.解压下载的压缩包
11 |
12 | * `tar -zxvf android-sdk_r24.4.1-linux.tgz`
13 |
14 | ## 3.安装32位库
15 | Android SDK中的adb程序是32位的,Ubuntu x64系统需要安装32位库文件,用于兼容32位的程序:
16 |
17 | * `sudo apt-get install -y libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1`
18 |
19 | 
20 |
21 | ## 4.配置环境变量
22 |
23 | * `export ANDROID_SDK_HOME=/home/XXX/android/sdk/android-sdk-linux`
24 | * `export PATH=$PATH:${ANDROID_SDK_HOME}/tools`
25 | * `export PATH=$PATH:${ANDROID_SDK_HOME}/platform-tools`
26 |
27 | 
28 |
29 | 通过 `vim /etc/profile` **查看** 或 **编辑** 环境变量的配置(或者直接通过`export`命令查看):
30 |
31 | 
32 |
33 | ## 5.下载最新SDK工具
34 |
35 | 进入`tools`目录下,输入`./android -v list sdk`命令查看可下载更新的`SDK`列表:
36 |
37 | 
38 |
39 | 
40 |
41 | 官方提供了一些参数供开发者选择性更新:
42 |
43 | > Action "update sdk":
44 | Updates the SDK by suggesting new platforms to install if available.
45 | Options:
46 | -f --force Forces replacement of a package or its parts, even if something has been modified
47 | -u --no-ui Updates from command-line (does not display the GUI)
48 | -o --obsolete Installs obsolete packages
49 | -t --filter A filter that limits the update to the specified types of packages in the form of a comma-separated list of [platform, tool, platform-tool, doc, sample, extra]
50 | -s --no-https Uses HTTP instead of HTTPS (the default) for downloads
51 | -n --dry-mode Simulates the update but does not download or install anything
52 |
53 | 上述参数通过`android update sdk --filter --no-ui`命令进行 **组件** 的过滤性筛选。
54 |
55 | 笔者选择了简单粗暴,直接通过`android update sdk --no-ui`命令下载所有版本的sdk。
56 |
57 | ## 6.将sdk配置到Jenkins
58 |
59 | 打开`Jenkins` 的 **系统配置**界面,将对应的SDK根目录配置给环境变量:
60 |
61 | 
62 |
63 | ## 7.构建错误处理
64 |
65 | ### 缺少License
66 |
67 | 错误日志:
68 |
69 | > What went wrong:
70 | A problem occurred configuring project ':xxx'.
71 | > Failed to install the following Android SDK packages as some licences have not been accepted.
72 | build-tools;27.0.3 Android SDK Build-Tools 27.0.3
73 | To build this project, accept the SDK license agreements and install the missing components using the Android Studio SDK Manager.
74 |
75 | 解决方案:
76 |
77 | 将本地sdk目录下的`licenses`文件夹中的License文件传到远程服务器中:
78 |
79 | 
80 |
81 | ### 对应版本的SDK Build-Tools不存在
82 |
83 | > 错误日志:Failed to install the following SDK components:
84 | build-tools;27.0.3 Android SDK Build-Tools 27.0.3
85 | The SDK directory is not writable (/home/sdk/android-sdk-linux)
86 |
87 |
88 | 
89 |
90 | 解决方案,更新对应的BuildTools版本:
91 |
92 | 查看所有版本列表:
93 |
94 | * `./android list sdk -a`
95 |
96 | 
97 |
98 | 更新对应的27.0.3版本:
99 |
100 | * `android update sdk -u -t 7 -a`
101 |
102 | 
103 |
--------------------------------------------------------------------------------
/src/Linux/Linux配置JDK和Tomcat.md:
--------------------------------------------------------------------------------
1 | 最近将公司的项目部署了`Jenkins`持续集成,遇到了几个麻烦的点,其中之一就是对`JDK`和`Tomcat`的配置,特此记录。
2 |
3 | > 本地系统:MacOS
4 | 远程系统:CentOS_7_04_64_20G_alibase_201701015.vhd
5 |
6 | ### 1.安装jdk
7 |
8 | * `yum search java|grep jdk` 查看yum库中都有哪些jdk版本
9 | * `yum install java-1.8.0-openjdk` 安装Java8
10 |
11 | ### 2.安装tomcat
12 |
13 | 首先将tomcat的安装包 [下载](https://tomcat.apache.org/download-80.cgi#8.5.34) 在对应文件夹下:
14 |
15 | 
16 |
17 | 解压压缩包:
18 |
19 | * `tar -zxvf apache-tomcat-8.5.34.tar.gz`
20 |
21 | 配置端口号,进入 `tomcat` 的 `conf` 目录下,修改 `server.xml` 文件:
22 |
23 | 
24 |
25 | 修改端口,默认 8080:
26 |
27 | 
28 |
29 | 进入`tomcat`的`bin`目录下,启动tomcat:
30 |
31 | * `./startup.sh`
32 |
33 | 
34 |
35 | 因为笔者用的是阿里云,直接输入`ip:端口`并没有成功访问tomcat,需要配置对应的安全组策略:
36 |
37 | > 本实例安全组 -> 配置规则 -> 添加安全组规则
38 |
39 | 
40 |
41 |
42 | 这样再次通过`ip:端口`就能够成功访问。
43 |
44 |
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/image1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Media/trans_alternative_android_visualizer/image1.gif
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/image2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Media/trans_alternative_android_visualizer/image2.gif
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/image3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Media/trans_alternative_android_visualizer/image3.gif
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/image4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Media/trans_alternative_android_visualizer/image4.gif
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/image5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Media/trans_alternative_android_visualizer/image5.gif
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/image6.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/Media/trans_alternative_android_visualizer/image6.jpeg
--------------------------------------------------------------------------------
/src/Media/trans_alternative_android_visualizer/trans_an_alternative_android_visualizer.md:
--------------------------------------------------------------------------------
1 | # [译] Android Visualizer 可视化器的自定义实现
2 |
3 | > 原文:[An alternative Android Visualizer](https://www.egeniq.com/blog/alternative-android-visualizer)
4 | > 作者:[Dániel Zolnai](https://www.egeniq.com/blog/alternative-android-visualizer)
5 | > 译者:[却把清梅嗅](https://github.com/qingmei2)
6 |
7 | 听音乐时,有时你会看到那些视觉上令人愉悦的跃动条,它们音量越大跳得越高。通常,左边的条形对应的频率较低(低音),而右边的条形对应较高的频率(高音):
8 |
9 | 
10 |
11 | 这些跃动条通常被称为 **视觉均衡器** 或 **可视化器**,若想在 `Android` 应用中展示类似的可视化效果,你可以使用 `Android` 原生的 `Visualizer` 类,它是`Android`框架中的一部分,且能够附加到你的 `AudioTrack`。
12 |
13 | 它是切实有效的,但有一个重要的缺陷:它需要申请 **麦克风权限** ,而从官方文档上来看,这是有确切考虑的:
14 |
15 | > To protect privacy of certain audio data (e.g voice mail) the use of the visualizer requires the permission.
16 | >
17 | >为了保护某些音频数据(例如语音邮件)的隐私,使用 `Visualizer` 需要获取权限。
18 |
19 | 问题是,用户不会允许音乐 `APP` 申请使用他们的麦克风权限(这毫无疑问)。而当我翻遍了 `Android` 官方提供的`API`或者其他三方库,却找不到实现这样 **可视化器** 效果的替代方案。
20 |
21 | 因此我考虑自己造轮子,第一个问题是,我需要思考如何将正在播放的音乐,转换成每个跳跃条对应的高度。
22 |
23 | ## 可视化器的工作原理
24 |
25 | 首先,让我们从输入开始。当数字化音频时,我们通常会对信号幅度进行非常频繁的采样,这称为**脉冲编码调制** (PCM)。振幅随之被量化,我们将其表示到我们自己的 **数字标度** 上。
26 |
27 | 举个例子,如果编码是 `PCM-16`,这个比例将是 `16 bit`,我们可在 `2` 的 `16` 次幂的数字范围内表示一个幅度,即 `65536` 个不同的幅度值。
28 |
29 | 如果您在多个 `channel` 上采样(如立体声,分别录制左右声道),这些幅度会相互跟随,因此首先是 `channel 0` 的幅度,然后是 `channel 1` 的幅度,然后是 `channel 0`,依此类推。 一旦我们获得了这些幅度值作为原始数据,我们就可以继续下一步。 为此,我们需要了解声音实际上是什么:
30 |
31 | > 我们听到的声音是物体振动的结果。例如人的声带、吉他的金属弦和木琴身。一般情况下,若不受特定声音振动的影响,空气分子会随机移动。
32 | >
33 | > 选自[《 Digital Sound and Music 》](http://digitalsoundandmusic.com/2-1-1-sound-waves-sine-waves-and-harmonic-motion/)
34 |
35 | 当敲击音叉时,它会以非常特定的 `440 次/秒 (Hz)` 振动,这种振动将通过空气传播到耳膜,在那里以相同的频率共振,大脑会将其解释为音符A。
36 |
37 | 在 `PCM` 中,这可以表示为正弦波,每秒重复 `440` 次。这些波的高度不会改变音符,但它们代表振幅;通俗点说,就是当听到它时,你耳朵里的响度。
38 |
39 | 但是当听音乐时,通常不仅有正在听的`音符A`(虽然我希望这样),而且还有过多的乐器和声音,从而导致 `PCM` 图形对人眼没有意义。实际上它是不同频率和振幅的不同正弦波大量振动的组合。
40 |
41 | 即使是非常简单的 `PCM` 信号(例如方波)在解构为不同的正弦波时也非常复杂:
42 |
43 | 
44 |
45 | > 方波解构为近似正弦和余弦波,参考自 [这里](https://visualizingmath.tumblr.com/post/63962473846/1ucasvb-the-fourier-transform-takes-an-input) 。
46 |
47 | 幸运的是,我们有算法来进行这种解构,我们称之为 **傅立叶变换** 。正如上文可视化器所展示的,它实际上是从正弦波和余弦波的组合中解构而出的。余弦基本上是一个 **延迟** 的正弦波,但是在这个算法中拥有它们非常有用,否则我们将无法为点 `0` 创建一个值,因为每个正弦波都是从 `0` 开始的,相乘仍然会得到 `0`。
48 |
49 | 执行 **傅里叶变换** 的算法之一是 **快速傅里叶变换 ( FFT )**。 在我们的 `PCM` 声音数据上运行此 `FFT` 算法时,我们将获得每个正弦波的幅度列表。这些波是声音的频率。在列表的开头,我们可以找到低频(低音),最后是高频(高音)。
50 |
51 | 这样,我们通过绘制一个这样的条形图,其高度由每个频率的幅度决定——我们得到了我们想要的可视化器。
52 |
53 | ## 技术实现
54 |
55 | 现在回到 `Android`。 首先,我们需要音频的 `PCM` 数据。 为此,我们可以将 `AudioProcessor` 配置给到我们的 `ExoPlayer` 实例,它会在转发之前接收每个音频字节。您还可以进行修改,例如更改 **幅度** 或 **过滤通道**,但不是现在。
56 |
57 | ```kotlin
58 | private val fftAudioProcessor = FFTAudioProcessor()
59 |
60 | val renderersFactory = object : DefaultRenderersFactory(this) {
61 | override fun buildAudioProcessors(): Array {
62 | val processors = super.buildAudioProcessors()
63 | return processors + fftAudioProcessor
64 | }
65 | }
66 | player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, DefaultTrackSelector())
67 | ```
68 |
69 | 在 `queueInput(inputBuffer: ByteBuffer)` 方法中,我们将收到捆在一起作为一帧的 `byte` 数据。
70 |
71 | 这些 `byte` 可能来自多个 `channel`,为此我取了所有 `channel` 的平均值,并且仅将其转发以进行处理。
72 |
73 | 为了使用 **傅立叶变换**,我使用了 [Noise](https://github.com/paramsen/noise) 库。变换需要一个具有给定样本大小的`float`列表。样本大小应该是 `2` 的因子,我选择了 `4096`。
74 |
75 | 增加这个数字可获得更精细的数据,但计算时间更长,计算也更不频繁(因为可针对每 `X` 字节的声音数据进行一次更新,其中 `X` 是样本大小)。如果数据是 `PCM-16`,则 `2` 个字节构成一个幅度。浮点值并不重要,因为它们可以缩放。如果您提交一个介于 `0` 和 `1` 之间的数字,则结果都将介于 `0` 和 `1` 之间(因为无需将正弦波幅度与更高的数字相乘)。
76 |
77 | 所得结果也将是一个`float`列表。我们可使用这些频率立即绘制 `4096` 个条形图,但这不切实际。
78 |
79 | 来看看如何改进这些结果数据。
80 |
81 | ## 频段
82 |
83 | 首先,我们可以将这些频率组合成更小的组。因此,假设我们将 `0-20kHz` 频谱划分为 `20` 个小节,每个小节跨越 `1kHz`。
84 |
85 | `20` 条比 `4096` 条更容易绘制,我们也无需那么多条。如果现在绘制这些值,可以看到,只有最左边的部分在大幅度移动。
86 |
87 | 
88 |
89 | 这是因为音乐中频率的适用范围大约是 `20-5000Hz`,而听`10kHz`的声音会让人很烦躁。若将音乐中的较高频率排除在外,你会注意到,它听起来会越来越沉闷,但与较低频率相比,这些频率的幅度非常小。
90 |
91 | 如果你看过录音室均衡器,则会发现频段也分布不均,频率的下半部分通常占用 `80-90%` 的频段:
92 |
93 | 
94 |
95 | 鉴于此,建议通过为较低频率分配更多频带来使这些频带具有可变宽度。下图是这样做的效果,它看起来会好一些:
96 |
97 | 
98 |
99 | 似乎不错,但仍然存在 `2` 个问题:
100 |
101 | 首先,右边的频率似乎移动得有点太多了。这是因为我们的采样并不完美,它引入了称为 **频谱泄漏** 的伪像,其中原始频率会涂抹到相邻的频率中。为了减少这种拖尾现象,我们可以应用一个窗口函数,在那里我们突出我们感兴趣的频率并调低其他频率。这些窗口有不同类型,但我将使用 **汉明窗口** (`Hamming-window`)。我们感兴趣的频率是中间的频段,并针对两端进行抑制:
102 |
103 | 
104 |
105 | 最后,还有一个小问题,这在上面的 `gif` 中无法体现,但当听音乐时会立即注意到:条跃动太早了,它们在你意料不到的时候跃动起来。
106 |
107 | ## 意外缓冲区
108 |
109 | 这种不同步的行为是因为在 `ExoPlayer AudioProcessor` 中,我们在数据传递到 `AudioTrack` 之前接收到数据,而 `AudioTrack` 有自己的缓冲区,这会导致视觉效果领先于音频效果,导致延迟输出。
110 |
111 | 对此的解决方案是将 `ExoPlayer` 缓冲区大小计算部分的代码进行复制,因此我的 `AudioProcessor` 中的缓冲区大小与 `AudioTrack` 完全相同。
112 |
113 | 我将传入的字节放在缓冲区的末尾,只处理缓冲区开头的字节(`FIFO` 队列),我如愿以偿延迟了 `FFT`。
114 |
115 | ## 最终效果
116 |
117 | 我创建了一个 [代码仓库](https://github.com/dzolnai/ExoVisualizer),这上面,我通过播放在线广播并使用我创建的可视化器进行绘图,以展示我的 `FFT` 处理器。它肯定不能直接用于线上产品,但如果您正在为音乐`APP`寻找可视化工具,它会提供一个很好的基础。
118 |
119 | ---
120 | ## 关于译者
121 |
122 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://blog.csdn.net/mq2553299) 或者 [GitHub](https://github.com/qingmei2)。
123 |
124 | 如果您觉得文章还差了那么点东西,也请通过 **关注** 督促我写出更好的文章——万一哪天我进步了呢?
125 |
126 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
127 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/main/error_collection.md)
128 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/main/appreciation.md)
129 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/main/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/thinking_in_android_index.md)
130 |
--------------------------------------------------------------------------------
/src/Others/year_2018.md:
--------------------------------------------------------------------------------
1 |
2 | 从大学毕业至今已经从事软件开发2年有余了,同样,写博客的习惯也已经坚持了2年。我很少写技术无关的文章,时值年终,我还是想通过分享自己的经历,同时分享这2年我对 **写博客的理解**,或者说是对 **学习方式的思考**。
3 |
4 | ## 坎坷的博文生涯
5 |
6 | **坚持写博客是一个很有意思的事情**,2016年年底之前,我还没有开始写博客,但是很多文章已经开始宣传 **写博客的好处**,我看的非常心动,毫不避讳的说,在博文生涯的伊始,我写博客的主要目的是—— **我想火**。
7 |
8 | 看看吧,相比较现在**抖音人造网红的低成本**,对于一个刚毕业不久的学生,尤其还是写代码的理工男,能想到 **通过写博客赚取收视率**,这是一个多么年轻的想法,如果让我穿越回去对当时的我说一句话,我真的会毫不犹豫地怼我自己:
9 |
10 | > 在想通过写博客赚粉丝之前,先想想你自己还剩下多少头发!
11 |
12 | 
13 |
14 | 当然,即使是当年的我,我也能想到,**写博客的道路很难走**,但我没想到的是——写博客的道路,**竟然他妈这么难走!**
15 |
16 | ### 1.那是一个非常丧的开始
17 |
18 | 2016年底,在写博客之初,我绞尽脑汁想通过博客文章分享自己的所得,但是令我沮丧的是,似乎我想写的东西,网上很多前辈都已经写烂了。
19 |
20 | 但是我不信邪,我依然写了几篇文章,现在回过头来看当初的文章,我都有些不忍直视......
21 |
22 | 那时,我每写一篇文章,大概要花费3~4个小时,因为我很清楚的记得,那时我都是周末下午2点左右开始写文章,写完天已经黑了,我就正好去楼下的面馆买一大份热干面。
23 |
24 | ——你可以想象,当时初出茅庐的我,想通过自己写的博客总结得到很多人的认可,于是我更用心斟酌文章中的每一句话每一个知识点的叙述,每篇文章我都会花费数个小时去撰写。
25 |
26 | **但那时我的文章质量真的很低**,但我依然每天都会 **兴致勃勃** 点开 [CSDN](https://www.csdn.net/) 看我文章的点击量,残酷的是,往往一周过去了,我新发布的文章点击量甚至还不到——100。
27 |
28 | 
29 |
30 |
31 | 就这样,我坚持了几个月,直到大半年后(2017年下半年),我的文章点击量才突破性的达到10000,那时我的博客数量已经有了20篇左右。
32 |
33 | 20篇博客,写了大半年,浏览量累计突破1w,这属实不是值得称贺的成绩(而且,我一直怀疑这些浏览量中,有多少是我自己不断刷新贡献的......)。即使是那样,当时的我也是非常激动的,**我依然认为这非常值得庆贺**——为我的努力和坚持。
34 |
35 | 至今为止,如果有人把我最初的博文链接发给我并指出我的错误,我依然会为我稚嫩的文字感到羞涩,**但是我对这个经历并不避讳**——半年来的博客经历让我养成了坚持写博客的习惯,**每当我在学习中有所感悟,我都会尝试通过博客进行分享和总结,并且在总结的过程中再一次归纳巩固自己的知识体系。**
36 |
37 | 微小的知识积累毫不起眼,在当时的我看来,**通过写博客火起来**已是昨日黄花,**该写博客总结一下最近的学习了** 的想法开始在我脑海中逐渐根深蒂固,——这也许就是 **习惯的力量** 把。
38 |
39 | ### 2. 转折——博客历程中的正向反馈
40 |
41 | 2017年下半年,我开始学习一些流行框架的原理,这要归功于当时公司的小伙伴们对我的帮,同时,在不断地学习中,我逐渐感受到了 **开源社区** 的强大,RxJava、Retrofit、Dagger等等——正是前辈们不断无私的奉献,我们才能站在巨人的肩膀上不断前行。
42 |
43 | 在这半年中,我开始大量学习开源社区的三方库及其原理,比如 `Dagger`,`Retrofit`、`RxJava`,`UnitTest`,`DataBinding`等等(其中部分库的学习使用,[JessYan](https://github.com/JessYanCoding) 的 [MVPArms](https://github.com/JessYanCoding/MVPArms) 的代码提供了很多帮助)。我尝试总结自己的博客,从某种意义上来说,**开源社区** 是我学习历程中最好的老师——记得在某篇文章中看到,有人自称他毕业于 **开源社区大学** ,我当时亦有同感。
44 |
45 | 我开始把这些知识总结成博客进行分享,令我感到惊喜的是,我多多少少开始收到一些朋友的认可,这让我更加有干劲去进行 **持续的学习和输出**。
46 |
47 | 慢慢的,我开始尝试对我个人比较满意的博客进行公众号的投稿,比如这两篇关于dagger的博客:
48 |
49 | * [Dagger2使用详解(四)Scope注解的使用及源码分析](https://blog.csdn.net/mq2553299/article/details/73414710)
50 |
51 | * [告别Dagger2模板代码:Dagger Android使用详解](https://blog.csdn.net/mq2553299/article/details/77485800)
52 |
53 | 我把当时我比较满意的两篇文章投稿给了 [郭霖大大](https://blog.csdn.net/guolin_blog/) 的微信公众号,结果当然是没有被收录,但是我总是能够很快收到郭霖大大的回复和指导——**难过总是难免的,但是郭霖大大的回复中对我的鼓励和肯定,也让我认识到自己的不足之处,从而看清自己,继续努力。**
54 |
55 | 在这两次投稿都遗憾失败了之后,我开始尝试潜心学习更深入的一些知识,2017年下半年期间,我学习了一系列三方库的源码,**当时我对于博客的理解,更注重于总结**——我开始经常翻看自己过去写的博客,这样忘掉的知识点总是能够第一时间内被我找回来。
56 |
57 | > 比如,我总是忘记 **Android渐变色** 以及 **Retrofit** 的一些用法,但是我很清楚知道自己曾经总结了这样的两篇博客,这些知识点我找回它们再容易不过了——毕竟都是自己一行一行总结出来的。
58 |
59 | 同时,这时我开始慢慢收到一些其他朋友的鼓励,这对我而言是意外之喜,这两点好处都开始形成博客经历中 **正反馈** 闭环的重要组件,换句话说,**我的确开始喜欢进行博客总结了。**
60 |
61 | ### 3. 滚雪球?
62 |
63 | 时间轴跳至2018年初,这时我的博客已经有6w左右的浏览量了——很奇怪,我花了大半年才有了1w的浏览量,不到半年却增加了5w。
64 |
65 | 在年初的时候,我开始给自己鼓气,**争取2018年年底之前,申请成为CSDN的博客专家**。
66 |
67 | 请不要数落我当时这样功利的想法,因为我确实希望能够 **得到一次这样的肯定**,至少当时我认为这是一个相当遥远的想法,因为当时申请博客专家的要求之一是浏览量10w以上。
68 |
69 | 很开心的是,我的一些文章开始得到更多人的认可,包括我投稿给郭霖大大的2篇文章,都成功通过郭霖大大的公众号分享给了大家。当时说实话我是很激动的,因为这意味着 **开拓者对后继者的肯定**;后来我在一个机缘巧合的机会接触到了[任玉刚大大](https://blog.csdn.net/singwhatiwanna/),承蒙刚哥的认可,我的文章也得以在 **玉刚说** 公众号上进行分享(在过去半年中,刚哥确实帮助我了很多,同时对我也非常宽容,感谢)。
70 |
71 | 技术文章的分享,能够让我 **最简单直接地和国内各大佬进行思想上的交流和探讨**,这是在现实工作中很难实现的,在这个不断进步学习的过程中,我认识了更多优秀的开发者和技术博主,他们于我亦师亦友,从他们的文章中,我了解到了更多我没有接触到的东西。
72 |
73 | 有趣的是,在今年年初我认为非常遥远的想法,还不到5个月,我就得到了 **CSDN** 官方的认可,成为了博客专家中的一员,当时的我确实没有想到这么简单一次就申请通过了,激动之余我还发了朋友圈得瑟了一下:
74 |
75 | 
76 |
77 | ### 4.对博客的思考和实践
78 |
79 | 在写博客的两年经历中,我不断的在思考一个问题,那就是:
80 |
81 | > 写博客的本质是什么?
82 |
83 | **博学之,审问之,慎思之,明辨之,笃行之。** ——我喜欢用这句话描述我对写博客的理解,在知识输出爆炸的今天,对于开发者来讲,**知识的获取** 已然不是问题,难题在于,**如何高效的进行筛选、过滤出高质量的文章进行学习?**
84 |
85 | 我开始找寻榜样,比如 **扔物线** 大大经典的 **RxJava教程** 和 [HenCoder系列](https://hencoder.com/) ,比如 [邹小创](https://www.jianshu.com/u/b9c5c66b6a08) 经典的 [单元测试系列](https://www.jianshu.com/u/b9c5c66b6a08),还有CSDN上我非常佩服的一位博主 [briblue](https://me.csdn.net/briblue)等等等等....
86 |
87 | 毫不避讳的说,我认为这些文章 **足以成为行业内知识普及文章的标杆**,文章的重点并没有完全放在 "如何使用API" 或者 "大段大段的源码解析" 上——相反,这些文章大部分更注重对 **对某个知识点进行系统化的讲解**,看完这些文章,也许我仍然不知道 **如何在项目中应用**,但是对于已经系统化掌握了其原理与思想的我,这些问题都不再是难以逾越的鸿沟。
88 |
89 | 是的,我认为 **思想的传递** 更为重要,因此我开始在自己的写作中进行这样的尝试,并写了这样一个系列的博客:
90 |
91 | * [深入浅出,争取打造 Android Jetpack 讲解最好的中文博客系列](https://blog.csdn.net/mq2553299/column/info/24151)
92 |
93 | 从时间成本来讲,这里面的每篇文章我都花费了更多的付出,以 [《Android官方架构组件ViewModel:从前世今生到追本溯源》](https://blog.csdn.net/mq2553299/article/details/84730135) 文章为例,我大概了花了至少10个小时,途中删删改改,最终达到了我满意的效果,即:
94 |
95 | > 尽量文章中代码相关只阐述`ViewModel`相关,不要和`Lifecycle`、`LiveData`或者`DataBinding`有太多牵扯,同时将`ViewModel`的本质即 **对状态的维护** ——原理和思想,都尽量深入浅出地叙述出来。
96 |
97 | 我坚信这是目前为止对于网络上 `ViewModel` 讲述最好的一篇博客,我也希望我能够继续这样坚持输出下去,从某种角度来讲,这种行为虽然耗时费力,但是它确实也 **让我更执着于每一个知识细节的较真**—— 只有完全搞懂了,我才能胸有成竹的将整个知识体系通过文字归纳出来。
98 |
99 | 在这个过程中,不断地尝试亦会带来各样的所得,这是我的实践,但它并不一定是对的,因为从本质上来讲,它似乎离博客 **学习、归纳、总结** 的主旨越来越远了,的确如此,但是我更享受于这种对自己每一篇文章都爱不释手的感觉,一方面这的确增强了我的 **得失心** (就是好面子),但另一方面,这种感觉也的确加强了自我的约束。
100 |
101 | ## 不知所言的小结
102 |
103 | 从某种角度来说,我的博客历程,也正是我的学习历程。写博客确实是一个很枯燥的过程,但也是一个很令人享受的过程,不得不承认,**博客的持续输出的确让我在面试中得到了更高的评价**,但说白了,谁还没写过博客呢,技术比我好的同行更比比皆是。
104 |
105 | 但是我更满意写博客这种行为带来习惯上的改变:我可以**更快检索和找回我遗忘的知识点**、博客归纳时等同于 **系统地复习** 一遍、和更多优秀的同行进行 **思想上的交流** ,这些东西都是语言描述难以夸大,却又实实在在感受到难以言喻的好处。
106 |
107 | 时光飞逝,逝者如斯,回顾两年历程,字不过寥寥数千,文将结尾,却又不知所言。不论一笑置之,亦或小有所得,都感谢您的阅读。
108 |
109 | 你带来微笑,我有幸得到。
110 |
111 | **--------------------------广告分割线------------------------------**
112 |
113 | ## 关于我
114 |
115 | Hello,我是[却把清梅嗅](https://github.com/qingmei2),如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的[博客](https://www.jianshu.com/u/df76f81fe3ff)或者[Github](https://github.com/qingmei2)。
116 |
117 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
118 |
--------------------------------------------------------------------------------
/src/Others/year_2019.md:
--------------------------------------------------------------------------------
1 | # 面向功利编程,面向Star开源? 一个开发者的2019反思总结
2 |
3 | 我是 [却把清梅嗅](https://github.com/qingmei2) ,`GitHub`开源社区内的一个 **功利** 的爱好者。
4 |
5 | 去年的这个时候,我同样做了一个简短的年终总结:
6 |
7 | [《2018我的博客历程:你带来微笑,我有幸得到》](https://juejin.im/post/5c18497bf265da614e2bff70)
8 |
9 | 我不是喜欢一个花时间写非技术类型文章的人,但是每年一篇年终总结于个人确实有所裨益——时隔一年再看,这篇文章段落中的一些吹嘘自己的文字,多少都让我有点尴尬。
10 |
11 | 同时,我看到了过去自己一些 **不成熟的想法** ,与一年后的自己的理念有所冲突,我是一个文字上比较耿直的人,我希望借助这篇年终总结,表达一些自己一年来的对某些观念的改变和新的理解,以及我 **以前错了哪里** ,和我 **接下来会怎么做**。
12 |
13 | 每个人都是在不断的 **自省** 中保持进步的,一年后的今天,我也希望能够再次回顾本文,到时候如果我还能够有新的想法新的观点,那就更好不过了。
14 |
15 | ## 先总结一波收获
16 |
17 | 照例先总结一下个人2019年的一些收获。
18 |
19 | ### 1.开源
20 |
21 | 需要声明的是,我认为 **功利** 不应该是一个纯粹的贬义词,至少它于我个人专业领域的发展而言,是有不可磨灭推进作用的,熟悉我的人应该知道,我在`GitHub`上开源了几个`Kotlin`的项目:
22 |
23 | 
24 |
25 | 至今为止断断续续收获了2k+的star,对于`Kotlin`这门语言来说,2k+的Star于我而言已是非常大的肯定。
26 |
27 | ### 2.博客输出
28 |
29 | 其次,我今年一共发表了24篇博客文章,平均一个月2篇左右,我很满意这样的输出频率,因为这其中的一部分文章,尤其是我下半年来写的 [反思系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md),说是呕心沥血字字雕琢也毫不为过——以 [这篇文章](https://juejin.im/post/5db06bb6518825646d79070b) 为例,全文篇幅共1w字,从前期调研、到列提纲、绘制思维导图流程图、到撰写完毕,个人花费了整整一个月的时间。
30 |
31 | 我有 **绝对自信** 这些文章是对应领域内 **最好的中文博客**,包括短期内的将来,因此我也非常直白在文章的最开始中这样声明:
32 |
33 | 
34 |
35 | 我不认为这种行为是狂悖的表现,**好的东西理所应当受到鼓励和发扬光大** ,这不仅仅是为读者、技术社区负责,也是为自己负责。
36 |
37 | 同时我再次向 `Android` 开发领域的小伙伴们自荐 [这些博客](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md),我相信它值得您为它点上一个`Star`。
38 |
39 | ### 3.技术演讲
40 |
41 | 今年下半年,有幸受邀作为演讲嘉宾参加了 `Droidcon Chengdu 2019` 和 `GDG Chengdu 2019 Flutter` 专场,分别针对 `Android` 和 `Flutter` 相关专题进行了分享。
42 |
43 | 
44 |
45 | 
46 |
47 | ### 4.生活方面
48 |
49 | 此外在生活方面,2019对我来说是一个非常重要的转折点,除了买房装修搬家诸如此类来来回回的折腾之外,我还结束了和我女朋友将近6年的爱情长跑,成功领证。
50 |
51 | ## 功利性的好处
52 |
53 | 略显尴尬的吹逼好像成了大家年终总结的惯例,因为我看大家好像都是这么写的——我觉得这样其实也挺好,**有谁会不喜欢别人的赞美呢**。
54 |
55 | 到目前为止,我展示的都是 **功利性** 的内容,这些都是浮于表面的,它们对我非常有用,至少在简历上摆出这些能让我增加足够多的面试机会,和HR谈薪资待遇的时候也能给我足够的底气。
56 |
57 | 但是问题随之而来,无论是 **开源项目** 、 **博客输出** 还是 **公开演讲**,这些我都付出了足够多的时间和精力,而为此得到的这些 **回报** 真的值得吗?
58 |
59 | 武断的 **全盘否定** 或者 **全盘肯定** 都不太好,在我接下来的个人职业规划中,如何对它们进行权衡,怎样才能借助这些行为最大化提升自己,这些反思所得的结果才是至关重要的。
60 |
61 | ## 反思现状、修正意图
62 |
63 | 反思并不能改变已经发生了的现状,但却可以让自己去修正自己的意图,那么,类似 **开源项目** 、 **文章输出** 还是 **公开演讲** ——这些行为最初创建的目的是什么呢?
64 |
65 | ### 1、开源本身应该面向Star吗?
66 |
67 | 从某种意义上说,面向`Star`开源没有什么问题——`Star`数量的多少本质上来源于社区内开发者对其的认可程度,一个优秀的开源项目理所应当收到足够多的`Star`。
68 |
69 | 当然,如果开源行为的目的纯粹是为了`Star`数量的多少(比如通过某宝花钱刷`Star`),则又是另外一种极端了。
70 |
71 | 这里我不想关于这一点深入讨论下去,我想引出的问题是,开源社区的最大优势是什么?
72 |
73 | 为什么这么问,因为我发现我在偏离 **开源精神** 的道路上越走越远了!2年来我维护了若干个开源项目,并且都有数量并不少的`Star`,但是我发现我越来越不开心,因为我被这些`Star`和虚假的优越感困住了。
74 |
75 | 陆陆续续的,我花费极大的精力去维护这几个项目,诚然它们的`Star`越来越多,但是我对这些代码 **越来越不满意** ,因为随着我个人专业能力的提升,这些代码设计在我看来有各种各样的瑕疵。
76 |
77 | 一切都不同了!!!开源的伊始,我为我的这些代码骄傲,但是逐渐的我开始厌弃它们,我甚至觉得它们不值那么多的`Star`,我脑子里有更多有趣的想法,但是我没有精力去实现这些想法,虚荣感和责任感让我持续为开源项目付出越来越多的精力。
78 |
79 | 一切似乎都变得不再有趣,直到有一天我突然想到,我为什么要一个人闭门造车呢?`GitHub`上仍然有那么多优秀的开源项目和开源组织,也许尝试和社区内其它优秀的开发者,齐心协力开发维护一个更优秀的项目,远远比一个人闭门造车要好得多。
80 |
81 | 这也是我近半年来不再随便造轮子的原因,每当我有一个好玩的想法,我会问自己,它真的有花费时间去实现并开源的必要吗?它代表着对开源项目和开源社区的责任感,这也能隐性节约我非常多的时间。
82 |
83 | ### 2、论博客标题的重要性
84 |
85 | 逐利并不可耻。
86 |
87 | 在技术社区中,技术文标题的重要性不言而喻。
88 |
89 | 每个读者阅读时间都是有限的,他们总会优先阅读标题比较有趣的文章,当然,一篇文章阅读量多了,其它文章的阅读量自然就少了。
90 |
91 | 我真的没有想到,我今年24篇博客中,阅读量和点赞量最多的文章是 [这篇](https://juejin.im/post/5d2dee0851882569755f5494) :
92 |
93 | 
94 |
95 | [这篇文章](https://juejin.im/post/5d2dee0851882569755f5494)从下笔到发表,我一共花费了不到2个小时,而它给我带来的流量的零头,也比其它花费10小时以上撰写的技术文章所得到的还多。
96 |
97 | 说实话,我并不感到兴奋,相反我对此感到恐惧,我看到很多优秀的文章,仅仅是因为标题不够吸睛,它们都随着时间被流放到在了博客平台不为人知的角落。甚至有的文章被洗稿的公众号换个标题党的名字发表,就能吸引过来巨大的流量和点赞。
98 |
99 | 不公平吗?其实很公平,因为这都是每位读者自己的选择。但是我希望这种现象能够被慢慢改变,好的文章不应该随意被埋没。
100 |
101 | 从个人而言,解决这种困境最好的方式,就是保持高质量的更新,没有啰里八嗦的闲聊,没有标题党,尝试 **用纯粹获取读者的信任**,上文中我多次提到的 [反思系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md) 就是我一次努力的尝试。
102 |
103 | 每一次的输出都保证,对读者负责,也对自己负责,无论结果,尽力就好。
104 |
105 | ### 3、除了知名度,公开演讲还能带来什么
106 |
107 | 无论是大型的技术大会,还是一次小型的的交流会,指望参加一次,能够打通自己的一个技能栈是不现实的。
108 |
109 | 因此,如果问我,你作为演讲嘉宾参加这种公开的活动,除了露个脸赚了一些知名度,还能够收获到其它的什么吗?
110 |
111 | 我的回答是,能,而且收获非常庞大的好处,最简单的,**它甚至能够打破你职业发展的天花板,让你重新定位自己**。
112 |
113 | 两次公开演讲结束后,我最大的收获就是认识了同为演讲嘉宾的其它开发者,还有与会的各个行业内的前辈,他们的阅历和开发经验都丰富到令我震惊。
114 |
115 | 下面是我上周参加完`GDG Flutter`专场后的感慨:
116 |
117 | 
118 |
119 | 原来,当我在一个人慢慢学习`Flutter`的时候,`Flutter`在其它更优秀的开发者手里已经运用到了驾轻就熟的程度了,优秀的技术人都在不断的涌现,每个人相对于这个时代的浪潮,真的就是:
120 |
121 | > 寄蜉蝣于天地,渺沧海之一粟。
122 |
123 | 因此,借此机会,认识到技术的发展方向和发展程度,能够重新定位自己,并找到接下来为之努力的目标,调整自己的职业规划,这种好处是不言而喻的。
124 |
125 | ## 总结,善用“功利心”
126 |
127 | 总而言之,善用”功利心”,它所带来的正向反馈能够帮助我坚持下去,无论是 **写作能力** 、 **表达能力** 、**专业知识** ,甚至在 **求职** 和 **谈薪** 时都能带来非常大的好处。
128 |
129 | 但同时也要把握住这个度,坚守本心,不要为一时的虚荣心和满足感拖慢了前进的脚步,时时刻刻保持自省,认清自己,不急不缓,保持住自己的节奏,这很难,但随时想想那些专业领域内的天才们的成果,功利心就能被按捺住很多。
130 |
131 | 也没必要过分自卑,我很喜欢我的个性签名:
132 |
133 | > 你微小,但你并不渺小。
134 |
135 | ---
136 |
137 | ## 关于我
138 |
139 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [Github](https://github.com/qingmei2)。
140 |
141 | 如果您觉得文章还差了那么点东西,也请通过**关注**督促我写出更好的文章——万一哪天我进步了呢?
142 |
143 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
144 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
145 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
146 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
147 |
--------------------------------------------------------------------------------
/src/UnitTest/-Android-Kotlin使用Mockito进行单元测试.md:
--------------------------------------------------------------------------------
1 | ## 简述
2 |
3 | 在日常项目开发中,基本没有什么机会用到Kotlin,几个月前学习的语法,基本上都忘光了,于是自己强迫自己在写Demo中使用Kotlin,同时,在目前开发的项目中开了一个测试分支,用来补全之前没有写的测试代码。
4 |
5 | ## 环境配置
6 |
7 | ### 1.MockAPI
8 |
9 | 单元测试中使用真实开发环境中的真实数据是不明智的,最好的方式是用本地的数据模拟网络请求,比如说我们有这样一个API,联网library我们选择Retrofit:
10 |
11 | ```
12 | //TestService
13 | interface TestService {
14 |
15 | @GET("/test/api")
16 | abstract fun getUser(@Query("login") login: String): Observable
17 | }
18 | ```
19 |
20 | 我们本地mock这个API会返回这样的Json数据:
21 | ```
22 | {
23 | "login": "qingmei2",
24 | "name": "qingmei"
25 | }
26 | ```
27 |
28 | 对应的data类:
29 | ```
30 | class User(val name: String = "defaultName",
31 | val login: String = "defaultLogin")
32 | ```
33 |
34 | 好的,接下来我们定义一个Asset类,负责管理本地Mock的API返回资源:
35 |
36 | ```
37 | object MockAssest {
38 |
39 | private val BASE_PATH = "app/src/test/java/cn/com/xxx/xxx/base/mocks/data"
40 |
41 | //User API对应的模拟json数据的文件路径
42 | val USER_DATA = BASE_PATH + "/userJson_test"
43 |
44 | //通过文件路径,读取Json数据
45 | fun readFile(path: String): String {
46 | val content = file2String(File(path))
47 | return content
48 | }
49 | //kotlin丰富的I/O API,我们可以通过file.readText(charset)直接获取结果
50 | fun file2String(f: File, charset: String = "UTF-8"): String {
51 | return f.readText(Charsets.UTF_8)
52 | }
53 | }
54 | ```
55 |
56 | 关于Kotlin更多强大的IO操作的API,可以参考这篇:[Kotlin IO操作](http://www.jianshu.com/p/adaf5136e197)
57 |
58 | ### 2.MockRetrofit
59 |
60 | 我们直接配置一个MockRetrofit进行API的拦截:
61 |
62 | ```
63 | class MockRetrofit {
64 |
65 | var path: String = ""
66 |
67 | fun create(clazz: Class): T {
68 |
69 | val client = OkHttpClient.Builder()
70 | .addInterceptor(Interceptor { chain ->
71 | val content = MockAssest.readFile(path)
72 | val body = ResponseBody.create(MediaType.parse("application/x-www-form-urlencoded"), content)
73 | val response = Response.Builder()
74 | .request(chain.request())
75 | .protocol(Protocol.HTTP_1_1)
76 | .code(200)
77 | .body(body)
78 | .message("Test Message")
79 | .build()
80 | response
81 | }).build()
82 |
83 | val retrofit = Retrofit.Builder()
84 | .baseUrl("http://api.***.com")
85 | .client(client)
86 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
87 | .addConverterFactory(GsonConverterFactory.create())
88 | .build()
89 | return retrofit.create(clazz)
90 | }
91 | }
92 | ```
93 |
94 | 这样我们直接通过MockRetrofit.create(APIService.class)直接mock一个对应的API Service对象。
95 |
96 | ### 3.对上面两个tool类的测试
97 |
98 | 在测试自己的业务代码之前,我们当然要先保证这两个工具类的逻辑正确,如果这两个脚手架都是错误的,那么接下来业务代码的单元测试毫无意义。
99 |
100 | * MockAsset.kt的Test
101 |
102 | ```
103 | class MockAssetTest {
104 |
105 | @Test
106 | fun assetTest() {
107 | //MockAssest读取文件,该函数所得结果将来会作为模拟的网络数据返回,我们这个单元测试的意义
108 | //就是保证模拟的网络数据能够正确的返回
109 | val content = MockAssest.readFile(MockAssest.USER_DATA)
110 | Observable.just(content)
111 | .test()
112 | .assertValue("{\n" + " \"login\": \"qingmei2\",\n" + " \"name\": \"qingmei\"\n" + "}")
113 | }
114 | }
115 | ```
116 | * MockRetrofit.kt的Test
117 |
118 | ```
119 | class MockRetrofitTest {
120 |
121 | @Test
122 | fun mockRetrofitTest() {
123 | // 这个测试是保证Retrofit能够成功拦截API请求,并返回本地的Mock数据
124 | val retrofit = MockRetrofit()
125 | val service = retrofit.create(TestService::class.java)
126 | retrofit.path = MockAssest.USER_DATA //设置Path,设置后,retrofit会拦截API,并返回对应Path下Json文件的数据
127 |
128 | service.getUser("test")
129 | .test()
130 | .assertValue { it ->
131 | it.login.equals("qingmei2")
132 | it.name.equals("qingmei")
133 | }
134 | }
135 | }
136 | ```
137 |
138 | ## 使用 Mockito-Kotlin
139 |
140 | 我尝试使用这个新的库,[mockito-kotlin:Using Mockito with Kotlin](https://github.com/nhaarman/mockito-kotlin)
141 |
142 | 我选择这个基于Mockito之上的拓展库的理由很简单,更方便入门(我无法保证之后的测试代码过程中会不会踩坑,但是首先我得能够进行单元测试)。
143 |
144 | 关于Mockito在Kotlin的使用中会遇到的一些问题,这篇文章也许会对你有些帮助:
145 |
146 | [在Kotlin上怎样用Mockito2 mock final 类(KAD 23)](http://www.cnblogs.com/figozhg/archive/2017/05/06/6817848.html)
147 |
148 | 我没有按照上面的步骤进行配置的尝试,但是当我在使用[mockito-kotlin](https://github.com/nhaarman/mockito-kotlin)踩到坑时,在这篇笔记中留下这样一个后路,也许不会让我碰得头破血流而束手无策。
149 |
150 | ### Mock依赖
151 |
152 | 我的项目中使用MVVM的架构,这意味着,ViewModel的测试至关重要。
153 |
154 | 首先我把一些常用的依赖放到了BaseViewModel中:
155 |
156 | ```
157 | public class BaseViewModel {
158 |
159 | @Inject
160 | protected AccountManager accountManager;//账户相关
161 | @Inject
162 | protected ServiceManager serviceManager;//API相关
163 |
164 | //保存不同的加载状态
165 | public final ObservableField loadingState = new ObservableField<>(LOAD_WAIT);
166 | ...
167 | ...
168 | ...
169 | }
170 | ```
171 |
172 | 我写了一个BaseTestViewModel类,他继承了BaseViewModel,这意味着同样持有accountManager和serviceManager。
173 |
174 | 我在setUp函数中初始化了这两个重要的对象,并进行简单的测试:
175 |
176 | ```
177 | open class BaseTestViewModel : BaseViewModel() {
178 |
179 | @Before
180 | fun setUp() {
181 | accountManager = mock()
182 | serviceManager = mock()
183 | }
184 |
185 | //测试accountManager 成功Mock
186 | @Test
187 | fun testAccountManager() {
188 | Assert.assertNotNull(accountManager)
189 | whenever(accountManager.toString()).thenReturn("mock AccountManager.ToString!")
190 | Assert.assertEquals(accountManager.toString(), "mock AccountManager.ToString!")
191 | }
192 |
193 | //测试serviceManager 成功Mock
194 | @Test
195 | fun testServiceManager() {
196 | Assert.assertNotNull(serviceManager)
197 | val alertService = mock()
198 | whenever(alertService.toString()).thenReturn("mock alertService")
199 | whenever(serviceManager.alertService).thenReturn(alertService)
200 | Assert.assertEquals(serviceManager.alertService.toString(), "mock alertService")
201 | }
202 |
203 | class TestViewModel : BaseTestViewModel() {
204 | //测试BaseTestViewModel的子类也能成功持有mock好了的accountManager
205 | @Test
206 | fun testSubTestClass() {
207 | Assert.assertNotNull(accountManager)
208 | whenever(accountManager.toString()).thenReturn("mock AccountManager.Sub ToString!")
209 | Assert.assertEquals(accountManager.toString(), "mock AccountManager.Sub ToString!")
210 | }
211 | }
212 | }
213 | ```
214 |
215 | 这几个测试pass之后,我可以尝试对我的不同业务代码下的ViewModel进行测试了。
216 |
217 | ## 交流
218 |
219 | 本文是简单的尝试下搭建的测试脚手架,如果您有更好的方式或思路,望请不吝指出。
220 |
--------------------------------------------------------------------------------
/src/android-RxJava/Android-RxCache使用详解.md:
--------------------------------------------------------------------------------
1 | ## 前言
2 |
3 | ### 我为什么使用这个库?
4 | 事实上Android开发中缓存功能的实现选择有很多种,File缓存,SP缓存,或者数据库缓存,当然还有一些简单的库/工具类,比如github上的这个:
5 |
6 | [【ASimpleCache】:a simple cache for android and java](https://github.com/yangfuhai/ASimpleCache)
7 |
8 | 但是都不是很好用(虽然可能学习成本比较低,因为它使用起来相对简单),我可能需要很多的静态常量来作为key存储缓存数据value,并设置缓存的有效期,这可能需要很多Java代码去实现,并且过程繁琐。
9 |
10 | 如果您使用的网络请求库是Retrofit+RxJava,那么我推荐使用RxCache,正如作者所说的:
11 |
12 | >RxCache is a reactive caching library for Android and Java which turns your caching needs into an interface.
13 | >
14 | > RxCache是一个用于Android和Java的响应式缓存库,它可将您的缓存需求转换为一个接口。
15 |
16 | ### 为什么写这样一篇文章
17 | 因为这个库的官方文档是!英!语!的!
18 | 这本身无可厚非,作为一个开发者,英语文档的阅读是不可避免的一项技能,但是笔者还是抽了一点时间将官方文档做了汉化:
19 |
20 | [RxCache官方文档中文翻译](http://blog.csdn.net/mq2553299/article/details/78056742)
21 |
22 | [RxCache库官方链接](https://github.com/VictorAlbertos/RxCache)
23 |
24 | 文档的翻译比想象中的费力(每一个词都试图翻译准确),但数小时的努力之后,译文的描述依然对于初次接触该库的开发者有着不小的学习难度,干脆自己写一个demo,并放到github上,供大家参考。
25 |
26 | [【Github】本文demo源码,点击进入](https://github.com/qingmei2/RxFamilyUsage-Android)
27 |
28 |
29 |
30 | ## 1.依赖配置
31 | 在您的build.gradle(Project)中添加JitPack仓库:
32 |
33 | ```gradle
34 | allprojects {
35 | repositories {
36 | jcenter()
37 | maven { url "https://jitpack.io" }
38 | }
39 | }
40 | ```
41 |
42 | 将下列的依赖添加到Module的build.gradle中:
43 |
44 | ```gradle
45 | dependencies {
46 | compile "com.github.VictorAlbertos.RxCache:runtime:1.8.1-2.x"
47 | compile "io.reactivex.rxjava2:rxjava:2.0.6"
48 | //我们再添加这个依赖,下面有说明
49 | compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.3'
50 | }
51 | ```
52 |
53 | 因为RxCache在内部使用 [Jolyglot](https://github.com/VictorAlbertos/Jolyglot) 对对象进行序列化和反序列化, 您需要选择下列的依赖中选择一个进行添加:
54 |
55 | ```gradle
56 | dependencies {
57 | // To use Gson
58 | compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.3'
59 |
60 | // To use Jackson
61 | compile 'com.github.VictorAlbertos.Jolyglot:jackson:0.0.3'
62 |
63 | // To use Moshi
64 | compile 'com.github.VictorAlbertos.Jolyglot:moshi:0.0.3'
65 | }
66 | ```
67 |
68 | ## 2.Retrofit请求示例
69 | 我们假设这样一个需求,通过传入user名,返回User对应信息,比如:
70 |
71 |
72 | ### Retrofit API接口
73 |
74 | ```java
75 | public interface GitHubService {
76 |
77 | @GET("users/{user}")
78 | Observable getRxUser(@Path("user") String user);
79 |
80 | }
81 | ```
82 |
83 | ### 该API请求的管理类ServiceManager
84 |
85 | ```java
86 | public class GitHubServiceManager {
87 |
88 | private GitHubService service;
89 |
90 | public GitHubServiceManager() {
91 | init();
92 | }
93 |
94 | private void init() {
95 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor()
96 | .setLevel(HttpLoggingInterceptor.Level.BODY);
97 |
98 | OkHttpClient client = new OkHttpClient()
99 | .newBuilder()
100 | .addInterceptor(interceptor)
101 | .build();
102 |
103 | service = new Retrofit.Builder()
104 | .baseUrl("https://api.github.com/")
105 | .addConverterFactory(GsonConverterFactory.create())
106 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
107 | .client(client)
108 | .build()
109 | .create(GitHubService.class);
110 | }
111 |
112 | public Observable getUser(String user){
113 | return service.getRxUser(user);
114 | }
115 | }
116 | ```
117 |
118 | ### User数据类
119 |
120 | ```
121 | @Data //lombok插件的注解,自动生成get、set方法
122 | public class User {
123 |
124 | public String login;
125 |
126 | public String name;
127 | }
128 | ```
129 |
130 | ### 最后在我们的Activity中获取数据:
131 | ```java
132 | new GitHubServiceManager()
133 | .getUser(userName)
134 | .subscribeOn(Schedulers.io())
135 | .observeOn(AndroidSchedulers.mainThread())
136 | .subscribe(user1 -> Toast.makeText(this, user1.toString(), Toast.LENGTH_SHORT).show());
137 | ```
138 |
139 | ok,非常简单,接下来我们来配置缓存,我们默认需求:缓存有效期为1分钟。
140 |
141 | ## 3.缓存配置
142 |
143 | ## 配置缓存接口
144 |
145 | 首先我们先配置Provider接口:
146 |
147 | ```java
148 | public interface UserCacheProviders {
149 |
150 | /**
151 | * LifeCache设置缓存过期时间. 如果没有设置@LifeCache , 数据将被永久缓存理除非你使用了 EvictProvider,EvictDynamicKey or EvictDynamicKeyGroup .
152 | * @param user
153 | * @param userName 驱逐与一个特定的键使用EvictDynamicKey相关的数据。比如分页,排序或筛选要求
154 | * @param evictDynamicKey 可以明确地清理指定的数据 DynamicKey.
155 | * @return
156 | */
157 | @LifeCache(duration = 1,timeUnit = TimeUnit.MINUTES)
158 | Observable getUser(Observable user, DynamicKey userName, EvictDynamicKey evictDynamicKey);
159 |
160 | }
161 |
162 | ```
163 | 很多同学到这里就有点蒙蒙的,不知道这些参数都是用来干嘛的,其实简单介绍一下就清楚了:
164 |
165 | > * @param user:这是个Observable类型的对象,简单来说,这就是你将要缓存的数据对象。
166 | > * @param userName:DynamicKey类型,顾名思义,就是一个动态的key,我们以它作为tag,将数据存储到对应名字的File中
167 | > * @param evictDynamicKey 可以明确地清理指定的数据 ,很简单,如果我们该参数传入为true,那么RxCache就会驱逐对应的缓存数据直接进行网络的新一次请求(即使缓存没有过期)。如果传入为false,说明不驱逐缓存数据,如果缓存数据没有过期,那么就不请求网络,直接读取缓存数据返回。
168 | > * @return 可以看到,该接口方法中,返回值为Observable,泛型为user,这个Observable的对象user和参数中传进来的Observable的对象user有什么区别呢?
169 | --- 很简单,返回值Observable中的数据为经过缓存处理的数据。
170 |
171 | ### 配置缓存Provider
172 | 我们还需要配置的有:
173 | 1.缓存文件存储到哪里?
174 | 2.如何解析缓存数据?
175 | ```
176 | public class CacheProviders {
177 |
178 | private static UserCacheProviders userCacheProviders;
179 |
180 | public synchronized static UserCacheProviders getUserCache() {
181 | if (userCacheProviders == null) {
182 | userCacheProviders = new RxCache.Builder()
183 | .persistence(BaseApplication.getApplication().getExternalCacheDir(), new GsonSpeaker())//缓存文件的配置、数据的解析配置
184 | .using(UserCacheProviders.class);//这些配置对应的缓存接口
185 | }
186 | return userCacheProviders;
187 | }
188 | }
189 | ```
190 |
191 | ### 代码中设置缓存功能:
192 |
193 | ```java
194 | private void requestHttp(String userName) {
195 | //网络请求数据
196 | Observable user = new GitHubServiceManager()
197 | .getUser(userName);
198 | //缓存配置
199 | CacheProviders.getUserCache()
200 | .getUser(user, new DynamicKey(userName), new EvictDynamicKey(false))//用户名作为动态key生成不同文件存储数据,默认不清除缓存数据
201 | .subscribeOn(Schedulers.io())
202 | .observeOn(AndroidSchedulers.mainThread())
203 | .subscribe(user1 -> Toast.makeText(this, user1.toString(), Toast.LENGTH_SHORT).show());
204 | }
205 | ```
206 | 配置好后,如果没有缓存或者缓存失效,则请求网络数据,缓存并展示数据。
207 | 如果有缓存数据且缓存未失效,则不加载网络数据,直接展示本地缓存数据。
208 |
--------------------------------------------------------------------------------
/src/反思系列/ANR/thinking_in_android_anr2.md:
--------------------------------------------------------------------------------
1 | # 反思|Android 输入系统 & ANR机制的设计与实现
2 |
3 | ## 四、ANR机制的设计与实现
4 |
5 | 对 **输入系统** 有了更初步整体的认知之后,接下来本文将针对`ANR`机制进行更深一步的探索。
6 |
7 | 通常来讲,`ANR`的来源分为`Service、Broadcast、Provider`以及`Input`两种。
8 |
9 | 这样区分的原因是,首先,前者发生在 **应用进程** 组件中的`ANR`问题通常是相对好解决的,若`ANR`本身容易复现,开发者通常仅需要确定组件的代码中是否在 **主线程中做了耗时处理**;而后者`ANR`发生的原因为 **输入事件** 分发超时,包括按键和屏幕的触摸事件,通过阅读上一章节,读者知道 **输入系统** 中负责处理`ANR`问题的是处于 **系统进程** 中的`InputDispatcher`,其整个流程相比前者而言逻辑更加复杂。
10 |
11 | 简单理解了之后,读者需要知道,「组件类`ANR`发生原因通常是由于 **主线程中做了耗时处理**」这种说法实际上是笼统的,更准确的讲,其本质的原因是 **组件任务调度超时**,而在设备资源紧凑的情况下,`ANR`的发生更多是综合性的原因。
12 |
13 | 而`Input`类型的`ANR`相对于`Service、Broadcast、Provider`,其内部的机制又截然不同。
14 |
15 | ### 1、第一类原理概述
16 |
17 | 具体不同在哪里呢,对于`Service、Broadcast、Provider`组件类的`ANR`而言,[Gityuan](http://gityuan.com/) 在 [这篇文章](http://gityuan.com/2019/04/06/android-anr/) 中做了一个非常精妙的解释:
18 |
19 | > `ANR`是一套监控`Android`应用响应是否及时的机制,可以把发生`ANR`比作是 **引爆炸弹**,那么整个流程包含三部分组成:
20 | > * **埋定时炸弹**:中控系统(`system_server`进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
21 | > * **拆炸弹**:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
22 | > * **引爆炸弹**:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(`traces`),便于后续的案件侦破(调试分析),最后是炸毁目标。
23 |
24 | 将组件的`ANR`机制比喻为 **定时炸弹** 非常贴切,以`Service`为例,对于`Android`系统而言,启动一个服务其本质是进程间的异步通信,那么,如何判断`Service`是否启动成功,如果一直没有成功,那么如何处理?
25 |
26 | 因此`Android`设计了一个 **置之死地而后生** 的机制,在尝试启动`Service`时,让中控系统`system_server`埋下一个 **定时炸弹** ,当`Service`完成启动,拆掉炸弹;否则在`system_server`的`ActivityManager`线程中引爆炸弹,这就是组件类`ANR`机制的原理:
27 |
28 | 
29 |
30 | 接下来简单了解一下 **输入系统** 流程中`ANR`机制的原理。
31 |
32 | ### 2、第二类原理概述
33 |
34 | `Input`类型的`ANR`在日常开发中更为常见且更复杂,比如用户或者测试反馈,点击屏幕中的UI元素导致「卡死」。
35 |
36 | 少数情况下开发者能够很快定位到问题,但更常见的情况是,该问题是 **随机** 且 **难以复现** 的,导致该问题的原因也更具有综合性,比如低端设备的系统本身资源已非常紧张,或者多线程相互持有彼此需要的资源导致 **死锁** ,亦或其它复杂的情况,因此处理这类型问题就需要开发者对 **输入系统** 中的`ANR`机制有一定的了解。
37 |
38 | 与组件类`ANR`不同的是,`Input`类型的超时机制并非时间到了一定就会爆炸,而是处理后续上报事件的过程才会去检测是否该爆炸,所以更像是 **扫雷** 的过程。
39 |
40 | 什么叫做 **扫雷** 呢,对于 **输入系统** 而言,即使某次事件执行时间超过预期的时长,只要用户后续没有再生成输入事件,那么也不需要`ANR`。
41 |
42 | 而只有当新一轮的输入事件到来,此时正在分发事件的窗口(即`App`应用本身)迟迟无法释放资源给新的事件去分发,这时`InputDispatcher`才会根据超时时间,动态的判断是否需要向对应的窗口提示`ANR`信息。
43 |
44 | 这也正是用户在第一次点击屏幕,即使事件处理超时,也没有弹出`ANR`窗口,而当用户下意识再次点击屏幕时,屏幕上才提示出了`ANR`信息的原因。
45 |
46 | 由此可见,组件类`ANR`和`Input ANR`原理上确实有所不同;除此之外,前者是在`ActivityManager`线程中处理的`ANR`信息,后者则是在`InputDispatcher`线程中处理的`ANR`,这里通过一张图简单了解一下后者的整体流程:
47 |
48 | 
49 |
50 | 现在我们对`Input`类型的`ANR`机制有了一个简单的了解,下文将针对其更深入性的细节实现进行探讨。
51 |
52 | ### 3、事件分发的异步机制
53 |
54 | 我们再次将目光转回到`InputDispatcher`的实现细节。
55 |
56 | 先抛出一个新的问题,对处于`system_server`进程`Native`层级的 **事件分发** 而言,其向下与 **应用进程** 的通信的过程应该是同步还是异步的?
57 |
58 | 对于读者而言,不难得出答案是异步的,因为两者之间双向通信的建立是通过`SocketPair`,并且,因为`system_server`中`InputDispatcher`对事件的分发实际上是一对多的,如果是同步的,那么一旦其中一个应用分发超时,那么`InputDispatcher`线程自然被卡住,其永远都不可能进入到下一轮的事件分发中,**扫雷** 机制更是无从谈起。
59 |
60 | 因此,与应用进程中事件分发不同的是,后者我们通常可以认为是在主线程中同步的,而对于整个 **输入系统** 而言,因为涉及到 **系统进程** 与多个 **应用进程** 之间异步的通信,因此其内部的实现更为复杂。
61 |
62 | 因为事件分发涉及到异步回调机制,因此`InputDispatcher`需要对事件进行维护和管理,那么问题就变成了,使用什么样的数据结构去维护这些输入事件比较合适。
63 |
64 | ### 4、三个队列
65 |
66 | `InputDispatcher`的源码实现中,整体的事件分发流程共使用到3个事件队列:
67 |
68 | * mInBoundQueue:用于记录`InputReader`发送过来的输入事件;
69 | * outBoundQueue:用于记录即将分发给目标应用窗口的输入事件;
70 | * waitQueue:用于记录已分发给目标应用,且应用尚未处理完成的输入事件。
71 |
72 | 下文,笔者通过2轮事件分发的示例,对三个队列的作用进行简单的梳理。
73 |
74 | #### 4.1 第一轮事件分发
75 |
76 | 首先`InputReader`线程通过`EventHub`监听到底层的输入事件上报,并将其放入了`mInBoundQueue`中,同时唤醒了`InputDispatcher`线程。
77 |
78 | 然后`InputDispatcher`开始了第一轮的事件分发,此时并没有正在处理的事件,因此`InputDispatcher`从`mInBoundQueue`队列头部取出事件,并重置`ANR`的计时,并检查窗口是否就绪,此时窗口准备就绪,将该事件转移到了`outBoundQueue`队列中,因为应用管道对端连接正常,因此事件从`outBoundQueue`取出,然后放入了`waitQueue`队列,因为`Socket`双向通信已经建立,接下来就是 **应用进程** 接收到新的事件,然后对其进行分发。
79 |
80 | 如果 **应用进程** 事件分发正常,那么会通过`Socket`向`system_server`通知完成,则对应的事件最终会从`waitQueue`队列中移除。
81 |
82 | #### 4.2 第二轮事件分发
83 |
84 | 如果第一轮事件分发尚未接收到回调通知,第二轮事件分发抵达又是如何处理的呢?
85 |
86 | 第二轮事件到达`InputDispatcher`时,此时`InputDispatcher`发现有事件正在处理,因此不会从`mInBoundQueue`取出新的事件,而是直接检查窗口是否就绪,若未就绪,则进入`ANR`检测状态。
87 |
88 | 以下几种情况会导致进入`ANR`检测状态:
89 |
90 | > 1、目标应用不会空,而目标窗口为空。说明应用程序在启动过程中出现了问题;
91 | > 2、目标`Activity`的状态是`Pause`,即不再是`Focused`的应用;
92 | > 3、目标窗口还在处理上一个事件。
93 |
94 | 读者需要理解,并非所有「目标窗口还在处理上一个事件」都会抛出`ANR`,而是需要通过检测时间,如果未超时,那么直接中止本轮事件分发,反之,如果事件分发超时,那么才会确定`ANR`的发生。
95 |
96 | 这也正是将`Input`类型的`ANR`描述为 **扫雷** 的原因:这里的扫雷是指当前输入系统中正在处理着某个耗时事件的前提下,后续的每一次`input`事件都会检测前一个正在处理的事件是否超时(进入扫雷状态),检测当前的时间距离上次输入事件分发时间点是否超时。如果前一个输入事件,则会重置`ANR`的`timeout`,从而不会爆炸。
97 |
98 | 至此,**输入系统** 检测到了`ANR`的发生,并向上层抛出了本次`ANR`的相关信息。
99 |
100 | ## 小结
101 |
102 | 本文旨在对`Android` **输入系统** 进行一个系统性的概述,读者不应将本文作为唯一的学习资料,而应该通过本文对该知识体系进行初步的了解,并根据自身要求进行单个方向细节性的突破。而已经掌握了骨骼架构的读者而言,更细节性的知识点也不过是待丰富的血肉而已。
103 |
104 | 本文从立题至发布,整个流程耗时近1个半月,在这个过程中,笔者参考了较本文内容数十倍的资料,受益颇深,也深感以 **举重若轻** 为写文目标之艰难——内容铺展容易,但通过 **简洁** 且 **连贯** 的语言来对一个庞大复杂的知识体系进行收拢,需要极强的 **克制力** ,在这种严苛的要求下,每一句的描述都需要极高的 **精确性** ,这对笔者而言是一个挑战,但真正完成之后,对整个知识体系的理解程度同样也是极高的。
105 |
106 | 而这也正是 [反思](https://github.com/qingmei2/android-programming-profile/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md) 系列的初衷,希望你能喜欢。
107 |
108 | ## 参考 & 扩展阅读
109 |
110 | 正如上文所言,**输入系统** 和 **ANR** 本身都是一个非常大的命题,除了宽广的知识体系,还需要亲身去实践和总结,下文列出若干相关参考资料,读者可根据自身需求选择性进行扩展阅读:
111 |
112 | [1、彻底理解安卓应用无响应机制 @Gityuan](http://gityuan.com/2019/04/06/android-anr/)
113 | [2、Input系统—ANR原理分析 @Gityuan](http://gityuan.com/2017/01/01/input-anr/)
114 | [3、理解Android ANR的触发原理 @Gityuan](http://gityuan.com/2016/07/02/android-anr/)
115 |
116 | 深入学习`ANR`机制资料,`Gityuan`的`ANR`博客系列绝对是先驱级别的,尤其是第1篇文章中,其对于 **定时炸弹** 和 **扫雷** 的形容,贴切且易理解,这种 **举重若轻** 的写作风格体现了作者本身对整个知识体系的深度掌握;而后两篇文章则针对两种类型的`ANR`分别进行了源码级别的分析,非常下饭。
117 |
118 | [4、图解Android-Android的 Event Input System @漫天尘沙](https://www.cnblogs.com/samchen2009/p/3368158.html)
119 |
120 | 笔者曾经想写一个 **图解Android** 系列,后来因为种种原因放弃了,没想到若干年前已经有先驱进行过了这样的尝试,并且,内容质量极高。笔者相信,能够花费非常大精力总结的文章一定不会被埋没,而这篇文章,注定会成为经典中的经典。
121 |
122 | [5、Android Input系列 @Stan_Z](https://www.jianshu.com/p/5a879b7ad3b2)
123 |
124 | 一个笔者最近关注非常优秀的作者,文章非常具有深度,其`Input`系列针对整个输入系统进行了更细致源码级别的分析,非常值得收藏。
125 |
126 | [6、Android 信号处理面面观 之 信号定义、行为和来源 @rambo2188](https://blog.csdn.net/rambo2188/article/details/6998349)
127 |
128 | 如果读者对「`Android`系统信号处理的行为」感兴趣,那么这篇文章绝对不能错过。
129 |
130 | 7、Android开发高手课 @张绍文
131 |
132 | 实战中的经典之作,该课程每一小结都极具深度,价值不可估量。因或涉及到利益相关,而且推荐了也从张老师那里拿不到钱,因此本文不加链接并放在最下面(笑)。
133 |
134 |
135 | ---
136 |
137 | ## 关于我
138 |
139 | Hello,我是 [却把清梅嗅](https://github.com/qingmei2) ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 [博客](https://juejin.im/user/588555ff1b69e600591e8462/posts) 或者 [GitHub](https://github.com/qingmei2)。
140 |
141 | 如果您觉得文章还差了那么点东西,也请通过 **关注** 督促我写出更好的文章——万一哪天我进步了呢?
142 |
143 | * [我的Android学习体系](https://github.com/qingmei2/blogs)
144 | * [关于文章纠错](https://github.com/qingmei2/blogs/blob/master/error_collection.md)
145 | * [关于知识付费](https://github.com/qingmei2/blogs/blob/master/appreciation.md)
146 | * [关于《反思》系列](https://github.com/qingmei2/blogs/blob/master/src/%E5%8F%8D%E6%80%9D%E7%B3%BB%E5%88%97/%E5%8F%8D%E6%80%9D%7C%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95.md)
147 |
--------------------------------------------------------------------------------
/src/反思系列/ANR/反思|Android 输入系统 & ANR机制的设计与实现.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/ANR/反思|Android 输入系统 & ANR机制的设计与实现.xmind
--------------------------------------------------------------------------------
/src/反思系列/Activity/LayoutInflater/LayoutInflater.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Activity/LayoutInflater/LayoutInflater.xmind
--------------------------------------------------------------------------------
/src/反思系列/ArchitectureComponents/组件化通信库的设计与实现.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/ArchitectureComponents/组件化通信库的设计与实现.xmind
--------------------------------------------------------------------------------
/src/反思系列/Git/Android源码模块化项目管理工具Repo分析.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Git/Android源码模块化项目管理工具Repo分析.xmind
--------------------------------------------------------------------------------
/src/反思系列/Jetpack/jetpack_workmanager1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Jetpack/jetpack_workmanager1.jpeg
--------------------------------------------------------------------------------
/src/反思系列/Jetpack/分页组件Paging的设计与实现.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Jetpack/分页组件Paging的设计与实现.xmind
--------------------------------------------------------------------------------
/src/反思系列/Jetpack/分页组件Paging的设计与实现2.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Jetpack/分页组件Paging的设计与实现2.xmind
--------------------------------------------------------------------------------
/src/反思系列/SharedPreferences/shared_preferences.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/SharedPreferences/shared_preferences.xmind
--------------------------------------------------------------------------------
/src/反思系列/Skin/bilibili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Skin/bilibili.png
--------------------------------------------------------------------------------
/src/反思系列/Skin/hot_update_wechat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Skin/hot_update_wechat.gif
--------------------------------------------------------------------------------
/src/反思系列/Skin/skin_juejin_design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Skin/skin_juejin_design.png
--------------------------------------------------------------------------------
/src/反思系列/Skin/skin_layout_inflater.drawio:
--------------------------------------------------------------------------------
1 | 7Vtdc6M2FP01nmkfkgEEAj/ajtNuJpnJNDtt2jcFZFtdjFgsr+3++kogzIdw4t1ga8HxQ4KuPkDn6FzrXpkBmCy3vyUoXjzQAIcDywi2A3AzsCwPQP5XGHaZwTHszDBPSJCZzMLwRP7D0mhI65oEeFVpyCgNGYmrRp9GEfZZxYaShG6qzWY0rN41RnOsGJ58FKrWv0jAFnJallvYf8dkvsjvbMJhVrNEeWM5xGqBArrJTOnkwHQAJgmlLLtabic4FNjluGQI3B6o3T9YgiN2TIc79+H+nz++3N19XY+uHgG6IZ+HVznM31C4ljOWT8t2OQQJXUcBFqMYAzDeLAjDTzHyRe2Gc85tC7YMecnklwFaLdK2eeERMYaTKLVYhrDKO+KE4e3BuZh7hPjKwnSJWbLjTWQHx3WyLnJVAVuWNwVHpifntijxYw2lEcl1Md+PXUDHLyR634Gk20kgzXwB/DxAAkcBDgdck7JIE7agcxqhcFpYx1Voizb3lMYSw38xYzvpYNCa0SrceEvYc+n6bzHUtSNLN1s5clrY5YWIz/e5XCj1EsWiW1rK+61YQr/gCQ1pks4PQHc0Ht7uGRXTfZ1Pjg5dJz5+TdnSWaJkjtlrcNvNCyTBIWLkW/VBWifbVFTzGW/ZnwRv3qeeGQnDEsCzGYa+3wR94A5fDKMlMTk1MbkNYjIaxAROpiXvQ0staMnqhJYsRUuflnx70hMx2aZ2MQ27IqaziAJ0QhRAEcV4zRiN+qAIR7cibKMrivipv17sTijJVpQ0DQgT27U+aGmoXUumAmPftTQdwTGE7WrJ6YSWHEVLtwla4nu0o+t25RQg7M0a5QR9D7/MTiMnB2iXE/iQUwtygp2QE1TkNKERxweRiPVHU9qzCZ4C8/PDvYIsEzuCCnxVmCIa4Rqm0oRCMhf5TZ8jhLl9LPAjPgpHsmJJgiAVahNfVUbPlD71ahmfhiDVbsqenooiU02oDSwYCkJmlM+pzBT8uqZ5xdUqdWkjAY0Rb4tKfjUX/0dxPKHLGLEiP5cNyx8zGzlr18G9IKznwLULzVRTOS2zWMoM9ZXGxoSReVYa1eRDyzTmuYzecqg9xWGqYW8O9ypGUQscFlH0nsVs5P6wqD24NvsWcNUh1h9wmb3chCs4698bqD80GPk8xCJsd32DfZo0nvdc0qYc2rUfh+jelNuXdwp7ilwEODZPnrkibXnyzhwT9oRtRyfbeZLmgtg+yWHy0Wzrzdtf3iGNXraBVrYv7wxBL9uWVrbtD7bPyvaBXfyZfhd0OK/TUm5OxGGfohmfDA+bvjtDZ74dEB8RTdVjZgd7gd20EjzrBZR2cO+Kv4BrV+IvO98jlWNmqyEAg6cKwBz15ErVehSMxPsivOSHaLUi/mvx8o9selXASoA4DXjktqMVIe/wSEm6gvOXJWqHVLCOc6Zp2auAWhkIgjcGyjSvDJRytp/2O2gcvqVZf09PIUuAEXzhRJVMP6rtiVgY15HQ9YqhyMe//HqssjuTgKG8P2GCYaetdDCsLZsGf3DWfAxsOiR9rzvYe9BDPrdzbsJry03UBzqxm4BW61/tzY5l6AaG66puIhIJWEM9a32Pt9DrBWpr+jb9NKnAMCb805LfqK8jt2Ef0ZLj4MXiTcxsIRavs4Lp/w==
--------------------------------------------------------------------------------
/src/反思系列/Skin/skin_layout_inflater.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Skin/skin_layout_inflater.png
--------------------------------------------------------------------------------
/src/反思系列/Skin/skin_skin_layout_inflater.drawio:
--------------------------------------------------------------------------------
1 | 7Vxbd6o4FP41PLZLbhEeleI5XatnzVmns2baxwhRmYPEwVh1fv0kEuQSQFpBxLYvJTshkL33t2+JSKq13H0L4WrxA7vIl5SBu5PUB0lRZNnQ6T9G2UcUYICIMA89lw9KCM/ef4gTB5y68Vy0zgwkGPvEW2WJDg4C5JAMDYYh3maHzbCffeoKzpFAeHagL1L/9lyyiKiGMkzo35E3X8RPloEZ9SxhPJivZL2ALt6mSKotqVaIMYmuljsL+Yx5MV+i+yYlvccXC1FA6tzw8H1nTfS3nf0HmczGj+HW2I/v+Cxv0N/wBfOXJfuYAyHeBC5ikwwkdbxdeAQ9r6DDerdU5pS2IEuftmR66cL14jA2bvyEhKAwOFCUAaPyJ6KQoF3pUuQjg6hmIbxEJNzTIfwGfci1iiuVqvH2NhGRbHC+L1LiUUxOhFwt5se5E87RC868dzBS6SUj5Rhm18NIVeAbcikieROHZIHnOIC+nVDHWc4mY54wXnEW/oMI2XPzAjcEZ7mNdh55SV2/sqnudd562PGZD4193Ajocl/SjdRdrJncdmjF961JiH8jC/s4PKxPdYfmdMB6Zp7vp+izGQKOcxQ0Y0O1mCnX8CZ0UAV3NW5CYThHpEqdzWK9CZEPifeWfZHGdUATwPQn2pG/PLQ9D1RlDC4XSRMY03MYGxZgbFCAMbUtiOlfEGsRYqAXEAMCxB6XNJa5EYxpctcYG/YFY51ixegFVgwBK+MNITi4BaDoXQPF7AtQeumM4vj+yhEWv2YKYrbrERb13QLIzK5BJssCG28dZVND1/QilBkOahxlSj9QJhYpLBi658d8tU3cZGLSv3ZQpqudo+zz1S5MMFQhEAWNZFdHw4ZR1o/ihSxWLywcUM5BLyBPcI83Z/q02mizdcuaTFpCW+dVjDjFS/H55ceTwFrCYogK/gU4QDnWcRL0vTmrtzqURYjSx4yBngP9Ee9Yeq57wHCRwLIivVA518iVmgrSYK2omtuajMQyg6QAnwlkhuma0pIC/25w3HG3Pli7EWPNYLVLOunVnP1//u0FFl6uIEkqg9G89D2jqaOBPQwfQb4o3z3Uhq2LMVV9ulU5Fhal5IvKUaxkNC3HuDJys0LsvGAim6VCXK9gcJ4QU4KLJosF18QDIg1JEvuSR/VfRzrP9xWxntI00JPMsQWoC+ICjoGms3YTyrwYu08o4zWcIUbzhBSFzKRLaTaZsAjSLIqi1CJp6m1JUyy/jByaj3pkf/+AHBwWFmI+U/4CtNy5nq7zF+WrotNmRUfhoczpik6J3lymoqNon04LLlk9r68Fg0614OvYTJs7lbW1QO60uquALy24Ci0wOtWC3hzvuXEtGHaqBcaXFlyFFoBOtaC8HtdUrYUlho/BjK6G5nHvztDl0xl6jfQuJ3CaDBiuVqQihjJVAWgmIVSHWiYh1OJdyHQSrxRkhKCtjFBTusR8gvPXNMxPYD6B+Ws8XwXmG0RwXEppf8f+cOsoDOE+NWCFvYCsUzP/ZAQptV2aLTcAPfcLqtx4TdWqxtOL6A0S7Tou5eMKp4rb3Fnzkq3Qn2dy4lmmaRs0Wq0qzdA0f7dgmvik4WmK673Vf/tIWwesQnbHi12s00czUr603LYGXDJTyFuDym3log0RSj688pWuomhXtYfLEDYVe7iGk9tevVjFL+h6+Bv1Z6sbWMcZanXeRug9/TvxWNE0Nhrw1S77TiY2sKxmojlZzvlbUBDNFdf3WwvnOi3w9y2cG151OKfGsflVh3PlR6nKjONHDGCjAWB+k7hmDFjfmhZFi1fFjSJjfL7Xe99SCl+i86c1w+AmhX3S91eceagv09puuw6lG9fe5GkLJf7sSaVrBxd17Z3u2vbNtV/sl7gfcu3HE3VX7drbOl1b7JT5+bs6zvhjVql+daYdMy0eMKxvnvtkh5s8w6gp15didbpj3jM7rMXfOLtOOwxyvwK7SjusiSnWA/LRnFrJQk18glPkZ7Wn/tHJEFHrDaeH+ZgucA7SyfWxpD9UoZp/so7fLB1PMab1pgJRpTZgcA+GJg/H3qcOifziIXg2WyMi2IAmpHTqbLPg+z7sPm1dMnRpDNjFyJYMVbI1aWxIhinZQDKBNDZZ13gkmSqjUPooosgSDTzYGE0aTQ5dlmTIkj2Rxha7phRKp71sZtqr8C5zVOlk6jjo4hz5jAPfz0sYkl9oRjV2UZrH19u3qbeEy2bwhevOhhdJbCHy4uajDJbr2Q1ty+u535m2GmXQZvKRzMj6JJ8aVe3/AQ==
--------------------------------------------------------------------------------
/src/反思系列/Skin/skin_skin_layout_inflater.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Skin/skin_skin_layout_inflater.png
--------------------------------------------------------------------------------
/src/反思系列/Skin/taobao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/Skin/taobao.png
--------------------------------------------------------------------------------
/src/反思系列/View/View布局流程.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/View布局流程.xmind
--------------------------------------------------------------------------------
/src/反思系列/View/View测量流程.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/View测量流程.xmind
--------------------------------------------------------------------------------
/src/反思系列/View/surfaceview/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/surfaceview/1.png
--------------------------------------------------------------------------------
/src/反思系列/View/surfaceview/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/surfaceview/2.png
--------------------------------------------------------------------------------
/src/反思系列/View/surfaceview/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/surfaceview/3.png
--------------------------------------------------------------------------------
/src/反思系列/View/surfaceview/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/surfaceview/4.jpg
--------------------------------------------------------------------------------
/src/反思系列/View/surfaceview/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/surfaceview/5.png
--------------------------------------------------------------------------------
/src/反思系列/View/事件分发流程.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/事件分发流程.xmind
--------------------------------------------------------------------------------
/src/反思系列/View/事件拦截机制.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qingmei2/blogs/01b88afcf7146d57579d2a272663a80a43499e6f/src/反思系列/View/事件拦截机制.xmind
--------------------------------------------------------------------------------
/src/反思系列/thinking_in_android_index.md:
--------------------------------------------------------------------------------
1 | # 关于反思系列(Thinking in Android)
2 |
3 | ## 起源
4 |
5 | 我曾经写过很多博客,这其中有一部分是源码分析相关的,不幸的是,这些恰恰是我所有博客中回顾最少的博客系列——里面涉及了大量代码片段的复制粘贴,以及到处可见一行一行啰嗦的注释,坦白的说,很多源码分析的博客写完之后,我再也没有去看过。
6 |
7 | 从结果来看,**源码+注释分析** 的博客总结是非常差的一种学习方式,因为这通常意味着从API入手,从结果倒推过程,这导致往往我只知其然而不知其所以然。
8 |
9 | 相反,我认为正确的学习总结应该是先理解思想,然后一点点去丰富代码,一点点完善整个体系的血肉,从大局到具体,从宏观到细节。
10 |
11 | 这种学习总结的方式的核心思想是,**文章的核心应该是思想的传递而非代码**,我不需要在执着于代码的细节,每一行代码的意义——即使很久之后我忘记了,通过快速阅读,我也能第一时间将这些知识捡回来,而不是看着大段大段似曾相识而又倍感陌生的 **源码+注释** 皱紧眉头。
12 |
13 | **反思系列(Thinking in Android)** 是我对这种学习总结方式的一种尝试,此处 **反思** 一词并非自我反省的意思,而是指 **对某个知识体系初步掌握后,对该体系进行的重新思考**,其最终目标是:每一篇文章都能被收藏和一遍又一遍地阅读回顾。
14 |
15 | PS: 一些朋友曾友好指出,这是否是对 **反思** 一词的误用,对此请参考[百度百科](https://baike.baidu.com/item/%E5%8F%8D%E6%80%9D/10319867#viewPageContent)的解释,可见该词的确有从另外的观点重新认知事物的含义。
16 |
17 | > I have written many blog posts, and among them, there is a part related to source code analysis. Unfortunately, these are the least reviewed blog series for me, as they involve a lot of copy-pasting of code snippets and verbose comments that can be seen everywhere. To be honest, after writing many source code analysis blog posts, I never went back to review them.
18 | >
19 | > In retrospect, I realized that the blog posts that focus on source code and annotations analysis are not an effective way of learning. This approach usually starts with the API and works backward to the result, which means that I only know the result but not the reason behind it.
20 | >
21 | > On the other hand, I believe that the correct way of learning and summarizing is to first understand the concept, then gradually enrich the code, and perfect the entire system from the macro to the detail.
22 | >
23 | > The core idea of this learning and summarizing approach is that the focus of the article should be on the transmission of ideas rather than code. I don't need to be obsessed with the details of the code and the meaning of each line of code - even if I forget it later, I can quickly pick up this knowledge through quick reading. Instead of looking at the large blocks of source code and annotations that seem familiar but strange, causing furrowed brows.
24 | >
25 | > The "Thinking in Android" series is my attempt at this learning and summarizing approach. The word "Thinking" used here does not mean self-reflection, but refers to the rethinking of a knowledge system after initial mastery, with the ultimate goal that each article can be collected and read over and over again.
26 |
27 | ## 系列目录
28 |
29 | * [反思|Android View机制设计与实现:测量流程](https://github.com/qingmei2/blogs/issues/12)
30 | * [反思|Android View机制设计与实现:布局流程](https://github.com/qingmei2/blogs/issues/13)
31 |
32 | * [反思|Android LayoutInflater机制的设计与实现](https://github.com/qingmei2/blogs/issues/25)
33 | * [反思|Android 事件分发机制的设计与实现](https://github.com/qingmei2/blogs/issues/27)
34 | * [反思|Android 事件拦截机制的设计与实现](https://github.com/qingmei2/blogs/issues/44)
35 |
36 | * [反思|Android 列表分页组件Paging的设计与实现:系统概述](https://github.com/qingmei2/blogs/issues/30)
37 | * [反思|Android 列表分页组件Paging的设计与实现:架构设计与原理解析](https://github.com/qingmei2/blogs/issues/31)
38 |
39 | * [反思|Android源码模块化管理工具Repo分析](https://github.com/qingmei2/blogs/issues/45)
40 |
41 | * [反思|Android 输入系统 & ANR机制的设计与实现](https://github.com/qingmei2/blogs/issues/46)
42 |
43 | * [反思|事件总线的局限性,及组件化开发流程中通信机制的设计与实现](https://github.com/qingmei2/blogs/issues/48)
44 |
45 | * [反思|官方也无力回天?Android SharedPreferences的设计与实现](https://github.com/qingmei2/blogs/issues/49)
46 |
--------------------------------------------------------------------------------